diff --git a/spa/api/v1/UserResource.py b/spa/api/v1/UserResource.py index 9f5c584..fe94448 100755 --- a/spa/api/v1/UserResource.py +++ b/spa/api/v1/UserResource.py @@ -154,3 +154,12 @@ class UserResource(BackboneCompatibleResource): del bundle.data['email'] del bundle.data['username'] return bundle + + def obj_update(self, bundle, skip_errors=False, **kwargs): + #Handle the patched items from backbone + if bundle.data['following']: + bundle.obj.get_profile().add_follower(bundle.request.user.get_profile()) + else: + bundle.obj.get_profile().remove_follower(bundle.request.user.get_profile()) + + return super(UserResource, self).obj_update(bundle, skip_errors, **kwargs) diff --git a/spa/models/userprofile.py b/spa/models/userprofile.py index 9234e33..13d0e35 100755 --- a/spa/models/userprofile.py +++ b/spa/models/userprofile.py @@ -114,6 +114,9 @@ class UserProfile(_BaseModel): self.logger.warning("Port: %s" % settings.EMAIL_PORT) self.logger.warning("Backend: %s" % settings.EMAIL_BACKEND) + def remove_follower(self, user): + self.followers.remove(user) + def is_follower(self, user): try: return user.get_profile() in self.followers.all() @@ -180,5 +183,22 @@ class UserProfile(_BaseModel): def get_default_avatar_image(cls): return urlparse.urljoin(settings.STATIC_URL, "img/default-avatar-32.png") + """ + handle custom patch methods from tastypie + feels smelly, maybe introduce a tier between + the API and the models to handle these patches + """ + def update_following(self, user, value): + try: + if user is None: + return + if user.is_authenticated(): + if value: + if self.favourites.filter(user=user).count() == 0: + ActivityFavourite(user=user.get_profile(), mix=self).save() + else: + self.favourites.filter(user=user).delete() + except Exception, ex: + self.logger.error("Exception updating favourite: %s" % ex.message) diff --git a/spa/signals.py b/spa/signals.py index 1caaad6..d4afe66 100755 --- a/spa/signals.py +++ b/spa/signals.py @@ -48,12 +48,12 @@ def send_activity_to_realtime(sender, instance, created, **kwargs): post_save.connect(send_activity_to_realtime, sender=ActivityPlay, dispatch_uid="activity-realtime-play") -def create_user_profile(sender, instance, created, **kwargs): - if not created: - UserProfile.objects.create(user=instance) - - -post_save.connect(create_user_profile, sender=User, dispatch_uid="users-profilecreation") +def create_profile(sender, **kw): + user = kw["instance"] + if kw["created"]: + up = UserProfile(user=user) + up.save() +post_save.connect(create_profile, sender=User) if "notification" in settings.INSTALLED_APPS: from notification import models as notification diff --git a/static/js/app/appv2.coffee b/static/js/app/appv2.coffee index 6ae2227..4d3c5b7 100755 --- a/static/js/app/appv2.coffee +++ b/static/js/app/appv2.coffee @@ -1,8 +1,9 @@ -define ['backbone', 'marionette', 'vent', - 'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', 'views/widgets/headerView', - 'views/sidebar/sidebarView', - 'models/mix/mixCollection'], -(Backbone, Marionette, vent, DssRouter, PanningRegion, RealtimeController, AudioController, HeaderView, SidebarView, MixCollection) -> +define ['backbone', 'marionette', 'vent', 'utils' + 'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', + 'views/widgets/headerView', 'views/sidebar/sidebarView', 'models/mix/mixCollection'], +(Backbone, Marionette, vent, utils, + DssRouter, PanningRegion, RealtimeController, AudioController, + HeaderView, SidebarView, MixCollection) -> App = new Marionette.Application(); App.audioController = new AudioController(); App.realtimeController = new RealtimeController(); @@ -61,6 +62,15 @@ define ['backbone', 'marionette', 'vent', social.sharePageToTwitter(model); true + @listenTo vent, "user:follow", (model)-> + console.log "App(vent): user:follow" + model.save 'following', !model.get('following'), patch: true + true + + @listenTo vent, "app:login", -> + utils.modal "/dlg/LoginView" + true + App.headerRegion.show(new HeaderView()); sidebarView = new SidebarView(); App.sidebarRegion.show(sidebarView) diff --git a/static/js/app/appv2.js b/static/js/app/appv2.js index 03f48b1..358fd33 100755 --- a/static/js/app/appv2.js +++ b/static/js/app/appv2.js @@ -1,15 +1,14 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.3.3 (function() { - define(['backbone', 'marionette', 'vent', 'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', 'views/widgets/headerView', 'views/sidebar/sidebarView', 'models/mix/mixCollection'], function(Backbone, Marionette, vent, DssRouter, PanningRegion, RealtimeController, AudioController, HeaderView, SidebarView, MixCollection) { - var App, sidebarView; + define(['backbone', 'marionette', 'vent', 'utils', 'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', 'views/widgets/headerView', 'views/sidebar/sidebarView', 'models/mix/mixCollection'], function(Backbone, Marionette, vent, utils, DssRouter, PanningRegion, RealtimeController, AudioController, HeaderView, SidebarView, MixCollection) { + var App, sidebarView; App = new Marionette.Application(); App.audioController = new AudioController(); App.realtimeController = new RealtimeController(); App.realtimeController.startSocketIO(); App.vent.on("routing:started", function() { var enablePushState, pushState; - console.log("App(vent): routing:started"); enablePushState = true; pushState = !!(enablePushState && window.history && window.history.pushState); @@ -35,7 +34,6 @@ App.addInitializer(function() { $(document).on("click", "a[href]:not([data-bypass])", function(evt) { var href, root; - href = { prop: $(this).prop("href"), attr: $(this).attr("href") @@ -64,7 +62,7 @@ }); return true; }); - return this.listenTo(vent, "mix:share", function(mode, model) { + this.listenTo(vent, "mix:share", function(mode, model) { console.log("App(vent): mix:share"); if (mode === "facebook") { social.sharePageToFacebook(model); @@ -73,6 +71,17 @@ } return true; }); + this.listenTo(vent, "user:follow", function(model) { + console.log("App(vent): user:follow"); + model.save('following', !model.get('following'), { + patch: true + }); + return true; + }); + return this.listenTo(vent, "app:login", function() { + utils.modal("/dlg/LoginView"); + return true; + }); }); App.headerRegion.show(new HeaderView()); sidebarView = new SidebarView(); diff --git a/static/js/app/views/user/userItemView.coffee b/static/js/app/views/user/userItemView.coffee index e2535e4..e9656a8 100755 --- a/static/js/app/views/user/userItemView.coffee +++ b/static/js/app/views/user/userItemView.coffee @@ -1,9 +1,26 @@ -define ['app', 'moment', 'marionette', 'text!/tpl/UserListItemView'], -(App, moment, Marionette, Template)-> +define ['app', 'moment', 'marionette', 'vent', 'text!/tpl/UserListItemView'], +(App, moment, Marionette, vent, Template)-> class UserItemView extends Marionette.ItemView template: _.template(Template) tagName: "tr" + events: + "click #follow-button": "followUser" + "click #follow-button-login": "promptLogin" + templateHelpers: humanise: (date)-> moment(date).fromNow() + + intialize: => + @listenTo(@model, 'change:profile.following', @render) + + + followUser: -> + console.log("UserItemView: followUser") + vent.trigger("user:follow", @model) + + promptLogin:-> + vent.trigger("app:login", @model) + + UserItemView diff --git a/static/js/app/views/user/userItemView.js b/static/js/app/views/user/userItemView.js index 19d64be..1d11633 100755 --- a/static/js/app/views/user/userItemView.js +++ b/static/js/app/views/user/userItemView.js @@ -1,32 +1,52 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.3.3 (function() { - var __hasProp = {}.hasOwnProperty, + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - define(['app', 'moment', 'marionette', 'text!/tpl/UserListItemView'], function(App, moment, Marionette, Template) { - var UserItemView, _ref; + define(['app', 'moment', 'marionette', 'vent', 'text!/tpl/UserListItemView'], function(App, moment, Marionette, vent, Template) { + var UserItemView; + UserItemView = (function(_super) { - return UserItemView = (function(_super) { __extends(UserItemView, _super); function UserItemView() { - _ref = UserItemView.__super__.constructor.apply(this, arguments); - return _ref; + this.intialize = __bind(this.intialize, this); + return UserItemView.__super__.constructor.apply(this, arguments); } UserItemView.prototype.template = _.template(Template); UserItemView.prototype.tagName = "tr"; + UserItemView.prototype.events = { + "click #follow-button": "followUser", + "click #follow-button-login": "promptLogin" + }; + UserItemView.prototype.templateHelpers = { humanise: function(date) { return moment(date).fromNow(); } }; + UserItemView.prototype.intialize = function() { + return this.listenTo(this.model, 'change:profile.following', this.render); + }; + + UserItemView.prototype.followUser = function() { + console.log("UserItemView: followUser"); + return vent.trigger("user:follow", this.model); + }; + + UserItemView.prototype.promptLogin = function() { + return vent.trigger("app:login", this.model); + }; + return UserItemView; })(Marionette.ItemView); + return UserItemView; }); }).call(this); diff --git a/static/js/app/views/widgets/headerView.coffee b/static/js/app/views/widgets/headerView.coffee index cf6ae8d..9d22237 100755 --- a/static/js/app/views/widgets/headerView.coffee +++ b/static/js/app/views/widgets/headerView.coffee @@ -31,7 +31,7 @@ define ["underscore", "marionette", "vent", "utils", "views/widgets/searchView", @searchRegion.show(new SearchView()) login: -> - utils.modal "/dlg/LoginView" + vent.trigger('app:login') logout: -> utils.showAlert "Success", "You are now logged out" diff --git a/static/js/app/views/widgets/headerView.js b/static/js/app/views/widgets/headerView.js index be2b982..0830032 100644 --- a/static/js/app/views/widgets/headerView.js +++ b/static/js/app/views/widgets/headerView.js @@ -52,7 +52,7 @@ Code provided under the BSD License: }; HeaderView.prototype.login = function() { - return utils.modal("/dlg/LoginView"); + return vent.trigger('app:login'); }; HeaderView.prototype.logout = function() { diff --git a/templates/views/UserEmptyView.html b/templates/views/UserEmptyView.html new file mode 100644 index 0000000..dfbad41 --- /dev/null +++ b/templates/views/UserEmptyView.html @@ -0,0 +1 @@ +

User has not uploaded any mixes

\ No newline at end of file diff --git a/templates/views/UserListItemView.html b/templates/views/UserListItemView.html index 9af6d2b..67f9032 100755 --- a/templates/views/UserListItemView.html +++ b/templates/views/UserListItemView.html @@ -16,3 +16,19 @@ <%= profile.following_count %> <%= humanise(date_joined) %> <%= humanise(last_login) %> + + + {% if user.is_authenticated %} + + {% else %} + + {% endif %} + diff --git a/templates/views/UserListView.html b/templates/views/UserListView.html index b2dfaaa..0a44aa7 100755 --- a/templates/views/UserListView.html +++ b/templates/views/UserListView.html @@ -23,6 +23,7 @@ Following Join date Last seen +