Fixed comments and disabled download

This commit is contained in:
Fergal Moran
2013-09-04 16:05:13 +01:00
parent 2ad6fa4a2e
commit 434ae7bf3c
21 changed files with 353 additions and 130 deletions

View File

@@ -117,6 +117,7 @@ TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
"allauth.account.context_processors.account",
"spa.context_processors.debug"
)
AUTHENTICATION_BACKENDS = global_settings.AUTHENTICATION_BACKENDS + (
"allauth.account.auth_backends.AuthenticationBackend",
)
@@ -129,6 +130,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'spa.middleware.cors.XsSharingMiddleware',
#'spa.middleware.uploadify.SWFUploadMiddleware',
#'spa.middleware.sqlprinter.SqlPrintingMiddleware' if DEBUG else None,
#'debug_toolbar.middleware.DebugToolbarMiddleware',
@@ -254,7 +256,11 @@ if DEBUG:
import mimetypes
mimetypes.add_type("image/png", ".png", True)
mimetypes.add_type("font/woff", ".woff", True)
mimetypes.add_type("image/png", ".png", True)
mimetypes.add_type("application/x-font-woff", ".woff", True)
mimetypes.add_type("application/vnd.ms-fontobject", ".eot", True)
mimetypes.add_type("font/ttf", ".ttf", True)
mimetypes.add_type("font/otf", ".otf", True)
# TODO(fergal.moran@gmail.com): #import localsettings - so all localsettings are part of import settings

View File

@@ -1,7 +1,10 @@
from tastypie import fields
from tastypie.authentication import Authentication
from tastypie.authorization import Authorization
from tastypie.exceptions import ImmediateHttpResponse
from tastypie.http import HttpForbidden, HttpBadRequest
from spa.api.v1.BackboneCompatibleResource import BackboneCompatibleResource
from spa.models import Mix
from spa.models.comment import Comment
@@ -18,14 +21,20 @@ class CommentResource(BackboneCompatibleResource):
authentication = Authentication()
always_return_data = True
def obj_create(self, bundle, request=None, **kwargs):
bundle.data['user'] = {'pk': request.user.pk}
return super(CommentResource, self).obj_create(bundle, request, user=request.user)
def obj_create(self, bundle, **kwargs):
bundle.data['user'] = bundle.request.user
"""
def dehydrate_date_created(self, bundle):
return self.humanize_date(bundle.obj.date_created)
"""
try:
if 'mix_id' in bundle.data:
mix = Mix.objects.get(pk=bundle.data['mix_id'])
if mix is not None:
return super(CommentResource, self).obj_create(bundle, user=bundle.request.user, mix=mix)
except Exception, e:
self.logger.error("Error creating comment (%s)" % e.message)
pass
raise ImmediateHttpResponse(
HttpBadRequest("Unable to hydrate comment from supplied data.")
)
def dehydrate(self, bundle):
bundle.data['avatar_image'] = bundle.obj.user.get_profile().get_small_profile_image()

View File

@@ -20,7 +20,7 @@ from spa.models.mix import Mix
class MixResource(BackboneCompatibleResource):
comments = fields.ToManyField('spa.api.v1.CommentResource.CommentResource', 'comments', null=True)
comments = fields.ToManyField('spa.api.v1.CommentResource.CommentResource', 'comments', null=True, full=True)
favourites = fields.ToManyField('spa.api.v1.UserResource.UserResource', 'favourites',
related_name='favourites', full=False, null=True)
@@ -37,6 +37,7 @@ class MixResource(BackboneCompatibleResource):
'comments': ALL_WITH_RELATIONS,
'favourites': ALL_WITH_RELATIONS,
'likes': ALL_WITH_RELATIONS,
'slug': ALL_WITH_RELATIONS,
}
authorization = Authorization()

39
spa/middleware/cors.py Normal file
View File

@@ -0,0 +1,39 @@
from django import http
try:
import settings
XS_SHARING_ALLOWED_ORIGINS = settings.XS_SHARING_ALLOWED_ORIGINS
XS_SHARING_ALLOWED_METHODS = settings.XS_SHARING_ALLOWED_METHODS
except:
XS_SHARING_ALLOWED_ORIGINS = '*'
XS_SHARING_ALLOWED_METHODS = ['POST','GET','OPTIONS', 'PUT', 'DELETE']
class XsSharingMiddleware(object):
"""
This middleware allows cross-domain XHR using the html5 postMessage API.
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE
"""
def process_request(self, request):
if 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META:
response = http.HttpResponse()
response['Access-Control-Allow-Origin'] = XS_SHARING_ALLOWED_ORIGINS
response['Access-Control-Allow-Methods'] = ",".join( XS_SHARING_ALLOWED_METHODS )
return response
return None
def process_response(self, request, response):
# Avoid unnecessary work
if response.has_header('Access-Control-Allow-Origin'):
return response
response['Access-Control-Allow-Origin'] = XS_SHARING_ALLOWED_ORIGINS
response['Access-Control-Allow-Methods'] = ",".join( XS_SHARING_ALLOWED_METHODS )
return response

View File

@@ -12,7 +12,7 @@ class Comment(_BaseModel):
mix = models.ForeignKey(Mix, editable=False, null=True, blank=True, related_name='comments')
comment = models.CharField(max_length=1024)
date_created = models.DateTimeField(auto_now=True)
time_index = models.IntegerField()
time_index = models.IntegerField(default=0)
def get_absolute_url(self):
return '/comment/%i' % self.id

View File

@@ -43,7 +43,10 @@ def get_dialog(request, dialog_name, **kwargs):
def get_javascript(request, template_name):
localsettings.JS_SETTINGS.update({
'CURRENT_USER_ID': request.user.get_profile().id if not request.user.is_anonymous() else -1
'CURRENT_USER_ID': request.user.get_profile().id if not request.user.is_anonymous() else -1,
'CURRENT_USER_NAME': request.user.get_profile().get_nice_name() if not request.user.is_anonymous() else -1,
'CURRENT_USER_URL': request.user.get_profile().get_profile_url() if not request.user.is_anonymous() else -1,
'AVATAR_IMAGE': request.user.get_profile().get_small_profile_image() if not request.user.is_anonymous() else ""
})
return render_to_response(
'javascript/%s.js' % template_name,

View File

@@ -1,16 +1,17 @@
// Generated by CoffeeScript 1.3.3
// Generated by CoffeeScript 1.6.2
(function() {
var __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(['marionette', 'vent', 'app.lib/controller'], function(Marionette, vent, Controller) {
var DssRouter;
return DssRouter = (function(_super) {
var DssRouter, _ref;
return DssRouter = (function(_super) {
__extends(DssRouter, _super);
function DssRouter() {
return DssRouter.__super__.constructor.apply(this, arguments);
_ref = DssRouter.__super__.constructor.apply(this, arguments);
return _ref;
}
DssRouter.prototype.controller = new Controller;

View File

@@ -1,6 +1,11 @@
define ['backbone'],
(Backbone) ->
class CommentItem extends Backbone.Model
urlRoot:com.podnoms.settings.urlRoot + "comments/"
define ['app.lib/backbone.dss.model'], \
(DSSModel) ->
class CommentItem extends DSSModel
urlRoot: com.podnoms.settings.urlRoot + "comments/"
defaults:
avatar_image: com.podnoms.settings.avatarImage
user_name: com.podnoms.settings.userName
user_url: com.podnoms.settings.userUrl
date_created: ""
CommentItem
CommentItem

View File

@@ -1,24 +1,31 @@
// Generated by CoffeeScript 1.3.3
// Generated by CoffeeScript 1.6.2
(function() {
var __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(['backbone'], function(Backbone) {
var CommentItem;
CommentItem = (function(_super) {
define(['app.lib/backbone.dss.model'], function(DSSModel) {
var CommentItem, _ref;
CommentItem = (function(_super) {
__extends(CommentItem, _super);
function CommentItem() {
return CommentItem.__super__.constructor.apply(this, arguments);
_ref = CommentItem.__super__.constructor.apply(this, arguments);
return _ref;
}
CommentItem.prototype.urlRoot = com.podnoms.settings.urlRoot + "comments/";
CommentItem.prototype.defaults = {
avatar_image: com.podnoms.settings.avatarImage,
user_name: com.podnoms.settings.userName,
user_url: com.podnoms.settings.userUrl,
date_created: ""
};
return CommentItem;
})(Backbone.Model);
({
urlRoot: com.podnoms.settings.urlRoot + "comments/"
});
})(DSSModel);
return CommentItem;
});

View File

@@ -1,6 +1,16 @@
define ['backbone', 'app.lib/backbone.dss.model'], \
(Backbone, DssModel) ->
class MixItem extends Backbone.Model
define ['backbone.relational', 'models/comment/commentCollection', 'models/comment/commentItem', 'app.lib/backbone.dss.model'], \
(Backbone, CommentCollection, CommentItem, DSSModel) ->
class MixItem extends DSSModel
urlRoot: com.podnoms.settings.urlRoot + "mix/"
"""
relations: [
type: Backbone.HasMany
key: "comments"
relatedModel: CommentItem
collectionType: CommentCollection
reverseRelation:
key: "hasItems"
includeInJSON: "id"
]
"""
MixItem

View File

@@ -3,7 +3,7 @@
var __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(['backbone', 'app.lib/backbone.dss.model'], function(Backbone, DssModel) {
define(['backbone.relational', 'models/comment/commentCollection', 'models/comment/commentItem', 'app.lib/backbone.dss.model'], function(Backbone, CommentCollection, CommentItem, DSSModel) {
var MixItem, _ref;
MixItem = (function(_super) {
@@ -16,9 +16,11 @@
MixItem.prototype.urlRoot = com.podnoms.settings.urlRoot + "mix/";
"relations: [\n type: Backbone.HasMany\n key: \"comments\"\n relatedModel: CommentItem\n collectionType: CommentCollection\n reverseRelation:\n key: \"hasItems\"\n includeInJSON: \"id\"\n ]";
return MixItem;
})(Backbone.Model);
})(DSSModel);
return MixItem;
});

View File

@@ -1,5 +1,5 @@
define ['marionette', 'views/comment/commentItemView', 'text!/tpl/CommentListView'],
(Marionette, CommentItemView, Template) ->
define ['marionette', 'models/comment/commentItem', 'views/comment/commentItemView', 'text!/tpl/CommentListView'],
(Marionette, CommentItem, CommentItemView, Template) ->
class CommentListView extends Marionette.CompositeView
template: _.template(Template)
@@ -8,7 +8,31 @@ define ['marionette', 'views/comment/commentItemView', 'text!/tpl/CommentListVie
itemView: CommentItemView
itemViewContainer: "#comment-list-container"
ui:
commentText: '#comment-text'
events:
"click #btn-add-comment": "addComment"
initialize: ->
console.log "CommentListView: initialize"
addComment: ->
console.log "CommentListView: addComment"
@collection.create
mix_id: @collection.mix.get("id")
comment: @ui.commentText.val()
,
success: (newItem) =>
@ui.commentText.val ""
true
error: (a, b, c) ->
console.log a
console.log b
console.log c
true
true
CommentListView

View File

@@ -1,16 +1,17 @@
// Generated by CoffeeScript 1.3.3
// Generated by CoffeeScript 1.6.2
(function() {
var __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(['marionette', 'views/comment/commentItemView', 'text!/tpl/CommentListView'], function(Marionette, CommentItemView, Template) {
var CommentListView;
CommentListView = (function(_super) {
define(['marionette', 'models/comment/commentItem', 'views/comment/commentItemView', 'text!/tpl/CommentListView'], function(Marionette, CommentItem, CommentItemView, Template) {
var CommentListView, _ref;
CommentListView = (function(_super) {
__extends(CommentListView, _super);
function CommentListView() {
return CommentListView.__super__.constructor.apply(this, arguments);
_ref = CommentListView.__super__.constructor.apply(this, arguments);
return _ref;
}
CommentListView.prototype.template = _.template(Template);
@@ -23,10 +24,40 @@
CommentListView.prototype.itemViewContainer = "#comment-list-container";
CommentListView.prototype.ui = {
commentText: '#comment-text'
};
CommentListView.prototype.events = {
"click #btn-add-comment": "addComment"
};
CommentListView.prototype.initialize = function() {
return console.log("CommentListView: initialize");
};
CommentListView.prototype.addComment = function() {
var _this = this;
console.log("CommentListView: addComment");
this.collection.create({
mix_id: this.collection.mix.get("id"),
comment: this.ui.commentText.val()
}, {
success: function(newItem) {
_this.ui.commentText.val("");
return true;
},
error: function(a, b, c) {
console.log(a);
console.log(b);
console.log(c);
return true;
}
});
return true;
};
return CommentListView;
})(Marionette.CompositeView);

View File

@@ -56,7 +56,7 @@ define ['moment', 'app', 'vent', 'marionette', 'utils', 'models/comment/commentC
comments = new CommentsCollection()
comments.url = @model.get("resource_uri") + "comments/"
comments.mix_id = @model.id
comments.mix = @model.get("resource_uri")
comments.mix = @model
comments.fetch success: (data) ->
console.log(data)
content = new CommentsListView(collection: comments).render()

View File

@@ -85,7 +85,7 @@
comments = new CommentsCollection();
comments.url = this.model.get("resource_uri") + "comments/";
comments.mix_id = this.model.id;
comments.mix = this.model.get("resource_uri");
comments.mix = this.model;
comments.fetch({
success: function(data) {
var content;

View File

@@ -1,16 +1,17 @@
// Generated by CoffeeScript 1.3.3
// Generated by CoffeeScript 1.6.2
(function() {
var __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(['marionette', 'vent', 'models/user/userItem', 'views/widgets/mixTabHeaderView', 'views/user/userItemView', 'views/mix/mixListView', 'text!/tpl/MixListLayoutView'], function(Marionette, vent, UserItem, MixTabHeaderView, UserItemView, MixListView, Template) {
var MixListRegionView;
MixListRegionView = (function(_super) {
var MixListRegionView, _ref;
MixListRegionView = (function(_super) {
__extends(MixListRegionView, _super);
function MixListRegionView() {
return MixListRegionView.__super__.constructor.apply(this, arguments);
_ref = MixListRegionView.__super__.constructor.apply(this, arguments);
return _ref;
}
MixListRegionView.prototype.template = _.template(Template);
@@ -36,6 +37,7 @@
MixListRegionView.prototype.showUserView = function(options) {
var user,
_this = this;
this.bodyRegion.show(new MixListView(options));
user = new UserItem({
id: options.user

View File

@@ -1,6 +1,6 @@
/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */
/**
* Backbone-relational.js 0.8.5
* Backbone-relational.js 0.8.6
* (c) 2011-2013 Paul Uithol and contributors (https://github.com/PaulUithol/Backbone-relational/graphs/contributors)
*
* Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
@@ -17,7 +17,8 @@
if ( typeof window === 'undefined' ) {
_ = require( 'underscore' );
Backbone = require( 'backbone' );
exports = module.exports = Backbone;
exports = Backbone;
typeof module === 'undefined' || ( module.exports = exports );
}
else {
_ = window._;
@@ -86,9 +87,21 @@
}
},
// Some of the queued events may trigger other blocking events. By
// copying the queue here it allows queued events to process closer to
// the natural order.
//
// queue events [ 'A', 'B', 'C' ]
// A handler of 'B' triggers 'D' and 'E'
// By copying `this._queue` this executes:
// [ 'A', 'B', 'D', 'E', 'C' ]
// The same order the would have executed if they didn't have to be
// delayed and queued.
process: function() {
while ( this._queue && this._queue.length ) {
this._queue.shift()();
var queue = this._queue;
this._queue = [];
while ( queue && queue.length ) {
queue.shift()();
}
},
@@ -298,7 +311,9 @@
rootModel = rootModel._superModel;
}
var coll = _.findWhere( this._collections, { model: rootModel } );
var coll = _.find( this._collections, function(item) {
return item.model === rootModel;
});
if ( !coll && create !== false ) {
coll = this._createCollection( rootModel );
@@ -407,6 +422,7 @@
var modelColl = model.collection;
coll.add( model );
this.listenTo( model, 'destroy', this.unregister, this );
this.listenTo( model, 'relational:unregister', this.unregister, this );
model.collection = modelColl;
}
},
@@ -446,10 +462,10 @@
* Remove a 'model' from the store.
* @param {Backbone.RelationalModel} model
*/
unregister: function( model ) {
this.stopListening( model, 'destroy', this.unregister );
unregister: function( model, collection, options ) {
this.stopListening( model );
var coll = this.getCollection( model );
coll && coll.remove( model );
coll && coll.remove( model, options );
},
/**
@@ -496,7 +512,12 @@
this.keyDestination = this.options.keyDestination || this.keySource || this.key;
this.model = this.options.model || this.instance.constructor;
this.relatedModel = this.options.relatedModel;
if ( _.isFunction( this.relatedModel ) && !( this.relatedModel.prototype instanceof Backbone.RelationalModel ) ) {
this.relatedModel = _.result( this, 'relatedModel' );
}
if ( _.isString( this.relatedModel ) ) {
this.relatedModel = Backbone.Relational.store.getObjectByName( this.relatedModel );
}
@@ -528,7 +549,7 @@
// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
if ( this.keySource !== this.key ) {
this.instance.unset( this.keySource, { silent: true } );
delete this.instance.attributes[ this.keySource ];
}
// Add this Relation to instance._relations
@@ -829,6 +850,9 @@
// Handle a custom 'collectionType'
this.collectionType = this.options.collectionType;
if ( _.isFunction( this.collectionType ) && this.collectionType !== Backbone.Collection && !( this.collectionType.prototype instanceof Backbone.Collection ) ) {
this.collectionType = _.result( this, 'collectionType' );
}
if ( _.isString( this.collectionType ) ) {
this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType );
}
@@ -1064,6 +1088,7 @@
_isInitialized: false,
_deferProcessing: false,
_queue: null,
_attributeChangeFired: false, // Keeps track of `change` event firing under some conditions (like nested `set`s)
subModelTypeAttribute: 'type',
subModelTypes: null,
@@ -1128,7 +1153,9 @@
// Determine if the `change` event is still valid, now that all relations are populated
var changed = true;
if ( eventName === 'change' ) {
changed = dit.hasChanged();
// `hasChanged` may have gotten reset by nested calls to `set`.
changed = dit.hasChanged() || dit._attributeChangeFired;
dit._attributeChangeFired = false;
}
else {
var attr = eventName.slice( 7 ),
@@ -1137,7 +1164,7 @@
if ( rel ) {
// If `attr` is a relation, `change:attr` get triggered from `Relation.onChange`.
// These take precedence over `change:attr` events triggered by `Model.set`.
// The relation set a fourth attribute to `true`. If this attribute is present,
// The relation sets a fourth attribute to `true`. If this attribute is present,
// continue triggering this event; otherwise, it's from `Model.set` and should be stopped.
changed = ( args[ 4 ] === true );
@@ -1152,6 +1179,9 @@
delete dit.changed[ attr ];
}
}
else if ( changed ) {
dit._attributeChangeFired = true;
}
}
changed && Backbone.Model.prototype.trigger.apply( dit, args );
@@ -1172,7 +1202,7 @@
this.acquire(); // Setting up relations often also involve calls to 'set', and we only want to enter this function once
this._relations = {};
_.each( this.relations || [], function( rel ) {
_.each( _.result( this, 'relations' ) || [], function( rel ) {
Backbone.Relational.store.initializeRelation( this, rel, options );
}, this );
@@ -1188,11 +1218,16 @@
updateRelations: function( options ) {
if ( this._isInitialized && !this.isLocked() ) {
_.each( this._relations, function( rel ) {
// Update from data in `rel.keySource` if set, or `rel.key` otherwise
// Update from data in `rel.keySource` if data got set in there, or `rel.key` otherwise
var val = this.attributes[ rel.keySource ] || this.attributes[ rel.key ];
if ( rel.related !== val ) {
this.trigger( 'relational:change:' + rel.key, this, val, options || {} );
}
// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
if ( rel.keySource !== rel.key ) {
delete rel.instance.attributes[ rel.keySource ];
}
}, this );
}
},
@@ -1244,7 +1279,7 @@
var setUrl,
requests = [],
rel = this.getRelation( key ),
idsToFetch = rel && ( rel.keyIds || ( ( rel.keyId || rel.keyId === 0 ) ? [ rel.keyId ] : [] ) );
idsToFetch = rel && ( ( rel.keyIds && rel.keyIds.slice( 0 ) ) || ( ( rel.keyId || rel.keyId === 0 ) ? [ rel.keyId ] : [] ) );
// On `refresh`, add the ids for current models in the relation to `idsToFetch`
if ( refresh ) {
@@ -1329,11 +1364,19 @@
// Go through all splits and return the final result
var splits = attr.split( '.' );
var result = _.reduce(splits, function( model, split ) {
if ( !( model instanceof Backbone.Model ) ) {
throw new Error( 'Attribute must be an instanceof Backbone.Model. Is: ' + model + ', currentSplit: ' + split );
if ( _.isNull(model) || _.isUndefined( model ) ) {
// Return undefined if the path cannot be expanded
return undefined;
}
else if ( model instanceof Backbone.Model ) {
return Backbone.Model.prototype.get.call( model, split );
}
else if ( model instanceof Backbone.Collection ) {
return Backbone.Collection.prototype.at.call( model, split )
}
else {
throw new Error( 'Attribute must be an instanceof Backbone.Model or Backbone.Collection. Is: ' + model + ', currentSplit: ' + split );
}
return Backbone.Model.prototype.get.call( model, split );
}, this );
if ( originalResult !== undefined && result !== undefined ) {
@@ -1389,30 +1432,6 @@
return result;
},
unset: function( attribute, options ) {
Backbone.Relational.eventQueue.block();
var result = Backbone.Model.prototype.unset.apply( this, arguments );
this.updateRelations( options );
// Try to run the global queue holding external events
Backbone.Relational.eventQueue.unblock();
return result;
},
clear: function( options ) {
Backbone.Relational.eventQueue.block();
var result = Backbone.Model.prototype.clear.apply( this, arguments );
this.updateRelations( options );
// Try to run the global queue holding external events
Backbone.Relational.eventQueue.unblock();
return result;
},
clone: function() {
var attributes = _.clone( this.attributes );
if ( !_.isUndefined( attributes[ this.idAttribute ] ) ) {
@@ -1569,57 +1588,92 @@
* @return {Backbone.Model}
*/
build: function( attributes, options ) {
var model = this;
// 'build' is a possible entrypoint; it's possible no model hierarchy has been determined yet.
this.initializeModelHierarchy();
// Determine what type of (sub)model should be built if applicable.
// Lookup the proper subModelType in 'this._subModels'.
if ( this._subModels && this.prototype.subModelTypeAttribute in attributes ) {
var subModelTypeAttribute = attributes[ this.prototype.subModelTypeAttribute ];
var subModelType = this._subModels[ subModelTypeAttribute ];
if ( subModelType ) {
model = subModelType;
}
}
var model = this._findSubModelType(this, attributes) || this;
return new model( attributes, options );
},
/**
* Determines what type of (sub)model should be built if applicable.
* Looks up the proper subModelType in 'this._subModels', recursing into
* types until a match is found. Returns the applicable 'Backbone.Model'
* or null if no match is found.
* @param {Backbone.Model} type
* @param {Object} attributes
* @return {Backbone.Model}
*/
_findSubModelType: function (type, attributes) {
if ( type._subModels && type.prototype.subModelTypeAttribute in attributes ) {
var subModelTypeAttribute = attributes[type.prototype.subModelTypeAttribute];
var subModelType = type._subModels[subModelTypeAttribute];
if ( subModelType ) {
return subModelType;
} else {
// Recurse into subModelTypes to find a match
for ( subModelTypeAttribute in type._subModels ) {
subModelType = this._findSubModelType(type._subModels[subModelTypeAttribute], attributes);
if ( subModelType ) {
return subModelType;
}
}
}
}
return null;
},
/**
*
*/
initializeModelHierarchy: function() {
// If we're here for the first time, try to determine if this modelType has a 'superModel'.
if ( _.isUndefined( this._superModel ) || _.isNull( this._superModel ) ) {
Backbone.Relational.store.setupSuperModel( this );
// If a superModel has been found, copy relations from the _superModel if they haven't been
// inherited automatically (due to a redefinition of 'relations').
// Otherwise, make sure we don't get here again for this type by making '_superModel' false so we fail
// the isUndefined/isNull check next time.
if ( this._superModel && this._superModel.prototype.relations ) {
// Find relations that exist on the `_superModel`, but not yet on this model.
// Inherit any relations that have been defined in the parent model.
this.inheritRelations();
// If we came here through 'build' for a model that has 'subModelTypes' then try to initialize the ones that
// haven't been resolved yet.
if ( this.prototype.subModelTypes ) {
var resolvedSubModels = _.keys(this._subModels);
var unresolvedSubModels = _.omit(this.prototype.subModelTypes, resolvedSubModels);
_.each( unresolvedSubModels, function( subModelTypeName ) {
var subModelType = Backbone.Relational.store.getObjectByName( subModelTypeName );
subModelType && subModelType.initializeModelHierarchy();
});
}
},
inheritRelations: function() {
// Bail out if we've been here before.
if (!_.isUndefined( this._superModel ) && !_.isNull( this._superModel )) {
return;
}
// Try to initialize the _superModel.
Backbone.Relational.store.setupSuperModel( this );
// If a superModel has been found, copy relations from the _superModel if they haven't been inherited automatically
// (due to a redefinition of 'relations').
if ( this._superModel ) {
// The _superModel needs a chance to initialize its own inherited relations before we attempt to inherit relations
// from the _superModel. You don't want to call 'initializeModelHierarchy' because that could cause sub-models of
// this class to inherit their relations before this class has had chance to inherit it's relations.
this._superModel.inheritRelations();
if ( this._superModel.prototype.relations ) {
// Find relations that exist on the '_superModel', but not yet on this model.
var inheritedRelations = _.select( this._superModel.prototype.relations || [], function( superRel ) {
return !_.any( this.prototype.relations || [], function( rel ) {
return superRel.relatedModel === rel.relatedModel && superRel.key === rel.key;
}, this );
}, this );
this.prototype.relations = inheritedRelations.concat( this.prototype.relations );
}
else {
this._superModel = false;
}
}
// If we came here through 'build' for a model that has 'subModelTypes', and not all of them have been resolved yet, try to resolve each.
if ( this.prototype.subModelTypes && _.keys( this.prototype.subModelTypes ).length !== _.keys( this._subModels ).length ) {
_.each( this.prototype.subModelTypes || [], function( subModelTypeName ) {
var subModelType = Backbone.Relational.store.getObjectByName( subModelTypeName );
subModelType && subModelType.initializeModelHierarchy();
});
// Otherwise, make sure we don't get here again for this type by making '_superModel' false so we fail the
// isUndefined/isNull check next time.
else {
this._superModel = false;
}
},
@@ -1638,7 +1692,7 @@
findOrCreate: function( attributes, options ) {
options || ( options = {} );
var parsedAttributes = ( _.isObject( attributes ) && options.parse && this.prototype.parse ) ?
this.prototype.parse( attributes ) : attributes;
this.prototype.parse( _.clone( attributes ) ) : attributes;
// Try to find an instance of 'this' model type in the store
var model = Backbone.Relational.store.find( this, parsedAttributes );
@@ -1647,8 +1701,9 @@
// If not, create an instance (unless 'options.create' is false).
if ( _.isObject( attributes ) ) {
if ( model && options.merge !== false ) {
// Make sure `options.collection` doesn't cascade to nested models
// Make sure `options.collection` and `options.url` doesn't cascade to nested models
delete options.collection;
delete options.url;
model.set( parsedAttributes, options );
}
@@ -1658,6 +1713,22 @@
}
return model;
},
/**
* Find an instance of `this` type in 'Backbone.Relational.store'.
* - If `attributes` is a string or a number, `find` will just query the `store` and return a model if found.
* - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`.
* @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
* @param {Object} [options]
* @param {Boolean} [options.merge=true]
* @param {Boolean} [options.parse=false]
* @return {Backbone.RelationalModel}
*/
find: function( attributes, options ) {
options || ( options = {} );
options.create = false;
return this.findOrCreate( attributes, options );
}
});
_.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore );
@@ -1765,14 +1836,14 @@
return remove.apply( this, arguments );
}
models = _.isArray( models ) ? models.slice() : [ models ];
models = _.isArray( models ) ? models.slice( 0 ) : [ models ];
options || ( options = {} );
var toRemove = [];
//console.debug('calling remove on coll=%o; models=%o, options=%o', this, models, options );
_.each( models, function( model ) {
model = this.get( model ) || this.get( model.cid );
model = this.get( model ) || ( model && this.get( model.cid ) );
model && toRemove.push( model );
}, this );
@@ -1827,7 +1898,7 @@
return trigger.apply( this, arguments );
}
if ( eventName === 'add' || eventName === 'remove' || eventName === 'reset' ) {
if ( eventName === 'add' || eventName === 'remove' || eventName === 'reset' || eventName === 'sort' ) {
var dit = this,
args = arguments;

View File

@@ -64,6 +64,10 @@ requirejs.config({
exports: 'Backbone',
deps: ['jquery', 'underscore']
},
'backbone.relational': {
exports: 'Backbone',
deps: ['backbone']
},
bootstrap: {
exports: 'bootstrap',
deps: ['jquery']

View File

@@ -15,6 +15,9 @@ com.podnoms.settings = {
staticUrl: '{{ STATIC_URL }}',
urlArgs: {{ IS_DEBUG }} ? "" : "bust="+ (new Date()).getTime(),
currentUser: {{ CURRENT_USER_ID }},
userName: "{{ CURRENT_USER_NAME }}",
userUrl: "{{ CURRENT_USER_URL }}",
avatarImage: "{{ AVATAR_IMAGE }}",
/** simple helper to take an api JSON object and initialise a player item */
setupPlayerWrapper: function (id, stream_url, el) {
com.podnoms.player.setupPlayer({

View File

@@ -1,7 +1,7 @@
{% if request.user.is_authenticated %}
<div class="comment-submit-area well">
<textarea style="width: 85%" id="id-comment-text" cols="80" rows="3" name="new-comment"></textarea>
<button class="btn btn-primary" id="id-btn-add-comment">Add comment (don't be a dick)</button>
<textarea style="width: 85%" id="comment-text" cols="80" rows="3" name="new-comment"></textarea>
<button class="btn btn-primary" id="btn-add-comment">Add comment (don't be a dick)</button>
</div>
{% endif %}
<h3 class="bordered">Comments</h3>

View File

@@ -111,14 +111,19 @@
class="icon-edit"></i>Edit</a>
</div>
{% endif %}
<% if (download_allowed) { %>
{% if user.is_authenticated %}
<% if (download_allowed) { %>
<div class="download-button footer-button-right">
<a class="btn btn-mini btn-success" data-id="<%= id %>">
<i class="icon-download"></i> Download</a>
</div>
<% } %>
{% else %}
<div class="footer-button-right">
<a class="btn btn-mini btn-success disabled" data-id="<%= id %>">
<i class="icon-download"></i> Login to download</a>
</div>
{% endif %}
<% } %>
</div>
</div>
</div>