Repository: flask-admin/flask-admin

Issue:
PostgreSql CITEXT column not shown/editable.
I have a Postgres table with a column of type CITEXT (it's a extension type, but a core extension type, Support is via this plug-in [here](https://github.com/mahmoudimus/sqlalchemy-citext)).

This column is not reflected in the create/edit dialogs of flask-admin.

It does show up in the table view.

It's probably fairly simple to do, but how can I convince flask-admin to allow editing of this column? For all functional purposes, it's basically just a column of type TEXT.



Discussion:
You can add custom model converter to add support for this type:
1. You need to create custom model converter: http://flask-admin.readthedocs.org/en/latest/api/mod_contrib_sqla/#flask_admin.contrib.sqla.ModelView.model_form_converter
2. Add support for CITEXT to the model converter: https://github.com/flask-admin/flask-admin/blob/master/flask_admin/contrib/sqla/form.py#L293

I recommend adding custom model converter to your base class, so new converter will be used for all model views.

Also, I don't mind getting pull request for the default SQLa model converter.

I'll have a poke, and see what I can do, then.

@fake-name, I just bumped into the same issue. Before trying to solve it myself, do you have a solution to share?
I never did get around to looking into the issue. Sorry!
Thanks for the quick reply! Will try to look at it.

Resolving patch:
diff --git a/flask_admin/contrib/sqla/form.py b/flask_admin/contrib/sqla/form.py
--- a/flask_admin/contrib/sqla/form.py
+++ b/flask_admin/contrib/sqla/form.py
@@ -264,7 +264,7 @@ def convert(self, model, mapper, name, prop, field_args, hidden_pk):
 
     @classmethod
     def _string_common(cls, column, field_args, **extra):
-        if isinstance(column.type.length, int) and column.type.length:
+        if hasattr(column.type, 'length') and isinstance(column.type.length, int) and column.type.length:
             field_args['validators'].append(validators.Length(max=column.type.length))
 
     @converts('String')  # includes VARCHAR, CHAR, and Unicode
@@ -290,7 +290,7 @@ def conv_String(self, column, field_args, **extra):
         self._string_common(column=column, field_args=field_args, **extra)
         return fields.StringField(**field_args)
 
-    @converts('Text', 'LargeBinary', 'Binary')  # includes UnicodeText
+    @converts('Text', 'LargeBinary', 'Binary', 'CIText')  # includes UnicodeText
     def conv_Text(self, field_args, **extra):
         self._string_common(field_args=field_args, **extra)
         return fields.TextAreaField(**field_args)


Test patch:
diff --git a/flask_admin/tests/sqla/test_postgres.py b/flask_admin/tests/sqla/test_postgres.py
--- a/flask_admin/tests/sqla/test_postgres.py
+++ b/flask_admin/tests/sqla/test_postgres.py
@@ -4,6 +4,7 @@
 from .test_basic import CustomModelView
 
 from sqlalchemy.dialects.postgresql import HSTORE, JSON
+from citext import CIText
 
 
 def test_hstore():
@@ -75,3 +76,39 @@ class JSONModel(db.Model):
     data = rv.data.decode('utf-8')
     ok_('json_test' in data)
     ok_('>{"test_key1": "test_value1"}<' in data)
+
+
+def test_citext():
+    app, db, admin = setup_postgres()
+
+    class CITextModel(db.Model):
+        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
+        citext_test = db.Column(CIText)
+
+    db.engine.execute('CREATE EXTENSION IF NOT EXISTS citext')
+    db.create_all()
+
+    view = CustomModelView(CITextModel, db.session)
+    admin.add_view(view)
+
+    client = app.test_client()
+
+    rv = client.get('/admin/citextmodel/')
+    eq_(rv.status_code, 200)
+
+    rv = client.post('/admin/citextmodel/new/', data={
+        'citext_test': 'Foo',
+    })
+    eq_(rv.status_code, 302)
+
+    rv = client.get('/admin/citextmodel/')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('citext_test' in data)
+    ok_('Foo' in data)
+
+    rv = client.get('/admin/citextmodel/edit/?id=1')
+    eq_(rv.status_code, 200)
+    data = rv.data.decode('utf-8')
+    ok_('name="citext_test"' in data)
+    ok_('>Foo<' in data)