Unverified Commit 4e3e7e68 authored by Serge S. Koval's avatar Serge S. Koval Committed by GitHub

Merge pull request #1875 from flask-admin/hybrid-prop-search

Support search on SQLAlchemy hybrid property
parents 920af0ad 62692891
...@@ -4,6 +4,7 @@ from flask import Flask, Markup ...@@ -4,6 +4,7 @@ from flask import Flask, Markup
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import composite from sqlalchemy.orm import composite
from sqlalchemy import sql, cast
import uuid import uuid
import random import random
import string import string
...@@ -85,9 +86,13 @@ class User(db.Model): ...@@ -85,9 +86,13 @@ class User(db.Model):
def phone_number(self): def phone_number(self):
if self.dialling_code and self.local_phone_number: if self.dialling_code and self.local_phone_number:
number = str(self.local_phone_number) number = str(self.local_phone_number)
return "+{} ({}){} {} {}".format(self.dialling_code, number[0], number[1:3], number[3:6], number[6::]) return "+{} ({}) {} {} {}".format(self.dialling_code, number[0], number[1:3], number[3:6], number[6::])
return return
@phone_number.expression
def phone_number(cls):
return sql.operators.ColumnOperators.concat(cast(cls.dialling_code, db.String), cls.local_phone_number)
def __str__(self): def __str__(self):
return "{}, {}".format(self.last_name, self.first_name) return "{}, {}".format(self.last_name, self.first_name)
...@@ -197,6 +202,7 @@ class UserAdmin(sqla.ModelView): ...@@ -197,6 +202,7 @@ class UserAdmin(sqla.ModelView):
column_searchable_list = [ column_searchable_list = [
'first_name', 'first_name',
'last_name', 'last_name',
'phone_number',
'email', 'email',
] ]
column_editable_list = ['type', 'currency', 'timezone'] column_editable_list = ['type', 'currency', 'timezone']
...@@ -233,6 +239,7 @@ class UserAdmin(sqla.ModelView): ...@@ -233,6 +239,7 @@ class UserAdmin(sqla.ModelView):
FilterEqual(column=User.last_name, name='Last Name'), FilterEqual(column=User.last_name, name='Last Name'),
FilterLastNameBrown(column=User.last_name, name='Last Name', FilterLastNameBrown(column=User.last_name, name='Last Name',
options=(('1', 'Yes'), ('0', 'No'))), options=(('1', 'Yes'), ('0', 'No'))),
'phone_number',
'email', 'email',
'ip_address', 'ip_address',
'currency', 'currency',
......
...@@ -572,12 +572,18 @@ class ModelView(BaseModelView): ...@@ -572,12 +572,18 @@ class ModelView(BaseModelView):
if self.column_searchable_list: if self.column_searchable_list:
self._search_fields = [] self._search_fields = []
for p in self.column_searchable_list: for name in self.column_searchable_list:
attr, joins = tools.get_field_with_path(self.model, p) attr, joins = tools.get_field_with_path(self.model, name)
if not attr: if not attr:
raise Exception('Failed to find field for search field: %s' % p) raise Exception('Failed to find field for search field: %s' % name)
if tools.is_hybrid_property(self.model, name):
column = attr
if isinstance(name, string_types):
column.key = name.split('.')[-1]
self._search_fields.append((column, joins))
else:
for column in tools.get_columns_for_field(attr): for column in tools.get_columns_for_field(attr):
self._search_fields.append((column, joins)) self._search_fields.append((column, joins))
......
...@@ -10,6 +10,7 @@ from flask_admin.contrib.sqla import ModelView, filters, tools ...@@ -10,6 +10,7 @@ from flask_admin.contrib.sqla import ModelView, filters, tools
from flask_babelex import Babel from flask_babelex import Babel
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import cast
from sqlalchemy_utils import EmailType, ChoiceType, UUIDType, URLType, CurrencyType, ColorType, ArrowType, IPAddressType from sqlalchemy_utils import EmailType, ChoiceType, UUIDType, URLType, CurrencyType, ColorType, ArrowType, IPAddressType
from . import setup from . import setup
...@@ -1559,6 +1560,14 @@ def test_hybrid_property(): ...@@ -1559,6 +1560,14 @@ def test_hybrid_property():
def number_of_pixels(self): def number_of_pixels(self):
return self.width * self.height return self.width * self.height
@hybrid_property
def number_of_pixels_str(self):
return str(self.number_of_pixels())
@number_of_pixels_str.expression
def number_of_pixels_str(cls):
return cast(cls.width * cls.height, db.String)
db.create_all() db.create_all()
db.session.add(Model1(id=1, name="test_row_1", width=25, height=25)) db.session.add(Model1(id=1, name="test_row_1", width=25, height=25))
...@@ -1571,7 +1580,8 @@ def test_hybrid_property(): ...@@ -1571,7 +1580,8 @@ def test_hybrid_property():
Model1, db.session, Model1, db.session,
column_default_sort='number_of_pixels', column_default_sort='number_of_pixels',
column_filters=[filters.IntGreaterFilter(Model1.number_of_pixels, column_filters=[filters.IntGreaterFilter(Model1.number_of_pixels,
'Number of Pixels')] 'Number of Pixels')],
column_searchable_list=['number_of_pixels_str', ]
) )
admin.add_view(view) admin.add_view(view)
...@@ -1592,6 +1602,13 @@ def test_hybrid_property(): ...@@ -1592,6 +1602,13 @@ def test_hybrid_property():
eq_(data[0].name, 'test_row_2') eq_(data[0].name, 'test_row_2')
eq_(data[1].name, 'test_row_1') eq_(data[1].name, 'test_row_1')
# searching
rv = client.get('/admin/model1/?search=100')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test_row_2' in data)
ok_('test_row_1' not in data)
def test_url_args(): def test_url_args():
app, db, admin = setup() app, db, admin = setup()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment