diff --git a/pandora/itemlist/views.py b/pandora/itemlist/views.py index 26d7786..8f54c17 100644 --- a/pandora/itemlist/views.py +++ b/pandora/itemlist/views.py @@ -352,17 +352,19 @@ def subscribeToList(request): data = json.loads(request.POST['data']) list = get_list_or_404_json(data['id']) user = request.user - if list.subscribed_users.filter(username=user.username).count() == 0: + if list.status == 'public' and \ + list.subscribed_users.filter(username=user.username).count() == 0: list.subscribed_users.add(user) - pos, created = models.Position.objects.get_or_create(list=list, user=request.user, section='public') + pos, created = models.Position.objects.get_or_create(list=list, user=user, section='public') if created: - qs = models.Position.objects.filter(user=request.user, section='public') + qs = models.Position.objects.filter(user=user, section='public') pos.position = qs.aggregate(Max('position'))['position__max'] + 1 pos.save() response = json_response() return render_to_json_response(response) actions.register(subscribeToList, cache=False) + @login_required_json def unsubscribeFromList(request): ''' @@ -380,7 +382,7 @@ def unsubscribeFromList(request): list = get_list_or_404_json(data['id']) user = request.user list.subscribed_users.remove(user) - models.Position.objects.filter(list=list, user=request.user, section='public').delete() + models.Position.objects.filter(list=list, user=user, section='public').delete() response = json_response() return render_to_json_response(response) actions.register(unsubscribeFromList, cache=False) @@ -416,8 +418,9 @@ def sortLists(request): pos = qs[0] else: pos = models.Position(list=list, user=user, section=section) - pos.position = position - pos.save() + if pos.position != position: + pos.position = position + pos.save() position += 1 models.Position.objects.filter(section=section, list=list).exclude(id=pos.id).delete() else: @@ -425,8 +428,9 @@ def sortLists(request): list = get_list_or_404_json(i) pos, created = models.Position.objects.get_or_create(list=list, user=request.user, section=section) - pos.position = position - pos.save() + if pos.position != position: + pos.position = position + pos.save() position += 1 response = json_response() diff --git a/pandora/user/models.py b/pandora/user/models.py index b350cf1..2a8d1aa 100644 --- a/pandora/user/models.py +++ b/pandora/user/models.py @@ -5,6 +5,7 @@ from datetime import datetime from django.contrib.auth.models import User from django.db import models +from django.db.models import Max from django.conf import settings from ox.utils import json @@ -39,12 +40,14 @@ class UserProfile(models.Model): def add(lists, section): ids = [] for l in lists: + qs = Position.objects.filter(section=section) if section == 'featured': pos, created = Position.objects.get_or_create(list=l, section=section) else: pos, created = Position.objects.get_or_create(list=l, user=self.user, section=section) + qs = qs.filter(user=self.user) if created: - pos.position = len(in_list) + pos.position = qs.aggregate(Max('position'))['position__max'] + 1 pos.save() id = l.get_id() if id not in ui['lists']: diff --git a/static/js/pandora.js b/static/js/pandora.js index 427c169..bffd4ec 100755 --- a/static/js/pandora.js +++ b/static/js/pandora.js @@ -24,7 +24,8 @@ var pandora = new Ox.App({ sectionElement: 'buttons', sectionLength: {}, selectedMovies: [], - showAllPublicLists: false + showFeaturedListsBrowser: false, + showPublicListsBrowser: false }, user: data.user }; @@ -1529,6 +1530,186 @@ var pandora = new Ox.App({ }; return that; }, + listsBar: function(id) { + var that = new Ox.Bar({ + size: 24 + }); + app.$ui.findListInput = new Ox.Input({ + placeholder: 'Find User', + width: 184 - app.ui.scrollbarSize + }) + .css({ + margin: '4px', + align: 'right' + }) + .appendTo(that); + return that; + }, + listsBrowser: function(id) { + var that = new Ox.SplitPanel({ + elements: [ + { + element: ui.listsBar(), + size: 24 + }, + { + element: app.$ui.sectionList[id == 'public' ? 1 : 2] = ui.listsList(id) + } + ], + orientation: 'vertical' + }); + return that; + }, + listsList: function(id) { + var columnWidth = (app.user.ui.sidebarSize - app.ui.scrollbarSize - 88) / 2, + i = ['my', 'public', 'featured'].indexOf(id), // fixme: find a better way + that = new Ox.TextList({ + columns: [ + { + format: function() { + return $('').attr({ + src: 'static/oxjs/build/png/ox.ui/icon16.png' + }); + }, + id: 'id', + operator: '+', + title: $('').attr({ + src: 'static/oxjs/build/png/ox.ui/icon16.png' + }), + unique: true, + visible: true, + width: 16 + }, + { + id: 'user', + operator: '+', + title: 'User', + visible: true, + width: Math.floor(columnWidth) + }, + { + id: 'name', + operator: '+', + title: 'Name', + visible: true, + width: Math.ceil(columnWidth) + }, + { + align: 'right', + id: 'items', + operator: '-', + title: 'Items', + visible: true, + width: 40 + }, + { + format: function(value) { + return $('') + .attr({ + src: 'static/oxjs/build/png/ox.ui.' + Ox.theme() + + '/symbolFind.png' + }) + .css({ + opacity: value == 'static' ? 0.1 : 1 + }); + }, + id: 'type', + operator: '+', + title: $('').attr({ + src: 'static/oxjs/build/png/ox.ui.' + + Ox.theme() + '/symbolFind.png' + }), + visible: true, + width: 16 + }, + { + clickable: true, + format: function(value) { + return $('') + .attr({ + src: 'static/oxjs/build/png/ox.ui.' + Ox.theme() + + '/symbol' + (id == 'public' ? 'Check' : 'Star') + '.png' + }) + .css({ + opacity: id == 'public' ? (value ? 1 : 0.1) : + (value == 'featured' ? 1 : 0.1) + }); + }, + id: id == 'public' ? 'subscribed' : 'status', + operator: '+', + title: $('').attr({ + src: 'static/oxjs/build/png/ox.ui.' + Ox.theme() + + '/symbol' + (id == 'public' ? 'Check' : 'Star') + '.png' + }), + visible: true, + width: 16 + }, + ], + columnsVisible: true, + pageLength: 1000, + request: function(data, callback) { + var query = id == 'public' ? {conditions: [ + {key: 'user', value: app.user.username, operator: '!'}, + {key: 'status', value: 'public', operator: '='} + ], operator: '&'} : {conditions: [ + {key: 'status', value: 'public', operator: '='}, + {key: 'status', value: 'public', operator: '='} + ], operator: '|'}; + return pandora.api.findLists($.extend(data, { + query: query + }), callback); + }, + selected: app.user.ui.list ? [app.user.ui.list] : [], + sort: [ + {key: 'name', operator: '+'} + ] + }) + .bindEvent({ + click: function(event, data) { + if (id == 'public') { + var subscribed = that.value(data.id, 'subscribed'); + pandora.api[subscribed ? 'unsubscribeFromList' : 'subscribeToList']({ + id: data.id, + }, function(result) { + that.value(data.id, 'subscribed', !subscribed); + }); + } else if (id == 'featured') { + pandora.api.editList({ + id: data.id, + status: that.value(data.id, 'status') == 'featured' ? 'public' : 'featured' + }, function(result) { + Ox.Request.emptyCache(); // fixme: remove + app.$ui.sectionList[ + result.data.user == app.user.username ? 0 : 1 + ].reloadList(); + that.value(data.id, 'status', result.data.status); + }); + } + }, + init: function(event, data) { + app.ui.sectionLength[id] = data.items; + app.$ui.section[i].$content.css({ + height: 40 + data.items * 16 + 'px' + }); + app.$ui.sectionList[i].css({ + height: 16 + data.items * 16 + 'px' + }); + resizeSections(); + }, + select: function(event, data) { + // fixme: duplicated + if (data.ids.length) { + app.$ui.sectionList.forEach(function($list, i) { + i != 2 && $list.options('selected', []); + }); + URL.set('?find=list:' + data.ids[0]); + } else { + URL.set(''); + } + } + }); + return that; + }, mainMenu: function() { var isGuest = app.user.group == 'guest', that = new Ox.MainMenu({ @@ -2054,36 +2235,6 @@ var pandora = new Ox.App({ }) return that; }, - publicLists: function() { - var that = new Ox.SplitPanel({ - elements: [ - { - element: ui.publicListsBar(), - size: 24 - }, - { - element: app.$ui.sectionList[1] = ui.publicListsList() - } - ], - orientation: 'vertical' - }); - return that; - }, - publicListsBar: function() { - var that = new Ox.Bar({ - size: 24 - }); - app.$ui.findListInput = new Ox.Input({ - placeholder: 'Find User', - width: 184 - app.ui.scrollbarSize - }) - .css({ - margin: '4px', - align: 'right' - }) - .appendTo(that); - return that; - }, publicListsDialog: function() { // fixme: unused var that = new Ox.Dialog({ buttons: [ @@ -2107,133 +2258,6 @@ var pandora = new Ox.App({ position: 'absolute' }); return that; - }, - publicListsList: function() { - var columnWidth = (app.user.ui.sidebarSize - app.ui.scrollbarSize - 88) / 2, - that = new Ox.TextList({ - columns: [ - { - format: function() { - return $('').attr({ - src: 'static/oxjs/build/png/ox.ui/icon16.png' - }); - }, - id: 'id', - operator: '+', - title: $('').attr({ - src: 'static/oxjs/build/png/ox.ui/icon16.png' - }), - unique: true, - visible: true, - width: 16 - }, - { - id: 'user', - operator: '+', - title: 'User', - visible: true, - width: Math.floor(columnWidth) - }, - { - id: 'name', - operator: '+', - title: 'Name', - visible: true, - width: Math.ceil(columnWidth) - }, - { - align: 'right', - id: 'items', - operator: '-', - title: 'Items', - visible: true, - width: 40 - }, - { - format: function(value) { - return $('') - .attr({ - src: 'static/oxjs/build/png/ox.ui.' + Ox.theme() + - '/symbolFind.png' - }) - .css({ - opacity: value == 'static' ? 0.1 : 1 - }); - }, - id: 'type', - operator: '+', - title: $('').attr({ - src: 'static/oxjs/build/png/ox.ui.' + - Ox.theme() + '/symbolFind.png' - }), - visible: true, - width: 16 - }, - { - clickable: true, - format: function(value) { - return $('') - .attr({ - src: 'static/oxjs/build/png/ox.ui.' + - Ox.theme() + '/symbolCheck.png' - }) - .css({ - opacity: value ? 1 : 0.1 - }); - }, - id: 'subscribed', - operator: '+', - title: $('').attr({ - src: 'static/oxjs/build/png/ox.ui.' + - Ox.theme() + '/symbolCheck.png' - }), - visible: true, - width: 16 - }, - ], - columnsVisible: true, - pageLength: 1000, - request: function(data, callback) { - var query = {conditions: [ - {key: 'user', value: app.user.username, operator: '!'}, - {key: 'status', value: 'public', operator: '='} - ], operator: ''}; - return pandora.api.findLists($.extend(data, { - query: query - }), callback); - }, - selected: app.user.ui.list ? [app.user.ui.list] : [], - sort: [ - {key: 'name', operator: '+'} - ] - }) - .bindEvent({ - click: function(event, data) { - alert('click') - }, - init: function(event, data) { - app.ui.sectionLength.public = data.items; - app.$ui.section[1].$content.css({ - height: 40 + data.items * 16 + 'px' - }); - app.$ui.sectionList[1].css({ - height: 16 + data.items * 16 + 'px' - }); - resizeSections(); - }, - select: function(event, data) { - // fixme: duplicated - if (data.ids.length) { - app.$ui.sectionList.forEach(function($list, i) { - i != 1 && $list.options('selected', []); - }); - URL.set('?find=list:' + data.ids[0]); - } else { - URL.set(''); - } - } - }); - return that; }, rightPanel: function() { var that = new Ox.SplitPanel({ @@ -2300,8 +2324,9 @@ var pandora = new Ox.App({ }); return that; }, - sectionList: function(id, i) { - var that = new Ox.TextList({ + sectionList: function(id) { + var i = ['my', 'public', 'featured'].indexOf(id), // fixme: find a better way + that = new Ox.TextList({ columns: [ { format: function() { @@ -2456,12 +2481,32 @@ var pandora = new Ox.App({ var $list = app.$ui.sectionList[i]; URL.set(''); $list.options({selected: []}); - pandora.api.removeList({ - id: data.ids[0] - }, function() { - Ox.Request.emptyCache(); // fixme: remove - $list.reloadList(); - }); + if (id == 'my') { + pandora.api.removeList({ + id: data.ids[0] + }, function(result) { + Ox.Request.emptyCache(); // fixme: remove + $list.reloadList(); + }); + } else if (id == 'public') { + pandora.api.unsubscribeFromList({ + id: data.ids[0] + }, function(result) { + Ox.Request.emptyCache(); // fixme: remove + $list.reloadList(); + }); + } else if (id == 'featured' && app.user.group == 'admin') { + pandora.api.editList({ + id: data.ids[0], + status: 'public' + }, function(result) { + Ox.Request.emptyCache(); // fixme: remove + app.$ui.sectionList[ + result.data.user == app.user.username ? 0 : 1 + ].reloadList(); + $list.reloadList(); + }); + } }, init: function(event, data) { app.ui.sectionLength[id] = data.items; // fixme: why by id, not by i? @@ -2523,24 +2568,84 @@ var pandora = new Ox.App({ app.$ui.section = []; app.$ui.sectionList = []; $.each(app.user.ui.sections, function(i, id) { - var menu = []; + var extras; if (id == 'my') { - menu = [ - { id: 'new', title: 'New List...' }, - { id: 'newfromselection', title: 'New List from Current Selection...', disabled: true }, - { id: 'newsmart', title: 'New Smart List...' }, - { id: 'newfromresults', title: 'New Smart List from Current Results...', disabled: true }, - {}, - { id: 'addselection', title: 'Add Selection to List...' } - ]; + extras = [new Ox.Select({ + items: [ + { id: 'new', title: 'New List...' }, + { id: 'newfromselection', title: 'New List from Current Selection...', disabled: true }, + { id: 'newsmart', title: 'New Smart List...' }, + { id: 'newfromresults', title: 'New Smart List from Current Results...', disabled: true }, + {}, + { id: 'addselection', title: 'Add Selection to List...' } + ], + max: 0, + min: 0, + selectable: false, + type: 'image' + }) + .bindEvent({ + click: function(event, data) { + var id; + if (data.id == 'new' || data.id == 'newsmart') { + pandora.api.addList({ + name: 'Untitled', + status: 'private', + type: data.id == 'new' ? 'static' : 'smart' + }, function(result) { + id = result.data.id; + URL.set('?find=list:' + id) + Ox.Request.emptyCache(); // fixme: remove + $list.reloadList().bindEvent({load: load}); + function load(event, data) { + $list.gainFocus() + .options({selected: [id]}) + .editCell(id, 'name'); + $list.unbindEvent({load: load}) // fixme: need bindEventOnce + } + }); + } + } + })]; } else if (id == 'public') { - menu = [ - { id: 'browse', title: 'More Public Lists...' }, - ]; - } + extras = [new Ox.Button({ + selectable: true, + style: 'symbol', + title: 'Edit', + tooltip: 'Manage Favorite Lists', + type: 'image' + }) + .bindEvent({ + change: function(event, data) { + app.ui.showPublicListsBrowser = !app.ui.showPublicListsBrowser; + if (app.ui.showPublicListsBrowser) { + app.$ui.sectionList[1].replaceWith(app.$ui.publicListsBrowser = ui.listsBrowser('public')); + } else { + app.$ui.publicListsBrowser.replaceWith(app.$ui.sectionList[1] = ui.sectionList('public')); + } + } + })]; + } else if (id == 'featured' && app.user.group == 'admin') { + extras = [new Ox.Button({ + selectable: true, + style: 'symbol', + title: 'Edit', + tooltip: 'Manage Featured Lists', + type: 'image' + }) + .bindEvent({ + change: function(event, data) { + app.ui.showFeaturedListsBrowser = !app.ui.showFeaturedListsBrowser; + if (app.ui.showFeaturedListsBrowser) { + app.$ui.sectionList[2].replaceWith(app.$ui.featuredListsBrowser = ui.listsBrowser('featured')); + } else { + app.$ui.featuredListsBrowser.replaceWith(app.$ui.sectionList[2] = ui.sectionList('featured')); + } + } + })]; } app.$ui.section[i] = new Ox.CollapsePanel({ id: id, - menu: menu, + extras: extras, size: 16, title: Ox.getObjectById(app.config.sections, id).title }) @@ -2566,10 +2671,7 @@ var pandora = new Ox.App({ } }); } else if (data.id == 'browse') { - //hasFocus = app.$ui.app.$ui.sectionList[1].hasFocus(); app.$ui.sectionList[1].replaceWith(app.$ui.publicLists = ui.publicLists()); - //hasFocus && app.$ui.publicLists.gainFocus(); - //app.$ui.publicListsDialog = ui.publicListsDialog().open(); app.ui.showAllPublicLists = true; } }, @@ -2580,7 +2682,7 @@ var pandora = new Ox.App({ } }); $sections.push(app.$ui.section[i]); - app.$ui.sectionList[i] = ui.sectionList(id, i) + app.$ui.sectionList[i] = ui.sectionList(id) .bindEvent({init: init}) .appendTo(app.$ui.section[i].$content); function init(event, data) { @@ -2804,6 +2906,20 @@ var pandora = new Ox.App({ return width; } + function getSectionsHeight() { + var height = 48; + $.each(app.user.ui.showSection, function(id, show) { + height += show * app.ui.sectionLength[id] * 16; + if (id == 'public' && app.ui.showPublicListsBrowser) { + height += show * 40; + } else if (id == 'featured' && app.ui.showFeaturedListsBrowser) { + height += show * 40; + } + }); + Ox.print('getSectionsHeight', height) + return height; + } + function getSectionsWidth() { var width = app.user.ui.sidebarSize; // fixme: don't use height(), look up in splitpanels @@ -2813,19 +2929,6 @@ var pandora = new Ox.App({ return width; } - function getSectionsHeight() { - var height = 48; - $.each(app.user.ui.showSection, function(id, show) { - Ox.print('*', id, app.ui.sectionLength[id]) - height += show * app.ui.sectionLength[id] * 16; - if (id == 'public' && app.ui.showAllPublicLists) { - height += show * 40 - } - }); - Ox.print('getSectionsHeight', height) - return height; - } - function getSortOperator(key) { // fixme: make static return ['person', 'string', 'text', 'title'].indexOf( Ox.getObjectById(app.config.sortKeys, key).type @@ -2877,7 +2980,10 @@ var pandora = new Ox.App({ app.$ui.sectionList.forEach(function($list, i) { var id = i == 1 ? 'id' : 'name'; $list.css({width: width + 'px'}); - if (i == 1 && app.ui.showAllPublicLists) { + if ( + i == 1 && app.ui.showPublicListsBrowser || + i == 2 && app.ui.showFeaturedListsBrowser + ) { $list.resizeColumn('user', Math.floor((width - 88) / 2)) .resizeColumn('name', Math.floor((width - 88) / 2)); } else {