diff --git a/pandora/place/managers.py b/pandora/place/managers.py index 4cdf181..2ee0767 100644 --- a/pandora/place/managers.py +++ b/pandora/place/managers.py @@ -1,35 +1,151 @@ # -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 from django.db.models import Q, Manager +from ox.django.query import QuerySet + +def parseCondition(condition, user): + ''' + condition: { + value: "war" + } + or + condition: { + key: "year", + value: "1970-1980, + operator: "!=" + } + ... + ''' + k = condition.get('key', 'name') + k = { + 'user': 'user__username', + }.get(k, k) + if not k: + k = 'name' + v = condition['value'] + op = condition.get('operator', None) + if not op: + op = '' + if op.startswith('!'): + op = op[1:] + exclude = True + else: + exclude = False + if k == 'id': + v = v.split('/') + if len(v) == 2: + q = Q(user__username=v[0], name=v[1]) + else: + q = Q(id__in=[]) + return q + if isinstance(v, bool): #featured and public flag + key = k + else: + if op == '=': + key = '%s__iexact'%k + elif op == '^': + v = v[1:] + key = '%s__istartswith'%k + elif op == '$': + v = v[:-1] + key = '%s__iendswith'%k + else: # default + key = '%s__icontains'%k + + key = str(key) + if exclude: + q = ~Q(**{key: v}) + else: + q = Q(**{key: v}) + return q + +def parseConditions(conditions, operator, user): + ''' + conditions: [ + { + value: "war" + } + { + key: "year", + value: "1970-1980, + operator: "!=" + }, + { + key: "country", + value: "f", + operator: "^" + } + ], + operator: "&" + ''' + conn = [] + for condition in conditions: + if 'conditions' in condition: + q = parseConditions(condition['conditions'], + condition.get('operator', '&'), user) + if q: + conn.append(q) + pass + else: + if condition.get('value', '') != '' or \ + condition.get('operator', '') == '=': + conn.append(parseCondition(condition, user)) + if conn: + q = conn[0] + for c in conn[1:]: + if operator == '|': + q = q | c + else: + q = q & c + return q + return None + class PlaceManager(Manager): def get_query_set(self): - return super(PlaceManager, self).get_query_set() + return QuerySet(self.model) - def find(self, q='', south=-180.0, west=-180.0, north=180.0, east=180.0): + def find(self, data, user): + ''' + query: { + conditions: [ + { + value: "war" + } + { + key: "year", + value: "1970-1980, + operator: "!=" + }, + { + key: "country", + value: "f", + operator: "^" + } + ], + operator: "&" + } + ''' + + #join query with operator qs = self.get_query_set() + + south=data['area']['south'] + west=data['area']['west'] + north=data['area']['north'] + east=data['area']['east'] qs = qs.filter(Q( Q(Q(south__gt=south)|Q(south__lt=north)|Q(west__gt=west)|Q(west__lt=east)) & Q(Q(south__gt=south)|Q(south__lt=north)|Q(west__lt=east)|Q(east__gt=east)) & Q(Q(north__gt=south)|Q(north__lt=north)|Q(west__gt=west)|Q(west__lt=east)) & Q(Q(north__gt=south)|Q(north__lt=north)|Q(east__gt=west)|Q(east__lt=east)) )) - if q: - qs = qs.filter(name_find__icontains=q) + + conditions = parseConditions(data['query'].get('conditions', []), + data['query'].get('operator', '&'), + user) + if conditions: + qs = qs.filter(conditions) return qs - ''' - #only return locations that have layers of videos visible to current user - if not identity.current.anonymous: - user = identity.current.user - if not user.in_group('admin'): - query = AND(query, - id == Layer.q.locationID, Layer.q.videoID == Video.q.id, - OR(Video.q.public == True, Video.q.creatorID == user.id) - ) - else: - query = AND(query, - id == Layer.q.locationID, Layer.q.videoID == Video.q.id, - Video.q.public == True) - ''' diff --git a/pandora/place/models.py b/pandora/place/models.py index 99ea7e3..3527b6e 100644 --- a/pandora/place/models.py +++ b/pandora/place/models.py @@ -24,6 +24,7 @@ class Place(models.Model): geoname = models.CharField(max_length=1024, unique=True) geoname_sort = models.CharField(max_length=1024, unique=True) + countryCode = models.CharField(max_length=16, default='') wikipediaId = models.CharField(max_length=1000, blank=True) @@ -57,7 +58,7 @@ class Place(models.Model): 'user': self.user.username, } for key in ('created', 'modified', - 'name', 'geoname', + 'name', 'geoname', 'countryCode', 'south', 'west', 'north', 'east', 'lat', 'lng', 'size'): j[key] = getattr(self, key) diff --git a/pandora/place/views.py b/pandora/place/views.py index e25e5b2..4c629dd 100644 --- a/pandora/place/views.py +++ b/pandora/place/views.py @@ -21,6 +21,7 @@ def addPlace(request): param data { name: "", geoname: "", + countryCode: '', south: float, west: float, north: float, @@ -45,7 +46,7 @@ def addPlace(request): for key in data: setattr(place, key, data[key]) place.save() - response = json_response(status=200, text='created') + response = json_response(place.json()) else: response = json_response(status=403, text='place name exists') return render_to_json_response(response) @@ -72,14 +73,17 @@ def editPlace(request): for name in names: if models.Place.objects.filter(name_find__icontains=u'|%s|'%name).exclude(id=place.id).count() != 0: conflict = True + if 'geoname' in data: + if models.Place.objects.filter(geoname=data['geoname']).exclude(id=place.id).count() != 0: + conflict = True if not conflict: for key in data: if key != 'id': setattr(place, key, data[key]) place.save() - response = json_response(status=200, text='updated') + response = json_response(place.json()) else: - response = json_response(status=403, text='place name/alias conflict') + response = json_response(status=403, text='place name/geoname conflict') else: response = json_response(status=403, text='permission denied') return render_to_json_response(response) @@ -108,13 +112,30 @@ actions.register(removePlace, cache=False) def parse_query(data, user): query = {} query['range'] = [0, 100] - query['sort'] = [{'key':'user', 'operator':'+'}, {'key':'name', 'operator':'+'}] - for key in ('keys', 'group', 'list', 'range', 'ids', 'sort'): + query['sort'] = [{'key':'name', 'operator':'+'}] + query['area'] = {'south': -180.0, 'west': -180.0, 'north': 180.0, 'east': 180.0} + for key in ('area', 'keys', 'group', 'list', 'range', 'ids', 'sort', 'query'): if key in data: query[key] = data[key] - query['qs'] = models.Place.objects.all() + query['qs'] = models.Place.objects.find(query, user) return query +def order_query(qs, sort): + order_by = [] + for e in sort: + operator = e['operator'] + if operator != '-': + operator = '' + key = { + 'name': 'name_sort', + 'geoname': 'geoname_sort', + }.get(e['key'], e['key']) + order = '%s%s' % (operator, key) + order_by.append(order) + if order_by: + qs = qs.order_by(*order_by, nulls_last=True) + return qs + def findPlaces(request): ''' param data { @@ -185,7 +206,7 @@ Positions response = json_response() query = parse_query(data, request.user) - qs = query['qs'] + qs = order_query(query['qs'], query['sort']) if 'keys' in data: qs = qs[query['range'][0]:query['range'][1]] response['data']['items'] = [p.json(request.user) for p in qs] diff --git a/pandora/templates/index.html b/pandora/templates/index.html index 2abe451..3882d03 100644 --- a/pandora/templates/index.html +++ b/pandora/templates/index.html @@ -17,6 +17,7 @@ if(typeof(console)=='undefined') { + diff --git a/static/js/pandora.api.js b/static/js/pandora.api.js index c7f36df..5a7593e 100755 --- a/static/js/pandora.api.js +++ b/static/js/pandora.api.js @@ -79,7 +79,7 @@ function constructList() { columnsMovable: false, columnsRemovable: false, id: 'actionList', - request: function(data, callback) { + items: function(data, callback) { function _sort(a, b) { if(a.name > b.name) return 1; diff --git a/static/js/pandora.js b/static/js/pandora.js index cd94c09..75c0274 100755 --- a/static/js/pandora.js +++ b/static/js/pandora.js @@ -571,16 +571,16 @@ width: ratio >= 1 ? size : size * ratio }; }, - keys: ['director', 'id', 'poster', 'title', 'year'], - max: 1, - min: 1, - orientation: 'horizontal', - request: function(data, callback) { + items: function(data, callback) { //Ox.print('data, Query.toObject', data, Query.toObject()) pandora.api.find($.extend(data, { query: Query.toObject() }), callback); }, + keys: ['director', 'id', 'poster', 'title', 'year'], + max: 1, + min: 1, + orientation: 'horizontal', selected: [app.user.ui.item], size: 64, sort: app.user.ui.lists[app.user.ui.list].sort, @@ -962,8 +962,7 @@ }, ], columnsVisible: true, - pageLength: 1000, - request: function(data, callback) { + items: function(data, callback) { var query = id == 'favorite' ? {conditions: [ {key: 'user', value: app.user.username, operator: '!'}, {key: 'status', value: 'public', operator: '='} @@ -975,6 +974,7 @@ query: query }), callback); }, + pageLength: 1000, // fixme: select if previously selected // selected: app.user.ui.list ? [app.user.ui.list] : [], sort: [ @@ -1062,9 +1062,7 @@ width: app.user.ui.sidebarSize - 16 } ], - max: 1, - min: 1, - request: function(data, callback) { + items: function(data, callback) { var result = {data: {}}; if (!data.range) { result.data.items = Ox.getObjectById(app.ui.sectionFolders.site, id).items.length; @@ -1073,6 +1071,8 @@ } callback(result); }, + max: 1, + min: 1, sort: [{key: '', operator: ''}] }) .bindEvent({ @@ -1165,10 +1165,7 @@ width: 16 } ], - max: 1, - min: 0, - pageLength: 1000, - request: function(data, callback) { + items: function(data, callback) { var query; if (id == 'personal') { query = {conditions: [ @@ -1187,6 +1184,9 @@ query: query }), callback); }, + max: 1, + min: 0, + pageLength: 1000, sort: [ {key: 'position', operator: '+'} ], @@ -1535,7 +1535,7 @@ ], columnsVisible: true, id: 'group_' + id, - request: function(data, callback) { + items: function(data, callback) { //Ox.print('sending request', data) delete data.keys; //alert(id + " Query.toObject " + JSON.stringify(Query.toObject(id)) + ' ' + JSON.stringify(data)) @@ -1995,7 +1995,7 @@ columnsResizable: true, columnsVisible: true, id: 'list', - request: function(data, callback) { + items: function(data, callback) { //Ox.print('data, Query.toObject', data, Query.toObject()) pandora.api.find($.extend(data, { query: Query.toObject() @@ -2042,13 +2042,13 @@ width: ratio >= 1 ? size : size * ratio }; }, - keys: ['director', 'id', 'poster', 'title', 'year'], - request: function(data, callback) { + items: function(data, callback) { //Ox.print('data, Query.toObject', data, Query.toObject()) pandora.api.find($.extend(data, { query: Query.toObject() }), callback); }, + keys: ['director', 'id', 'poster', 'title', 'year'], size: 128, sort: app.user.ui.lists[app.user.ui.list].sort, unique: 'id' @@ -2150,7 +2150,7 @@ orientation: 'horizontal' }) .bindEvent('resize', function() { - app.$ui.map.triggerResize(); + app.$ui.map.resize(); }); } else { $list = new Ox.Element('
') @@ -2611,6 +2611,8 @@ app.$ui.accountDialog = (app.user.level == 'guest' ? ui.accountDialog('login') : ui.accountLogoutDialog()).open(); } else if (data.id == 'places') { + app.$ui.placesDialog = ui.placesDialog().open(); + /* var $manage = new Ox.SplitPanel({ elements: [ { @@ -2727,7 +2729,7 @@ .bindEvent({ submit: function(event, data) { app.$ui.map.find(data.value, function(location) { - /* + app.$ui.placeNameInput.options({ disabled: false, value: location.name @@ -2745,7 +2747,7 @@ app.$ui.addPlaceButton.options({ disabled: false }); - */ + }); } }) @@ -2882,6 +2884,7 @@ }).css({ overflow: 'hidden' }).append($manage).open(); + */ } else if (data.id == 'query') { var $dialog = new Ox.Dialog({ buttons: [ @@ -2932,6 +2935,73 @@ }) return that; }, + placesDialog: function() { + var height = Math.round(document.height * 0.8), + width = Math.round(document.width * 0.8), + that = new Ox.Dialog({ + buttons: [ + new Ox.Button({ + id: 'done', + title: 'Done' + }).bindEvent({ + click: function() { + that.close(); + } + }) + ], + content: app.$ui.placesElement = new Ox.ListMap({ + height: height - 48, + places: function(data, callback) { + return pandora.api.findPlaces($.extend(data, { + query: {conditions: [], operator: ''} + }), callback); + }, + width: width + }) + .bindEvent({ + addplace: function(event, data) { + Ox.print('ADDPLACE', data) + pandora.api.addPlace(data.place, function(result) { + var id = result.data.id; + Ox.print("ID", result.data.id, result) + Ox.Request.clearCache(); // fixme: remove + Ox.print('AAAAA') + app.$ui.placesElement + .reloadList() + .bindEvent({loadlist: load}); + Ox.print('BBBBB') + function load(event, data) { + Ox.print('LOAD') + app.$ui.placesElement + .focusList() + .options({selected: [id]}) + .unbindEvent({loadlist: load}); // fixme: need bindEventOnce + } + }); + }, + removeplace: function(event, data) { + pandora.api.removePlace(data.id, function(result) { + // fixme: duplicated + Ox.Request.clearCache(); // fixme: remove + app.$ui.placesElement + .reloadList() + .bindEvent({loadlist: load}); + function load(event, data) { + app.$ui.placesElement + .focusList() + .unbindEvent({loadlist: load}); // fixme: need bindEventOnce + } + }); + } + }), + height: height, + keys: {enter: 'done', escape: 'done'}, + padding: 0, + title: 'Manage Places', + width: width + }); + return that; + }, publicListsDialog: function() { // fixme: unused var that = new Ox.Dialog({ buttons: [ @@ -2994,7 +3064,7 @@ resizeGroups(data); app.$ui.list.size(); if (app.user.ui.lists[app.user.ui.list].listView == 'map') { - app.$ui.map.triggerResize(); + app.$ui.map.resize(); } } else { app.$ui.browser.scrollToSelection(); @@ -3342,7 +3412,7 @@ function reloadGroups(i) { var query = Query.toObject(); app.$ui.list.options({ - request: function(data, callback) { + items: function(data, callback) { return pandora.api.find($.extend(data, { query: query }), callback); @@ -3352,7 +3422,7 @@ if (i_ != i) { //Ox.print('setting groups request', i, i_) app.$ui.groups[i_].options({ - request: function(data, callback) { + items: function(data, callback) { delete data.keys; return pandora.api.find($.extend(data, { group: group_.id, @@ -3435,7 +3505,7 @@ app.$ui.list.size(); resizeGroups(app.$ui.rightPanel.width()); if (app.user.ui.listView == 'map') { - app.$ui.map.triggerResize(); + app.$ui.map.resize(); } } else { //Ox.print('app.$ui.window.resize');