From 758772b3310fb04b2a58af88ce2b9b42cad9c15a Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Thu, 4 Jul 2013 21:45:58 +0100 Subject: [PATCH] Added follow button to user list --- spa/api/v1/UserResource.py | 173 ++++++++----------- spa/models/userprofile.py | 3 +- static/js/app/appv2.coffee | 18 +- static/js/app/appv2.js | 28 ++- static/js/app/views/user/userItemView.coffee | 4 +- static/js/app/views/user/userItemView.js | 14 +- templates/views/UserListItemView.html | 16 +- 7 files changed, 117 insertions(+), 139 deletions(-) diff --git a/spa/api/v1/UserResource.py b/spa/api/v1/UserResource.py index c6d0323..e874710 100755 --- a/spa/api/v1/UserResource.py +++ b/spa/api/v1/UserResource.py @@ -14,44 +14,67 @@ from spa.models.userprofile import UserProfile from spa.models.mix import Mix -class UserProfileResource(BackboneCompatibleResource): - mix_count = fields.IntegerField(readonly=True) - +class UserResource(BackboneCompatibleResource): class Meta: - queryset = UserProfile.objects.all() - resource_name = 'profile' - include_resource_uri = False - include_absolute_url = False - always_return_data = True + queryset = UserProfile.objects.all().annotate(mix_count=Count('mixes')).order_by('-mix_count') + resource_name = 'user' + excludes = ['is_active', 'is_staff', 'is_superuser', 'password'] + ordering = ['mix_count'] authorization = DjangoAuthorization() authentication = Authentication() - - def get_object_list(self, request): - return super(UserProfileResource, self).get_object_list(request).annotate(mix_count=Count('mixes')) + favourites = fields.ToManyField(MixResource, 'favourites') def _hydrateBitmapOption(self, source, comparator): return True if (source & comparator) != 0 else False - def hydrate(self, bundle): - if 'activity_sharing_likes' in bundle.data: - likes = UserProfile.ACTIVITY_SHARE_LIKES if bundle.data['activity_sharing_likes'] else 0 - favourites = UserProfile.ACTIVITY_SHARE_FAVOURITES if bundle.data['activity_sharing_favourites'] else 0 - comments = UserProfile.ACTIVITY_SHARE_COMMENTS if bundle.data['activity_sharing_comments'] else 0 - bundle.data['activity_sharing'] = (likes | favourites | comments) - del bundle.data['activity_sharing_likes'] - del bundle.data['activity_sharing_favourites'] - del bundle.data['activity_sharing_comments'] + def prepend_urls(self): + return [ + url(r"^(?P%s)/(?P[\w\d_.-]+)/favourites%s$" % ( + self._meta.resource_name, trailing_slash()), + self.wrap_view('get_user_favourites'), name="api_get_user_favourites"), + url(r"^(?P%s)/(?P\d+)/$" % self._meta.resource_name, + self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), + url(r"^(?P%s)/(?P[\w\d_.-]+)/$" % self._meta.resource_name, + self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), + ] - if 'activity_sharing_networks_facebook' in bundle.data: - facebook = UserProfile.ACTIVITY_SHARE_NETWORK_FACEBOOK if bundle.data[ - 'activity_sharing_networks_facebook'] else 0 - twitter = UserProfile.ACTIVITY_SHARE_NETWORK_TWITTER if bundle.data[ - 'activity_sharing_networks_twitter'] else 0 - bundle.data['activity_sharing_networks'] = (facebook | twitter) - del bundle.data['activity_sharing_networks_facebook'] - del bundle.data['activity_sharing_networks_twitter'] + """ + Stub method, not actually needed just yet + but take heed of note below when implementing in the future + def apply_sorting(self, obj_list, options=None): + #apply the sort to the obj_list, not the super call + """ + + def apply_filters(self, request, applicable_filters): + semi_filtered = super(UserResource, self).apply_filters(request, applicable_filters) + q = request.GET.get('q', None) + if q is not None: + semi_filtered = semi_filtered.filter( + Q(user_first_name__icontains=q) | + Q(user_last_name__icontains=q) + ) + + return semi_filtered + + def get_user_favourites(self, request, **kwargs): + try: + obj = self.cached_obj_get(bundle=self.build_bundle(request=request), + **self.remove_api_resource_names(kwargs)) + except ObjectDoesNotExist: + return HttpGone() + except MultipleObjectsReturned: + return HttpMultipleChoices("More than one resource is found at this URI.") + + mixes = MixResource() + return mixes.get_list(request, favourites__user=obj.get_profile()) + + def _patch_resource(self, bundle): + #Handle the patched items from backbone + if bundle.data['following']: + bundle.obj.add_follower(bundle.request.user.get_profile()) + else: + bundle.obj.remove_follower(bundle.request.user.get_profile()) - return bundle def obj_update(self, bundle, skip_errors=False, **kwargs): """ @@ -64,7 +87,9 @@ class UserProfileResource(BackboneCompatibleResource): if 'activity_sharing_favourites' in kwargs: del kwargs['activity_sharing_favourites'] if 'activity_sharing_comments' in kwargs: del kwargs['activity_sharing_comments'] - return super(UserProfileResource, self).obj_update(bundle, skip_errors, **kwargs) + self._patch_resource(bundle) + + return super(UserResource, self).obj_update(bundle, skip_errors, **kwargs) def dehydrate(self, bundle): del bundle.data['activity_sharing'] @@ -92,75 +117,29 @@ class UserProfileResource(BackboneCompatibleResource): bundle.data['following_count'] = bundle.obj.following.count() bundle.data['following'] = bundle.obj.is_follower(bundle.request.user) bundle.data['url'] = bundle.obj.get_profile_url() + bundle.data['date_joined'] = bundle.obj.user.date_joined + bundle.data['last_login'] = bundle.obj.user.last_login + bundle.data['mix_count'] = bundle.obj.mix_count + return bundle - def dehydrate_mix_count(self, bundle): - return bundle.obj.mixes.count() + def hydrate(self, bundle): + if 'activity_sharing_likes' in bundle.data: + likes = UserProfile.ACTIVITY_SHARE_LIKES if bundle.data['activity_sharing_likes'] else 0 + favourites = UserProfile.ACTIVITY_SHARE_FAVOURITES if bundle.data['activity_sharing_favourites'] else 0 + comments = UserProfile.ACTIVITY_SHARE_COMMENTS if bundle.data['activity_sharing_comments'] else 0 + bundle.data['activity_sharing'] = (likes | favourites | comments) + del bundle.data['activity_sharing_likes'] + del bundle.data['activity_sharing_favourites'] + del bundle.data['activity_sharing_comments'] + if 'activity_sharing_networks_facebook' in bundle.data: + facebook = UserProfile.ACTIVITY_SHARE_NETWORK_FACEBOOK if bundle.data[ + 'activity_sharing_networks_facebook'] else 0 + twitter = UserProfile.ACTIVITY_SHARE_NETWORK_TWITTER if bundle.data[ + 'activity_sharing_networks_twitter'] else 0 + bundle.data['activity_sharing_networks'] = (facebook | twitter) + del bundle.data['activity_sharing_networks_facebook'] + del bundle.data['activity_sharing_networks_twitter'] -class UserResource(BackboneCompatibleResource): - profile = fields.ToOneField(UserProfileResource, attribute='userprofile', related_name='user', full=True) - - class Meta: - queryset = User.objects.all() - resource_name = 'user' - excludes = ['is_active', 'is_staff', 'is_superuser', 'password'] - ordering = ['mix_count'] - authorization = DjangoAuthorization() - authentication = Authentication() - favourites = fields.ToManyField(MixResource, 'favourites') - - def prepend_urls(self): - return [ - url(r"^(?P%s)/(?P[\w\d_.-]+)/favourites%s$" % ( - self._meta.resource_name, trailing_slash()), - self.wrap_view('get_user_favourites'), name="api_get_user_favourites"), - url(r"^(?P%s)/(?P\d+)/$" % self._meta.resource_name, - self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), - url(r"^(?P%s)/(?P[\w\d_.-]+)/$" % self._meta.resource_name, - self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), - ] - - def apply_filters(self, request, applicable_filters): - semi_filtered = super(UserResource, self).apply_filters(request, applicable_filters) - q = request.GET.get('q', None) - if q is not None: - semi_filtered = semi_filtered.filter( - Q(first_name__icontains=q) | - Q(last_name__icontains=q) - ) - - return semi_filtered - - def apply_sorting(self, obj_list, options=None): - return super(UserResource, self).apply_sorting(obj_list, options) - - def get_object_list(self, request): - return super(UserResource, self).get_object_list(request) - - def get_user_favourites(self, request, **kwargs): - try: - obj = self.cached_obj_get(bundle=self.build_bundle(request=request), - **self.remove_api_resource_names(kwargs)) - except ObjectDoesNotExist: - return HttpGone() - except MultipleObjectsReturned: - return HttpMultipleChoices("More than one resource is found at this URI.") - - mixes = MixResource() - return mixes.get_list(request, favourites__user=obj.get_profile()) - - def dehydrate(self, bundle): - if bundle.obj.id != bundle.request.user.id: - 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 7dddf6f..150fd19 100755 --- a/spa/models/userprofile.py +++ b/spa/models/userprofile.py @@ -4,6 +4,7 @@ import urlparse from django.contrib.auth.models import User from django.core.exceptions import SuspiciousOperation from django.db import models +from django.db.models import Count from django_gravatar.helpers import has_gravatar, get_gravatar_url from sorl.thumbnail import get_thumbnail @@ -23,7 +24,7 @@ def avatar_name(instance, filename): class UserProfileManager(models.Manager): def get_query_set(self): - return super(UserProfileManager, self).get_query_set() + return super(UserProfileManager, self).get_query_set().annotate(mix_count=Count('mixes')) class UserProfile(_BaseModel): diff --git a/static/js/app/appv2.coffee b/static/js/app/appv2.coffee index 4d3c5b7..5b3d801 100755 --- a/static/js/app/appv2.coffee +++ b/static/js/app/appv2.coffee @@ -44,6 +44,10 @@ define ['backbone', 'marionette', 'vent', 'utils' true App.addInitializer -> + @listenTo vent, "app:login", -> + utils.modal "/dlg/LoginView" + true + @listenTo vent, "mix:favourite", (model) -> console.log "App(vent): mix:favourite" model.save 'favourited', !model.get('favourited'), patch: true @@ -54,6 +58,11 @@ define ['backbone', 'marionette', 'vent', 'utils' model.save 'liked', !model.get('liked'), patch: true true + @listenTo vent, "user:follow", (model)-> + console.log "App(vent): user:follow" + model.save 'following', !model.get('following'), patch: true + true + @listenTo vent, "mix:share", (mode, model) -> console.log "App(vent): mix:share" if (mode == "facebook") @@ -62,15 +71,6 @@ define ['backbone', 'marionette', 'vent', 'utils' 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 b0b6807..b6bdda8 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', '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") @@ -50,6 +48,10 @@ return true; }); App.addInitializer(function() { + this.listenTo(vent, "app:login", function() { + utils.modal("/dlg/LoginView"); + return true; + }); this.listenTo(vent, "mix:favourite", function(model) { console.log("App(vent): mix:favourite"); model.save('favourited', !model.get('favourited'), { @@ -64,15 +66,6 @@ }); return true; }); - this.listenTo(vent, "mix:share", function(mode, model) { - console.log("App(vent): mix:share"); - if (mode === "facebook") { - social.sharePageToFacebook(model); - } else if (mode === "twitter") { - social.sharePageToTwitter(model); - } - return true; - }); this.listenTo(vent, "user:follow", function(model) { console.log("App(vent): user:follow"); model.save('following', !model.get('following'), { @@ -80,8 +73,13 @@ }); return true; }); - return this.listenTo(vent, "app:login", function() { - utils.modal("/dlg/LoginView"); + return this.listenTo(vent, "mix:share", function(mode, model) { + console.log("App(vent): mix:share"); + if (mode === "facebook") { + social.sharePageToFacebook(model); + } else if (mode === "twitter") { + social.sharePageToTwitter(model); + } return true; }); }); diff --git a/static/js/app/views/user/userItemView.coffee b/static/js/app/views/user/userItemView.coffee index e9656a8..f86f72f 100755 --- a/static/js/app/views/user/userItemView.coffee +++ b/static/js/app/views/user/userItemView.coffee @@ -12,8 +12,8 @@ define ['app', 'moment', 'marionette', 'vent', 'text!/tpl/UserListItemView'], humanise: (date)-> moment(date).fromNow() - intialize: => - @listenTo(@model, 'change:profile.following', @render) + initialize: => + @listenTo(@model, 'change:following', @render) followUser: -> diff --git a/static/js/app/views/user/userItemView.js b/static/js/app/views/user/userItemView.js index ffb4988..6ad764c 100755 --- a/static/js/app/views/user/userItemView.js +++ b/static/js/app/views/user/userItemView.js @@ -1,18 +1,18 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.3.3 (function() { 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', 'vent', 'text!/tpl/UserListItemView'], function(App, moment, Marionette, vent, Template) { - var UserItemView, _ref; - + var UserItemView; UserItemView = (function(_super) { + __extends(UserItemView, _super); function UserItemView() { - this.intialize = __bind(this.intialize, this); _ref = UserItemView.__super__.constructor.apply(this, arguments); - return _ref; + this.initialize = __bind(this.initialize, this); + return UserItemView.__super__.constructor.apply(this, arguments); } UserItemView.prototype.template = _.template(Template); @@ -30,8 +30,8 @@ } }; - UserItemView.prototype.intialize = function() { - return this.listenTo(this.model, 'change:profile.following', this.render); + UserItemView.prototype.initialize = function() { + return this.listenTo(this.model, 'change:following', this.render); }; UserItemView.prototype.followUser = function() { diff --git a/templates/views/UserListItemView.html b/templates/views/UserListItemView.html index 67f9032..0cc6056 100755 --- a/templates/views/UserListItemView.html +++ b/templates/views/UserListItemView.html @@ -1,19 +1,19 @@ - <%= profile.display_name %> + <%= display_name %> - <%= profile.mix_count%> + <%= mix_count%> - <%= profile.like_count%> + <%= like_count%> - <%= profile.favourite_count%> + <%= favourite_count%> - <%= profile.follower_count %> + <%= follower_count %> -<%= profile.following_count %> +<%= following_count %> <%= humanise(date_joined) %> <%= humanise(last_login) %> @@ -21,13 +21,13 @@ {% if user.is_authenticated %} {% else %} {% endif %}