]> git.parisson.com Git - mezzo.git/commitdiff
merge
authorGuillaume Pellerin <guillaume.pellerin@ircam.fr>
Thu, 7 Jul 2016 17:02:03 +0000 (19:02 +0200)
committerGuillaume Pellerin <guillaume.pellerin@ircam.fr>
Thu, 7 Jul 2016 17:02:03 +0000 (19:02 +0200)
109 files changed:
.gitignore
app/local_settings.py
app/organization/__init__.py
app/organization/core/__init__.py [new file with mode: 0644]
app/organization/core/admin.py [new file with mode: 0644]
app/organization/core/apps.py [new file with mode: 0644]
app/organization/core/management/__init__.py [new file with mode: 0644]
app/organization/core/management/commands/__init__.py [new file with mode: 0644]
app/organization/core/management/commands/create-admin-user.py [new file with mode: 0644]
app/organization/core/management/commands/festival-sync-eve-events.py [new file with mode: 0644]
app/organization/core/management/commands/wait-for-db.py [new file with mode: 0644]
app/organization/core/migrations/0001_initial.py [new file with mode: 0644]
app/organization/core/migrations/0002_auto_20160707_1614.py [new file with mode: 0644]
app/organization/core/migrations/__init__.py [new file with mode: 0644]
app/organization/core/models.py [new file with mode: 0644]
app/organization/core/related.py [new file with mode: 0644]
app/organization/core/templatetags/organization_tags.py [new file with mode: 0644]
app/organization/core/tests.py [new file with mode: 0644]
app/organization/core/translation.py [new file with mode: 0644]
app/organization/core/urls.py [new file with mode: 0644]
app/organization/core/views.py [new file with mode: 0644]
app/organization/featured/__init__.py [new file with mode: 0644]
app/organization/featured/admin.py [new file with mode: 0644]
app/organization/featured/apps.py [new file with mode: 0644]
app/organization/featured/migrations/0001_initial.py [new file with mode: 0644]
app/organization/featured/migrations/0002_auto_20160707_1614.py [new file with mode: 0644]
app/organization/featured/migrations/__init__.py [new file with mode: 0644]
app/organization/featured/models.py [new file with mode: 0644]
app/organization/featured/templatetags/featured_tags.py [new file with mode: 0644]
app/organization/featured/tests.py [new file with mode: 0644]
app/organization/featured/urls.py [new file with mode: 0644]
app/organization/featured/views.py [new file with mode: 0644]
app/organization/festival/__init__.py [new file with mode: 0644]
app/organization/festival/admin.py [new file with mode: 0644]
app/organization/festival/apps.py [new file with mode: 0644]
app/organization/festival/migrations/0001_initial.py [new file with mode: 0644]
app/organization/festival/migrations/__init__.py [new file with mode: 0644]
app/organization/festival/models.py [new file with mode: 0644]
app/organization/festival/routers.py [new file with mode: 0644]
app/organization/festival/templatetags/festival_tags.py [new file with mode: 0644]
app/organization/festival/tests.py [new file with mode: 0644]
app/organization/festival/translation.py [new file with mode: 0644]
app/organization/festival/urls.py [new file with mode: 0644]
app/organization/festival/views.py [new file with mode: 0644]
app/organization/magazine/__init__.py [new file with mode: 0644]
app/organization/magazine/admin.py [new file with mode: 0644]
app/organization/magazine/apps.py [new file with mode: 0644]
app/organization/magazine/migrations/0001_initial.py [new file with mode: 0644]
app/organization/magazine/migrations/0002_article.py [new file with mode: 0644]
app/organization/magazine/migrations/__init__.py [new file with mode: 0644]
app/organization/magazine/models.py [new file with mode: 0644]
app/organization/magazine/tests.py [new file with mode: 0644]
app/organization/magazine/translation.py [new file with mode: 0644]
app/organization/magazine/urls.py [new file with mode: 0644]
app/organization/magazine/views.py [new file with mode: 0644]
app/organization/media/__init__.py [new file with mode: 0644]
app/organization/media/admin.py [new file with mode: 0644]
app/organization/media/apps.py [new file with mode: 0644]
app/organization/media/migrations/0001_initial.py [new file with mode: 0644]
app/organization/media/migrations/__init__.py [new file with mode: 0644]
app/organization/media/models.py [new file with mode: 0644]
app/organization/media/tests.py [new file with mode: 0644]
app/organization/media/translation.py [new file with mode: 0644]
app/organization/media/urls.py [new file with mode: 0644]
app/organization/media/views.py [new file with mode: 0644]
app/organization/project/__init__.py [new file with mode: 0644]
app/organization/project/admin.py [new file with mode: 0644]
app/organization/project/apps.py [new file with mode: 0644]
app/organization/project/migrations/0001_initial.py [new file with mode: 0644]
app/organization/project/migrations/0002_auto_20160707_1053.py [new file with mode: 0644]
app/organization/project/migrations/__init__.py [new file with mode: 0644]
app/organization/project/models.py [new file with mode: 0644]
app/organization/project/tests.py [new file with mode: 0644]
app/organization/project/urls.py [new file with mode: 0644]
app/organization/project/views.py [new file with mode: 0644]
app/organization/team/__init__.py [new file with mode: 0644]
app/organization/team/admin.py [new file with mode: 0644]
app/organization/team/apps.py [new file with mode: 0644]
app/organization/team/migrations/0001_initial.py [new file with mode: 0644]
app/organization/team/migrations/0002_auto_20160707_1614.py [new file with mode: 0644]
app/organization/team/migrations/__init__.py [new file with mode: 0644]
app/organization/team/models.py [new file with mode: 0644]
app/organization/team/tests.py [new file with mode: 0644]
app/organization/team/urls.py [new file with mode: 0644]
app/organization/team/views.py [new file with mode: 0644]
app/organization/urls.py [new file with mode: 0644]
app/settings.py
app/templates/agenda/event_booking.html
app/templates/agenda/event_detail.html
app/templates/agenda/event_iframe.html
app/templates/agenda/includes/event_metainfo.html
app/templates/agenda/includes/event_metainfo_slider.html
app/templates/base.html
app/templates/blog/blog_post_detail.html
app/templates/includes/slider.html
app/templates/index.html
app/templates/pages/basicpage.html [new file with mode: 0644]
app/templates/pages/page.html
app/templates/styles.html
app/templates/templates/festival/artist_detail.html
app/templates/templates/festival/video_list.html
app/urls.py
doc/__init__.py [new file with mode: 0644]
requirements-dev.txt
requirements.txt
scripts/backup_db.sh
scripts/makemigrations.sh [new file with mode: 0755]
scripts/migrate.sh [new file with mode: 0755]
scripts/restore_db.sh

index af68934c9056740c3d95a7a32beb8d33ad7435c9..ce8bf43897a532dfcda9d231c8bc13363966ea2f 100644 (file)
@@ -15,6 +15,8 @@ develop-eggs
 *.directory
 var
 data/static
+data/mysql
+data/postgresql
 .thumbnails
 
 # Installer logs
index 5b6f5d61a08e9c4be1ed95ae0426f4fcf50c3e02..96e92ada86c9634ed541abb151ea12b4a77d8aa2 100644 (file)
@@ -12,12 +12,12 @@ NEVERCACHE_KEY = "m)u^%r@uh#r3wu0&$=#$1ogx)uy4hv93^2lt%c3@xi=^gifoj8paozijdihaze
 
 DATABASES = {
     'default': {
-     'ENGINE': 'django.db.backends.mysql',  # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
-         'USER': os.environ.get('DB_ENV_MYSQL_USER'),      # Not used with sqlite3.
-         'PASSWORD': os.environ.get('DB_ENV_MYSQL_PASSWORD'),  # Not used with sqlite3.
-         'NAME': os.environ.get('DB_ENV_MYSQL_DATABASE'),
-         'HOST': 'db',      # Set to empty string for localhost. Not used with sqlite3.
-         'PORT': '3306',      # Set to empty string for default. Not used with sqlite3.
+        'ENGINE': 'django.db.backends.postgresql_psycopg2',
+        'NAME': 'postgres',
+        'USER': 'postgres',
+        'PASSWORD': os.environ.get('DB_ENV_POSTGRES_PASSWORD'),
+        'HOST': 'db',
+        'PORT': '5432',
     },
     'eve': {
         'ENGINE': 'django.db.backends.postgresql_psycopg2',
@@ -27,15 +27,6 @@ DATABASES = {
         'HOST': 'pgdb',
         'PORT': '5432',
     },
-    #'eve': {
-    #     'ENGINE': 'django.db.backends.postgresql_psycopg2',
-    #     'NAME': 'eve',
-    #     'USER': 'django',
-    #     'PASSWORD': 'q2nqzt0WGnwWé,256',
-    #     'HOST': 'eve.ircam.fr',
-    #     'PORT': '5432',
-    #},
-
 }
 
 # DATABASE_ROUTERS = ['eve.routers.EveRouter',]
@@ -69,24 +60,29 @@ FILEBROWSER_SELECT_FORMATS = {
 
 EMAIL_HOST = 'smtp.ircam.fr'
 EMAIL_PORT = '25'
-DEFAULT_FROM_EMAIL = 'manifeste2016@ircam.fr'
-EMAIL_SUBJECT_PREFIX = "IRCAM Manifeste 2016"
+DEFAULT_FROM_EMAIL = 'www@ircam.fr'
+EMAIL_SUBJECT_PREFIX = "[IRCAM WWW]"
 
-SITE_TITLE = 'Manifeste 2016'
-SITE_TAGLINE = 'Festival 2 juin | 2 juillet 2016'
+SITE_TITLE = 'IRCAM'
+SITE_TAGLINE = 'Institut de Recherche et de Coordination Acoustique et Musique'
 
 SILENCED_SYSTEM_CHECKS = ['fields.W342',]
 
 ADMIN_MENU_ORDER = (
-    (_("Content"), ("pages.Page", "blog.BlogPost", "mezzanine_agenda.Event",
-        "festival.Artist", "festival.Video", "festival.Audio", "festival.Playlist",
-        "festival.Featured",
-        "generic.ThreadedComment", (_("Media Library"), "fb_browse"),)),
-    (_("Site"), ("sites.Site", "redirects.Redirect", "conf.Setting")),
-    (_("Users"), ("auth.User", "auth.Group",)),
-    (_("Festival"), ("mezzanine_agenda.EventLocation",
-        "mezzanine_agenda.EventCategory", "mezzanine_agenda.EventPrice",
-        "festival.PageCategory",)),
+    (_('Content'), ('pages.Page', 'blog.BlogPost', 'mezzanine_agenda.Event',
+        'generic.ThreadedComment', (_('Media Library'), 'fb_browse'),)),
+    (_("Magazine"), ("magazine.Article",)),
+    (_('team'), ('organization.team.Organization', 'organization.team.Team',
+        'organization.team.Department', 'organization.team.Person',
+        'organization.team.Activity')),
+    (_('Projects'), ('organization.project.Project')),
+    (_('Festival'), ('organization.festival.Artist', 'organization.festival.Video',
+    'organization.festival.Audio', 'organization.festival.Playlist',
+    'organization.festival.Featured', 'mezzanine_agenda.EventLocation',
+        'mezzanine_agenda.EventCategory', 'mezzanine_agenda.EventPrice',
+        'festival.PageCategory',)),
+    (_('Users'), ('auth.User', 'auth.Group',)),
+    (_('Site'), ('sites.Site', 'redirects.Redirect', 'conf.Setting')),
 )
 
 GRAPPELLI_ADMIN_TITLE = 'IRCAM Admin'
@@ -116,6 +112,10 @@ BREAKING_NEWS_FEATURED_ID = 4
 
 BLOG_POST_PER_PAGE = 200
 
+# The numeric mode to set newly-uploaded files to. The value should be
+# a mode you'd pass directly to os.chmod.
+FILE_UPLOAD_PERMISSIONS = 0o664
+FILE_UPLOAD_TEMP_DIR = '/srv/media/uploads/tmp/'
 FILEBROWSER_MAX_UPLOAD_SIZE = 512000000
 
 if DEBUG:
@@ -137,3 +137,6 @@ DEBUG_TOOLBAR_PANELS = [
     'debug_toolbar.panels.logging.LoggingPanel',
     'debug_toolbar.panels.redirects.RedirectsPanel',
 ]
+
+# slug
+BLOG_SLUG = 'article'
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..efe892afb7c36464583291857c15b3b80b3704e3 100644 (file)
@@ -0,0 +1,2 @@
+
+__version__ = 0.1
diff --git a/app/organization/core/__init__.py b/app/organization/core/__init__.py
new file mode 100644 (file)
index 0000000..141c2fb
--- /dev/null
@@ -0,0 +1,7 @@
+"""
+Provides abstract models and admin features used throughout the various
+Mezzanine apps.
+"""
+from __future__ import unicode_literals
+
+default_app_config = 'organization.core.apps.CoreConfig'
diff --git a/app/organization/core/admin.py b/app/organization/core/admin.py
new file mode 100644 (file)
index 0000000..14b200a
--- /dev/null
@@ -0,0 +1,9 @@
+from django.contrib import admin
+from copy import deepcopy
+from mezzanine.pages.models import Page
+from mezzanine.pages.admin import PageAdmin
+from mezzanine.pages.models import RichTextPage
+from organization.core.models import BasicPage
+
+
+admin.site.register(BasicPage, PageAdmin)
diff --git a/app/organization/core/apps.py b/app/organization/core/apps.py
new file mode 100644 (file)
index 0000000..b05d77a
--- /dev/null
@@ -0,0 +1,9 @@
+from django.apps import AppConfig
+
+from django.core.checks import register
+
+
+class CoreConfig(AppConfig):
+
+    name = 'organization.core'
+    label = 'organization core'
diff --git a/app/organization/core/management/__init__.py b/app/organization/core/management/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/app/organization/core/management/commands/__init__.py b/app/organization/core/management/commands/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
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 (file)
index 0000000..e7e3cad
--- /dev/null
@@ -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 (file)
index 0000000..070cadb
--- /dev/null
@@ -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 (file)
index 0000000..e2bacf0
--- /dev/null
@@ -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/0001_initial.py b/app/organization/core/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..9f09456
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-07-06 16:48
+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 = [
+        ('pages', '0004_auto_20151223_1313'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='BasicPage',
+            fields=[
+                ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pages.Page')),
+                ('content', mezzanine.core.fields.RichTextField(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')),
+                ('sub_title', models.TextField(blank=True, max_length=1024, verbose_name='sub title')),
+                ('sub_title_fr', models.TextField(blank=True, max_length=1024, null=True, verbose_name='sub title')),
+                ('sub_title_en', models.TextField(blank=True, max_length=1024, null=True, verbose_name='sub title')),
+            ],
+            options={
+                'verbose_name': 'basic page',
+                'ordering': ('_order',),
+            },
+            bases=('pages.page', models.Model),
+        ),
+    ]
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 (file)
index 0000000..5cebb5f
--- /dev/null
@@ -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/migrations/__init__.py b/app/organization/core/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/app/organization/core/models.py b/app/organization/core/models.py
new file mode 100644 (file)
index 0000000..c0d3ae0
--- /dev/null
@@ -0,0 +1,51 @@
+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
+
+
+class Named(models.Model):
+    """Named object with description"""
+
+    name = models.CharField(_('name'), max_length=512)
+    description = models.TextField(_('description'), blank=True)
+
+    class Meta:
+        abstract = True
+
+    def __unicode__(self):
+        return self.name
+
+    @property
+    def slug(self):
+        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)
+
+    class Meta:
+        abstract = True
+
+
+class BasicPage(Page, RichText, SubTitle):
+
+    class Meta:
+        verbose_name = 'basic page'
diff --git a/app/organization/core/related.py b/app/organization/core/related.py
new file mode 100644 (file)
index 0000000..5b1affb
--- /dev/null
@@ -0,0 +1,32 @@
+from django.core import exceptions
+from django.db.models.fields.related import ForeignKey
+from django.db.utils import ConnectionHandler, ConnectionRouter
+
+connections = ConnectionHandler()
+router = ConnectionRouter()
+
+
+class SpanningForeignKey(ForeignKey):
+
+    def validate(self, value, model_instance):
+        if self.rel.parent_link:
+            return
+        # Call the grandparent rather than the parent to skip validation
+        super(ForeignKey, self).validate(value, model_instance)
+        if value is None:
+            return
+
+        using = router.db_for_read(self.rel.to, instance=model_instance)
+        qs = self.rel.to._default_manager.using(using).filter(
+            **{self.rel.field_name: value}
+        )
+        qs = qs.complex_filter(self.get_limit_choices_to())
+        if not qs.exists():
+            raise exceptions.ValidationError(
+                self.error_messages['invalid'],
+                code='invalid',
+                params={
+                    'model': self.rel.to._meta.verbose_name, 'pk': value,
+                    'field': self.rel.field_name, 'value': value,
+                },  # 'pk' is included for backwards compatibility
+            )
diff --git a/app/organization/core/templatetags/organization_tags.py b/app/organization/core/templatetags/organization_tags.py
new file mode 100644 (file)
index 0000000..5f3c958
--- /dev/null
@@ -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/tests.py b/app/organization/core/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/app/organization/core/translation.py b/app/organization/core/translation.py
new file mode 100644 (file)
index 0000000..571ac7c
--- /dev/null
@@ -0,0 +1,15 @@
+from modeltranslation.translator import translator, register, TranslationOptions
+from mezzanine.pages.models import Page, RichText
+from mezzanine.pages.translation import TranslatedRichText
+
+from organization.core.models import BasicPage
+
+# @register(SubTitle)
+# class SubTitleTranslationOptions(TranslationOptions):
+#
+#     fields = ('sub_title',)
+
+@register(BasicPage)
+class BasicPageTranslationOptions(TranslationOptions):
+
+    fields = ('sub_title',)
diff --git a/app/organization/core/urls.py b/app/organization/core/urls.py
new file mode 100644 (file)
index 0000000..68af3c4
--- /dev/null
@@ -0,0 +1,15 @@
+from __future__ import unicode_literals
+
+import django.views.i18n
+from django.conf.urls import patterns, include, url
+from django.conf.urls.i18n import i18n_patterns
+
+from mezzanine.core.views import direct_to_template
+from mezzanine.conf import settings
+
+from organization.core.views import HomeView
+
+
+urlpatterns = [
+    url("^$", HomeView.as_view(), name="home"),
+]
diff --git a/app/organization/core/views.py b/app/organization/core/views.py
new file mode 100644 (file)
index 0000000..82dc1b0
--- /dev/null
@@ -0,0 +1,20 @@
+from django.shortcuts import render, get_object_or_404
+from django.http import Http404
+from django.views.generic.base import View
+from django.views.generic import DetailView, ListView, TemplateView
+
+
+class SlugMixin(object):
+
+    def get_object(self):
+        objects = self.model.objects.all()
+        return get_object_or_404(objects, slug=self.kwargs['slug'])
+
+
+class HomeView(TemplateView):
+
+    template_name = 'index.html'
+
+    def get_context_data(self, **kwargs):
+        context = super(HomeView, self).get_context_data(**kwargs)
+        return context
diff --git a/app/organization/featured/__init__.py b/app/organization/featured/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/app/organization/featured/admin.py b/app/organization/featured/admin.py
new file mode 100644 (file)
index 0000000..1318a91
--- /dev/null
@@ -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 (file)
index 0000000..10cae19
--- /dev/null
@@ -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 (file)
index 0000000..00bd148
--- /dev/null
@@ -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 (file)
index 0000000..2816f5c
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/app/organization/featured/models.py b/app/organization/featured/models.py
new file mode 100644 (file)
index 0000000..ae59ea4
--- /dev/null
@@ -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 (file)
index 0000000..79e3267
--- /dev/null
@@ -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 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/app/organization/featured/urls.py b/app/organization/featured/urls.py
new file mode 100644 (file)
index 0000000..cea975a
--- /dev/null
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+
+import django.views.i18n
+from django.conf.urls import patterns, include, url
+from django.conf.urls.i18n import i18n_patterns
+
+from mezzanine.core.views import direct_to_template
+from mezzanine.conf import settings
+
+
+urlpatterns = [
+
+]
diff --git a/app/organization/featured/views.py b/app/organization/featured/views.py
new file mode 100644 (file)
index 0000000..91ea44a
--- /dev/null
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/app/organization/festival/__init__.py b/app/organization/festival/__init__.py
new file mode 100644 (file)
index 0000000..e5988d7
--- /dev/null
@@ -0,0 +1,7 @@
+"""
+Provides abstract models and admin features used throughout the various
+Mezzanine apps.
+"""
+from __future__ import unicode_literals
+
+default_app_config = 'organization.festival.apps.FestivalConfig'
diff --git a/app/organization/festival/admin.py b/app/organization/festival/admin.py
new file mode 100644 (file)
index 0000000..eac37c6
--- /dev/null
@@ -0,0 +1,27 @@
+from __future__ import unicode_literals
+
+from copy import deepcopy
+
+from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+
+from mezzanine_agenda.models import Event, EventLocation
+from mezzanine_agenda.admin import *
+
+from mezzanine.conf import settings
+from mezzanine.core.admin import DisplayableAdmin, OwnableAdmin
+
+from organization.festival.models import *
+
+
+class ArtistAdmin(admin.ModelAdmin):
+
+    model = Artist
+
+
+class ArtistAdminDisplayable(DisplayableAdmin):
+
+    fieldsets = deepcopy(ArtistAdmin.fieldsets)
+
+
+admin.site.register(Artist, ArtistAdminDisplayable)
diff --git a/app/organization/festival/apps.py b/app/organization/festival/apps.py
new file mode 100644 (file)
index 0000000..0ab1f17
--- /dev/null
@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+
+
+class FestivalConfig(AppConfig):
+
+    name = 'organization.festival'
+    label = 'organization festival app'
diff --git a/app/organization/festival/migrations/0001_initial.py b/app/organization/festival/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..f74881b
--- /dev/null
@@ -0,0 +1,65 @@
+# -*- 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
+import mezzanine.utils.models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('sites', '0002_alter_domain_unique'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Artist',
+            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')),
+                ('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')),
+                ('site', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
+            ],
+            options={
+                'ordering': ['last_name'],
+                'verbose_name': 'artist',
+            },
+            bases=(mezzanine.utils.models.AdminThumbMixin, models.Model),
+        ),
+    ]
diff --git a/app/organization/festival/migrations/__init__.py b/app/organization/festival/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/app/organization/festival/models.py b/app/organization/festival/models.py
new file mode 100644 (file)
index 0000000..430ac4b
--- /dev/null
@@ -0,0 +1,36 @@
+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 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.blog.models import BlogPost
+from mezzanine.pages.models import Page
+
+from organization.core.models import *
+from organization.media.models import *
+
+import requests
+from pyquery import PyQuery as pq
+
+
+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)
+    search_fields = ("title", "bio")
+
+    class Meta:
+        verbose_name = _('artist')
+        ordering = ['last_name',]
+
+    def __unicode__(self):
+        return self.title
+
+    @property
+    def name(self):
+        return self.title
diff --git a/app/organization/festival/routers.py b/app/organization/festival/routers.py
new file mode 100644 (file)
index 0000000..6bde6a3
--- /dev/null
@@ -0,0 +1,31 @@
+
+class FestivalRouter(object):
+    """
+    A router to control all database operations on models in festival
+    """
+
+    def db_for_read(self, model, **hints):
+        if model._meta.app_label == 'festival':
+            return 'default'
+        return None
+
+    def db_for_write(self, model, **hints):
+        if model._meta.app_label == 'festival':
+            return 'default'
+        return None
+
+    def allow_relation(self, obj1, obj2, **hints):
+        if obj1._meta.app_label == 'festival' or \
+           obj2._meta.app_label == 'festival':
+           return True
+        return None
+
+    # def allow_migrate(self, db, app_label, model=None, **hints):
+    #     if app_label == 'festival':
+    #         return db == 'default'
+    #     return None
+
+    def allow_migrate(self, db, app_label, model_name=None, **hints):
+        if 'target_db' in hints:
+            return db == hints['target_db']
+        return True
diff --git a/app/organization/festival/templatetags/festival_tags.py b/app/organization/festival/templatetags/festival_tags.py
new file mode 100644 (file)
index 0000000..5f3c958
--- /dev/null
@@ -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/festival/tests.py b/app/organization/festival/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/app/organization/festival/translation.py b/app/organization/festival/translation.py
new file mode 100644 (file)
index 0000000..b9e4936
--- /dev/null
@@ -0,0 +1,8 @@
+from modeltranslation.translator import register, TranslationOptions
+
+from organization.festival.models import *
+
+@register(Artist)
+class ArtistTranslationOptions(TranslationOptions):
+
+    fields = ('title', 'description', 'bio', 'content')
diff --git a/app/organization/festival/urls.py b/app/organization/festival/urls.py
new file mode 100644 (file)
index 0000000..c13eedb
--- /dev/null
@@ -0,0 +1,16 @@
+from __future__ import unicode_literals
+
+import django.views.i18n
+from django.conf.urls import patterns, include, url
+from django.conf.urls.i18n import i18n_patterns
+
+from mezzanine.core.views import direct_to_template
+from mezzanine.conf import settings
+
+from organization.festival.views import *
+
+
+urlpatterns = [
+    url(r'^artists/$', ArtistListView.as_view(), name="festival-artist-list"),
+    url(r'^artists/detail/(?P<slug>.*)/$', ArtistDetailView.as_view(), name="festival-artist-detail"),
+]
diff --git a/app/organization/festival/views.py b/app/organization/festival/views.py
new file mode 100644 (file)
index 0000000..beba181
--- /dev/null
@@ -0,0 +1,33 @@
+from django.shortcuts import render
+from django.views.generic import *
+from django.views.generic.base import *
+from django.shortcuts import get_object_or_404
+
+from organization.festival.models import *
+from mezzanine_agenda.models import EventLocation
+
+from organization.core.views import *
+
+
+class ArtistListView(ListView):
+
+    model = Artist
+    template_name='festival/artist_list.html'
+
+    def get_queryset(self, **kwargs):
+        return self.model.objects.published()
+
+    def get_context_data(self, **kwargs):
+        context = super(ArtistListView, self).get_context_data(**kwargs)
+        return context
+
+
+class ArtistDetailView(SlugMixin, DetailView):
+
+    model = Artist
+    template_name='festival/artist_detail.html'
+    context_object_name = 'artist'
+
+    def get_context_data(self, **kwargs):
+        context = super(ArtistDetailView, self).get_context_data(**kwargs)
+        return context
diff --git a/app/organization/magazine/__init__.py b/app/organization/magazine/__init__.py
new file mode 100644 (file)
index 0000000..043bee8
--- /dev/null
@@ -0,0 +1,10 @@
+"""
+Provides abstract models and admin features used throughout the various
+Mezzanine apps.
+"""
+from __future__ import unicode_literals
+
+from mezzanine import __version__  # noqa
+
+
+default_app_config = 'organization.magazine.apps.MagazineConfig'
diff --git a/app/organization/magazine/admin.py b/app/organization/magazine/admin.py
new file mode 100644 (file)
index 0000000..aa592f0
--- /dev/null
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+from organization.magazine.models import *
diff --git a/app/organization/magazine/apps.py b/app/organization/magazine/apps.py
new file mode 100644 (file)
index 0000000..78483d7
--- /dev/null
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class MagazineConfig(AppConfig):
+
+    name = 'organization.magazine'
+    label = 'organization magazine'
diff --git a/app/organization/magazine/migrations/0001_initial.py b/app/organization/magazine/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..683c85d
--- /dev/null
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-07-06 16:48
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Category',
+            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')),
+            ],
+            options={
+                'verbose_name': 'category',
+            },
+        ),
+        migrations.CreateModel(
+            name='Topic',
+            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')),
+            ],
+            options={
+                'verbose_name': 'topic',
+            },
+        ),
+    ]
diff --git a/app/organization/magazine/migrations/0002_article.py b/app/organization/magazine/migrations/0002_article.py
new file mode 100644 (file)
index 0000000..6370480
--- /dev/null
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-07-07 10:01
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('blog', '0003_auto_20151223_1313'),
+        ('organization magazine', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Article',
+            fields=[
+                ('blogpost_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='blog.BlogPost')),
+                ('sub_title', models.CharField(blank=True, max_length=1000, verbose_name='sub title')),
+                ('sub_title_fr', models.CharField(blank=True, max_length=1000, null=True, verbose_name='sub title')),
+                ('sub_title_en', models.CharField(blank=True, max_length=1000, null=True, verbose_name='sub title')),
+            ],
+            options={
+                'verbose_name': 'article',
+            },
+            bases=('blog.blogpost',),
+        ),
+    ]
diff --git a/app/organization/magazine/migrations/__init__.py b/app/organization/magazine/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/app/organization/magazine/models.py b/app/organization/magazine/models.py
new file mode 100644 (file)
index 0000000..412eaf0
--- /dev/null
@@ -0,0 +1,40 @@
+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 *
+
+from mezzanine_agenda.models import Event
+
+from organization.magazine.models import *
+from organization.core.models import *
+
+
+class Article(BlogPost):
+
+    sub_title = models.CharField(_('sub title'), blank=True, max_length=1000)
+
+    class Meta:
+        verbose_name = _('article')
+
+
+class Category(Named):
+    """(Category description)"""
+
+    class Meta:
+        verbose_name = _('category')
+
+    def __unicode__(self):
+        return self.name
+
+
+class Topic(Named):
+    """(Topic description)"""
+
+    class Meta:
+        verbose_name = _('topic')
+
+    def __unicode__(self):
+        return self.name
diff --git a/app/organization/magazine/tests.py b/app/organization/magazine/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/app/organization/magazine/translation.py b/app/organization/magazine/translation.py
new file mode 100644 (file)
index 0000000..db66e9b
--- /dev/null
@@ -0,0 +1,13 @@
+from modeltranslation.translator import translator, register, TranslationOptions
+from mezzanine.pages.models import Page, RichText
+from modeltranslation.translator import TranslationOptions
+from mezzanine.core.translation import (TranslatedSlugged,
+                                        TranslatedDisplayable,
+                                        TranslatedRichText)
+from organization.magazine.models import Article
+
+@register(Article)
+#class ArticleTranslationOptions(TranslatedDisplayable, TranslatedRichText):
+class ArticleTranslationOptions(TranslationOptions):
+
+    fields = ('sub_title',)
diff --git a/app/organization/magazine/urls.py b/app/organization/magazine/urls.py
new file mode 100644 (file)
index 0000000..cea975a
--- /dev/null
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+
+import django.views.i18n
+from django.conf.urls import patterns, include, url
+from django.conf.urls.i18n import i18n_patterns
+
+from mezzanine.core.views import direct_to_template
+from mezzanine.conf import settings
+
+
+urlpatterns = [
+
+]
diff --git a/app/organization/magazine/views.py b/app/organization/magazine/views.py
new file mode 100644 (file)
index 0000000..91ea44a
--- /dev/null
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/app/organization/media/__init__.py b/app/organization/media/__init__.py
new file mode 100644 (file)
index 0000000..a4e2211
--- /dev/null
@@ -0,0 +1,10 @@
+"""
+Provides abstract models and admin features used throughout the various
+Mezzanine apps.
+"""
+from __future__ import unicode_literals
+
+from mezzanine import __version__  # noqa
+
+
+default_app_config = 'organization.media.apps.MediaConfig'
diff --git a/app/organization/media/admin.py b/app/organization/media/admin.py
new file mode 100644 (file)
index 0000000..9ea7f15
--- /dev/null
@@ -0,0 +1,39 @@
+from copy import deepcopy
+from django.contrib import admin
+from mezzanine.core.admin import DisplayableAdmin, OwnableAdmin
+from organization.media.models import *
+
+
+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/apps.py b/app/organization/media/apps.py
new file mode 100644 (file)
index 0000000..d5dd59a
--- /dev/null
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class MediaConfig(AppConfig):
+
+    name = 'organization.media'
+    label = 'organization media'
diff --git a/app/organization/media/migrations/0001_initial.py b/app/organization/media/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..32ee568
--- /dev/null
@@ -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/migrations/__init__.py b/app/organization/media/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/app/organization/media/models.py b/app/organization/media/models.py
new file mode 100644 (file)
index 0000000..c4dfffa
--- /dev/null
@@ -0,0 +1,128 @@
+from __future__ import unicode_literals
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+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 Photo(models.Model):
+    """Photo bundle with credits"""
+
+    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_card = FileField(_('card photo'), upload_to='images/photos/card', max_length=1024, blank=True, format="Image")
+    photo_card_credits = models.CharField(_('photo card credits'), max_length=255, blank=True, null=True)
+
+    photo_slider = FileField(_('slider photo'), upload_to='images/photos/slider', max_length=1024, blank=True, format="Image")
+    photo_slider_credits = models.CharField(_('photo slider credits'), max_length=255, blank=True, null=True)
+
+    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/tests.py b/app/organization/media/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/app/organization/media/translation.py b/app/organization/media/translation.py
new file mode 100644 (file)
index 0000000..bd09714
--- /dev/null
@@ -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 (file)
index 0000000..4b0faa6
--- /dev/null
@@ -0,0 +1,17 @@
+from __future__ import unicode_literals
+
+import django.views.i18n
+from django.conf.urls import patterns, include, url
+from django.conf.urls.i18n import i18n_patterns
+
+from mezzanine.core.views import direct_to_template
+from mezzanine.conf import settings
+
+from organization.media.views import *
+
+
+urlpatterns = [
+    url(r'^videos/$', VideoListView.as_view(), name="festival-video-list"),
+    url(r'^videos/detail/(?P<slug>.*)/$', VideoDetailView.as_view(), name="festival-video-detail"),
+    url(r'^videos/category/(?P<slug>.*)/$', VideoListCategoryView.as_view(), name="festival-video-list-category"),
+]
diff --git a/app/organization/media/views.py b/app/organization/media/views.py
new file mode 100644 (file)
index 0000000..933be23
--- /dev/null
@@ -0,0 +1,41 @@
+from django.shortcuts import render
+
+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/project/__init__.py b/app/organization/project/__init__.py
new file mode 100644 (file)
index 0000000..0358620
--- /dev/null
@@ -0,0 +1,10 @@
+"""
+Provides abstract models and admin features used throughout the various
+Mezzanine apps.
+"""
+from __future__ import unicode_literals
+
+from mezzanine import __version__  # noqa
+
+
+default_app_config = 'organization.project.apps.ProjectConfig'
diff --git a/app/organization/project/admin.py b/app/organization/project/admin.py
new file mode 100644 (file)
index 0000000..988416d
--- /dev/null
@@ -0,0 +1,25 @@
+from django.contrib import admin
+from mezzanine.blog.admin import BlogPostAdmin
+from organization.magazine.models import Article
+#from custom.admin import SubTitleAdmin
+from copy import deepcopy
+
+from django.contrib import admin
+from django.utils.translation import ugettext_lazy as _
+from mezzanine.core.admin import DisplayableAdmin, OwnableAdmin
+
+# class ArticleAdmin(BlogPostAdmin, SubTitleAdmin):
+#
+#     model = Article
+
+#admin.site.register(Article, BlogPostAdmin)
+class ArticleAdmin(admin.ModelAdmin):
+
+    model = Article
+
+
+class ArticleAdminDisplayable(DisplayableAdmin):
+
+    fieldsets = deepcopy(ArticleAdmin.fieldsets)
+
+admin.site.register(Article, ArticleAdminDisplayable)
diff --git a/app/organization/project/apps.py b/app/organization/project/apps.py
new file mode 100644 (file)
index 0000000..34d023b
--- /dev/null
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class ProjectConfig(AppConfig):
+
+    name = 'organization.project'
+    label = 'organization project'
diff --git a/app/organization/project/migrations/0001_initial.py b/app/organization/project/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..c96edf5
--- /dev/null
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-07-07 08:53
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import mezzanine.core.fields
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Project',
+            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')),
+                ('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')),
+                ('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')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+    ]
diff --git a/app/organization/project/migrations/0002_auto_20160707_1053.py b/app/organization/project/migrations/0002_auto_20160707_1053.py
new file mode 100644 (file)
index 0000000..5d78ef1
--- /dev/null
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-07-07 08:53
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('sites', '0002_alter_domain_unique'),
+        ('organization project', '0001_initial'),
+        ('organization team', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='project',
+            name='partners',
+            field=models.ManyToManyField(to='organization team.Organization', verbose_name='organizations'),
+        ),
+        migrations.AddField(
+            model_name='project',
+            name='persons',
+            field=models.ManyToManyField(to='organization team.Person', verbose_name='persons'),
+        ),
+        migrations.AddField(
+            model_name='project',
+            name='site',
+            field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
+        ),
+    ]
diff --git a/app/organization/project/migrations/__init__.py b/app/organization/project/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/app/organization/project/models.py b/app/organization/project/models.py
new file mode 100644 (file)
index 0000000..31a7792
--- /dev/null
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from mezzanine.core.models import RichText, Displayable, Slugged
+
+from organization.team.models import Person, Organization
+
+
+class Project(Displayable, RichText):
+    """(Project description)"""
+
+    persons = models.ManyToManyField(Person, verbose_name=_('persons'))
+    partners = models.ManyToManyField(Organization, verbose_name=_('organizations'))
+
+    def __unicode__(self):
+        return self.title
diff --git a/app/organization/project/tests.py b/app/organization/project/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/app/organization/project/urls.py b/app/organization/project/urls.py
new file mode 100644 (file)
index 0000000..cea975a
--- /dev/null
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+
+import django.views.i18n
+from django.conf.urls import patterns, include, url
+from django.conf.urls.i18n import i18n_patterns
+
+from mezzanine.core.views import direct_to_template
+from mezzanine.conf import settings
+
+
+urlpatterns = [
+
+]
diff --git a/app/organization/project/views.py b/app/organization/project/views.py
new file mode 100644 (file)
index 0000000..91ea44a
--- /dev/null
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/app/organization/team/__init__.py b/app/organization/team/__init__.py
new file mode 100644 (file)
index 0000000..2de9352
--- /dev/null
@@ -0,0 +1,10 @@
+"""
+Provides abstract models and admin features used throughout the various
+Mezzanine apps.
+"""
+from __future__ import unicode_literals
+
+from mezzanine import __version__  # noqa
+
+
+default_app_config = 'organization.team.apps.teamConfig'
diff --git a/app/organization/team/admin.py b/app/organization/team/admin.py
new file mode 100644 (file)
index 0000000..8c38f3f
--- /dev/null
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/app/organization/team/apps.py b/app/organization/team/apps.py
new file mode 100644 (file)
index 0000000..81f7caf
--- /dev/null
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class teamConfig(AppConfig):
+
+    name = 'organization.team'
+    label  = 'organization team'
diff --git a/app/organization/team/migrations/0001_initial.py b/app/organization/team/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..4b4382b
--- /dev/null
@@ -0,0 +1,193 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.7 on 2016-07-07 08:53
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django_countries.fields
+import mezzanine.core.fields
+import mezzanine.utils.models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('sites', '0002_alter_domain_unique'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Activity',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date_begin', models.DateField(blank=True, null=True, verbose_name='begin date')),
+                ('date_end', models.DateField(blank=True, null=True, verbose_name='end date')),
+                ('role', models.CharField(blank=True, max_length=512, verbose_name='role')),
+                ('work', models.TextField(blank=True, verbose_name='work')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Address',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('address', models.TextField(blank=True, verbose_name='description')),
+                ('postal_code', models.CharField(blank=True, max_length=16, verbose_name='postal code')),
+                ('country', django_countries.fields.CountryField(max_length=2, verbose_name='country')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Department',
+            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')),
+                ('url', models.URLField(blank=True, max_length=512, verbose_name='URL')),
+                ('weaving_class', models.CharField(blank=True, max_length=64, verbose_name='weaving class')),
+            ],
+            options={
+                'verbose_name': 'department',
+            },
+        ),
+        migrations.CreateModel(
+            name='Link',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('url', models.URLField(verbose_name='URL')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='LinkType',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=256, verbose_name='Name')),
+                ('slug', models.SlugField(blank=True, help_text='Use this field to define a simple identifier that can be used to style the different link types (i.e. assign social media icons to them)', max_length=256, verbose_name='Slug')),
+                ('ordering', models.PositiveIntegerField(blank=True, null=True, verbose_name='Ordering')),
+            ],
+            options={
+                'ordering': ['ordering'],
+            },
+        ),
+        migrations.CreateModel(
+            name='Modelname',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Nationality',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=128, verbose_name='name')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='OrganizationType',
+            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')),
+            ],
+            options={
+                'verbose_name': 'organization type',
+            },
+        ),
+        migrations.CreateModel(
+            name='Person',
+            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')),
+                ('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')),
+                ('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')),
+                ('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')),
+                ('person_title', models.CharField(blank=True, choices=[('Dr', 'Dr'), ('Prof', 'Prof'), ('Prof Dr', 'Prof Dr')], max_length=16, verbose_name='title')),
+                ('gender', models.CharField(blank=True, choices=[('male', 'male'), ('female', 'female')], max_length=16, verbose_name='gender')),
+                ('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')),
+                ('birthday', models.DateField(blank=True, verbose_name='birthday')),
+                ('site', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
+                ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='user')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=(mezzanine.utils.models.AdminThumbMixin, models.Model),
+        ),
+        migrations.CreateModel(
+            name='Team',
+            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')),
+                ('department', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organization team.Department', verbose_name='department')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='Organization',
+            fields=[
+                ('address_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='organization team.Address')),
+                ('name', models.CharField(max_length=512, verbose_name='name')),
+                ('description', models.TextField(blank=True, verbose_name='description')),
+                ('url', models.URLField(blank=True, max_length=512, verbose_name='URL')),
+                ('type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organization team.OrganizationType', verbose_name='organization type')),
+            ],
+            options={
+                'verbose_name': 'organization',
+            },
+            bases=('organization team.address', models.Model),
+        ),
+        migrations.AddField(
+            model_name='link',
+            name='link_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organization team.LinkType', verbose_name='Link type'),
+        ),
+        migrations.AddField(
+            model_name='link',
+            name='person',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organization team.Person', verbose_name='Person'),
+        ),
+        migrations.AddField(
+            model_name='activity',
+            name='person',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organization team.Person', verbose_name='person'),
+        ),
+        migrations.AddField(
+            model_name='activity',
+            name='teams',
+            field=models.ManyToManyField(to='organization team.Team', verbose_name='teams'),
+        ),
+        migrations.AddField(
+            model_name='person',
+            name='organization',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organization team.Organization', verbose_name='organization'),
+        ),
+        migrations.AddField(
+            model_name='department',
+            name='organization',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='organization team.Organization', verbose_name='organization'),
+        ),
+    ]
diff --git a/app/organization/team/migrations/0002_auto_20160707_1614.py b/app/organization/team/migrations/0002_auto_20160707_1614.py
new file mode 100644 (file)
index 0000000..28aa1de
--- /dev/null
@@ -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 team', '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/team/migrations/__init__.py b/app/organization/team/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/app/organization/team/models.py b/app/organization/team/models.py
new file mode 100644 (file)
index 0000000..139ccfe
--- /dev/null
@@ -0,0 +1,202 @@
+from __future__ import unicode_literals
+
+import os
+import re
+import pwd
+import time
+import urllib
+import string
+import datetime
+import mimetypes
+
+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 django.contrib.auth.models import User
+
+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 django_countries.fields import CountryField
+
+from organization.media.models import Photo
+from organization.core.models import Named
+
+# Hack to have these strings translated
+mr = _('Mr')
+mrs = _('Ms')
+
+GENDER_CHOICES = [
+    ('male', _('male')),
+    ('female', _('female')),
+]
+
+TITLE_CHOICES = [
+    ('Dr', _('Dr')),
+    ('Prof', _('Prof')),
+    ('Prof Dr', _('Prof Dr')),
+]
+
+ALIGNMENT_CHOICES = (('left', _('left')), ('right', _('right')))
+
+
+
+class Address(models.Model):
+    """(Address description)"""
+
+    address = models.TextField(_('description'), blank=True)
+    postal_code = models.CharField(_('postal code'), max_length=16, blank=True)
+    country = CountryField(_('country'))
+
+    def __unicode__(self):
+        return u"Address"
+
+        class Meta:
+            abstract = True
+
+
+class Organization(Named, Address):
+    """(Organization description)"""
+
+    type = models.ForeignKey('OrganizationType', verbose_name=_('organization type'), blank=True, null=True, on_delete=models.SET_NULL)
+    url = models.URLField(_('URL'), max_length=512, blank=True)
+
+    def __unicode__(self):
+        return self.name
+
+    class Meta:
+        verbose_name = _('organization')
+
+
+class OrganizationType(Named):
+    """(OrganizationType description)"""
+
+    class Meta:
+        verbose_name = _('organization type')
+
+
+class Department(Named):
+    """(Department description)"""
+
+    organization = models.ForeignKey('Organization', verbose_name=_('organization'))
+    url = models.URLField(_('URL'), max_length=512, blank=True)
+    weaving_class = models.CharField(_('weaving class'), max_length=64, blank=True)
+
+    def __unicode__(self):
+        return self.name
+
+    class Meta:
+        verbose_name = _('department')
+
+
+class Team(Named):
+    """(Team description)"""
+
+    department = models.ForeignKey('Department', verbose_name=_('department'), blank=True, null=True, on_delete=models.SET_NULL)
+
+    def __unicode__(self):
+        return u"Team"
+
+
+class Person(Displayable, RichText, AdminThumbMixin, Photo):
+    """(Person description)"""
+
+    user = models.ForeignKey(User, verbose_name=_('user'), blank=True, null=True, on_delete=models.SET_NULL)
+    person_title = models.CharField(_('title'), max_length=16, choices=TITLE_CHOICES, blank=True)
+    gender = models.CharField(_('gender'), max_length=16, choices=GENDER_CHOICES, blank=True)
+    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)
+    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)"""
+
+    name = models.CharField(_('name'), max_length=128)
+
+    def __unicode__(self):
+        return self.name
+
+
+class Link(models.Model):
+    """A person can have many links."""
+
+    person = models.ForeignKey('Person', verbose_name=_('Person'))
+    link_type = models.ForeignKey('LinkType', verbose_name=_('Link type'))
+    url = models.URLField(verbose_name=_('URL'))
+
+    def __str__(self):
+        return self.url
+
+
+class LinkType(models.Model):
+    """
+    A link type could be ``Facebook`` or ``Twitter`` or ``Website``.
+    This is masterdata that should be created by the admins when the site is
+    deployed for the first time.
+    :ordering: Enter numbers here if you want links to be displayed in a
+      special order.
+    """
+
+    name = models.CharField(max_length=256, verbose_name=_('Name'))
+    slug = models.SlugField(max_length=256, verbose_name=_('Slug'), help_text=_(
+            'Use this field to define a simple identifier that can be used'
+            ' to style the different link types (i.e. assign social media'
+            ' icons to them)'),
+        blank=True,
+    )
+    ordering = models.PositiveIntegerField(verbose_name=_('Ordering'), null=True, blank=True)
+
+    class Meta:
+        ordering = ['ordering', ]
+
+    def __str__(self):
+        return self.name
+
+
+class Activity(models.Model):
+    """(Activity description)"""
+
+    person = models.ForeignKey('Person', verbose_name=_('person'))
+    teams = models.ManyToManyField('Team', verbose_name=_('teams'))
+    date_begin = models.DateField(_('begin date'), null=True, blank=True)
+    date_end = models.DateField(_('end date'), null=True, blank=True)
+    role = models.CharField(_('role'), blank=True, max_length=512)
+    work = models.TextField(_('work'), blank=True)
+
+    def __unicode__(self):
+        return ' - '.join((self.person, self.role, self.date_begin, self.date_end))
diff --git a/app/organization/team/tests.py b/app/organization/team/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/app/organization/team/urls.py b/app/organization/team/urls.py
new file mode 100644 (file)
index 0000000..ace2c07
--- /dev/null
@@ -0,0 +1,15 @@
+from __future__ import unicode_literals
+
+import django.views.i18n
+from django.conf.urls import patterns, include, url
+from django.conf.urls.i18n import i18n_patterns
+
+from mezzanine.core.views import direct_to_template
+from mezzanine.conf import settings
+
+from organization.team.views import *
+
+
+urlpatterns = [
+
+]
diff --git a/app/organization/team/views.py b/app/organization/team/views.py
new file mode 100644 (file)
index 0000000..91ea44a
--- /dev/null
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/app/organization/urls.py b/app/organization/urls.py
new file mode 100644 (file)
index 0000000..adafa01
--- /dev/null
@@ -0,0 +1,21 @@
+from __future__ import unicode_literals
+
+import django.views.i18n
+from django.conf.urls import patterns, include, url
+from django.conf.urls.i18n import i18n_patterns
+from django.contrib import admin
+
+admin.autodiscover()
+
+from mezzanine.core.views import direct_to_template
+from mezzanine.conf import settings
+
+
+urlpatterns = [
+    url("^", include('organization.core.urls')),
+    url("^festival/", include('organization.festival.urls')),
+    url("^magazine/", include('organization.magazine.urls')),
+    url("^media/", include('organization.media.urls')),
+    url("^project/", include('organization.project.urls')),
+    url("^team/", include('organization.team.urls')),
+]
index 18b8e05f7326e4f7c003ff101406c748a0cc8ad9..788d0eba077aa8a00ddd69e8395a4ac49430754b 100644 (file)
@@ -124,11 +124,6 @@ USE_L10N = True
 
 AUTHENTICATION_BACKENDS = ("mezzanine.core.auth_backends.MezzanineBackend",)
 
-# The numeric mode to set newly-uploaded files to. The value should be
-# a mode you'd pass directly to os.chmod.
-FILE_UPLOAD_PERMISSIONS = 0o644
-
-# MAX_UPLOAD_SIZE = 429916160
 
 #############
 # DATABASES #
@@ -213,7 +208,6 @@ INSTALLED_APPS = [
     "mezzanine.core",
     "mezzanine.generic",
     "mezzanine.pages",
-    "custom",
     "mezzanine.blog",
     "mezzanine.forms",
     "mezzanine.galleries",
@@ -224,8 +218,13 @@ INSTALLED_APPS = [
     'djangobower',
     "meta",
     "mezzanine_agenda",
-    "festival",
-    "organization"
+    "organization.core",
+    "organization.team",
+    "organization.festival",
+    "organization.magazine",
+    "organization.media",
+    "organization.project",
+    "organization.featured",
 ]
 
 
@@ -247,8 +246,9 @@ MIGRATION_MODULES = {
 }
 
 MODELTRANSLATION_TRANSLATION_FILES = (
-    'custom.translations',
-    'translations',
+    'organization.core.translation',
+    'organization.festival.translation',
+    'organization.magazine.translation'
 )
 
 TEMPLATES = [{'APP_DIRS': True,
@@ -291,7 +291,7 @@ MIDDLEWARE_CLASSES = (
     # Uncomment the following if using any of the SSL settings:
     # "mezzanine.core.middleware.SSLRedirectMiddleware",
     "mezzanine.pages.middleware.PageMiddleware",
-    "mezzanine.core.middleware.FetchFromCacheMiddleware",
+    "mezzanine.core.middleware.FetchFromCacheMiddleware",
 )
 
 # Store these package names here as they may change in the future since
index 05812b252b173998dc76a7d0f0e4b10f26f6e967..880ded65e4f204d6fb544f7303d06b249f7c337d 100644 (file)
@@ -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 %}
index 4eb5e14a1e8870e8719d2a19f300464cf0510fb0..e6df9efe32904a2a62844d1307c7a1b10b227d1c 100644 (file)
@@ -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 %}
 
index 769f91d4670ff17bffa53a61a17e35f7833735e3..61f0befd6adae544c78689765f6939639f613a1d 100644 (file)
@@ -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 }}
index adfbb11c9d152eeeba96de782c603d73fa301901..c836e03d862b6ec4276fbf3315925f7812266cc6 100644 (file)
@@ -1,4 +1,4 @@
-{% load i18n mezzanine_tags event_tags festival_tags %}
+{% load i18n mezzanine_tags event_tags featured_tags %}
 <div class="event__meta">
     <div class="event__meta__inner">
         {{ event.start }}
index adfda78815518107c1f63c1229fc9c32dabca5d7..a748baff10cb1000e407948f349ebf4281f3006c 100644 (file)
@@ -1,4 +1,4 @@
-{% load i18n mezzanine_tags event_tags festival_tags %}
+{% load i18n mezzanine_tags event_tags featured_tags %}
 <div class="event__meta--alt">
     <div class="event__meta__inner">
         <div class="split-container">
index 1cf860e876f8e41a10f68094c3a88e41e357ca94..7b35715f7e39d0445bb775358d915c613fe8892d 100644 (file)
@@ -1,6 +1,6 @@
 <!doctype html>
 <html lang="{{ LANGUAGE_CODE }}"{% if LANGUAGE_BIDI %} dir="rtl"{% endif %}>
-{% 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 %}
 <head>
     <meta http-equiv="Content-type" content="text/html; charset=utf-8">
index d052deee6e2350fde6dd8466e51273773bb00dfb..9cb3f5368f4f82dd70427990a860de7e565e7ef2 100644 (file)
@@ -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 %}
 
index 6c6a887af5fc8eacde91b9bd9bd7b4f6e3d555bc..fdfca906732df6c0f9140043a37b62a9d264eb78 100644 (file)
@@ -1,4 +1,4 @@
-{% load i18n event_tags festival_tags mezzanine_tags %}
+{% load i18n event_tags featured_tags mezzanine_tags %}
 <div class="hero__slider">
     <ul id="lightSlider">
         {% featured_events as events %}
index a315a7ce53e812658a97eec872c35bf0b5eca8ae..b54288be888adbbf831cdbbb57a94ee3529d8ae1 100644 (file)
@@ -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
new file mode 100644 (file)
index 0000000..f36fcf9
--- /dev/null
@@ -0,0 +1,45 @@
+{% extends "base.html" %}
+{% load i18n mezzanine_tags keyword_tags featured_tags %}
+
+{% block meta_title %}{{ page.meta_title }}{% endblock %}
+
+{% block meta_keywords %}{% metablock %}
+{% keywords_for page as keywords %}
+{% for keyword in keywords %}
+    {% if not forloop.first %}, {% endif %}
+    {{ keyword }}
+{% endfor %}
+{% endmetablock %}{% endblock %}
+
+{% block meta_description %}{% metablock %}
+{{ page.description }}
+{% endmetablock %}{% endblock %}
+
+{% block title %}
+  {% editable page.title %}
+    {{ page.title }}
+  {% endeditable %}
+
+  {% editable page.basicpage.sub_title %}
+    {{ page.basicpage.sub_title }}
+  {% endeditable %}
+{% endblock %}
+
+{% block main %}
+
+  {% editable page.basicpage.content %}
+    {{ page.basicpage.content|richtext_filters|safe }}
+  {% endeditable %}
+
+  <br/>
+  <br/>
+  <br/>
+  <br/>
+
+  {{ page.basicpage.photo }}<br/>
+  {{ page.basicpage.photo_credits }}<br/>
+  {{ page.basicpage.photo_alignment }}<br/>
+  {{ page.basicpage.photo_description }}<br/>
+  {{ page.basicpage.photo_featured }}<br/>
+  {{ page.basicpage.photo_featured_credits }}<br/>
+{% endblock %}
index 141c6013a30fc677c690b9b7e96c7c4922635762..b56657f4f7f79b393a67d9f4c87a7baf8a1255e0 100644 (file)
@@ -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 %}
 
index 0d2f875f6491224d8bb0f2026fe1531304497bc2..f6f43a6e646dfd1c2e451cc9b11c6a6d1af928c8 100644 (file)
@@ -1,5 +1,5 @@
 {% extends "base.html" %}
-{% load i18n festival_tags %}
+{% load i18n featured_tags %}
 
 {% block meta_title %}{% trans "Styles" %}{% endblock %}
 
index 5e1087c230beb25e758e4afacea51065e8afdb38..95ca5cd91def2f6a115c4761bdb96edbb1715e77 100644 (file)
@@ -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 }}
index dcb45e660eb50b0fa19852a150c8e6a1af251cd3..f951aa2a69886f29134d4d76e012cf1e86023d21 100644 (file)
@@ -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" %}
index f952c6374971ff9785ec1e133554250431121459..6e0a5d17c4016a6d8e890c3f3b5a1b3c53c7ad71 100644 (file)
@@ -27,8 +27,11 @@ if settings.USE_MODELTRANSLATION:
 
 
 urlpatterns += [
-    url(r'^festival/', include('festival.urls')),
+    # App urls
+
+    url("^", include('organization.urls')),
     url("^%s/" % settings.EVENT_SLUG, include("mezzanine_agenda.urls")),
+    url("^styles/$", direct_to_template, {"template": "styles.html"}, name="styles"),
 
     # We don't want to presume how your homepage works, so here are a
     # few patterns you can use to set it up.
@@ -40,8 +43,7 @@ urlpatterns += [
     # one homepage pattern, so if you use a different one, comment this
     # one out.
 
-    url("^styles/$", direct_to_template, {"template": "styles.html"}, name="styles"),
-    url("^$", direct_to_template, {"template": "index.html"}, name="home"),
+    # url("^$", direct_to_template, {"template": "index.html"}, name="home"),
 
     # HOMEPAGE AS AN EDITABLE PAGE IN THE PAGE TREE
     # ---------------------------------------------
diff --git a/doc/__init__.py b/doc/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
index 327f10f473a6fce6e7ef4f40d8866baa7d611a43..381037a59353272dc59cf0bc16d7e16705773551 100644 (file)
@@ -1,5 +1,6 @@
 -e git+https://github.com/yomguy/mezzanine-agenda.git#egg=mezzanine-agenda-0.2.2
 -e git+https://github.com/stephenmcd/mezzanine.git#egg=mezzanine-4.1-dev
+-e git+https://github.com/stephenmcd/grappelli-safe#egg=grappelli-safe-0.4.2
 #https://forge.ircam.fr/p/django-eve/source/download/dev/
 #
 #
index 8a9376efa8cb153261d3ad308cbf458bbed9f65a..080247cda17ed84719479dcaa798c5a47a9d60c6 100644 (file)
@@ -11,3 +11,4 @@ django-meta
 django-bower
 django-debug-toolbar
 django-extensions
+django-countries
index 43b57a3fe26ae6bbb69652accac7952b24bb0671..35b7e2f58799d928b316240cf568406ce9d8a5fe 100755 (executable)
@@ -1,4 +1,7 @@
 #!/bin/bash
 
-mysqldump -hdb -uroot -phyRob0otlaz4 ircam-www | gzip > /srv/backup/ircam-www.sql.gz
+export PGPASSWORD=$POSTGRES_PASSWORD
+
+pg_dump -Fc -hdb -Upostgres -dpostgres > /srv/backup/ircam-www.dump
+
 echo "Backup done!"
diff --git a/scripts/makemigrations.sh b/scripts/makemigrations.sh
new file mode 100755 (executable)
index 0000000..e43f5ca
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+docker-compose run app /srv/app/manage.py makemigrations
diff --git a/scripts/migrate.sh b/scripts/migrate.sh
new file mode 100755 (executable)
index 0000000..8b06ce0
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+docker-compose run app /srv/app/manage.py migrate
index 559caac449e85c4897cb0d60f6f705d88ec88d93..ad8f18bf44f73ab45fe5766d72caa39d05281837 100755 (executable)
@@ -1,4 +1,7 @@
 #!/bin/bash
 
-gunzip < /srv/backup/ircam-www.sql.gz | mysql -hdb -uroot -phyRob0otlaz4 ircam-www
+export PGPASSWORD=$POSTGRES_PASSWORD
+
+pg_restore --clean -Fc -hdb -Upostgres -d postgres /srv/backup/ircam-www.dump
+
 echo "Restore done!"