From 6bce40ad3a1667e8d66980d46aa5503e1ae9b7d6 Mon Sep 17 00:00:00 2001 From: Jan Gerber Date: Thu, 14 May 2015 13:03:49 +0200 Subject: [PATCH] add sort names/titles --- oml/item/api.py | 3 + oml/item/person.py | 6 ++ oml/item/person_api.py | 113 +++++++++++++++++++++++ oml/item/title_api.py | 74 +++++++++++++++ static/js/mainMenu.js | 8 ++ static/js/namesDialog.js | 183 ++++++++++++++++++++++++++++++++++++ static/js/titlesDialog.js | 190 ++++++++++++++++++++++++++++++++++++++ static/json/js.json | 2 + 8 files changed, 579 insertions(+) create mode 100644 oml/item/person_api.py create mode 100644 oml/item/title_api.py create mode 100644 static/js/namesDialog.js create mode 100644 static/js/titlesDialog.js diff --git a/oml/item/api.py b/oml/item/api.py index c9caa7e..7f7285c 100644 --- a/oml/item/api.py +++ b/oml/item/api.py @@ -17,6 +17,9 @@ import settings import state import utils +from . import person_api +from . import title_api + import logging logger = logging.getLogger('oml.item.api') diff --git a/oml/item/person.py b/oml/item/person.py index 9ce9f4f..9dc6101 100644 --- a/oml/item/person.py +++ b/oml/item/person.py @@ -46,3 +46,9 @@ class Person(db.Model): state.db.session.add(self) state.db.session.commit() + def json(self, keys=None): + r = {} + r['name'] = self.name + r['sortname'] = self.sortname + r['numberofnames'] = self.numberofnames + return r diff --git a/oml/item/person_api.py b/oml/item/person_api.py new file mode 100644 index 0000000..d208b00 --- /dev/null +++ b/oml/item/person_api.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +import unicodedata + +from oxtornado import actions +from queryparser import get_operator +from .person import Person + + +import logging +logger = logging.getLogger('oml.item.person_api') + + +def parse(data, model): + query = {} + query['range'] = [0, 100] + if not 'group' in data: + query['sort'] = [{'key':'sortname', 'operator':'+'}] + for key in ('keys', 'group', 'list', 'range', 'sort', 'query'): + if key in data: + query[key] = data[key] + # print(data) + query['qs'] = model.query + if 'query' in data and data['query'].get('conditions'): + conditions = [] + for c in data['query']['conditions']: + op = get_operator(c['operator']) + conditions.append(op(getattr(model, c['key']), c['value'])) + if data['query'].get('operator') == '|': + q = conditions[0] + for c in conditions[1:]: + q = q | c + q = [q] + else: + q = conditions + for c in q: + query['qs'] = query['qs'].filter(c) + + query['qs'] = order(query['qs'], query['sort']) + return query + +def order(qs, sort): + order_by = [] + for e in sort: + operator = e['operator'] + if operator != '-': + operator = '' + else: + operator = ' DESC' + key = {}.get(e['key'], e['key']) + order = '%s%s' % (key, operator) + order_by.append(order) + if order_by: + #nulllast not supported in sqlite, use IS NULL hack instead + #order_by = map(nullslast, order_by) + _order_by = [] + for order in order_by: + nulls = "%s IS NULL" % order.split(' ')[0] + _order_by.append(nulls) + _order_by.append(order) + order_by = _order_by + qs = qs.order_by(*order_by) + return qs + +def editName(data): + ''' + takes { + name string + sortanme string + } + ''' + response = {} + person = Person.get(data['name']) + person.sortname = unicodedata.normalize('NFKD', data['sortname']) + person.save() + response['name'] = person.name + response['sortname'] = person.sortname + return response +actions.register(editName) + +def findNames(data): + ''' + takes { + query { + conditions [{}] + operator string + } + keys [string] + sort [{}] + range [int, int] + } + ''' + response = {} + q = parse(data, Person) + if 'position' in data: + pass + #ids = [i.id for i in q['qs'].options(load_only('id'))] + #response['position'] = utils.get_positions(ids, [data['qs'][0].id])[0] + print('fixme', data) + elif 'positions' in data: + #ids = [i.id for i in q['qs'].options(load_only('id'))] + #response['positions'] = utils.get_positions(ids, data['positions']) + response['positions'] = [] + print('fixme', data) + elif 'keys' in data: + response['items'] = [] + for i in q['qs'][q['range'][0]:q['range'][1]]: + j = i.json() + response['items'].append({k:j[k] for k in j if not data['keys'] or k in data['keys']}) + else: + response['items'] = q['qs'].count() + return response +actions.register(findNames) diff --git a/oml/item/title_api.py b/oml/item/title_api.py new file mode 100644 index 0000000..c38dde8 --- /dev/null +++ b/oml/item/title_api.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 + +from oxtornado import actions +from . import models +from . import query + + +import logging +logger = logging.getLogger('oml.item.title_api') + + +def editTitle(data): + ''' + takes { + id string + sortname string + } + ''' + response = {} + ''' + item = models.Item.get(data['id']) + item.sortname = unicodedata.normalize('NFKD', data['sortname']) + item.save() + response['name'] = item.name + response['sortname'] = item.sortname + ''' + return response +actions.register(editTitle) + +def findTitles(data): + ''' + takes { + query { + conditions [{}] + operator string + } + keys [string] + sort [{}] + range [int, int] + } + ''' + response = {} + #q = query.parse(data) + q = { + 'qs': models.Item.query, + } + if 'range' in data: + q['range'] = data['range'] + + if 'position' in data: + pass + #ids = [i.id for i in q['qs'].options(load_only('id'))] + #response['position'] = utils.get_positions(ids, [data['qs'][0].id])[0] + print('fixme', data) + elif 'positions' in data: + #ids = [i.id for i in q['qs'].options(load_only('id'))] + #response['positions'] = utils.get_positions(ids, data['positions']) + response['positions'] = [] + print('fixme', data) + elif 'keys' in data: + response['items'] = [] + for i in q['qs'][q['range'][0]:q['range'][1]]: + j = {} + for key in (data['keys'] or ['title', 'sorttitle']): + if key == 'title': + j[key] = i.info.get(key) + elif key == 'sorttitle': + j[key] = i.sort[0].title + response['items'].append(j) + else: + response['items'] = q['qs'].count() + return response +actions.register(findTitles) diff --git a/static/js/mainMenu.js b/static/js/mainMenu.js index 2d73e00..2ffdccd 100644 --- a/static/js/mainMenu.js +++ b/static/js/mainMenu.js @@ -456,6 +456,14 @@ oml.ui.mainMenu = function() { oml.localStorage('enableDebugMode', true); } window.location.reload(); + } else if (id == 'sortnames') { + (oml.$ui.namesDialog || ( + oml.$ui.namesDialog = oml.ui.namesDialog() + )).open(); + } else if (id == 'sorttitles') { + (oml.$ui.titlesDialog || ( + oml.$ui.titlesDialog = oml.ui.titlesDialog() + )).open(); } else { Ox.print('MAIN MENU DOES NOT YET HANDLE', id); } diff --git a/static/js/namesDialog.js b/static/js/namesDialog.js new file mode 100644 index 0000000..89b9a49 --- /dev/null +++ b/static/js/namesDialog.js @@ -0,0 +1,183 @@ +'use strict'; + +oml.ui.namesDialog = function() { + + var height = Math.round((window.innerHeight - 48) * 0.9), + width = 576 + Ox.UI.SCROLLBAR_SIZE, + + $findInput = Ox.Input({ + changeOnKeypress: true, + clear: true, + placeholder: Ox._('Find'), + width: 192 + }) + .css({float: 'right', margin: '4px'}) + .bindEvent({ + change: function(data) { + var query = { + conditions: [ + { + key: 'name', + operator: '=', + value: data.value + }, + { + key: 'sortname', + operator: '=', + value: data.value + } + ], + operator: '|' + }; + $list.options({ + query: query, + }); + } + }), + + $list = Ox.TableList({ + columns: [ + { + id: 'name', + operator: '+', + removable: false, + title: Ox._('Name'), + visible: true, + width: 256 + }, + { + editable: true, + id: 'sortname', + operator: '+', + title: Ox._('Sort Name'), + tooltip: Ox._('Edit Sort Name'), + visible: true, + width: 256 + }, + { + id: 'numberofnames', + align: 'right', + operator: '-', + title: Ox._('Names'), + visible: true, + width: 64 + }, + ], + columnsVisible: true, + items: oml.api.findNames, + max: 1, + scrollbarVisible: true, + sort: [{key: 'sortname', operator: '+'}], + unique: 'name' + }) + .bindEvent({ + init: function(data) { + $status.html( + Ox.toTitleCase(Ox.formatCount(data.items, 'name')) + ); + }, + open: function(data) { + $list.find('.OxItem.OxSelected > .OxCell.OxColumnSortname') + .trigger('mousedown') + .trigger('mouseup'); + }, + select: function(data) { + $findButton.options({disabled: !data.ids.length}); + }, + submit: function(data) { + Ox.Request.clearCache('findNames'); + oml.api.editName({ + name: data.id, + sortname: data.value + }); + } + }), + + $findButton = Ox.Button({ + disabled: true, + title: Ox._('Find'), + width: 48 + }).bindEvent({ + click: function() { + that.close(); + oml.URL.push('/author==' + + $list.value($list.options('selected'), 'name')); + } + }), + + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + title: Ox._('Sort Titles...') + }).bindEvent({ + click: function() { + that.close(); + (oml.$ui.titlesDialog || ( + oml.$ui.titlesDialog = oml.ui.titlesDialog() + )).open(); + } + }), + {}, + $findButton, + Ox.Button({ + title: Ox._('Done'), + width: 48 + }).bindEvent({ + click: function() { + that.close(); + } + }) + ], + closeButton: true, + content: Ox.SplitPanel({ + elements: [ + { + element: Ox.Bar({size: 24}) + .append($status) + .append( + $findInput + ), + size: 24 + }, + { + element: $list + } + ], + orientation: 'vertical' + }), + height: height, + maximizeButton: true, + minHeight: 256, + minWidth: 512, + padding: 0, + title: Ox._('Sort Names'), + width: width + }) + .bindEvent({ + resizeend: function(data) { + var width = (data.width - 64 - Ox.UI.SCROLLBAR_SIZE) / 2; + [ + {id: 'name', width: Math.ceil(width)}, + {id: 'sortname', width: Math.floor(width)} + ].forEach(function(column) { + $list.resizeColumn(column.id, column.width); + }); + } + }), + + $status = $('
') + .css({ + position: 'absolute', + top: '4px', + left: '128px', + right: '128px', + bottom: '4px', + paddingTop: '2px', + fontSize: '9px', + textAlign: 'center' + }) + .appendTo(that.find('.OxButtonsbar')); + + return that; + +}; diff --git a/static/js/titlesDialog.js b/static/js/titlesDialog.js new file mode 100644 index 0000000..1ba071b --- /dev/null +++ b/static/js/titlesDialog.js @@ -0,0 +1,190 @@ +'use strict'; + +oml.ui.titlesDialog = function() { + + var height = Math.round((window.innerHeight - 48) * 0.9), + width = 512 + Ox.UI.SCROLLBAR_SIZE, + + $findInput = Ox.Input({ + changeOnKeypress: true, + clear: true, + placeholder: Ox._('Find'), + width: 192 + }) + .css({float: 'right', margin: '4px'}) + .bindEvent({ + change: function(data) { + var query = { + conditions: [ + { + key: 'title', + operator: '=', + value: data.value + }, + { + key: 'sorttitle', + operator: '=', + value: data.value + } + ], + operator: '|' + }; + $list.options({ + query: query + }); + } + }), + + $list = Ox.TableList({ + columns: [ + { + id: 'id', + title: Ox._('ID'), + visible: false + }, + { + id: 'title', + operator: '+', + removable: false, + title: Ox._('Title'), + visible: true, + width: 256 + }, + { + editable: true, + id: 'sorttitle', + operator: '+', + title: Ox._('Sort Title'), + visible: true, + width: 256 + }, + ], + columnsVisible: true, + items: oml.api.findTitles, + keys: [], + max: 1, + scrollbarVisible: true, + sort: [{key: 'sorttitle', operator: '+'}], + unique: 'id' + }) + .bindEvent({ + init: function(data) { + $status.html( + Ox.toTitleCase(Ox.formatCount(data.items, 'title')) + ); + }, + open: function(data) { + $list.find('.OxItem.OxSelected > .OxCell.OxColumnSorttitle') + .trigger('mousedown') + .trigger('mouseup'); + }, + select: function(data) { + $findButton.options({disabled: !data.ids.length}); + }, + submit: function(data) { + Ox.Request.clearCache('findTitles'); + oml.api.editTitle({ + id: data.id, + sorttitle: data.value + }); + } + }), + + $findButton = Ox.Button({ + disabled: true, + title: Ox._('Find'), + width: 48 + }).bindEvent({ + click: function() { + that.close(); + oml.UI.set({find: { + conditions: [{ + key: 'title', + value: $list.value( + $list.options('selected'), 'title' + ), + operator: '=' + }], + operator: '&' + }}); + oml.$ui.findElement.updateElement(); + } + }), + + that = Ox.Dialog({ + buttons: [ + Ox.Button({ + title: Ox._('Sort Names...') + }).bindEvent({ + click: function() { + that.close(); + (oml.$ui.namesDialog || ( + oml.$ui.namesDialog = oml.ui.namesDialog() + )).open(); + } + }), + {}, + $findButton, + Ox.Button({ + title: Ox._('Done'), + width: 48 + }).bindEvent({ + click: function() { + that.close(); + } + }) + ], + closeButton: true, + content: Ox.SplitPanel({ + elements: [ + { + element: Ox.Bar({size: 24}) + .append($status) + .append( + $findInput + ), + size: 24 + }, + { + element: $list + } + ], + orientation: 'vertical' + }), + height: height, + maximizeButton: true, + minHeight: 256, + minWidth: 512, + padding: 0, + title: Ox._('Sort Titles'), + width: width + }) + .bindEvent({ + resizeend: function(data) { + var width = (data.width - Ox.UI.SCROLLBAR_SIZE) / 2; + [ + {id: 'title', width: Math.ceil(width)}, + {id: 'sorttitle', width: Math.floor(width)} + ].forEach(function(column) { + $list.resizeColumn(column.id, column.width); + }); + } + }), + + $status = $('
') + .css({ + position: 'absolute', + top: '4px', + left: '128px', + right: '128px', + bottom: '4px', + paddingTop: '2px', + fontSize: '9px', + textAlign: 'center' + }) + .appendTo(that.find('.OxButtonsbar')); + + return that; + +}; + diff --git a/static/json/js.json b/static/json/js.json index 32d0604..10a8ae9 100644 --- a/static/json/js.json +++ b/static/json/js.json @@ -46,6 +46,7 @@ "loadingIcon.js", "mainMenu.js", "mainPanel.js", + "namesDialog.js", "notificationsButton.js", "openButton.js", "preferencesDialog.js", @@ -58,6 +59,7 @@ "sortElement.js", "statusIcon.js", "statusbar.js", + "titlesDialog.js", "transfersDialog.js", "updateBotton.js", "userButton.js",