diff --git a/dss/localsettings.initial.py b/dss/localsettings.initial.py new file mode 100644 index 0000000..e5c96a6 --- /dev/null +++ b/dss/localsettings.initial.py @@ -0,0 +1,40 @@ +import os +DEBUG = True +if os.name == 'posix': + DSS_TEMP_PATH = "/tmp/" + DSS_LAME_PATH = "lame" + DSS_WAVE_PATH = "waveformgen" +else: + DSS_TEMP_PATH = "d:\\temp\\" + DSS_LAME_PATH = "D:\\Apps\\lame\\lame.exe" + DSS_WAVE_PATH = "d:\\Apps\\waveformgen.exe" + +DATABASE_NAME = 'deepsouthsounds' +DATABASE_USER = 'root' +DATABASE_PASSWORD = '' +#DATABASE_HOST = '' + +PIPELINE_YUI_BINARY = "" +FACEBOOK_APP_SECRET = '' + +JS_SETTINGS = { + 'CHAT_HOST' : "ext-test.deepsouthsounds.com:8081", + 'API_URL' : "/api/v1/", + 'LIVE_STREAM_URL' : "radio.deepsouthsounds.com", + 'LIVE_STREAM_PORT' : "8000", + 'LIVE_STREAM_MOUNT' : "mp3", + 'DEFAULT_AUDIO_VOLUME' : "1", + 'SM_DEBUG_MODE' : False, + 'LIVE_STREAM_INFO_URL' : "radio.deepsouthsounds.com:8000/mp3" +} +""" +WAVEFORM_URL = 'http://waveforms.podnoms.com/' +IMAGE_URL = 'http://images.podnoms.com/' +STATIC_URL = 'http://static.podnoms.com/' +""" +IMAGE_URL = 'http://ext-test.deepsouthsounds.com:8000/media/' +GOOGLE_ANALYTICS_CODE = '' +SENDFILE_BACKEND = 'sendfile.backends.development' +#SENDFILE_BACKEND = 'sendfile.backends.xsendfile' +#SENDFILE_BACKEND = 'sendfile.backends.nginx' + diff --git a/dss/settings.py b/dss/settings.py index 1f19808..9758493 100644 --- a/dss/settings.py +++ b/dss/settings.py @@ -142,6 +142,7 @@ INSTALLED_APPS = ( 'django_extensions', 'compressor', 'djcelery', + 'polymodels', 'sorl.thumbnail', 'south', # the only requirement for SCT 'avatar', diff --git a/spa/api/v1/ActivityResource.py b/spa/api/v1/ActivityResource.py index 7809974..c82d329 100644 --- a/spa/api/v1/ActivityResource.py +++ b/spa/api/v1/ActivityResource.py @@ -13,6 +13,22 @@ class ActivityResource(BackboneCompatibleResource): authentication = Authentication() always_return_data = True - def dehydrate(self, bundle): + def get_object_list(self, request): + return self._meta.queryset.select_subclasses() - return bundle \ No newline at end of file + def dehydrate(self, bundle): + try: + if bundle.obj.user is not None: + bundle.data["message"] = "%s %s a %s on %s" %\ + (bundle.obj.user.get_full_name(), + bundle.obj.get_verb_passed(), + bundle.obj.get_object_singular(), + bundle.obj.date) + return bundle + + except AttributeError, ae: + self.logger.debug("AttributeError: Error dehydrating activity, %s" % ae.message) + except TypeError, te: + self.logger.debug("TypeError: Error dehydrating activity, %s" % te.message) + except Exception, ee: + self.logger.debug("Exception: Error dehydrating activity, %s" % ee.message) diff --git a/spa/api/v1/BackboneCompatibleResource.py b/spa/api/v1/BackboneCompatibleResource.py index 22b9638..d7c2053 100644 --- a/spa/api/v1/BackboneCompatibleResource.py +++ b/spa/api/v1/BackboneCompatibleResource.py @@ -1,3 +1,4 @@ +import logging from django.conf.urls import url from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from tastypie import fields @@ -7,4 +8,5 @@ from tastypie.utils import trailing_slash class BackboneCompatibleResource(ModelResource): + logger = logging.getLogger(__name__) pass diff --git a/spa/api/v1/MixResource.py b/spa/api/v1/MixResource.py index f903264..545d43a 100644 --- a/spa/api/v1/MixResource.py +++ b/spa/api/v1/MixResource.py @@ -6,6 +6,7 @@ from tastypie.authorization import Authorization from tastypie.constants import ALL_WITH_RELATIONS from tastypie.http import HttpMultipleChoices, HttpGone from core.serialisers import json +from spa.api.v1.ActivityResource import ActivityResource from spa.api.v1.BackboneCompatibleResource import BackboneCompatibleResource from spa.api.v1.CommentResource import CommentResource from spa.models import Genre @@ -15,8 +16,8 @@ from spa.models.Mix import Mix class MixResource(BackboneCompatibleResource): - #comments = fields.ToManyField('spa.api.v1.CommentResource.CommentResource', 'comments', 'mix', null=True, full=True) comments = fields.ToManyField('spa.api.v1.CommentResource.CommentResource', 'comments', 'mix', null=True) + activity = fields.ToManyField('spa.api.v1.ActivityResource.ActivityResource', 'activity', 'mix', null=True) class Meta: queryset = Mix.objects.filter(is_active=True) @@ -39,13 +40,20 @@ class MixResource(BackboneCompatibleResource): return ret + def _unpackGenreList(self, bundle, genres): + genre_list = self._parseGenreList(genres) + bundle.obj.genres = genre_list + bundle.obj.save() + def prepend_urls(self): return [ - url(r"^(?P%s)/(?P\w[\w/-]*)/children%s$" % - (self._meta.resource_name, trailing_slash()), self.wrap_view('get_children'), name="api_get_children"), + url(r"^(?P%s)/(?P\w[\w/-]*)/comments%s$" % + (self._meta.resource_name, trailing_slash()), self.wrap_view('get_comments'), name="api_get_comments"), + url(r"^(?P%s)/(?P\w[\w/-]*)/activity%s$" % + (self._meta.resource_name, trailing_slash()), self.wrap_view('get_activity'), name="api_get_activity"), ] - def get_children(self, request, **kwargs): + def get_comments(self, request, **kwargs): try: obj = self.cached_obj_get(request=request, **self.remove_api_resource_names(kwargs)) except ObjectDoesNotExist: @@ -54,10 +62,14 @@ class MixResource(BackboneCompatibleResource): child_resource = CommentResource() return child_resource.get_list(request, mix=obj) - def _unpackGenreList(self, bundle, genres): - genre_list = self._parseGenreList(genres) - bundle.obj.genres = genre_list - bundle.obj.save() + def get_activity(self, request, **kwargs): + try: + obj = self.cached_obj_get(request=request, **self.remove_api_resource_names(kwargs)) + except ObjectDoesNotExist: + return HttpGone() + + child_resource = ActivityResource() + return child_resource.get_list(request, mix=obj) def obj_create(self, bundle, request=None, **kwargs): file_name = "mixes/%s.%s" % (bundle.data['upload-hash'], bundle.data['upload-extension']) diff --git a/spa/models/MixDownload.py b/spa/models/MixDownload.py index 0be2cb3..2785380 100644 --- a/spa/models/MixDownload.py +++ b/spa/models/MixDownload.py @@ -3,3 +3,9 @@ from spa.models._Activity import _Activity class MixDownload(_Activity): mix = models.ForeignKey('spa.Mix', related_name='downloads') + + def get_verb_passed(self): + return "downloaded" + + def get_object_singular(self): + return "mix" diff --git a/spa/models/MixFavourite.py b/spa/models/MixFavourite.py index 3a05e5f..c43e5a3 100644 --- a/spa/models/MixFavourite.py +++ b/spa/models/MixFavourite.py @@ -3,3 +3,9 @@ from django.db import models class MixFavourite(_Activity): mix = models.ForeignKey(Mix, related_name='favourites') + + def get_verb_passed(self): + return "favourited" + + def get_object_singular(self): + return "mix" diff --git a/spa/models/MixLike.py b/spa/models/MixLike.py index eeb9fa2..5728820 100644 --- a/spa/models/MixLike.py +++ b/spa/models/MixLike.py @@ -6,3 +6,9 @@ class MixLike(_Activity): def __unicode__(self): return "%s on %s" % (self.user.get_full_name(), self.mix.title) + + def get_verb_passed(self): + return "liked" + + def get_object_singular(self): + return "mix" diff --git a/spa/models/MixPlay.py b/spa/models/MixPlay.py index 5613180..889f880 100644 --- a/spa/models/MixPlay.py +++ b/spa/models/MixPlay.py @@ -2,4 +2,10 @@ from django.db import models from spa.models._Activity import _Activity class MixPlay(_Activity): - mix = models.ForeignKey('spa.Mix', related_name='plays') \ No newline at end of file + mix = models.ForeignKey('spa.Mix', related_name='plays') + + def get_verb_passed(self): + return "played" + + def get_object_singular(self): + return "mix" diff --git a/spa/models/UserProfile.py b/spa/models/UserProfile.py index 94e199b..6b44d8c 100644 --- a/spa/models/UserProfile.py +++ b/spa/models/UserProfile.py @@ -99,9 +99,9 @@ class UserProfile(_BaseModel): image = "%s%s" % (settings.MEDIA_URL, get_thumbnail(image, "170x170", crop='center').name) return image except SuspiciousOperation, ex: - self.logger.warn("Error getting small profile image: %s", ex.message) + self.logger.warn("Error getting medium profile image: %s", ex.message) except IOError, ex: - self.logger.warn("Error getting small profile image: %s", ex.message) + self.logger.warn("Error getting medium profile image: %s", ex.message) def get_small_profile_image(self): try: @@ -114,7 +114,6 @@ class UserProfile(_BaseModel): except IOError, ex: self.logger.warn("Error getting small profile image: %s", ex.message) - def get_avatar_image(self, size=150): avatar_type = self.avatar_type if avatar_type == 'gravatar': diff --git a/spa/models/_Activity.py b/spa/models/_Activity.py index b7b163d..1a86355 100644 --- a/spa/models/_Activity.py +++ b/spa/models/_Activity.py @@ -1,8 +1,28 @@ from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType from django.db import models +import abc +from model_utils.managers import InheritanceManager from spa.models._BaseModel import _BaseModel class _Activity(_BaseModel): - date = models.DateTimeField(auto_now=True) user = models.ForeignKey(User, null=True) - uid = models.CharField(max_length=50, blank=True, null = True) \ No newline at end of file + uid = models.CharField(max_length=50, blank=True, null=True) + date = models.DateTimeField(auto_now=True) + objects = InheritanceManager() + + @abc.abstractmethod + def get_verb_passed(self): + return + + @abc.abstractmethod + def get_verb_present(self): + return + + @abc.abstractmethod + def get_object_singular(self): + return + + @abc.abstractmethod + def get_object_plural(self): + return diff --git a/spa/models/_BaseModel.py b/spa/models/_BaseModel.py index 94a95b8..60af7ff 100644 --- a/spa/models/_BaseModel.py +++ b/spa/models/_BaseModel.py @@ -1,17 +1,12 @@ import logging -from django.contrib.contenttypes.models import ContentType from django.db import models -from django.db.models import ForeignKey from django.utils import simplejson import os -from polymodels.models import BasePolymorphicModel from core.utils import url from dss import localsettings, settings -class _BaseModel(BasePolymorphicModel): +class _BaseModel(models.Model): logger = logging.getLogger(__name__) - content_type = ForeignKey(ContentType, null=True) - CONTENT_TYPE_FIELD = 'content_type' class Meta: abstract = True diff --git a/static/js/app/app.js b/static/js/app/app.js index b34d23b..f31cf2d 100644 --- a/static/js/app/app.js +++ b/static/js/app/app.js @@ -61,13 +61,6 @@ var AppRouter = Backbone.Router.extend({ var mixList = new MixCollection(); mixList.type = type || 'latest'; $('#site-content-fill').html(''); - this.sidebarView = new SidebarView(); - $('#sidebar').html(this.sidebarView.el); - startChat( - $('#chat-messages-body', this.sidebarView.el), - $('#input', this.sidebarView.el), - $('#status', this.sidebarView.el), - $('#header-profile-edit').text()); var payload = $.extend(type != undefined ? {type:type} : null, data); mixList.fetch({ @@ -83,6 +76,13 @@ var AppRouter = Backbone.Router.extend({ } } }); + this.sidebarView = new SidebarView(); + $('#sidebar').html(this.sidebarView.el); + startChat( + $('#chat-messages-body', this.sidebarView.el), + $('#input', this.sidebarView.el), + $('#status', this.sidebarView.el), + $('#header-profile-edit').text()); }, mixDetails:function (id) { var mix = new Mix({ @@ -239,7 +239,7 @@ var AppRouter = Backbone.Router.extend({ } }); -com.podnoms.utils.loadTemplate(['HeaderView', 'SidebarView', 'UserView', 'MixListView', 'MixListItemView', 'MixView', 'MixCreateView', 'CommentListView', 'CommentListItemView', 'ReleaseListView', 'ReleaseListItemView', 'ReleaseItemView', 'ReleaseView', 'ReleaseCreateView', 'ReleaseAudioListView', 'ReleaseAudioItemView', 'EventCreateView', 'EventListView', 'EventListItemView', 'EventView', 'EventItemView'], function () { +com.podnoms.utils.loadTemplate(['HeaderView', 'SidebarView', 'UserView', 'MixListView', 'MixListItemView', 'MixView', 'MixCreateView', 'CommentListView', 'CommentListItemView', 'ActivityListView', 'ActivityListItemView', 'ReleaseListView', 'ReleaseListItemView', 'ReleaseItemView', 'ReleaseView', 'ReleaseCreateView', 'ReleaseAudioListView', 'ReleaseAudioItemView', 'EventCreateView', 'EventListView', 'EventListItemView', 'EventView', 'EventItemView'], function () { window.app = new AppRouter(); $(document).on('click', 'a:internal:not(.no-click)', function (event) { Backbone.history.navigate($(this).attr('href'), { diff --git a/static/js/app/models/activity.js b/static/js/app/models/activity.js new file mode 100644 index 0000000..e491e54 --- /dev/null +++ b/static/js/app/models/activity.js @@ -0,0 +1,19 @@ +/** @license + + ---------------------------------------------- + + Copyright (c) 2012, Fergal Moran. All rights reserved. + Code provided under the BSD License: + + */ +var Activity = DSSModel.extend({ + urlRoot:com.podnoms.settings.urlRoot + "activity/" +}); + +var ActivityCollection = TastypieCollection.extend({ + model: Activity, + url:com.podnoms.settings.urlRoot + "activity/", + comparator: function (activity) { + return -activity.get("id"); + } +}); \ No newline at end of file diff --git a/static/js/app/views/activity.js b/static/js/app/views/activity.js new file mode 100644 index 0000000..ee7570b --- /dev/null +++ b/static/js/app/views/activity.js @@ -0,0 +1,33 @@ +/** @license + + ---------------------------------------------- + + Copyright (c) 2012, Fergal Moran. All rights reserved. + Code provided under the BSD License: + + */ +window.ActivityListItemView = Backbone.View.extend({ + tagName:"li", + initialize:function () { + $(this.el).data("id", this.model.get("id")); + $(this.el).addClass("activity-entry"); + }, + render:function () { + $(this.el).html(this.template({"item":this.model.toJSON()})); + return this; + } +}); + +window.ActivityListView = Backbone.View.extend({ + initialize:function () { + //this.collection.bind('add', this.render); + this.render(); + }, + render:function () { + $(this.el).html(this.template()).append('
    '); + this.collection.each(function (item) { + $('.activity-listing', this.el).append(new ActivityListItemView({model:item}).render().el); + }, this); + return this; + } +}); diff --git a/static/js/app/views/sidebar.js b/static/js/app/views/sidebar.js index 6649c7e..67ef98c 100644 --- a/static/js/app/views/sidebar.js +++ b/static/js/app/views/sidebar.js @@ -8,11 +8,11 @@ */ window.SidebarView = Backbone.View.extend({ - events:{ - "click #sidebar-play-pause-button-small":"togglePlayState", - "click #sidebar-listen-live":"playLive" + events: { + "click #sidebar-play-pause-button-small": "togglePlayState", + "click #sidebar-listen-live": "playLive" }, - initialize:function () { + initialize: function () { this.render(); _.bindAll(this, "trackChanged"); _.bindAll(this, "trackPlaying"); @@ -26,27 +26,36 @@ window.SidebarView = Backbone.View.extend({ $("#live-now-playing", this.el).text(data.title); }); }, - render:function () { + render: function () { $(this.el).html(this.template()); + var activity = new ActivityCollection(); + activity.fetch({ + success: function () { + var content = new ActivityListView({ + collection: activity + }).el; + $('.sidebar-content-activity', this.el).html(content.el); + } + }); return this; }, - togglePlayState:function () { + togglePlayState: function () { }, - trackChanged:function (data) { + trackChanged: function (data) { $(this.el).find('#now-playing').text(data.title); if (data.item_url != undefined) $(this.el).find('#now-playing').attr("href", "#" + data.item_url); }, - trackPlaying:function (data) { + trackPlaying: function (data) { $(this.el).find('#header-play-button-icon').removeClass('icon-play'); $(this.el).find('#header-play-button-icon').addClass('icon-pause'); }, - trackPaused:function (data) { + trackPaused: function (data) { $(this.el).find('#header-play-button-icon').removeClass('icon-pause'); $(this.el).find('#header-play-button-icon').addClass('icon-play'); }, - playLive:function () { + playLive: function () { var liveButton = $(this.el).find('#sidebar-listen-live'); if ((liveButton).hasClass('btn-danger')) { com.podnoms.player.stopPlaying(); @@ -55,7 +64,7 @@ window.SidebarView = Backbone.View.extend({ } else { liveButton.button('loading'); com.podnoms.player.playLive({ - success:function () { + success: function () { $.getJSON( 'ajax/live_now_playing/', function (data) { @@ -66,7 +75,7 @@ window.SidebarView = Backbone.View.extend({ liveButton.removeClass('btn-success').addClass('btn-danger').text('Stop listening'); } ); - } + } }); } _eventAggregator.trigger("track_playing") diff --git a/templates/base.html b/templates/base.html index 11fc3f9..c76f677 100644 --- a/templates/base.html +++ b/templates/base.html @@ -7,18 +7,18 @@ {% load compress %} {% compress css %} - - - - - - - - - - - - + + + + + + + + + + + + {% endcompress %} {% compress js %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% endcompress %} {% block footerscripts %} {% endblock %} diff --git a/templates/inc/activity.html b/templates/inc/activity.html new file mode 100644 index 0000000..5e2fcfd --- /dev/null +++ b/templates/inc/activity.html @@ -0,0 +1,15 @@ +
    + + + + +
    + + <%= item.user_name %> + +
    +

    <%= item.activity %>

    + <%= item.date_created %> +
    +
    +
    diff --git a/templates/views/ActivityListItemView.html b/templates/views/ActivityListItemView.html new file mode 100644 index 0000000..367294d --- /dev/null +++ b/templates/views/ActivityListItemView.html @@ -0,0 +1,17 @@ +{% load account_tags %} + +
    + + + + +
    + + <%= item.user_name %> + +
    +

    <%= item.activity %>

    + <%= item.date_created %> +
    +
    +
    diff --git a/templates/views/ActivityListView.html b/templates/views/ActivityListView.html new file mode 100644 index 0000000..1d594b6 --- /dev/null +++ b/templates/views/ActivityListView.html @@ -0,0 +1,2 @@ +

    Activity

    +
    diff --git a/templates/views/SidebarView.html b/templates/views/SidebarView.html index 0477422..204087f 100644 --- a/templates/views/SidebarView.html +++ b/templates/views/SidebarView.html @@ -1,9 +1,13 @@
    -
    +
    + +
    +
    {% include 'inc/side-player.html' %} {% include 'inc/twitter.html' %}