From: Guillaume Pellerin Date: Thu, 7 Jul 2016 14:18:24 +0000 (+0200) Subject: add featured app, reorganize models and views X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=ff1889045a2132e5fadd8df92cb021476dd5bdcd;p=mezzo.git add featured app, reorganize models and views --- diff --git a/app/organization/core/management/__init__.py b/app/organization/core/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/organization/core/management/commands/__init__.py b/app/organization/core/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/organization/core/management/commands/create-admin-user.py b/app/organization/core/management/commands/create-admin-user.py new file mode 100644 index 00000000..e7e3cad5 --- /dev/null +++ b/app/organization/core/management/commands/create-admin-user.py @@ -0,0 +1,24 @@ +from optparse import make_option +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User + + +class Command(BaseCommand): + help = """Create a default admin user if it doesn't exist. + you SHOULD change the password and the email afterwards!""" + + username = 'admin' + password = 'admin' + email = 'root@example.com' + + def handle(self, *args, **options): + admin = User.objects.filter(username=self.username) + if not admin: + user = User(username=self.username) + user.set_password(self.password) + user.email = self.email + user.is_superuser = True + user.is_staff = True + user.save() + print('User ' + self.username + ' created') diff --git a/app/organization/core/management/commands/festival-sync-eve-events.py b/app/organization/core/management/commands/festival-sync-eve-events.py new file mode 100644 index 00000000..070cadbd --- /dev/null +++ b/app/organization/core/management/commands/festival-sync-eve-events.py @@ -0,0 +1,86 @@ +from datetime import datetime, timedelta +from optparse import make_option + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User +from django.core.mail import EmailMessage + +import mezzanine_agenda.models as ma_models +from mezzanine.generic.models import AssignedKeyword, Keyword + +import eve.models as eve_models + + +class Command(BaseCommand): + """Synchronize events from E-vement to mezzanine_agenda""" + + + option_list = BaseCommand.option_list + ( + make_option('-m', '--meta_event', + dest='meta_event', + help='define eve meta_event'), + ) + + default_user = User.objects.get(username='admin') + + def cleanup(self): + # for event in ma_models.Event.objects.all(): + # event.delete() + # for location in ma_models.EventLocation.objects.all(): + # location.delete() + for event_price in ma_models.EventPrice.objects.all(): + event_price.delete() + + def handle(self, *args, **kwargs): + # self.cleanup() + meta_event_name = kwargs.get('meta_event') + meta_trans_all = eve_models.MetaEventTranslation.objects.all() + for meta_trans in meta_trans_all: + if meta_trans.name == meta_event_name: + break + eve_events = eve_models.Event.objects.filter(meta_event=meta_trans.id) + for eve_event in eve_events: + event_trans = eve_models.EventTranslation.objects.filter(id=eve_event, lang='fr')[0] + manifestations = eve_event.manifestations.all().order_by('happens_at') + first = True + for manifestation in manifestations: + events = ma_models.Event.objects.filter(external_id=manifestation.id) + if not events: + event = ma_models.Event(external_id=manifestation.id) + else: + event = events[0] + event.start = manifestation.happens_at + event.end = manifestation.happens_at + timedelta(seconds=manifestation.duration) + event.title = event_trans.name + event.user = self.default_user + + locations = ma_models.EventLocation.objects.filter(title=manifestation.location.name) + if locations: + location = locations[0] + else: + location = ma_models.EventLocation(title=manifestation.location.name) + address = '\n'.join([manifestation.location.address, manifestation.location.postalcode + ' ' + manifestation.location.city]) + location.address = address + location.external_id = manifestation.id + location.clean() + location.save() + event.location = location + event.save() + keyword, _ = Keyword.objects.get_or_create(title=eve_event.event_category.name) + event.keywords.add(AssignedKeyword(keyword=keyword), bulk=False) + + eve_prices = eve_models.PriceManifestation.objects.filter(manifestation=manifestation) + for price in eve_prices: + event_price, c = ma_models.EventPrice.objects.get_or_create(value=float(price.value)) + if event: + if not event_price in event.prices.all(): + event.prices.add(event_price) + + if not first: + event.parent = parent + else: + parent = event + first = False + + event.save() diff --git a/app/organization/core/management/commands/wait-for-db.py b/app/organization/core/management/commands/wait-for-db.py new file mode 100644 index 00000000..e2bacf00 --- /dev/null +++ b/app/organization/core/management/commands/wait-for-db.py @@ -0,0 +1,30 @@ +import os, time + +from optparse import make_option +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.db import connections + + +class Command(BaseCommand): + help = "wait for default DB connection" + + db_name = 'default' + N = 20 + + def handle(self, *args, **options): + i = 0 + connected = False + db_conn = connections[self.db_name] + while not connected: + try: + c = db_conn.cursor() + connected = True + except: + print('error connecting to DB...') + if i > self.N: + print('...exiting') + raise + print('...retrying') + i += 1 + time.sleep(1) diff --git a/app/organization/core/migrations/0002_auto_20160707_1614.py b/app/organization/core/migrations/0002_auto_20160707_1614.py new file mode 100644 index 00000000..5cebb5ff --- /dev/null +++ b/app/organization/core/migrations/0002_auto_20160707_1614.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-07 14:14 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('organization core', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='basicpage', + name='photo', + ), + migrations.RemoveField( + model_name='basicpage', + name='photo_alignment', + ), + migrations.RemoveField( + model_name='basicpage', + name='photo_card', + ), + migrations.RemoveField( + model_name='basicpage', + name='photo_card_credits', + ), + migrations.RemoveField( + model_name='basicpage', + name='photo_credits', + ), + migrations.RemoveField( + model_name='basicpage', + name='photo_description', + ), + migrations.RemoveField( + model_name='basicpage', + name='photo_slider', + ), + migrations.RemoveField( + model_name='basicpage', + name='photo_slider_credits', + ), + ] diff --git a/app/organization/core/models.py b/app/organization/core/models.py index 885583a4..c0d3ae04 100644 --- a/app/organization/core/models.py +++ b/app/organization/core/models.py @@ -1,15 +1,10 @@ from django.db import models +from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse, reverse_lazy + from mezzanine.pages.models import Page, RichText from mezzanine.core.fields import RichTextField, OrderField, FileField -from django.conf import settings - -from organization.media.models import Photos - -ALIGNMENT_CHOICES = (('left', _('left')), ('right', _('right'))) -MEDIA_BASE_URL = getattr(settings, 'MEDIA_BASE_URL', 'http://medias.ircam.fr/embed/media/') - class Named(models.Model): @@ -29,6 +24,19 @@ class Named(models.Model): return slugify(self.__unicode__()) +class Titled(models.Model): + """Base object with title and description""" + + title = models.CharField(_('title'), max_length=512) + description = models.TextField(_('description'), blank=True) + + class Meta: + abstract = True + + def __unicode__(self): + return self.title + + class SubTitle(models.Model): sub_title = models.TextField(_('sub title'), blank=True, max_length=1024) @@ -37,7 +45,7 @@ class SubTitle(models.Model): abstract = True -class BasicPage(Page, RichText, SubTitle, Photos): +class BasicPage(Page, RichText, SubTitle): class Meta: verbose_name = 'basic page' diff --git a/app/organization/core/templatetags/organization_tags.py b/app/organization/core/templatetags/organization_tags.py new file mode 100644 index 00000000..5f3c9583 --- /dev/null +++ b/app/organization/core/templatetags/organization_tags.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +from mezzanine.pages.models import Page +from mezzanine.blog.models import BlogPost +from mezzanine.template import Library +from mezzanine_agenda.models import Event +from mezzanine.conf import settings +from random import shuffle + +from organization.festival.models import * +from organization.magazine.models import * + +register = Library() + + +@register.filter +def subtract(value, arg): + return value - arg + +@register.as_tag +def featured_edito(*args): + qs = Page.objects.filter(slug="edito") + if qs: + return qs[0].get_content_model() + else: + return None + +@register.as_tag +def featured_events(*args): + featured = Featured.objects.all() + if featured: + return featured[0].events.order_by('start') + return None + +@register.as_tag +def featured(*args): + featured_list = [] + featured = Featured.objects.filter(id=settings.HOME_FEATURED_ID) + if featured: + featured = featured[0] + for post in featured.blogposts.all(): + featured_list.append(post) + for video in featured.videos.all(): + featured_list.append(video) + for artist in featured.artists.all(): + featured_list.append(artist) + for playlist in featured.playlists.all(): + featured_list.append(playlist) + shuffle(featured_list) + return featured_list + +@register.as_tag +def featured_breaking_news_content(*args): + featured = Featured.objects.filter(id=settings.BREAKING_NEWS_FEATURED_ID) + if featured: + featured = featured[0] + news = featured.pages.all() + if news: + return news[0].richtextpage.content + else: + return '' + return '' + +@register.filter +def get_class(obj): + return obj.__class__.__name__ + +@register.filter +def unique_posts(events): + post_list = [] + for event in events: + for post in event.blog_posts.all(): + print(post) + if not post in post_list: + post_list.append(post) + return post_list + +@register.filter +def no_parents(events): + return events.filter(parent=None) diff --git a/app/organization/core/views.py b/app/organization/core/views.py index 91ea44a2..34f51cd6 100644 --- a/app/organization/core/views.py +++ b/app/organization/core/views.py @@ -1,3 +1,8 @@ from django.shortcuts import render -# Create your views here. + +class SlugMixin(object): + + def get_object(self): + objects = self.model.objects.all() + return get_object_or_404(objects, slug=self.kwargs['slug']) diff --git a/app/organization/featured/__init__.py b/app/organization/featured/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/organization/featured/admin.py b/app/organization/featured/admin.py new file mode 100644 index 00000000..1318a91e --- /dev/null +++ b/app/organization/featured/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin + +from organization.featured.models import * + + +class FeaturedAdmin(admin.ModelAdmin): + + model = Featured + list_display = ('__unicode__',) + filter_horizontal = ['events', 'videos', 'articles', 'pages', 'playlists'] + + +admin.site.register(Featured, FeaturedAdmin) diff --git a/app/organization/featured/apps.py b/app/organization/featured/apps.py new file mode 100644 index 00000000..10cae19d --- /dev/null +++ b/app/organization/featured/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class FeaturedConfig(AppConfig): + name = 'featured' diff --git a/app/organization/featured/migrations/0001_initial.py b/app/organization/featured/migrations/0001_initial.py new file mode 100644 index 00000000..00bd148a --- /dev/null +++ b/app/organization/featured/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-07 14:14 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('organization magazine', '0002_article'), + ('mezzanine_agenda', '0014_event_brochure'), + ('organization core', '0002_auto_20160707_1614'), + ] + + operations = [ + migrations.CreateModel( + name='Featured', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=512, verbose_name='name')), + ('description', models.TextField(blank=True, verbose_name='description')), + ('articles', models.ManyToManyField(blank=True, related_name='featured', to='organization magazine.Article', verbose_name='articles')), + ('events', models.ManyToManyField(blank=True, related_name='featured', to='mezzanine_agenda.Event', verbose_name='events')), + ('pages', models.ManyToManyField(blank=True, related_name='featured', to='organization core.BasicPage', verbose_name='pages')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/app/organization/featured/migrations/0002_auto_20160707_1614.py b/app/organization/featured/migrations/0002_auto_20160707_1614.py new file mode 100644 index 00000000..2816f5c3 --- /dev/null +++ b/app/organization/featured/migrations/0002_auto_20160707_1614.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-07 14:14 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('featured', '0001_initial'), + ('organization media', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='featured', + name='playlists', + field=models.ManyToManyField(blank=True, related_name='featured', to='organization media.Playlist', verbose_name='playlists'), + ), + migrations.AddField( + model_name='featured', + name='videos', + field=models.ManyToManyField(blank=True, related_name='featured', to='organization media.Video', verbose_name='videos'), + ), + ] diff --git a/app/organization/featured/migrations/__init__.py b/app/organization/featured/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/organization/featured/models.py b/app/organization/featured/models.py new file mode 100644 index 00000000..ae59ea49 --- /dev/null +++ b/app/organization/featured/models.py @@ -0,0 +1,25 @@ +from __future__ import unicode_literals + +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.core.urlresolvers import reverse, reverse_lazy +from django.conf import settings + +from organization.core.models import * +from organization.magazine.models import * +from organization.media.models import * + +from mezzanine_agenda.models import Event + + +class Featured(Named): + """(Featured description)""" + + pages = models.ManyToManyField(BasicPage, verbose_name=_('pages'), related_name='featured', blank=True) + articles = models.ManyToManyField(Article, verbose_name=_('articles'), related_name='featured', blank=True) + events = models.ManyToManyField(Event, verbose_name=_('events'), related_name='featured', blank=True) + videos = models.ManyToManyField(Video, verbose_name=_('videos'), related_name='featured', blank=True) + playlists = models.ManyToManyField(Playlist, verbose_name=_('playlists'), related_name='featured', blank=True) + + def __unicode__(self): + return self.name diff --git a/app/organization/featured/templatetags/featured_tags.py b/app/organization/featured/templatetags/featured_tags.py new file mode 100644 index 00000000..79e32677 --- /dev/null +++ b/app/organization/featured/templatetags/featured_tags.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +from mezzanine.pages.models import Page +from mezzanine.blog.models import BlogPost +from mezzanine.template import Library +from mezzanine_agenda.models import Event +from mezzanine.conf import settings +from random import shuffle + +from organization.festival.models import * +from organization.featured.models import * + +register = Library() + + +@register.filter +def subtract(value, arg): + return value - arg + +@register.as_tag +def featured_edito(*args): + qs = Page.objects.filter(slug="edito") + if qs: + return qs[0].get_content_model() + else: + return None + +@register.as_tag +def featured_events(*args): + featured = Featured.objects.all() + if featured: + return featured[0].events.order_by('start') + return None + +@register.as_tag +def featured(*args): + featured_list = [] + featured = Featured.objects.filter(id=settings.HOME_FEATURED_ID) + if featured: + featured = featured[0] + for post in featured.blogposts.all(): + featured_list.append(post) + for video in featured.videos.all(): + featured_list.append(video) + for artist in featured.artists.all(): + featured_list.append(artist) + for playlist in featured.playlists.all(): + featured_list.append(playlist) + shuffle(featured_list) + return featured_list + +@register.as_tag +def featured_breaking_news_content(*args): + featured = Featured.objects.filter(id=settings.BREAKING_NEWS_FEATURED_ID) + if featured: + featured = featured[0] + news = featured.pages.all() + if news: + return news[0].richtextpage.content + else: + return '' + return '' + +@register.filter +def get_class(obj): + return obj.__class__.__name__ + +@register.filter +def unique_posts(events): + post_list = [] + for event in events: + for post in event.blog_posts.all(): + print(post) + if not post in post_list: + post_list.append(post) + return post_list + +@register.filter +def no_parents(events): + return events.filter(parent=None) diff --git a/app/organization/featured/tests.py b/app/organization/featured/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/app/organization/featured/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/organization/featured/views.py b/app/organization/featured/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/app/organization/featured/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/app/organization/festival/admin.py b/app/organization/festival/admin.py index 70fb8fcc..eac37c67 100644 --- a/app/organization/festival/admin.py +++ b/app/organization/festival/admin.py @@ -19,50 +19,9 @@ class ArtistAdmin(admin.ModelAdmin): model = Artist -class VideoAdmin(admin.ModelAdmin): - - model = Video - - -class VideoAdminDisplayable(DisplayableAdmin): - - fieldsets = deepcopy(VideoAdmin.fieldsets) - filter_horizontal = ['artists'] - - -class AudioAdmin(admin.ModelAdmin): - - model = Audio - - -class AudioAdminDisplayable(DisplayableAdmin): - - fieldsets = deepcopy(AudioAdmin.fieldsets) - filter_horizontal = ['artists'] - - class ArtistAdminDisplayable(DisplayableAdmin): fieldsets = deepcopy(ArtistAdmin.fieldsets) -class PlaylistAdmin(admin.ModelAdmin): - - model = Playlist - list_display = ('__unicode__',) - filter_horizontal = ['audios'] - - -class FeaturedAdmin(admin.ModelAdmin): - - model = Featured - list_display = ('__unicode__',) - filter_horizontal = ['artists', 'events', 'videos', 'pages', 'blogposts', 'pages', 'playlists'] - - admin.site.register(Artist, ArtistAdminDisplayable) -admin.site.register(Video, VideoAdminDisplayable) -admin.site.register(Audio, AudioAdminDisplayable) -admin.site.register(Playlist, PlaylistAdmin) -admin.site.register(Featured, FeaturedAdmin) -admin.site.register(VideoCategory) diff --git a/app/organization/festival/management/__init__.py b/app/organization/festival/management/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/app/organization/festival/management/commands/__init__.py b/app/organization/festival/management/commands/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/app/organization/festival/management/commands/create-admin-user.py b/app/organization/festival/management/commands/create-admin-user.py deleted file mode 100644 index e7e3cad5..00000000 --- a/app/organization/festival/management/commands/create-admin-user.py +++ /dev/null @@ -1,24 +0,0 @@ -from optparse import make_option -from django.conf import settings -from django.core.management.base import BaseCommand, CommandError -from django.contrib.auth.models import User - - -class Command(BaseCommand): - help = """Create a default admin user if it doesn't exist. - you SHOULD change the password and the email afterwards!""" - - username = 'admin' - password = 'admin' - email = 'root@example.com' - - def handle(self, *args, **options): - admin = User.objects.filter(username=self.username) - if not admin: - user = User(username=self.username) - user.set_password(self.password) - user.email = self.email - user.is_superuser = True - user.is_staff = True - user.save() - print('User ' + self.username + ' created') diff --git a/app/organization/festival/management/commands/festival-sync-eve-events.py b/app/organization/festival/management/commands/festival-sync-eve-events.py deleted file mode 100644 index 070cadbd..00000000 --- a/app/organization/festival/management/commands/festival-sync-eve-events.py +++ /dev/null @@ -1,86 +0,0 @@ -from datetime import datetime, timedelta -from optparse import make_option - -from django.conf import settings -from django.core.management.base import BaseCommand, CommandError -from django.contrib.auth.models import User -from django.core.mail import EmailMessage - -import mezzanine_agenda.models as ma_models -from mezzanine.generic.models import AssignedKeyword, Keyword - -import eve.models as eve_models - - -class Command(BaseCommand): - """Synchronize events from E-vement to mezzanine_agenda""" - - - option_list = BaseCommand.option_list + ( - make_option('-m', '--meta_event', - dest='meta_event', - help='define eve meta_event'), - ) - - default_user = User.objects.get(username='admin') - - def cleanup(self): - # for event in ma_models.Event.objects.all(): - # event.delete() - # for location in ma_models.EventLocation.objects.all(): - # location.delete() - for event_price in ma_models.EventPrice.objects.all(): - event_price.delete() - - def handle(self, *args, **kwargs): - # self.cleanup() - meta_event_name = kwargs.get('meta_event') - meta_trans_all = eve_models.MetaEventTranslation.objects.all() - for meta_trans in meta_trans_all: - if meta_trans.name == meta_event_name: - break - eve_events = eve_models.Event.objects.filter(meta_event=meta_trans.id) - for eve_event in eve_events: - event_trans = eve_models.EventTranslation.objects.filter(id=eve_event, lang='fr')[0] - manifestations = eve_event.manifestations.all().order_by('happens_at') - first = True - for manifestation in manifestations: - events = ma_models.Event.objects.filter(external_id=manifestation.id) - if not events: - event = ma_models.Event(external_id=manifestation.id) - else: - event = events[0] - event.start = manifestation.happens_at - event.end = manifestation.happens_at + timedelta(seconds=manifestation.duration) - event.title = event_trans.name - event.user = self.default_user - - locations = ma_models.EventLocation.objects.filter(title=manifestation.location.name) - if locations: - location = locations[0] - else: - location = ma_models.EventLocation(title=manifestation.location.name) - address = '\n'.join([manifestation.location.address, manifestation.location.postalcode + ' ' + manifestation.location.city]) - location.address = address - location.external_id = manifestation.id - location.clean() - location.save() - event.location = location - event.save() - keyword, _ = Keyword.objects.get_or_create(title=eve_event.event_category.name) - event.keywords.add(AssignedKeyword(keyword=keyword), bulk=False) - - eve_prices = eve_models.PriceManifestation.objects.filter(manifestation=manifestation) - for price in eve_prices: - event_price, c = ma_models.EventPrice.objects.get_or_create(value=float(price.value)) - if event: - if not event_price in event.prices.all(): - event.prices.add(event_price) - - if not first: - event.parent = parent - else: - parent = event - first = False - - event.save() diff --git a/app/organization/festival/management/commands/wait-for-db.py b/app/organization/festival/management/commands/wait-for-db.py deleted file mode 100644 index e2bacf00..00000000 --- a/app/organization/festival/management/commands/wait-for-db.py +++ /dev/null @@ -1,30 +0,0 @@ -import os, time - -from optparse import make_option -from django.conf import settings -from django.core.management.base import BaseCommand, CommandError -from django.db import connections - - -class Command(BaseCommand): - help = "wait for default DB connection" - - db_name = 'default' - N = 20 - - def handle(self, *args, **options): - i = 0 - connected = False - db_conn = connections[self.db_name] - while not connected: - try: - c = db_conn.cursor() - connected = True - except: - print('error connecting to DB...') - if i > self.N: - print('...exiting') - raise - print('...retrying') - i += 1 - time.sleep(1) diff --git a/app/organization/festival/migrations/0001_initial.py b/app/organization/festival/migrations/0001_initial.py index bab8206e..f74881be 100644 --- a/app/organization/festival/migrations/0001_initial.py +++ b/app/organization/festival/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.7 on 2016-07-07 09:13 +# Generated by Django 1.9.7 on 2016-07-07 14:14 from __future__ import unicode_literals from django.db import migrations, models @@ -14,9 +14,6 @@ class Migration(migrations.Migration): dependencies = [ ('sites', '0002_alter_domain_unique'), - ('mezzanine_agenda', '0014_event_brochure'), - ('blog', '0003_auto_20151223_1313'), - ('pages', '0004_auto_20151223_1313'), ] operations = [ @@ -44,159 +41,25 @@ class Migration(migrations.Migration): ('content', mezzanine.core.fields.RichTextField(verbose_name='Content')), ('content_fr', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), ('content_en', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), + ('photo', mezzanine.core.fields.FileField(blank=True, max_length=1024, verbose_name='photo')), + ('photo_credits', models.CharField(blank=True, max_length=255, null=True, verbose_name='photo credits')), + ('photo_alignment', models.CharField(blank=True, choices=[('left', 'left'), ('center', 'center'), ('right', 'right')], default='left', max_length=32, verbose_name='photo alignment')), + ('photo_description', models.TextField(blank=True, verbose_name='photo description')), + ('photo_card', mezzanine.core.fields.FileField(blank=True, max_length=1024, verbose_name='card photo')), + ('photo_card_credits', models.CharField(blank=True, max_length=255, null=True, verbose_name='photo card credits')), + ('photo_slider', mezzanine.core.fields.FileField(blank=True, max_length=1024, verbose_name='slider photo')), + ('photo_slider_credits', models.CharField(blank=True, max_length=255, null=True, verbose_name='photo slider credits')), ('first_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='first name')), ('last_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='last name')), ('bio', mezzanine.core.fields.RichTextField(blank=True, verbose_name='biography')), ('bio_fr', mezzanine.core.fields.RichTextField(blank=True, null=True, verbose_name='biography')), ('bio_en', mezzanine.core.fields.RichTextField(blank=True, null=True, verbose_name='biography')), - ('photo', mezzanine.core.fields.FileField(blank=True, max_length=1024, verbose_name='photo')), - ('photo_credits', models.CharField(blank=True, max_length=255, null=True, verbose_name='photo credits')), - ('photo_alignment', models.CharField(blank=True, choices=[('left', 'left'), ('right', 'right')], default='left', max_length=32, verbose_name='photo alignment')), - ('photo_description', models.TextField(blank=True, verbose_name='photo description')), - ('photo_featured', mezzanine.core.fields.FileField(blank=True, max_length=1024, verbose_name='photo featured')), - ('photo_featured_credits', models.CharField(blank=True, max_length=255, null=True, verbose_name='photo featured credits')), - ('events', models.ManyToManyField(blank=True, related_name='artists', to='mezzanine_agenda.Event', verbose_name='events')), ('site', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')), ], options={ - 'verbose_name': 'artist', 'ordering': ['last_name'], + 'verbose_name': 'artist', }, - bases=(models.Model, mezzanine.utils.models.AdminThumbMixin), - ), - migrations.CreateModel( - name='Audio', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('keywords_string', models.CharField(blank=True, editable=False, max_length=500)), - ('title', models.CharField(max_length=500, verbose_name='Title')), - ('title_fr', models.CharField(max_length=500, null=True, verbose_name='Title')), - ('title_en', models.CharField(max_length=500, null=True, verbose_name='Title')), - ('slug', models.CharField(blank=True, help_text='Leave blank to have the URL auto-generated from the title.', max_length=2000, null=True, verbose_name='URL')), - ('_meta_title', models.CharField(blank=True, help_text='Optional title to be used in the HTML title tag. If left blank, the main title field will be used.', max_length=500, null=True, verbose_name='Title')), - ('description', models.TextField(blank=True, verbose_name='Description')), - ('description_fr', models.TextField(blank=True, null=True, verbose_name='Description')), - ('description_en', models.TextField(blank=True, null=True, verbose_name='Description')), - ('gen_description', models.BooleanField(default=True, help_text='If checked, the description will be automatically generated from content. Uncheck if you want to manually set a custom description.', verbose_name='Generate description')), - ('created', models.DateTimeField(editable=False, null=True)), - ('updated', models.DateTimeField(editable=False, null=True)), - ('status', models.IntegerField(choices=[(1, 'Draft'), (2, 'Published')], default=2, help_text='With Draft chosen, will only be shown for admin users on the site.', verbose_name='Status')), - ('publish_date', models.DateTimeField(blank=True, db_index=True, help_text="With Published chosen, won't be shown until this time", null=True, verbose_name='Published from')), - ('expiry_date', models.DateTimeField(blank=True, help_text="With Published chosen, won't be shown after this time", null=True, verbose_name='Expires on')), - ('short_url', models.URLField(blank=True, null=True)), - ('in_sitemap', models.BooleanField(default=True, verbose_name='Show in sitemap')), - ('content', mezzanine.core.fields.RichTextField(verbose_name='Content')), - ('content_fr', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), - ('content_en', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), - ('media_id', models.CharField(max_length=128, verbose_name='media id')), - ('open_source_url', models.URLField(blank=True, max_length=1024, verbose_name='open source URL')), - ('closed_source_url', models.URLField(blank=True, max_length=1024, verbose_name='closed source URL')), - ('poster_url', models.URLField(blank=True, max_length=1024, verbose_name='poster')), - ('artists', models.ManyToManyField(blank=True, related_name='audios', to='organization festival app.Artist', verbose_name='artists')), - ('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='audios', to='mezzanine_agenda.Event', verbose_name='event')), - ('site', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')), - ], - options={ - 'verbose_name': 'audio', - }, - ), - migrations.CreateModel( - name='Featured', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=512, verbose_name='name')), - ('description', models.TextField(blank=True, verbose_name='description')), - ('artists', models.ManyToManyField(blank=True, related_name='featured', to='organization festival app.Artist', verbose_name='artists')), - ('blogposts', models.ManyToManyField(blank=True, related_name='featured', to='blog.BlogPost', verbose_name='blog posts')), - ('events', models.ManyToManyField(blank=True, related_name='featured', to='mezzanine_agenda.Event', verbose_name='events')), - ('pages', models.ManyToManyField(blank=True, related_name='featured', to='pages.Page', verbose_name='pages')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Playlist', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=512, verbose_name='title')), - ('description', models.TextField(blank=True, verbose_name='description')), - ('audios', models.ManyToManyField(blank=True, related_name='playlists', to='organization festival app.Audio', verbose_name='audios')), - ('event', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='playlists', to='mezzanine_agenda.Event', verbose_name='event')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Video', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('keywords_string', models.CharField(blank=True, editable=False, max_length=500)), - ('title', models.CharField(max_length=500, verbose_name='Title')), - ('title_fr', models.CharField(max_length=500, null=True, verbose_name='Title')), - ('title_en', models.CharField(max_length=500, null=True, verbose_name='Title')), - ('slug', models.CharField(blank=True, help_text='Leave blank to have the URL auto-generated from the title.', max_length=2000, null=True, verbose_name='URL')), - ('_meta_title', models.CharField(blank=True, help_text='Optional title to be used in the HTML title tag. If left blank, the main title field will be used.', max_length=500, null=True, verbose_name='Title')), - ('description', models.TextField(blank=True, verbose_name='Description')), - ('description_fr', models.TextField(blank=True, null=True, verbose_name='Description')), - ('description_en', models.TextField(blank=True, null=True, verbose_name='Description')), - ('gen_description', models.BooleanField(default=True, help_text='If checked, the description will be automatically generated from content. Uncheck if you want to manually set a custom description.', verbose_name='Generate description')), - ('created', models.DateTimeField(editable=False, null=True)), - ('updated', models.DateTimeField(editable=False, null=True)), - ('status', models.IntegerField(choices=[(1, 'Draft'), (2, 'Published')], default=2, help_text='With Draft chosen, will only be shown for admin users on the site.', verbose_name='Status')), - ('publish_date', models.DateTimeField(blank=True, db_index=True, help_text="With Published chosen, won't be shown until this time", null=True, verbose_name='Published from')), - ('expiry_date', models.DateTimeField(blank=True, help_text="With Published chosen, won't be shown after this time", null=True, verbose_name='Expires on')), - ('short_url', models.URLField(blank=True, null=True)), - ('in_sitemap', models.BooleanField(default=True, verbose_name='Show in sitemap')), - ('content', mezzanine.core.fields.RichTextField(verbose_name='Content')), - ('content_fr', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), - ('content_en', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), - ('media_id', models.CharField(max_length=128, verbose_name='media id')), - ('open_source_url', models.URLField(blank=True, max_length=1024, verbose_name='open source URL')), - ('closed_source_url', models.URLField(blank=True, max_length=1024, verbose_name='closed source URL')), - ('poster_url', models.URLField(blank=True, max_length=1024, verbose_name='poster')), - ('artists', models.ManyToManyField(blank=True, related_name='videos', to='organization festival app.Artist', verbose_name='artists')), - ], - options={ - 'verbose_name': 'video', - }, - ), - migrations.CreateModel( - name='VideoCategory', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=500, verbose_name='Title')), - ('slug', models.CharField(blank=True, help_text='Leave blank to have the URL auto-generated from the title.', max_length=2000, null=True, verbose_name='URL')), - ('site', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')), - ], - options={ - 'verbose_name': 'video category', - }, - ), - migrations.AddField( - model_name='video', - name='category', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='videos', to='organization festival app.VideoCategory', verbose_name='category'), - ), - migrations.AddField( - model_name='video', - name='event', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='videos', to='mezzanine_agenda.Event', verbose_name='event'), - ), - migrations.AddField( - model_name='video', - name='site', - field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), - ), - migrations.AddField( - model_name='featured', - name='playlists', - field=models.ManyToManyField(blank=True, related_name='featured', to='organization festival app.Playlist', verbose_name='playlists'), - ), - migrations.AddField( - model_name='featured', - name='videos', - field=models.ManyToManyField(blank=True, related_name='featured', to='organization festival app.Video', verbose_name='videos'), + bases=(mezzanine.utils.models.AdminThumbMixin, models.Model), ), ] diff --git a/app/organization/festival/models.py b/app/organization/festival/models.py index 5703b40b..430ac4bf 100644 --- a/app/organization/festival/models.py +++ b/app/organization/festival/models.py @@ -9,56 +9,19 @@ from mezzanine.utils.models import AdminThumbMixin, upload_to from mezzanine.blog.models import BlogPost from mezzanine.pages.models import Page -from mezzanine_agenda.models import Event +from organization.core.models import * +from organization.media.models import * import requests from pyquery import PyQuery as pq -ALIGNMENT_CHOICES = (('left', _('left')), ('right', _('right'))) -MEDIA_BASE_URL = getattr(settings, 'MEDIA_BASE_URL', 'http://medias.ircam.fr/embed/media/') - - - -class BaseNameModel(models.Model): - """Base object with name and description""" - - name = models.CharField(_('name'), max_length=512) - description = models.TextField(_('description'), blank=True) - - class Meta: - abstract = True - - def __unicode__(self): - return self.name - -class BaseTitleModel(models.Model): - """Base object with title and description""" - - title = models.CharField(_('title'), max_length=512) - description = models.TextField(_('description'), blank=True) - - class Meta: - abstract = True - - def __unicode__(self): - return self.title - - -class Artist(Displayable, RichText, AdminThumbMixin): +class Artist(Displayable, RichText, AdminThumbMixin, Photo): """Artist""" first_name = models.CharField(_('first name'), max_length=255, blank=True, null=True) last_name = models.CharField(_('last name'), max_length=255, blank=True, null=True) bio = RichTextField(_('biography'), blank=True) - photo = FileField(_('photo'), upload_to='images/photos', max_length=1024, blank=True, format="Image") - photo_credits = models.CharField(_('photo credits'), max_length=255, blank=True, null=True) - photo_alignment = models.CharField(_('photo alignment'), choices=ALIGNMENT_CHOICES, max_length=32, default="left", blank=True) - photo_description = models.TextField(_('photo description'), blank=True) - photo_featured = FileField(_('photo featured'), upload_to='images/photos', max_length=1024, blank=True, format="Image") - photo_featured_credits = models.CharField(_('photo featured credits'), max_length=255, blank=True, null=True) - events = models.ManyToManyField(Event, related_name='artists', verbose_name=_('events'), blank=True) - search_fields = ("title", "bio") class Meta: @@ -71,142 +34,3 @@ class Artist(Displayable, RichText, AdminThumbMixin): @property def name(self): return self.title - - def get_absolute_url(self): - return reverse("festival-artist-detail", kwargs={'slug': self.slug}) - - def set_names(self): - names = self.title.split(' ') - if len(names) == 1: - self.first_name = '' - self.last_name = names[0] - elif len(names) == 2: - self.first_name = names[0] - self.last_name = names[1] - else: - self.first_name = names[0] - self.last_name = ' '.join(names[1:]) - - def clean(self): - super(Artist, self).clean() - self.set_names() - - def save(self, *args, **kwargs): - self.set_names() - super(Artist, self).save(*args, **kwargs) - - @property - def featured_image(self): - if self.photo_featured: - return self.photo_featured - else: - return self.photo - - -class Media(Displayable, RichText): - """Media""" - - media_id = models.CharField(_('media id'), max_length=128) - open_source_url = models.URLField(_('open source URL'), max_length=1024, blank=True) - closed_source_url = models.URLField(_('closed source URL'), max_length=1024, blank=True) - poster_url = models.URLField(_('poster'), max_length=1024, blank=True) - - class Meta: - abstract = True - - def __unicode__(self): - return self.title - - @property - def uri(self): - return MEDIA_BASE_URL + self.media_id - - def get_html(self): - r = requests.get(self.uri) - return r.content - - def clean(self): - super(Media, self).clean() - self.q = pq(self.get_html()) - sources = self.q('source') - for source in sources: - if self.open_source_mime_type in source.attrib['type']: - self.open_source_url = source.attrib['src'] - elif self.closed_source_mime_type in source.attrib['type']: - self.closed_source_url = source.attrib['src'] - video = self.q('video') - if len(video): - if 'poster' in video[0].attrib.keys(): - self.poster_url = video[0].attrib['poster'] - - -class Audio(Media): - """Audio""" - - open_source_mime_type = 'audio/ogg' - closed_source_mime_type = 'audio/mp4' - - event = models.ForeignKey(Event, related_name='audios', verbose_name=_('event'), blank=True, null=True, on_delete=models.SET_NULL) - artists = models.ManyToManyField(Artist, verbose_name=_('artists'), related_name='audios', blank=True) - - class Meta: - verbose_name = _('audio') - - def get_absolute_url(self): - return reverse("festival-video-detail", kwargs={"slug": self.slug}) - - -class Video(Media): - """Video""" - - open_source_mime_type = 'video/webm' - closed_source_mime_type = 'video/mp4' - - event = models.ForeignKey(Event, related_name='videos', verbose_name=_('event'), blank=True, null=True, on_delete=models.SET_NULL) - category = models.ForeignKey('VideoCategory', related_name='videos', verbose_name=_('category'), blank=True, null=True, on_delete=models.SET_NULL) - artists = models.ManyToManyField(Artist, verbose_name=_('artists'), related_name='videos', blank=True) - - class Meta: - verbose_name = _('video') - - @property - def html(self): - #TODO: get html content from medias.ircam.fr with request module - pass - - def get_absolute_url(self): - return reverse("festival-video-detail", kwargs={"slug": self.slug}) - - -class Playlist(BaseTitleModel): - """(Playlist description)""" - - audios = models.ManyToManyField(Audio, verbose_name=_('audios'), related_name='playlists', blank=True) - event = models.ForeignKey(Event, related_name='playlists', verbose_name=_('event'), blank=True, null=True, on_delete=models.SET_NULL) - - def __str__(self): - return self.title - - -class Featured(BaseNameModel): - """(Featured description)""" - - artists = models.ManyToManyField(Artist, verbose_name=_('artists'), related_name='featured', blank=True) - events = models.ManyToManyField(Event, verbose_name=_('events'), related_name='featured', blank=True) - videos = models.ManyToManyField(Video, verbose_name=_('videos'), related_name='featured', blank=True) - blogposts = models.ManyToManyField(BlogPost, verbose_name=_('blog posts'), related_name='featured', blank=True) - pages = models.ManyToManyField(Page, verbose_name=_('pages'), related_name='featured', blank=True) - playlists = models.ManyToManyField(Playlist, verbose_name=_('playlists'), related_name='featured', blank=True) - - def __unicode__(self): - return self.name - - -class VideoCategory(Slugged): - """Video Category""" - - class Meta: - verbose_name = _('video category') - - def count(self): - return self.videos.published().count()+1 diff --git a/app/organization/festival/templatetags/festival_tags.py b/app/organization/festival/templatetags/festival_tags.py index 824c59a2..5f3c9583 100644 --- a/app/organization/festival/templatetags/festival_tags.py +++ b/app/organization/festival/templatetags/festival_tags.py @@ -7,6 +7,7 @@ from mezzanine.conf import settings from random import shuffle from organization.festival.models import * +from organization.magazine.models import * register = Library() diff --git a/app/organization/festival/translation.py b/app/organization/festival/translation.py index fabf2bf0..b9e4936b 100644 --- a/app/organization/festival/translation.py +++ b/app/organization/festival/translation.py @@ -1,32 +1,8 @@ from modeltranslation.translator import register, TranslationOptions -from mezzanine_agenda.models import Event, EventLocation from organization.festival.models import * - -@register(Event) -class EventTranslationOptions(TranslationOptions): - - fields = ('title', 'description', 'content') - -@register(EventLocation) -class EventLocationTranslationOptions(TranslationOptions): - - fields = ('description',) - @register(Artist) class ArtistTranslationOptions(TranslationOptions): fields = ('title', 'description', 'bio', 'content') - - -@register(Video) -class VideoTranslationOptions(TranslationOptions): - - fields = ('title', 'description', 'content') - - -@register(Audio) -class AudioTranslationOptions(TranslationOptions): - - fields = ('title', 'description', 'content') diff --git a/app/organization/festival/urls.py b/app/organization/festival/urls.py index dbf17f42..920e5eff 100644 --- a/app/organization/festival/urls.py +++ b/app/organization/festival/urls.py @@ -13,7 +13,4 @@ from organization.festival.views import * urlpatterns = [ url(r'^artists/$', ArtistListView.as_view(), name="festival-artist-list"), url(r'^artists/detail/(?P.*)/$', ArtistDetailView.as_view(), name="festival-artist-detail"), - url(r'^videos/$', VideoListView.as_view(), name="festival-video-list"), - url(r'^videos/detail/(?P.*)/$', VideoDetailView.as_view(), name="festival-video-detail"), - url(r'^videos/category/(?P.*)/$', VideoListCategoryView.as_view(), name="festival-video-list-category"), ] diff --git a/app/organization/festival/views.py b/app/organization/festival/views.py index 0c55cf80..beba1813 100644 --- a/app/organization/festival/views.py +++ b/app/organization/festival/views.py @@ -6,12 +6,7 @@ from django.shortcuts import get_object_or_404 from organization.festival.models import * from mezzanine_agenda.models import EventLocation - -class SlugMixin(object): - - def get_object(self): - objects = self.model.objects.all() - return get_object_or_404(objects, slug=self.kwargs['slug']) +from organization.core.views import * class ArtistListView(ListView): @@ -36,40 +31,3 @@ class ArtistDetailView(SlugMixin, DetailView): def get_context_data(self, **kwargs): context = super(ArtistDetailView, self).get_context_data(**kwargs) return context - - -class VideoListView(ListView): - - model = Video - template_name='festival/video_list.html' - - def get_queryset(self, **kwargs): - return self.model.objects.published() - - def get_context_data(self, **kwargs): - context = super(VideoListView, self).get_context_data(**kwargs) - context['categories'] = VideoCategory.objects.all() - return context - - -class VideoDetailView(SlugMixin, DetailView): - - model = Video - template_name='festival/video_detail.html' - context_object_name = 'video' - - def get_context_data(self, **kwargs): - context = super(VideoDetailView, self).get_context_data(**kwargs) - return context - - -class VideoListCategoryView(VideoListView): - - def get_queryset(self): - self.category = VideoCategory.objects.get(slug=self.kwargs['slug']) - return self.model.objects.filter(category=self.category) - - def get_context_data(self, **kwargs): - context = super(VideoListCategoryView, self).get_context_data(**kwargs) - context['category'] = self.category - return context diff --git a/app/organization/magazine/admin.py b/app/organization/magazine/admin.py index 8c38f3f3..aa592f08 100644 --- a/app/organization/magazine/admin.py +++ b/app/organization/magazine/admin.py @@ -1,3 +1,3 @@ from django.contrib import admin -# Register your models here. +from organization.magazine.models import * diff --git a/app/organization/magazine/models.py b/app/organization/magazine/models.py index 53dd878a..412eaf01 100644 --- a/app/organization/magazine/models.py +++ b/app/organization/magazine/models.py @@ -1,9 +1,16 @@ from __future__ import unicode_literals + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse, reverse_lazy -from mezzanine.blog.models import BlogPost -from organization.core.models import Named + +from mezzanine.blog.models import * + +from mezzanine_agenda.models import Event + +from organization.magazine.models import * +from organization.core.models import * + class Article(BlogPost): @@ -12,6 +19,7 @@ class Article(BlogPost): class Meta: verbose_name = _('article') + class Category(Named): """(Category description)""" diff --git a/app/organization/media/admin.py b/app/organization/media/admin.py index 8c38f3f3..9ea7f15c 100644 --- a/app/organization/media/admin.py +++ b/app/organization/media/admin.py @@ -1,3 +1,39 @@ +from copy import deepcopy from django.contrib import admin +from mezzanine.core.admin import DisplayableAdmin, OwnableAdmin +from organization.media.models import * -# Register your models here. + +class VideoAdmin(admin.ModelAdmin): + + model = Video + + +class VideoAdminDisplayable(DisplayableAdmin): + + fieldsets = deepcopy(VideoAdmin.fieldsets) + #filter_horizontal = ['artists'] + + +class AudioAdmin(admin.ModelAdmin): + + model = Audio + + +class AudioAdminDisplayable(DisplayableAdmin): + + fieldsets = deepcopy(AudioAdmin.fieldsets) + # filter_horizontal = ['artists'] + + +class PlaylistAdmin(admin.ModelAdmin): + + model = Playlist + list_display = ('__unicode__',) + filter_horizontal = ['audios'] + + +admin.site.register(Video, VideoAdminDisplayable) +admin.site.register(Audio, AudioAdminDisplayable) +admin.site.register(Playlist, PlaylistAdmin) +admin.site.register(VideoCategory) diff --git a/app/organization/media/migrations/0001_initial.py b/app/organization/media/migrations/0001_initial.py new file mode 100644 index 00000000..32ee5682 --- /dev/null +++ b/app/organization/media/migrations/0001_initial.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-07 14:14 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import mezzanine.core.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('sites', '0002_alter_domain_unique'), + ] + + operations = [ + migrations.CreateModel( + name='Audio', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('keywords_string', models.CharField(blank=True, editable=False, max_length=500)), + ('title', models.CharField(max_length=500, verbose_name='Title')), + ('title_fr', models.CharField(max_length=500, null=True, verbose_name='Title')), + ('title_en', models.CharField(max_length=500, null=True, verbose_name='Title')), + ('slug', models.CharField(blank=True, help_text='Leave blank to have the URL auto-generated from the title.', max_length=2000, null=True, verbose_name='URL')), + ('_meta_title', models.CharField(blank=True, help_text='Optional title to be used in the HTML title tag. If left blank, the main title field will be used.', max_length=500, null=True, verbose_name='Title')), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('description_fr', models.TextField(blank=True, null=True, verbose_name='Description')), + ('description_en', models.TextField(blank=True, null=True, verbose_name='Description')), + ('gen_description', models.BooleanField(default=True, help_text='If checked, the description will be automatically generated from content. Uncheck if you want to manually set a custom description.', verbose_name='Generate description')), + ('created', models.DateTimeField(editable=False, null=True)), + ('updated', models.DateTimeField(editable=False, null=True)), + ('status', models.IntegerField(choices=[(1, 'Draft'), (2, 'Published')], default=2, help_text='With Draft chosen, will only be shown for admin users on the site.', verbose_name='Status')), + ('publish_date', models.DateTimeField(blank=True, db_index=True, help_text="With Published chosen, won't be shown until this time", null=True, verbose_name='Published from')), + ('expiry_date', models.DateTimeField(blank=True, help_text="With Published chosen, won't be shown after this time", null=True, verbose_name='Expires on')), + ('short_url', models.URLField(blank=True, null=True)), + ('in_sitemap', models.BooleanField(default=True, verbose_name='Show in sitemap')), + ('content', mezzanine.core.fields.RichTextField(verbose_name='Content')), + ('content_fr', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), + ('content_en', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), + ('media_id', models.CharField(max_length=128, verbose_name='media id')), + ('open_source_url', models.URLField(blank=True, max_length=1024, verbose_name='open source URL')), + ('closed_source_url', models.URLField(blank=True, max_length=1024, verbose_name='closed source URL')), + ('poster_url', models.URLField(blank=True, max_length=1024, verbose_name='poster')), + ('site', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')), + ], + options={ + 'verbose_name': 'audio', + }, + ), + migrations.CreateModel( + name='Playlist', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=512, verbose_name='title')), + ('description', models.TextField(blank=True, verbose_name='description')), + ('audios', models.ManyToManyField(blank=True, related_name='playlists', to='organization media.Audio', verbose_name='audios')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Video', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('keywords_string', models.CharField(blank=True, editable=False, max_length=500)), + ('title', models.CharField(max_length=500, verbose_name='Title')), + ('title_fr', models.CharField(max_length=500, null=True, verbose_name='Title')), + ('title_en', models.CharField(max_length=500, null=True, verbose_name='Title')), + ('slug', models.CharField(blank=True, help_text='Leave blank to have the URL auto-generated from the title.', max_length=2000, null=True, verbose_name='URL')), + ('_meta_title', models.CharField(blank=True, help_text='Optional title to be used in the HTML title tag. If left blank, the main title field will be used.', max_length=500, null=True, verbose_name='Title')), + ('description', models.TextField(blank=True, verbose_name='Description')), + ('description_fr', models.TextField(blank=True, null=True, verbose_name='Description')), + ('description_en', models.TextField(blank=True, null=True, verbose_name='Description')), + ('gen_description', models.BooleanField(default=True, help_text='If checked, the description will be automatically generated from content. Uncheck if you want to manually set a custom description.', verbose_name='Generate description')), + ('created', models.DateTimeField(editable=False, null=True)), + ('updated', models.DateTimeField(editable=False, null=True)), + ('status', models.IntegerField(choices=[(1, 'Draft'), (2, 'Published')], default=2, help_text='With Draft chosen, will only be shown for admin users on the site.', verbose_name='Status')), + ('publish_date', models.DateTimeField(blank=True, db_index=True, help_text="With Published chosen, won't be shown until this time", null=True, verbose_name='Published from')), + ('expiry_date', models.DateTimeField(blank=True, help_text="With Published chosen, won't be shown after this time", null=True, verbose_name='Expires on')), + ('short_url', models.URLField(blank=True, null=True)), + ('in_sitemap', models.BooleanField(default=True, verbose_name='Show in sitemap')), + ('content', mezzanine.core.fields.RichTextField(verbose_name='Content')), + ('content_fr', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), + ('content_en', mezzanine.core.fields.RichTextField(null=True, verbose_name='Content')), + ('media_id', models.CharField(max_length=128, verbose_name='media id')), + ('open_source_url', models.URLField(blank=True, max_length=1024, verbose_name='open source URL')), + ('closed_source_url', models.URLField(blank=True, max_length=1024, verbose_name='closed source URL')), + ('poster_url', models.URLField(blank=True, max_length=1024, verbose_name='poster')), + ], + options={ + 'verbose_name': 'video', + }, + ), + migrations.CreateModel( + name='VideoCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=500, verbose_name='Title')), + ('slug', models.CharField(blank=True, help_text='Leave blank to have the URL auto-generated from the title.', max_length=2000, null=True, verbose_name='URL')), + ('site', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')), + ], + options={ + 'verbose_name': 'video category', + }, + ), + migrations.AddField( + model_name='video', + name='category', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='videos', to='organization media.VideoCategory', verbose_name='category'), + ), + migrations.AddField( + model_name='video', + name='site', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'), + ), + ] diff --git a/app/organization/media/models.py b/app/organization/media/models.py index 3e1ad397..c4dfffa0 100644 --- a/app/organization/media/models.py +++ b/app/organization/media/models.py @@ -7,10 +7,15 @@ from mezzanine.core.models import RichText, Displayable, Slugged from mezzanine.core.fields import RichTextField, OrderField, FileField from mezzanine.utils.models import AdminThumbMixin, upload_to +from mezzanine_agenda.models import Event +from organization.core.models import * + + ALIGNMENT_CHOICES = (('left', _('left')), ('center', _('center')), ('right', _('right'))) +MEDIA_BASE_URL = getattr(settings, 'MEDIA_BASE_URL', 'http://medias.ircam.fr/embed/media/') -class Photos(models.Model): +class Photo(models.Model): """Photo bundle with credits""" photo = FileField(_('photo'), upload_to='images/photos', max_length=1024, blank=True, format="Image") @@ -26,3 +31,98 @@ class Photos(models.Model): class Meta: abstract = True + + @property + def card(self): + if self.photo_card: + return self.photo_card + else: + return self.photo + + +class Media(Displayable, RichText): + """Media""" + + media_id = models.CharField(_('media id'), max_length=128) + open_source_url = models.URLField(_('open source URL'), max_length=1024, blank=True) + closed_source_url = models.URLField(_('closed source URL'), max_length=1024, blank=True) + poster_url = models.URLField(_('poster'), max_length=1024, blank=True) + + class Meta: + abstract = True + + def __unicode__(self): + return self.title + + @property + def uri(self): + return MEDIA_BASE_URL + self.media_id + + def get_html(self): + r = requests.get(self.uri) + return r.content + + def clean(self): + super(Media, self).clean() + self.q = pq(self.get_html()) + sources = self.q('source') + for source in sources: + if self.open_source_mime_type in source.attrib['type']: + self.open_source_url = source.attrib['src'] + elif self.closed_source_mime_type in source.attrib['type']: + self.closed_source_url = source.attrib['src'] + video = self.q('video') + if len(video): + if 'poster' in video[0].attrib.keys(): + self.poster_url = video[0].attrib['poster'] + + +class Audio(Media): + """Audio""" + + open_source_mime_type = 'audio/ogg' + closed_source_mime_type = 'audio/mp4' + + class Meta: + verbose_name = _('audio') + + def get_absolute_url(self): + return reverse("festival-video-detail", kwargs={"slug": self.slug}) + + +class Video(Media): + """Video""" + + open_source_mime_type = 'video/webm' + closed_source_mime_type = 'video/mp4' + category = models.ForeignKey('VideoCategory', related_name='videos', verbose_name=_('category'), blank=True, null=True, on_delete=models.SET_NULL) + + class Meta: + verbose_name = _('video') + + @property + def html(self): + #TODO: get html content from medias.ircam.fr with request module + pass + + def get_absolute_url(self): + return reverse("festival-video-detail", kwargs={"slug": self.slug}) + + +class VideoCategory(Slugged): + """Video Category""" + + class Meta: + verbose_name = _('video category') + + def count(self): + return self.videos.published().count()+1 + + +class Playlist(Titled): + """(Playlist description)""" + + audios = models.ManyToManyField('Audio', verbose_name=_('audios'), related_name='playlists', blank=True) + + def __str__(self): + return self.title diff --git a/app/organization/media/translation.py b/app/organization/media/translation.py new file mode 100644 index 00000000..bd097142 --- /dev/null +++ b/app/organization/media/translation.py @@ -0,0 +1,15 @@ +from modeltranslation.translator import translator, register, TranslationOptions + +from organization.media.models import * + + +@register(Video) +class VideoTranslationOptions(TranslationOptions): + + fields = ('title', 'description', 'content') + + +@register(Audio) +class AudioTranslationOptions(TranslationOptions): + + fields = ('title', 'description', 'content') diff --git a/app/organization/media/urls.py b/app/organization/media/urls.py new file mode 100644 index 00000000..50ba7290 --- /dev/null +++ b/app/organization/media/urls.py @@ -0,0 +1,17 @@ +from __future__ import unicode_literals + +from django.conf.urls import patterns, include, url +from django.conf.urls.i18n import i18n_patterns +from django.contrib import admin + +from mezzanine.core.views import direct_to_template +from mezzanine.conf import settings + +from organization.festival.views import * + + +urlpatterns = [ + url(r'^videos/$', VideoListView.as_view(), name="festival-video-list"), + url(r'^videos/detail/(?P.*)/$', VideoDetailView.as_view(), name="festival-video-detail"), + url(r'^videos/category/(?P.*)/$', VideoListCategoryView.as_view(), name="festival-video-list-category"), +] diff --git a/app/organization/media/views.py b/app/organization/media/views.py index 91ea44a2..933be23d 100644 --- a/app/organization/media/views.py +++ b/app/organization/media/views.py @@ -1,3 +1,41 @@ from django.shortcuts import render -# Create your views here. +from organization.media.models import * +from organization.core.views import * + + +class VideoListView(ListView): + + model = Video + template_name='festival/video_list.html' + + def get_queryset(self, **kwargs): + return self.model.objects.published() + + def get_context_data(self, **kwargs): + context = super(VideoListView, self).get_context_data(**kwargs) + context['categories'] = VideoCategory.objects.all() + return context + + +class VideoDetailView(SlugMixin, DetailView): + + model = Video + template_name='festival/video_detail.html' + context_object_name = 'video' + + def get_context_data(self, **kwargs): + context = super(VideoDetailView, self).get_context_data(**kwargs) + return context + + +class VideoListCategoryView(VideoListView): + + def get_queryset(self): + self.category = VideoCategory.objects.get(slug=self.kwargs['slug']) + return self.model.objects.filter(category=self.category) + + def get_context_data(self, **kwargs): + context = super(VideoListCategoryView, self).get_context_data(**kwargs) + context['category'] = self.category + return context diff --git a/app/organization/structure/migrations/0002_auto_20160707_1614.py b/app/organization/structure/migrations/0002_auto_20160707_1614.py new file mode 100644 index 00000000..07f60e12 --- /dev/null +++ b/app/organization/structure/migrations/0002_auto_20160707_1614.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-07 14:14 +from __future__ import unicode_literals + +from django.db import migrations +import mezzanine.core.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('organization structure', '0001_initial'), + ] + + operations = [ + migrations.DeleteModel( + name='Modelname', + ), + migrations.AlterModelOptions( + name='person', + options={'ordering': ['last_name'], 'verbose_name': 'person'}, + ), + migrations.AddField( + model_name='person', + name='bio', + field=mezzanine.core.fields.RichTextField(blank=True, verbose_name='biography'), + ), + ] diff --git a/app/organization/structure/models.py b/app/organization/structure/models.py index d126734d..139ccfe1 100644 --- a/app/organization/structure/models.py +++ b/app/organization/structure/models.py @@ -21,7 +21,7 @@ from mezzanine.utils.models import AdminThumbMixin, upload_to from django_countries.fields import CountryField -from organization.media.models import Photos +from organization.media.models import Photo from organization.core.models import Named # Hack to have these strings translated @@ -100,7 +100,7 @@ class Team(Named): return u"Team" -class Person(Displayable, RichText, AdminThumbMixin, Photos): +class Person(Displayable, RichText, AdminThumbMixin, Photo): """(Person description)""" user = models.ForeignKey(User, verbose_name=_('user'), blank=True, null=True, on_delete=models.SET_NULL) @@ -110,10 +110,38 @@ class Person(Displayable, RichText, AdminThumbMixin, Photos): last_name = models.CharField(_('last name'), max_length=255, blank=True, null=True) birthday = models.DateField(_('birthday'), blank=True) organization = models.ForeignKey('Organization', verbose_name=_('organization'), blank=True, null=True, on_delete=models.SET_NULL) + bio = RichTextField(_('biography'), blank=True) + + class Meta: + verbose_name = _('person') + ordering = ['last_name',] def __unicode__(self): return ' '.join((self.user.first_name, self.user.last_name)) + # def get_absolute_url(self): + # return reverse("festival-artist-detail", kwargs={'slug': self.slug}) + + def set_names(self): + names = self.title.split(' ') + if len(names) == 1: + self.first_name = '' + self.last_name = names[0] + elif len(names) == 2: + self.first_name = names[0] + self.last_name = names[1] + else: + self.first_name = names[0] + self.last_name = ' '.join(names[1:]) + + def clean(self): + super(Person, self).clean() + self.set_names() + + def save(self, *args, **kwargs): + self.set_names() + super(Person, self).save(*args, **kwargs) + class Nationality(models.Model): """(Nationality description)""" @@ -172,11 +200,3 @@ class Activity(models.Model): def __unicode__(self): return ' - '.join((self.person, self.role, self.date_begin, self.date_end)) - - -class Modelname(models.Model): - """( description)""" - - - def __unicode__(self): - return u"" diff --git a/app/settings.py b/app/settings.py index eaa2f40d..c601f7ad 100644 --- a/app/settings.py +++ b/app/settings.py @@ -229,6 +229,7 @@ INSTALLED_APPS = [ "organization.magazine", "organization.media", "organization.project", + "organization.featured", ] diff --git a/app/templates/agenda/event_booking.html b/app/templates/agenda/event_booking.html index 05812b25..880ded65 100644 --- a/app/templates/agenda/event_booking.html +++ b/app/templates/agenda/event_booking.html @@ -1,5 +1,5 @@ {% extends "agenda/event_detail.html" %} -{% load mezzanine_tags comment_tags keyword_tags rating_tags i18n future disqus_tags event_tags festival_tags %} +{% load mezzanine_tags comment_tags keyword_tags rating_tags i18n future disqus_tags event_tags featured_tags %} {% block event_detail_postedby %} {% endblock %} diff --git a/app/templates/agenda/event_detail.html b/app/templates/agenda/event_detail.html index 4eb5e14a..e6df9efe 100644 --- a/app/templates/agenda/event_detail.html +++ b/app/templates/agenda/event_detail.html @@ -1,5 +1,5 @@ {% extends "agenda/event_list.html" %} -{% load mezzanine_tags comment_tags keyword_tags rating_tags i18n future disqus_tags event_tags festival_tags %} +{% load mezzanine_tags comment_tags keyword_tags rating_tags i18n future disqus_tags event_tags featured_tags %} {% block meta_title %}{{ event.meta_title }}{% endblock %} diff --git a/app/templates/agenda/event_iframe.html b/app/templates/agenda/event_iframe.html index 769f91d4..61f0befd 100644 --- a/app/templates/agenda/event_iframe.html +++ b/app/templates/agenda/event_iframe.html @@ -1,5 +1,5 @@ {% extends "agenda/event_detail.html" %} -{% load mezzanine_tags comment_tags keyword_tags rating_tags i18n future disqus_tags event_tags festival_tags %} +{% load mezzanine_tags comment_tags keyword_tags rating_tags i18n future disqus_tags event_tags featured_tags %} {% block title %} {{ title }} diff --git a/app/templates/agenda/includes/event_metainfo.html b/app/templates/agenda/includes/event_metainfo.html index adfbb11c..c836e03d 100644 --- a/app/templates/agenda/includes/event_metainfo.html +++ b/app/templates/agenda/includes/event_metainfo.html @@ -1,4 +1,4 @@ -{% load i18n mezzanine_tags event_tags festival_tags %} +{% load i18n mezzanine_tags event_tags featured_tags %}
{{ event.start }} diff --git a/app/templates/agenda/includes/event_metainfo_slider.html b/app/templates/agenda/includes/event_metainfo_slider.html index adfda788..a748baff 100644 --- a/app/templates/agenda/includes/event_metainfo_slider.html +++ b/app/templates/agenda/includes/event_metainfo_slider.html @@ -1,4 +1,4 @@ -{% load i18n mezzanine_tags event_tags festival_tags %} +{% load i18n mezzanine_tags event_tags featured_tags %}
diff --git a/app/templates/base.html b/app/templates/base.html index 1cf860e8..7b35715f 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,6 +1,6 @@ -{% load i18n pages_tags mezzanine_tags staticfiles keyword_tags event_tags festival_tags %} +{% load i18n pages_tags mezzanine_tags staticfiles keyword_tags event_tags featured_tags %} {% get_language_info_list for LANGUAGES as languages %} diff --git a/app/templates/blog/blog_post_detail.html b/app/templates/blog/blog_post_detail.html index d052deee..9cb3f536 100644 --- a/app/templates/blog/blog_post_detail.html +++ b/app/templates/blog/blog_post_detail.html @@ -1,5 +1,5 @@ {% extends "blog/blog_post_list.html" %} -{% load mezzanine_tags comment_tags keyword_tags rating_tags i18n disqus_tags festival_tags %} +{% load mezzanine_tags comment_tags keyword_tags rating_tags i18n disqus_tags featured_tags %} {% block meta_title %}{{ blog_post.meta_title }}{% endblock %} diff --git a/app/templates/includes/slider.html b/app/templates/includes/slider.html index 6c6a887a..fdfca906 100644 --- a/app/templates/includes/slider.html +++ b/app/templates/includes/slider.html @@ -1,4 +1,4 @@ -{% load i18n event_tags festival_tags mezzanine_tags %} +{% load i18n event_tags featured_tags mezzanine_tags %}
    {% featured_events as events %} diff --git a/app/templates/index.html b/app/templates/index.html index a315a7ce..b54288be 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load i18n festival_tags %} +{% load i18n featured_tags %} {% block meta_title %}{% trans "Home" %}{% endblock %} diff --git a/app/templates/pages/basicpage.html b/app/templates/pages/basicpage.html index efa355ff..f36fcf9e 100644 --- a/app/templates/pages/basicpage.html +++ b/app/templates/pages/basicpage.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load i18n mezzanine_tags keyword_tags festival_tags %} +{% load i18n mezzanine_tags keyword_tags featured_tags %} {% block meta_title %}{{ page.meta_title }}{% endblock %} diff --git a/app/templates/pages/page.html b/app/templates/pages/page.html index 141c6013..b56657f4 100644 --- a/app/templates/pages/page.html +++ b/app/templates/pages/page.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load i18n mezzanine_tags keyword_tags festival_tags %} +{% load i18n mezzanine_tags keyword_tags featured_tags %} {% block meta_title %}{{ page.meta_title }}{% endblock %} diff --git a/app/templates/styles.html b/app/templates/styles.html index 0d2f875f..f6f43a6e 100644 --- a/app/templates/styles.html +++ b/app/templates/styles.html @@ -1,5 +1,5 @@ {% extends "base.html" %} -{% load i18n festival_tags %} +{% load i18n featured_tags %} {% block meta_title %}{% trans "Styles" %}{% endblock %} diff --git a/app/templates/templates/festival/artist_detail.html b/app/templates/templates/festival/artist_detail.html index 5e1087c2..95ca5cd9 100644 --- a/app/templates/templates/festival/artist_detail.html +++ b/app/templates/templates/festival/artist_detail.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load i18n %} -{% load mezzanine_tags keyword_tags festival_tags %} +{% load mezzanine_tags keyword_tags featured_tags %} {% block title %} {{ artist.name }} diff --git a/app/templates/templates/festival/video_list.html b/app/templates/templates/festival/video_list.html index dcb45e66..f951aa2a 100644 --- a/app/templates/templates/festival/video_list.html +++ b/app/templates/templates/festival/video_list.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% load i18n %} -{% load mezzanine_tags keyword_tags festival_tags %} +{% load mezzanine_tags keyword_tags featured_tags %} {% block title %} {% trans "Videos" %} diff --git a/doc/__init__.py b/doc/__init__.py new file mode 100644 index 00000000..e69de29b