Added search bar

This commit is contained in:
Fergal Moran
2013-06-30 15:42:47 +01:00
parent 1453720c9d
commit c519f16fc6
21 changed files with 2585 additions and 66 deletions

View File

@@ -3,6 +3,7 @@ import logging
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
from django.core import serializers
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import get_model
from django.http import HttpResponse, HttpResponseNotFound
@@ -52,6 +53,7 @@ class AjaxHandler(object):
name='ajax_upload_release_image'),
url(r'^upload_avatar_image/$', 'spa.ajax.upload_avatar_image', name='ajax_upload_avatar_image'),
url(r'^upload_mix_file_handler/$', 'spa.ajax.upload_mix_file_handler', name='ajax_upload_mix_file_handler'),
url(r'^lookup/search/$', 'spa.ajax.lookup_search', name='ajax_lookup'),
url(r'^lookup/(?P<source>\w+)/$', 'spa.ajax.lookup', name='ajax_lookup'),
]
return pattern_list
@@ -138,6 +140,7 @@ def live_now_playing(request):
return None
@render_to('inc/release_player.html')
def release_player(request, release_id):
return HttpResponse('Hello Sailor')
@@ -311,6 +314,19 @@ def upload_mix_file_handler(request):
return HttpResponse(_get_json("Failed"), mimetype='application/json')
@csrf_exempt
def lookup_search(request):
query = request.GET['query'] if 'query' in request.GET else request.GET['q'] if 'q' in request.GET else ''
if query != '':
filter_field = Mix.get_lookup_filter_field()
kwargs = {
'{0}__{1}'.format(filter_field, 'icontains'): query,
}
rows = Mix.objects.values("title").filter(**kwargs)
#results = serializers.serialize("json", rows, fields="title",)
results = json.dumps(rows)
return HttpResponse(results, mimetype='application/json')
@csrf_exempt
def lookup(request, source):
query = request.GET['query'] if 'query' in request.GET else request.GET['q'] if 'q' in request.GET else ''

View File

@@ -1,6 +1,8 @@
from django.conf.urls import url
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator, InvalidPage
from django.db.models import Count
from django.http import Http404
from django.template.loader import render_to_string
from tastypie import fields
from tastypie.authorization import Authorization
@@ -52,14 +54,17 @@ class MixResource(BackboneCompatibleResource):
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/search%s$" % (self._meta.resource_name, trailing_slash()),
self.wrap_view('get_search'),
name="api_get_search"),
url(r"^(?P<resource_name>%s)/(?P<id>[\d]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'),
name="api_dispatch_detail"),
url(r"^(?P<resource_name>%s)/(?P<slug>[\w\d-]+)/$" % self._meta.resource_name,
self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
url(r"^(?P<resource_name>%s)/(?P<slug>\w[\w/-]*)/comments%s$" % (
self._meta.resource_name, trailing_slash()), self.wrap_view('get_comments'), name="api_get_comments"),
self._meta.resource_name, trailing_slash()), self.wrap_view('get_comments'), name="api_get_comments"),
url(r"^(?P<resource_name>%s)/(?P<slug>\w[\w/-]*)/activity%s$" % (
self._meta.resource_name, trailing_slash()), self.wrap_view('get_activity'), name="api_get_activity"),
self._meta.resource_name, trailing_slash()), self.wrap_view('get_activity'), name="api_get_activity"),
]
def get_comments(self, request, **kwargs):
@@ -137,7 +142,6 @@ class MixResource(BackboneCompatibleResource):
if user is not None:
semi_filtered = semi_filtered.filter(user__slug=user)
return semi_filtered
def hydrate_favourited(self, bundle):
@@ -166,3 +170,30 @@ class MixResource(BackboneCompatibleResource):
return bundle
def get_search(self, request, **kwargs):
self.method_check(request, allowed=['get'])
self.is_authenticated(request)
self.throttle_check(request)
# Do the query.
sqs = Mix.objects.filter(title__icontains=request.GET.get('q', ''))
paginator = Paginator(sqs, 20)
try:
page = paginator.page(int(request.GET.get('page', 1)))
except InvalidPage:
raise Http404("Sorry, no results on that page.")
objects = []
for result in page.object_list:
bundle = self.build_bundle(obj=result, request=request)
bundle = self.full_dehydrate(bundle)
objects.append(bundle)
object_list = {
'objects': objects,
}
self.log_throttled_access(request)
return self.create_response(request, object_list)

View File

@@ -41,7 +41,7 @@ class _BaseModel(models.Model):
def get_lookup_filter_field(cls):
field_list = cls._meta.get_all_field_names()
for field in field_list:
if field.endswith("name") or field.endswith("description"):
if field.endswith("title") or field.endswith("name") or field.endswith("description"):
return field
return "description"

View File

@@ -451,15 +451,17 @@ div.event-content td {
.now-playing-play {
background-position: -90px 0px;
}
.now-playing-pause{
.now-playing-pause {
background-position: -210px 0px;
}
.now-playing-bio p {
display: inline-block;
margin-left: 5px;
}
#aaaa{
#aaaa {
border: 1px solid #802c59;
-webkit-border-radius: 15px;
@@ -476,13 +478,111 @@ div.event-content td {
padding: 8px;
}
.div-small-heading{
.div-small-heading {
width: 98%;
text-align: center;
margin-bottom: 8px;
}
dss-datatable{
dss-datatable {
cellpadding: 0;
cellspacing: 0;
border: 0;
}
}
img.flag {
height: 10px;
width: 15px;
padding-right: 10px;
}
.search-result td {
vertical-align: top
}
.search-result-image img {
height: 48px;
width: 48px;
}
.search-result-info {
padding-left: 10px;
vertical-align: top;
}
.search-result-synopsis {
font-size: .8em;
color: #888;
}
.navbar-search .search-query {
padding-left: 29px !important;
}
.twitter-typeahead .tt-query,
.twitter-typeahead .tt-hint {
margin-bottom: 0;
}
.tt-dropdown-menu {
width: 280px;
margin-top: 2px;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, .2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
}
.tt-suggestion {
display: block;
padding: 3px 20px;
}
.tt-suggestion.tt-is-under-cursor {
color: #fff;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
}
.tt-suggestion.tt-is-under-cursor a {
color: #fff;
}
.tt-suggestion p {
margin: 0;
}
.navbar-search {
position: relative;
}
.navbar-search .search-query {
padding-left: 29px;
}
.navbar-search .icon-search {
position: absolute;
top: 7px;
left: 11px;
background-image: url("../img/glyphicons-halflings.png");
}
.tt-hint {
font-size: 0 !important;
}

1093
static/data/repos.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,8 @@
define ['backbone', 'marionette', 'vent',
'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', 'views/header',
'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) ->
Marionette.Region.prototype.open = (view) ->
@.$el.hide();
@.$el.html(view.el);
@.$el.slideDown("fast");
true
App = new Marionette.Application();
App.audioController = new AudioController();
App.realtimeController = new RealtimeController();
@@ -32,7 +26,6 @@ define ['backbone', 'marionette', 'vent',
footerRegion: "#footer"
sidebarRegion: "#sidebar"
App.addInitializer ->
console.log("App: routing starting");
App.Router = new DssRouter();
@@ -69,7 +62,7 @@ define ['backbone', 'marionette', 'vent',
true
App.headerRegion.show(new HeaderView());
sidebarView = new SidebarView();
App.sidebarRegion.show(sidebarView)
#sidebarView = new SidebarView();
#App.sidebarRegion.show(sidebarView)
return App;

View File

@@ -1,21 +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/header', 'views/sidebar/sidebarView', 'models/mix/mixCollection'], function(Backbone, Marionette, vent, DssRouter, PanningRegion, RealtimeController, AudioController, HeaderView, SidebarView, MixCollection) {
var App, sidebarView;
Marionette.Region.prototype.open = function(view) {
this.$el.hide();
this.$el.html(view.el);
this.$el.slideDown("fast");
return true;
};
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;
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);
@@ -41,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")
@@ -81,8 +73,6 @@
});
});
App.headerRegion.show(new HeaderView());
sidebarView = new SidebarView();
App.sidebarRegion.show(sidebarView);
return App;
});

View File

@@ -1,12 +1,13 @@
define ['app', 'marionette',
define ['app', 'marionette', 'vent',
'views/chat/chatView',
'models/mix/mixItem', 'views/mix/mixListView', 'views/mix/mixDetailView', 'views/mix/mixEditView',
'models/user/userItem', 'views/user/userListView', 'views/user/userEditView'],
(App, Marionette, ChatView, MixItem, MixListView, MixDetailView, MixEditView, UserItem, UserListView, UserEditView)->
(App, Marionette, vent, ChatView, MixItem, MixListView, MixDetailView, MixEditView, UserItem, UserListView, UserEditView)->
class DssController extends Marionette.Controller
home: ->
console.log "Controller: home"
@showMixList()
#@showMixList()
true
_showMixList: (options) ->

View File

@@ -1,28 +1,25 @@
// Generated by CoffeeScript 1.6.2
// Generated by CoffeeScript 1.3.3
(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(['app', 'marionette', 'views/chat/chatView', 'models/mix/mixItem', 'views/mix/mixListView', 'views/mix/mixDetailView', 'views/mix/mixEditView', 'models/user/userItem', 'views/user/userListView', 'views/user/userEditView'], function(App, Marionette, ChatView, MixItem, MixListView, MixDetailView, MixEditView, UserItem, UserListView, UserEditView) {
var DssController, _ref;
define(['app', 'marionette', 'vent', 'views/chat/chatView', 'models/mix/mixItem', 'views/mix/mixListView', 'views/mix/mixDetailView', 'views/mix/mixEditView', 'models/user/userItem', 'views/user/userListView', 'views/user/userEditView'], function(App, Marionette, vent, ChatView, MixItem, MixListView, MixDetailView, MixEditView, UserItem, UserListView, UserEditView) {
var DssController;
DssController = (function(_super) {
__extends(DssController, _super);
function DssController() {
_ref = DssController.__super__.constructor.apply(this, arguments);
return _ref;
return DssController.__super__.constructor.apply(this, arguments);
}
DssController.prototype.home = function() {
console.log("Controller: home");
this.showMixList();
return true;
};
DssController.prototype._showMixList = function(options) {
var app;
console.log("Controller: _showMixList");
app = require('app');
app.contentRegion.show(new MixListView(options));
@@ -38,7 +35,6 @@
DssController.prototype.showMix = function(slug) {
var app, mix;
console.log("Controller: showMix");
app = require('app');
mix = new MixItem({
@@ -57,7 +53,6 @@
DssController.prototype.uploadMix = function() {
var app, mix;
console.log("Controller: mixUpload");
app = require('app');
mix = new MixItem({
@@ -74,7 +69,6 @@
DssController.prototype.editMix = function(slug) {
var app, mix;
console.log("Controller: mixEdit");
app = require('app');
mix = new MixItem({
@@ -92,7 +86,6 @@
DssController.prototype.showChat = function() {
var app;
console.log("Controller: showChat");
app = require('app');
return app.contentRegion.show(new ChatView());
@@ -100,7 +93,6 @@
DssController.prototype.showUserList = function(type) {
var app;
console.log("Controller: showUserList");
app = require('app');
return app.contentRegion.show(new UserListView());
@@ -152,7 +144,6 @@
DssController.prototype.editUser = function() {
var app, user;
console.log("Controller: editUser");
app = require('app');
user = new UserItem({

View File

@@ -1,5 +1,5 @@
define ['marionette', 'app.lib/controller'],
(Marionette, Controller) ->
define ['marionette', 'vent', 'app.lib/controller'],
(Marionette, vent, Controller) ->
class DssRouter extends Marionette.AppRouter
controller: new Controller,
appRoutes:
@@ -22,5 +22,10 @@ define ['marionette', 'app.lib/controller'],
"user/:slug": "showUserDetail"
"me": "editUser"
initialize: ->
console.log "Router: initializing"
@listenTo vent, "navigate:mix", (slug)->
@navigate 'mix/' + slug, true

View File

@@ -1,17 +1,16 @@
// Generated by CoffeeScript 1.6.2
// Generated by CoffeeScript 1.3.3
(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', 'app.lib/controller'], function(Marionette, Controller) {
var DssRouter, _ref;
define(['marionette', 'vent', 'app.lib/controller'], function(Marionette, vent, Controller) {
var DssRouter;
return DssRouter = (function(_super) {
__extends(DssRouter, _super);
function DssRouter() {
_ref = DssRouter.__super__.constructor.apply(this, arguments);
return _ref;
return DssRouter.__super__.constructor.apply(this, arguments);
}
DssRouter.prototype.controller = new Controller;
@@ -34,6 +33,13 @@
"me": "editUser"
};
DssRouter.prototype.initialize = function() {
console.log("Router: initializing");
return this.listenTo(vent, "navigate:mix", function(slug) {
return this.navigate('mix/' + slug, true);
});
};
return DssRouter;
})(Marionette.AppRouter);

View File

@@ -58,10 +58,8 @@ $(document).ajaxSend(function (event, xhr, settings) {
if (com.podnoms.settings.isDebug) {
$(document).on({
ajaxStart: function () {
console.log("Site: ajax request starting");
},
ajaxStop: function () {
console.log("Site: ajax request finished");
}
});
} else {

View File

@@ -6,9 +6,9 @@
Copyright (c) 2012, Fergal Moran. All rights reserved.
Code provided under the BSD License:
###
define ["underscore", "backbone", "vent", "utils", "text!/tpl/HeaderView"],
(_, Backbone, vent, utils, Template) ->
class HeaderView extends Backbone.View
define ["underscore", "marionette", "vent", "utils", "views/widgets/searchView", "text!/tpl/HeaderView"],
(_, Marionette, vent, utils, SearchView, Template) ->
class HeaderView extends Marionette.Layout
template: _.template(Template)
events:
"click #header-play-pause-button": "togglePlayState"
@@ -18,11 +18,18 @@ define ["underscore", "backbone", "vent", "utils", "text!/tpl/HeaderView"],
ui:
liveButton: "#header-live-button"
regions:
searchRegion: "#header-search"
initialize: ->
@render()
@listenTo vent, "mix:play", @trackPlaying
@listenTo vent, "mix:pause", @trackPaused
onShow: ->
@searchRegion.show(new SearchView())
login: ->
utils.modal "/dlg/LoginView"

View File

@@ -14,7 +14,7 @@ Code provided under the BSD License:
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(["underscore", "backbone", "vent", "utils", "text!/tpl/HeaderView"], function(_, Backbone, vent, utils, Template) {
define(["underscore", "marionette", "vent", "utils", "views/widgets/searchView", "text!/tpl/HeaderView"], function(_, Marionette, vent, utils, SearchView, Template) {
var HeaderView;
HeaderView = (function(_super) {
@@ -37,12 +37,20 @@ Code provided under the BSD License:
liveButton: "#header-live-button"
};
HeaderView.prototype.regions = {
searchRegion: "#header-search"
};
HeaderView.prototype.initialize = function() {
this.render();
this.listenTo(vent, "mix:play", this.trackPlaying);
return this.listenTo(vent, "mix:pause", this.trackPaused);
};
HeaderView.prototype.onShow = function() {
return this.searchRegion.show(new SearchView());
};
HeaderView.prototype.login = function() {
return utils.modal("/dlg/LoginView");
};
@@ -85,7 +93,7 @@ Code provided under the BSD License:
return HeaderView;
})(Backbone.View);
})(Marionette.Layout);
return HeaderView;
});

View File

@@ -0,0 +1,37 @@
define ['jquery', 'underscore', 'libs/bootstrap/bootstrap-typeahead', 'marionette', 'vent', 'text!/tpl/SearchView',
'text!/tpl/SearchResultView'],
($, _, Typeahead, Marionette, vent, Template, SearchResultView) ->
class SearchView extends Marionette.CompositeView
template: _.template(Template)
ui:
searchText: '#search-text'
engine:
compile: (template) ->
compiled = _.template(template)
render: (context) ->
compiled context
onShow: ->
console.log("SearchView: onShow")
t = $('#search-text', @el).typeahead
name: "search"
engine: @engine
valueKey: "title"
template: SearchResultView
remote:
url: "/api/v1/mix/search?q=%QUERY"
dataType: "json"
filter: (parsedResponse)->
parsedResponse.objects
$('.tt-hint', @el).addClass('search-query');
$('.tt-hint', @el).addClass('span3');
t.on 'typeahead:selected': (event, datum, dataset_name) ->
console.log("SearchView: Selected")
vent.trigger 'navigate:mix', datum.slug
$('#search-text', @el).blur()
$('.tt-hint', @el).blur()
SearchView

View File

@@ -0,0 +1,68 @@
// Generated by CoffeeScript 1.3.3
(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(['jquery', 'underscore', 'libs/bootstrap/bootstrap-typeahead', 'marionette', 'vent', 'text!/tpl/SearchView', 'text!/tpl/SearchResultView'], function($, _, Typeahead, Marionette, vent, Template, SearchResultView) {
var SearchView;
SearchView = (function(_super) {
__extends(SearchView, _super);
function SearchView() {
return SearchView.__super__.constructor.apply(this, arguments);
}
SearchView.prototype.template = _.template(Template);
SearchView.prototype.ui = {
searchText: '#search-text'
};
SearchView.prototype.engine = {
compile: function(template) {
var compiled;
compiled = _.template(template);
return {
render: function(context) {
return compiled(context);
}
};
}
};
SearchView.prototype.onShow = function() {
var t;
console.log("SearchView: onShow");
t = $('#search-text', this.el).typeahead({
name: "search",
engine: this.engine,
valueKey: "title",
template: SearchResultView,
remote: {
url: "/api/v1/mix/search?q=%QUERY",
dataType: "json",
filter: function(parsedResponse) {
return parsedResponse.objects;
}
}
});
$('.tt-hint', this.el).addClass('search-query');
$('.tt-hint', this.el).addClass('span3');
return t.on({
'typeahead:selected': function(event, datum, dataset_name) {
console.log("SearchView: Selected");
vent.trigger('navigate:mix', datum.slug);
$('#search-text', this.el).blur();
return $('.tt-hint', this.el).blur();
}
});
};
return SearchView;
})(Marionette.CompositeView);
return SearchView;
});
}).call(this);

File diff suppressed because it is too large Load Diff

View File

@@ -41,7 +41,9 @@
<a class="pull-right btn btn-primary" href="/mix/upload" id='upload'>
<i class="icon-hand-up icon-white"></i>Upload</a>
{% endif %}
<ul class="nav pull-right">
<li id="header-search"></li>
<li>
<a class="volume-button" data-mode="volume" id="header-volume-button">
<i class="icon-volume-up icon-white" id="header-volume-icon"></i>
@@ -51,11 +53,11 @@
<a id="header-profile-edit" href="/me">{{ user|nice_name }}</a>
</li>
</ul>
{% comment %}
{% comment %}
<form class="navbar-search pull-right" action="">
<input type="text" class="search-query span2" placeholder="Search">
</form>
{% endcomment %}
{% endcomment %}
</div>
</div>
</div>

View File

@@ -0,0 +1,10 @@
<table class="search-result">
<tbody>
<tr>
<td class="search-result-image"><img src="<%= mix_image %>"></td>
<td class="search-result-info">
<div class="search-result-title"><%= title %></div>
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,4 @@
<form class="navbar-search">
<input id="search-text" type="text" class="search-query span3" placeholder="Search">
<div class="icon-search"></div>
</form>