2014-05-04 17:26:43 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# vi:si:et:sw=4:sts=4:ts=4
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
import unicodedata
|
2014-05-21 22:41:29 +00:00
|
|
|
from sqlalchemy.sql import operators
|
2014-05-04 17:26:43 +00:00
|
|
|
|
|
|
|
import utils
|
|
|
|
import settings
|
|
|
|
|
2014-05-18 23:24:04 +00:00
|
|
|
import logging
|
2015-11-29 14:56:38 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
2014-05-18 23:24:04 +00:00
|
|
|
|
2014-05-04 17:26:43 +00:00
|
|
|
def get_operator(op, type='str'):
|
|
|
|
return {
|
|
|
|
'str': {
|
2014-05-26 16:02:41 +00:00
|
|
|
'==': operators.eq,
|
2014-05-04 17:26:43 +00:00
|
|
|
'>': operators.gt,
|
|
|
|
'>=': operators.ge,
|
|
|
|
'<': operators.lt,
|
|
|
|
'<=': operators.le,
|
|
|
|
'^': operators.startswith_op,
|
|
|
|
'$': operators.endswith_op,
|
|
|
|
},
|
|
|
|
'int': {
|
|
|
|
'==': operators.eq,
|
|
|
|
'>': operators.gt,
|
|
|
|
'>=': operators.ge,
|
|
|
|
'<': operators.lt,
|
|
|
|
'<=': operators.le,
|
|
|
|
}
|
|
|
|
}[type].get(op, {
|
|
|
|
'str': operators.contains_op,
|
|
|
|
'int': operators.eq
|
|
|
|
}[type])
|
|
|
|
|
|
|
|
|
|
|
|
class Parser(object):
|
|
|
|
|
2016-01-06 04:24:23 +00:00
|
|
|
def __init__(self, model, user_items):
|
2014-05-04 17:26:43 +00:00
|
|
|
self._model = model
|
|
|
|
self._find = model.find.mapper.class_
|
2014-05-27 18:10:55 +00:00
|
|
|
self._sort = model.sort.mapper.class_
|
2014-05-04 17:26:43 +00:00
|
|
|
self._user = model.users.mapper.class_
|
2016-01-06 04:24:23 +00:00
|
|
|
self._user_items = user_items
|
2014-05-04 17:26:43 +00:00
|
|
|
self._list = model.lists.mapper.class_
|
|
|
|
self.item_keys = model.item_keys
|
|
|
|
self.filter_keys = model.filter_keys
|
|
|
|
|
|
|
|
def parse_condition(self, condition):
|
|
|
|
'''
|
|
|
|
condition: {
|
|
|
|
value: "war"
|
|
|
|
}
|
|
|
|
or
|
|
|
|
condition: {
|
|
|
|
key: "year",
|
|
|
|
value: [1970, 1980],
|
|
|
|
operator: "="
|
|
|
|
}
|
|
|
|
...
|
|
|
|
'''
|
2014-05-19 01:36:37 +00:00
|
|
|
#logger.debug('parse_condition %s', condition)
|
2014-05-18 23:24:04 +00:00
|
|
|
if not 'value' in condition:
|
|
|
|
return None
|
2014-05-04 17:26:43 +00:00
|
|
|
k = condition.get('key', '*')
|
|
|
|
if not k:
|
|
|
|
k = '*'
|
|
|
|
v = condition['value']
|
|
|
|
op = condition.get('operator')
|
|
|
|
if not op:
|
|
|
|
op = '='
|
|
|
|
if op.startswith('!'):
|
|
|
|
op = op[1:]
|
|
|
|
exclude = True
|
|
|
|
else:
|
|
|
|
exclude = False
|
|
|
|
|
|
|
|
key_type = (utils.get_by_id(self.item_keys, k) or {'type': 'string'}).get('type')
|
|
|
|
if isinstance(key_type, list):
|
|
|
|
key_type = key_type[0]
|
|
|
|
if k == 'list':
|
|
|
|
key_type = ''
|
|
|
|
|
|
|
|
if (not exclude and op == '=' or op in ('$', '^')) and v == '':
|
|
|
|
return None
|
|
|
|
elif k == 'resolution':
|
|
|
|
q = self.parse_condition({'key': 'width', 'value': v[0], 'operator': op}) \
|
|
|
|
& self.parse_condition({'key': 'height', 'value': v[1], 'operator': op})
|
|
|
|
if exclude:
|
|
|
|
q = ~q
|
|
|
|
return q
|
|
|
|
elif isinstance(v, list) and len(v) == 2 and op == '=':
|
|
|
|
q = self.parse_condition({'key': k, 'value': v[0], 'operator': '>='}) \
|
|
|
|
& self.parse_condition({'key': k, 'value': v[1], 'operator': '<'})
|
|
|
|
if exclude:
|
|
|
|
q = ~q
|
|
|
|
return q
|
|
|
|
elif key_type == 'boolean':
|
|
|
|
q = getattr(self._model, 'find_%s' % k) == v
|
|
|
|
if exclude:
|
|
|
|
q = ~q
|
|
|
|
return q
|
|
|
|
elif key_type in ("string", "text"):
|
2014-09-02 22:32:44 +00:00
|
|
|
if isinstance(v, str):
|
2014-05-04 17:26:43 +00:00
|
|
|
v = unicodedata.normalize('NFKD', v).lower()
|
2014-05-26 16:02:41 +00:00
|
|
|
else:
|
|
|
|
v = v.lower()
|
|
|
|
q = get_operator(op)(self._find.findvalue, v)
|
2014-05-04 17:26:43 +00:00
|
|
|
if k != '*':
|
|
|
|
q &= (self._find.key == k)
|
2014-05-26 23:45:29 +00:00
|
|
|
self._joins.append(self._find)
|
2014-05-04 17:26:43 +00:00
|
|
|
if exclude:
|
|
|
|
q = ~q
|
|
|
|
return q
|
|
|
|
elif k == 'list':
|
2014-05-18 23:24:04 +00:00
|
|
|
nickname, name = v.split(':', 1)
|
2014-05-04 17:26:43 +00:00
|
|
|
if nickname:
|
2016-01-06 04:24:23 +00:00
|
|
|
u = self._user.query.filter_by(nickname=nickname).one()
|
2014-05-04 17:26:43 +00:00
|
|
|
else:
|
2016-01-06 04:24:23 +00:00
|
|
|
u = self._user.query.filter_by(id=settings.USER_ID).one()
|
2016-01-06 18:06:48 +00:00
|
|
|
l = self._list.query.filter_by(user_id=u.id, name=name).one()
|
|
|
|
if exclude:
|
|
|
|
ids = [i.id for i in l.get_items()]
|
|
|
|
q = operators.notin_op(self._model.id, ids)
|
2014-05-04 17:26:43 +00:00
|
|
|
else:
|
2016-01-06 18:06:48 +00:00
|
|
|
if l.type == 'smart':
|
|
|
|
data = l._query
|
|
|
|
q = self.parse_conditions(data.get('conditions', []),
|
|
|
|
data.get('operator', '&'))
|
2014-05-26 23:45:29 +00:00
|
|
|
else:
|
2016-01-06 18:06:48 +00:00
|
|
|
q = (self._list.id == l.id)
|
|
|
|
self._joins.append(self._list.items)
|
2014-05-04 17:26:43 +00:00
|
|
|
return q
|
|
|
|
elif key_type == 'date':
|
|
|
|
def parse_date(d):
|
|
|
|
while len(d) < 3:
|
|
|
|
d.append(1)
|
|
|
|
return datetime(*[int(i) for i in d])
|
|
|
|
#using sort here since find only contains strings
|
|
|
|
v = parse_date(v.split('-'))
|
2014-05-27 18:10:55 +00:00
|
|
|
vk = getattr(self._sort, k)
|
2014-05-04 17:26:43 +00:00
|
|
|
q = get_operator(op, 'int')(vk, v)
|
2014-05-27 18:10:55 +00:00
|
|
|
self._joins.append(self._sort)
|
2014-05-04 17:26:43 +00:00
|
|
|
if exclude:
|
|
|
|
q = ~q
|
|
|
|
return q
|
|
|
|
else: #integer, float, time
|
2014-05-27 18:10:55 +00:00
|
|
|
q = get_operator(op, 'int')(getattr(self._sort, k), v)
|
|
|
|
self._joins.append(self._sort)
|
2014-05-04 17:26:43 +00:00
|
|
|
if exclude:
|
|
|
|
q = ~q
|
|
|
|
return q
|
|
|
|
|
|
|
|
def parse_conditions(self, conditions, operator):
|
|
|
|
'''
|
|
|
|
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 = self.parse_conditions(condition['conditions'],
|
|
|
|
condition.get('operator', '&'))
|
|
|
|
else:
|
|
|
|
q = self.parse_condition(condition)
|
|
|
|
if isinstance(q, list):
|
|
|
|
conn += q
|
|
|
|
else:
|
|
|
|
conn.append(q)
|
|
|
|
conn = [q for q in conn if not isinstance(q, None.__class__)]
|
|
|
|
if conn:
|
|
|
|
if operator == '|':
|
|
|
|
q = conn[0]
|
|
|
|
for c in conn[1:]:
|
|
|
|
q = q | c
|
|
|
|
q = [q]
|
|
|
|
else:
|
|
|
|
q = conn
|
|
|
|
return q
|
|
|
|
return []
|
|
|
|
|
|
|
|
def find(self, data):
|
|
|
|
'''
|
|
|
|
query: {
|
|
|
|
conditions: [
|
|
|
|
{
|
|
|
|
value: "war"
|
|
|
|
}
|
|
|
|
{
|
|
|
|
key: "year",
|
|
|
|
value: "1970-1980,
|
|
|
|
operator: "!="
|
|
|
|
},
|
|
|
|
{
|
|
|
|
key: "country",
|
|
|
|
value: "f",
|
|
|
|
operator: "^"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
operator: "&"
|
|
|
|
}
|
|
|
|
'''
|
|
|
|
|
|
|
|
#join query with operator
|
|
|
|
qs = self._model.query
|
|
|
|
#only include items that have hard metadata
|
2014-05-26 23:45:29 +00:00
|
|
|
self._joins = []
|
2014-05-04 17:26:43 +00:00
|
|
|
conditions = self.parse_conditions(data.get('query', {}).get('conditions', []),
|
|
|
|
data.get('query', {}).get('operator', '&'))
|
|
|
|
for c in conditions:
|
2014-05-26 23:45:29 +00:00
|
|
|
if self._joins:
|
|
|
|
qs = qs.join(self._joins.pop(0))
|
|
|
|
qs = qs.filter(c)
|
2016-01-04 10:33:37 +00:00
|
|
|
# FIXME: group_by needed here to avoid
|
2016-01-04 12:14:05 +00:00
|
|
|
# duplicates due to joins.
|
|
|
|
qs = qs.group_by(self._model.id)
|
2016-01-06 04:24:23 +00:00
|
|
|
#print(qs)
|
2014-05-04 17:26:43 +00:00
|
|
|
return qs
|