# HG changeset patch # User Chris Ridgway # Date 1321851546 21600 # Node ID a878a98292a8c14f9e3c8ac5c7cdb59e2ad5ea9d # Parent aaac975df679d7875434c9244580060c1fd2145b# Parent ee1a90652c1ebc30b825b31d4b9edabe5e1524b6 Merging new front page design from experimental UI branch. diff -r ee1a90652c1e -r a878a98292a8 .hgignore --- 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 diff -r ee1a90652c1e -r a878a98292a8 bns_website/core/tests/view_tests.py --- 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. diff -r ee1a90652c1e -r a878a98292a8 bns_website/settings/base.py --- 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 diff -r ee1a90652c1e -r a878a98292a8 bns_website/static/css/base.css --- 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 { diff -r ee1a90652c1e -r a878a98292a8 bns_website/static/css/bx_styles.css --- 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*/ diff -r ee1a90652c1e -r a878a98292a8 bns_website/templates/base.html --- 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 @@ {% block custom_meta %}{% endblock %} - + {% block custom_css %}{% endblock %} {% block custom_js %}{% endblock %} diff -r ee1a90652c1e -r a878a98292a8 bns_website/templates/home.html --- 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 @@

Buy Now ยป

{% social_sharing 'Brave New Surf' 'http://bravenewsurf.com' %} -
+

News

{% list_news 5 %} diff -r ee1a90652c1e -r a878a98292a8 bns_website/templates/videos.html --- 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' %} -

Watch

-

TODO: embed some YouTube videos of the bands.

-{% endblock %} diff -r ee1a90652c1e -r a878a98292a8 bns_website/templates/videos/index.html --- /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' %} +

Watch

+ +{% if videos %} +

+Please enjoy this {{ videos|length }} video playlist of the bands that performed on the +Brave New Surf compilation. You can use the button that looks like a widescreen TV +at the bottom of the player to scroll through all the videos. +

+
+ + + +{% else %} +

Videos of the bands are coming soon. Please check back later.

+{% endif %} +{% endblock %} diff -r ee1a90652c1e -r a878a98292a8 bns_website/urls.py --- 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)), ) diff -r ee1a90652c1e -r a878a98292a8 bns_website/videos/admin.py --- /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) diff -r ee1a90652c1e -r a878a98292a8 bns_website/videos/fixtures/playlist.json --- /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 diff -r ee1a90652c1e -r a878a98292a8 bns_website/videos/models.py --- /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)" diff -r ee1a90652c1e -r a878a98292a8 bns_website/videos/tests/__init__.py --- /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 * diff -r ee1a90652c1e -r a878a98292a8 bns_website/videos/tests/view_tests.py --- /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') diff -r ee1a90652c1e -r a878a98292a8 bns_website/videos/urls.py --- /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'), +) diff -r ee1a90652c1e -r a878a98292a8 bns_website/videos/views.py --- /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, + }) diff -r ee1a90652c1e -r a878a98292a8 tools/get_vids.py --- /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)