changeset 67:a878a98292a8

Merging new front page design from experimental UI branch.
author Chris Ridgway <ckridgway@gmail.com>
date Sun, 20 Nov 2011 22:59:06 -0600
parents aaac975df679 (diff) ee1a90652c1e (current diff)
children 1d50a0db4f21
files bns_website/static/css/base.css bns_website/templates/home.html
diffstat 18 files changed, 306 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Thu Nov 17 22:11:38 2011 -0600
+++ b/.hgignore	Sun Nov 20 22:59:06 2011 -0600
@@ -3,4 +3,5 @@
 *.pyc
 *.swp
 secrets.json
-*.db
\ No newline at end of file
+*.db
+*.mp3
--- a/bns_website/core/tests/view_tests.py	Thu Nov 17 22:11:38 2011 -0600
+++ b/bns_website/core/tests/view_tests.py	Sun Nov 20 22:59:06 2011 -0600
@@ -26,15 +26,6 @@
         self.assertEqual(response.status_code, 200)
         self.assertTemplateUsed(response, 'music.html')
 
-    def test_video(self):
-        """
-        Tests the video page to ensure it displays without errors.
-
-        """
-        response = self.client.get(reverse('videos'))
-        self.assertEqual(response.status_code, 200)
-        self.assertTemplateUsed(response, 'videos.html')
-
     def test_buy(self):
         """
         Tests the buy page to ensure it displays without errors.
--- a/bns_website/settings/base.py	Thu Nov 17 22:11:38 2011 -0600
+++ b/bns_website/settings/base.py	Sun Nov 20 22:59:06 2011 -0600
@@ -128,6 +128,7 @@
     'bands',
     'news',
     'reviews',
+    'videos',
 ]
 
 # A sample logging configuration. The only tangible logging
--- a/bns_website/static/css/base.css	Thu Nov 17 22:11:38 2011 -0600
+++ b/bns_website/static/css/base.css	Sun Nov 20 22:59:06 2011 -0600
@@ -1,5 +1,4 @@
 body {
-   background: url('static/images/polonez_car.png');
    padding-top: 60px
 }
 .social-sharing {
--- a/bns_website/static/css/bx_styles.css	Thu Nov 17 22:11:38 2011 -0600
+++ b/bns_website/static/css/bx_styles.css	Sun Nov 20 22:59:06 2011 -0600
@@ -31,10 +31,12 @@
 .bx-pager a {
    margin-right: 5px;
    color: #fff;
-   padding: 3px 8px 3px 6px;
+   padding: 3px 7px 3px 7px;
    font-size: 12px;
    zoom:1;
-   background: url(../images/gray_pager.png) no-repeat 0 -20px;
+   background-color: #aaa;
+   -moz-border-radius: 20px;
+   -webkit-border-radius: 20px;
 }
 
 /*auto start button*/
@@ -63,7 +65,8 @@
 /*pager links hover and active states*/
 .bx-pager .pager-active,
 .bx-pager a:hover {
-   background-position: 0 0;
+   background-color: #666;
+   text-decoration: none;
 }
 
 /*pager wrapper*/
--- a/bns_website/templates/base.html	Thu Nov 17 22:11:38 2011 -0600
+++ b/bns_website/templates/base.html	Sun Nov 20 22:59:06 2011 -0600
@@ -8,7 +8,7 @@
 <meta charset="utf-8" />
 {% block custom_meta %}{% endblock %}
 <link rel="shortcut icon" href="{{ STATIC_URL }}images/favicon.ico" />
-<link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.3.0/bootstrap.min.css" />
+<link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css" />
 <link rel="stylesheet" href="{{ STATIC_URL }}css/base.css" />
 {% block custom_css %}{% endblock %}
 {% block custom_js %}{% endblock %}
--- a/bns_website/templates/home.html	Thu Nov 17 22:11:38 2011 -0600
+++ b/bns_website/templates/home.html	Sun Nov 20 22:59:06 2011 -0600
@@ -107,7 +107,7 @@
    <p><a href="{% url 'buy' %}" class="btn primary">Buy Now ยป</a></p>
    {% social_sharing 'Brave New Surf' 'http://bravenewsurf.com' %}
    </div>
-   <div class="span7">
+   <div class="span6 offset1">
       <div class="alert-message block-message info">
          <h3>News</h3>
          {% list_news 5 %}
--- a/bns_website/templates/videos.html	Thu Nov 17 22:11:38 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-{% extends 'base.html' %}
-{% load core_tags %}
-{% block title %}Watch{% endblock %}
-{% block content %}
-{% navbar 'videos' %}
-<h1>Watch</h1>
-<p><strong>TODO</strong>: embed some YouTube videos of the bands.</p>
-{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bns_website/templates/videos/index.html	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,41 @@
+{% extends 'base.html' %}
+{% load core_tags %}
+{% block title %}Watch{% endblock %}
+{% block content %}
+{% navbar 'videos' %}
+<h1>Watch</h1>
+
+{% if videos %}
+<p>
+Please enjoy this {{ videos|length }} video playlist of the bands that performed on the 
+<em>Brave New Surf</em> compilation. You can use the button that looks like a widescreen TV
+at the bottom of the player to scroll through all the videos.
+</p>
+<div id="player"></div>
+
+<script>
+var tag = document.createElement('script');
+tag.src = "http://www.youtube.com/player_api";
+var firstScriptTag = document.getElementsByTagName('script')[0];
+firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
+
+var player;
+function onYouTubePlayerAPIReady() {
+  player = new YT.Player('player', {
+    videoId: '{{ videos|first }}',
+    {% if videos|length > 1 %}
+    playerVars: { playlist: [
+       {% for video in videos|slice:"1:" %}
+         {% if not forloop.first %},{% endif %}'{{ video }}'
+       {% endfor %} ]},
+    {% endif %}
+    width: '853',
+    height: '480'
+  });
+}
+</script>
+
+{% else %}
+   <p>Videos of the bands are coming soon. Please check back later.</p>
+{% endif %}
+{% endblock %}
--- a/bns_website/urls.py	Thu Nov 17 22:11:38 2011 -0600
+++ b/bns_website/urls.py	Sun Nov 20 22:59:06 2011 -0600
@@ -25,17 +25,10 @@
     url(r'^listen/$',
         TemplateView.as_view(template_name="music.html"),
         name="music"),
-    url(r'^watch/$',
-        TemplateView.as_view(template_name="videos.html"),
-        name="videos"),
+    url(r'^watch/$', include('videos.urls')),
     url(r'^buy/$',
         TemplateView.as_view(template_name="buy.html"),
         name="buy"),
-
-    # Examples:
-    # url(r'^$', 'bns_website.views.home', name='home'),
-    # url(r'^bns_website/', include('bns_website.foo.urls')),
-
     url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
     url(r'^admin/', include(admin.site.urls)),
 )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bns_website/videos/admin.py	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,95 @@
+"""
+Automatic admin definitions for the videos application.
+
+"""
+import datetime
+import urlparse
+
+try:
+    from urlparse import parse_qs
+except ImportError:
+    from cgi import parse_qs        # for Python 2.5
+
+from django.contrib import admin
+from django.contrib import messages
+from gdata.youtube.service import YouTubeService
+
+from videos.models import Playlist
+
+
+class PlaylistAdmin(admin.ModelAdmin):
+    list_display = ['__unicode__', 'playlist_url', 'sync_date']
+    readonly_fields = ['playlist_title', 'video_list', 'sync_date']
+    actions = ['sync']
+
+    def sync(self, request, queryset):
+        for playlist in queryset:
+            self.sync_playlist(request, playlist)
+
+    sync.short_description = 'Synchronize with YouTube'
+
+    def sync_playlist(self, request, playlist):
+        """
+        Retrieve the title and list of videos for a
+        YouTube playlist.
+
+        """
+        # Find the playlist ID:
+        parts = urlparse.urlparse(playlist.playlist_url)
+        query = parse_qs(parts.query)
+        if 'list' not in query:
+            messages.error(request, 'Invalid playlist %s' %
+                    playlist.playlist_url)
+            return
+
+        playlist_id = query['list'][0]
+        if not playlist_id.startswith('PL'):
+            messages.error(request, 'Invalid playlist ID in %s' %
+                    playlist.playlist_url)
+            return
+        playlist_id = playlist_id[2:]
+
+        # Get the playlist feed:
+        yt = YouTubeService()
+        feed = yt.GetYouTubePlaylistVideoFeed(playlist_id=playlist_id)
+        feed_title = feed.title.text
+        expected_count = int(feed.total_results.text)
+
+        # Get all the videos in the feed; this may take multiple requests:
+        vids = []
+        while True:
+            vids.extend(feed.entry)
+            next_link = feed.GetNextLink()
+            if not next_link:
+                break
+            feed = yt.Query(next_link.href)
+
+        if len(vids) != expected_count:
+            messages.error(request, "%s: expected %d videos, got %d" %
+                    (playlist.playlist_url, expected_count, len(vids)))
+
+        # Find the video ID for each video
+
+        vid_ids = []
+        for vid in vids:
+            for link in vid.link:
+                parts = urlparse.urlparse(link.href)
+                query = parse_qs(parts.query)
+                if 'v' in query:
+                    vid_ids.append(query['v'][0])
+                    break
+            else:
+                messages.error(request, "%s: video id not found for %s" %
+                        (playlist.playlist_url, vid.title.text))
+
+        # Okay, save what we got
+        playlist.playlist_title = feed_title
+        playlist.video_list = ",".join(vid_ids)
+        playlist.sync_date = datetime.datetime.now()
+        playlist.save()
+
+        messages.info(request, "Synchronized %s (%s)" % (feed_title,
+            playlist.playlist_url))
+
+
+admin.site.register(Playlist, PlaylistAdmin)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bns_website/videos/fixtures/playlist.json	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,12 @@
+[
+    {
+        "pk": 1, 
+        "model": "videos.playlist", 
+        "fields": {
+            "playlist_url": "http://www.youtube.com/playlist?list=PL26E22C14D94D323F", 
+            "sync_date": "2011-11-19 12:28:01", 
+            "video_list": "jTJE2PDKkLM,RsWH6Hvk0ec,mBj3BKwyF50,xDYT0Vu0Xrk,XiNjzIBLwSE,kG626uATPcM,J618hE-GUXY,MKU8mgMwkyY,0LGUqEZcjtU,HACgijBE0JM,NFKwh967CQg,l2mTHH1WEFY,e_y6qWBkZvA,d_WvX06VNKU,sAItSEypYrA,ZfcXla0Rppc,HugPW5XZWQ4,PNkxPAReT5I,env8hOFFn44,_mDxMVQy3UY,u5gDyFMluio,sGDm3Wx2hNQ,am59GF0Dc3s,-JKKZaTSEAk,KUzsXvHVTjU,8b1r7t4LT1g,SXtr1u6Yizw,OxunllVGQQo,YJNzGRvI_kU,IFnyaCPyJSk,hIEGYQjkn0I,i9J0-OQkfRc,F8H8w0cLSS8,yxqC67op4gc,EQok0Jsi85k,p5R3HOv5S5k,RiHOgdLjD7g,CHGeikLt-bc,cVBm-XljTuM,Pt3lStrELhc,HgT8LJvdbL0,HACgijBE0JM,QKG0Fu8o7-w,z6bwirjUTTI,JwgfzvNyHoU,R7FEfqy1Xg8,i9J0-OQkfRc,2EI1JVoajBw,oASw2F86koo,chK776zOK-M", 
+            "playlist_title": "Brave New Surf Bands"
+        }
+    }
+]
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bns_website/videos/models.py	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,38 @@
+"""
+Models for the videos app.
+
+"""
+from django.db import models
+
+
+# The whole purpose of this application is to shuffle the videos in
+# a YouTube playlist.
+#
+# We'd like to embed a video player to show videos from all the bands that
+# participated in the compilation. So we create a YouTube playlist with a long
+# list of videos. YouTube no longer offers a way to shuffle the playlist via
+# the embed code. Instead we will use the YouTube Javascript API to accomplish
+# this. However, the Javascript API works off of video IDs. So we now need
+# a way to obtain all the video IDs from a YouTube playlist. This model
+# stores the playlist URL and an admin function can be run to retrieve the
+# video ID list via the Python GData YouTube API. The video list is also
+# stored in this model. The view function can then read the video ID list,
+# randomize it, and give it to the template to build the appropriate 
+# Javascript.
+
+class Playlist(models.Model):
+    """
+    This model stores a YouTube playlist URL and a list of video IDs that
+    make up the playlist.
+
+    """
+    playlist_title = models.CharField(max_length=128, blank=True)
+    playlist_url = models.URLField()
+    video_list = models.TextField(blank=True)
+    sync_date = models.DateTimeField(null=True, blank=True)
+
+    def __unicode__(self):
+        if self.playlist_title:
+            return self.playlist_title
+
+        return u"(Please sync with YouTube to retrieve the videos)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bns_website/videos/tests/__init__.py	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,1 @@
+from view_tests import *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bns_website/videos/tests/view_tests.py	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,33 @@
+"""
+View tests for the videos application.
+
+"""
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+
+
+class NoVideosTest(TestCase):
+
+    def test_index(self):
+        """
+        Test that the page displays without any videos in the database.
+
+        """
+        response = self.client.get(reverse('videos'))
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(len(response.context['videos']), 0)
+        self.assertTemplateUsed(response, 'videos/index.html')
+
+
+class SomeVideosTest(TestCase):
+    fixtures = ['playlist.json']
+
+    def test_index(self):
+        """
+        Test that the page displays with videos in the database.
+
+        """
+        response = self.client.get(reverse('videos'))
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(len(response.context['videos']), 50)
+        self.assertTemplateUsed(response, 'videos/index.html')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bns_website/videos/urls.py	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,9 @@
+"""
+URLs for the videos application.
+
+"""
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('videos.views',
+    url(r'^$', 'index', name='videos'),
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bns_website/videos/views.py	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,20 @@
+"""
+Views for the videos application.
+
+"""
+import random
+from django.shortcuts import render
+from videos.models import Playlist
+
+
+def index(request):
+    qs = Playlist.objects.all()
+    videos = []
+    for playlist in qs:
+        videos.extend(playlist.video_list.split(','))
+
+    random.shuffle(videos)
+
+    return render(request, 'videos/index.html', {
+        'videos': videos,
+        })
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/get_vids.py	Sun Nov 20 22:59:06 2011 -0600
@@ -0,0 +1,45 @@
+"""
+Quick & dirty Python script to retrieve the video IDs of all the videos in a
+playlist on YouTube.
+
+"""
+import urlparse
+
+from gdata.youtube.service import YouTubeService
+
+
+PLAYLIST_ID = '26E22C14D94D323F'
+
+yt = YouTubeService()
+feed = yt.GetYouTubePlaylistVideoFeed(playlist_id=PLAYLIST_ID)
+
+print "Feed contains %s videos" % feed.total_results.text
+
+vids = []
+while True:
+    vids.extend(feed.entry)
+    next_link = feed.GetNextLink()
+    if not next_link:
+        break
+    feed = yt.Query(next_link.href)
+
+print "Got %d videos" % len(vids)
+
+vid_ids = []
+problems = []
+for vid in vids:
+    for link in vid.link:
+        url_data = urlparse.urlparse(link.href)
+        query = urlparse.parse_qs(url_data.query)
+        if 'v' in query:
+            vid_ids.append(query['v'][0])
+            break
+    else:
+        print "Video id not found for %s" % vid.title.text
+
+video_id = vid_ids[0]
+playlist = vid_ids[1:]
+
+print "videoId: '%s'," % video_id
+print "playerVars: { playlist: [ %s ] }," % ','.join("'%s'" % v for v in
+        playlist)