Commit 37ae5a07 authored by Serge S. Koval's avatar Serge S. Koval

Multi-select for many-to-one relationships, jQuery Chosen for drop downs, fixes.

parent 52131105
......@@ -3,14 +3,12 @@
- Override base URL (/admin/)
- Model Admin
- Ability to sort by fields that are not visible?
- Proper error display
- SQLA Model Admin
- Validation of the joins in the query
- Automatic joined load for foreign keys
- Automatic PK detection
- Filtering
- Many2Many editing
- One2Many editor
- File admin
- Documentation
- Examples
......
......@@ -37,6 +37,9 @@ class Post(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
user = db.relationship(User, backref='posts')
def __unicode__(self):
return self.title
# Flask routes
@app.route('/')
......
from sqlalchemy.orm.properties import RelationshipProperty, ColumnProperty
from sqlalchemy.orm.interfaces import MANYTOONE
from sqlalchemy.orm.interfaces import MANYTOONE, ONETOMANY
from sqlalchemy.orm.attributes import InstrumentedAttribute
from sqlalchemy.sql.expression import desc
from wtforms.ext.sqlalchemy.orm import model_form, ModelConverter
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
from flask import flash
from flaskext import wtf
from flask.ext.adminex.model import BaseModelView
from flask.ext.adminex.form import AdminForm
class AdminModelConverter(ModelConverter):
"""
SQLAlchemy model to form converter
"""
def __init__(self, session):
def __init__(self, view):
super(AdminModelConverter, self).__init__()
self.session = session
self.view = view
def convert(self, model, mapper, prop, field_args):
if isinstance(prop, RelationshipProperty):
local_column = prop.local_remote_pairs[0][0]
remote_model = prop.mapper.class_
kwargs = {
'validators': [],
'filters': [],
'allow_blank': local_column.nullable,
'default': None
}
if field_args:
kwargs.update(field_args)
if prop.direction is MANYTOONE:
def query_factory():
return self.session.query(prop.argument)
def query_factory():
return self.view.session.query(remote_model)
if prop.direction is MANYTOONE:
return QuerySelectField(query_factory=query_factory, **kwargs)
elif prop.direction is ONETOMANY:
# Skip backrefs
if not local_column.foreign_keys and self.view.hide_backrefs:
return None
return QuerySelectMultipleField(query_factory=query_factory, **kwargs)
else:
# Ignore pk/fk
if isinstance(prop, ColumnProperty):
......@@ -58,6 +69,12 @@ class ModelView(BaseModelView):
admin = ModelView(User, db.session)
"""
hide_backrefs = True
"""
Set this to False if you want to see multiselect for model backrefs.
"""
def __init__(self, model, session,
name=None, category=None, endpoint=None, url=None):
"""
......@@ -115,9 +132,14 @@ class ModelView(BaseModelView):
for p in mapper.iterate_properties:
if isinstance(p, ColumnProperty):
# TODO: Check for multiple columns
# Sanity check
if len(p.columns) > 1:
raise Exception('Automatic form scaffolding is not supported' +
' for multi-column properties (%s.%s)' % (self.model.__name__, p.key))
column = p.columns[0]
# Can't sort by on primary and foreign keys by default
if column.foreign_keys or column.primary_key:
continue
......@@ -130,10 +152,10 @@ class ModelView(BaseModelView):
Create form from the model.
"""
return model_form(self.model,
wtf.Form,
AdminForm,
self.form_columns,
field_args=self.form_args,
converter=AdminModelConverter(self.session))
converter=AdminModelConverter(self))
# Database-related API
def get_list(self, page, sort_column, sort_desc, execute=True):
......
from flask.ext import wtf
class AdminForm(wtf.Form):
@property
def has_file_field(self):
# TODO: Optimize me
for f in self:
if isinstance(f, wtf.FileField):
return True
return False
......@@ -220,7 +220,7 @@ class BaseModelView(BaseView):
def scaffold_form(self):
"""
Create WTForm class from the model. Must be implemented in
Create `form.AdminForm` class from the model. Must be implemented in
the child class.
"""
raise NotImplemented('Please implement scaffold_form method')
......
This diff is collapsed.
This diff is collapsed.
......@@ -2,14 +2,18 @@
<html>
<head>
<title>{% block title %}{% if view.category %}{{ view.category }} - {% endif %}{{ view.name }} - {{ view.admin.name }}{% endblock %}</title>
{% block head %}
{% block head_meta %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
{% endblock %}
{% block head_css %}
<link href="{{ url_for('admin.static', filename='bootstrap/css/bootstrap.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='bootstrap/css/bootstrap-responsive.css') }}" rel="stylesheet">
<link href="{{ url_for('admin.static', filename='css/admin.css') }}" rel="stylesheet">
{% endblock %}
{% block head %}
{% endblock %}
</head>
<body>
{% block page_body %}
......@@ -68,5 +72,9 @@
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<script src="{{ url_for('admin.static', filename='bootstrap/js/bootstrap.min.js') }}" type="text/javascript"></script>
<script src="{{ url_for('admin.static', filename='chosen/chosen.jquery.min.js') }}" type="text/javascript"></script>
{% block tail %}
{% endblock %}
</body>
</html>
{% extends 'admin/master.html' %}
{% block head %}
<link href="{{ url_for('admin.static', filename='chosen/chosen.css') }}" rel="stylesheet">
{% endblock %}
{% block body %}
<form action="" method="POST" class="form-horizontal">
<form action="" method="POST" class="form-horizontal"{% if form.has_file_field %} enctype="multipart/form-data"{% endif %}>
<fieldset>
{{ form.csrf }}
{% for f in form if f.label.text != 'Csrf' %}
{% if f.name in form.errors %}
<div class="control-group error">
{% else %}
<div class="control-group">
{% endif %}
<div class="control-group{% if f.errors %} error{% endif %}">
{{ f.label(class='control-label') }}
<div class="controls">
<div>
{{ f }}
</div>
{% if f.name in form.errors %}
{% if f.errors %}
<ul>
{% for e in form.errors[f.name] %}
{% for e in f.errors %}
<li>{{ e }}</li>
{% endfor %}
</ul>
......@@ -35,3 +35,9 @@
</fieldset>
</form>
{% endblock %}
{% block tail %}
<script>
$("select").chosen({allow_single_deselect: true});
</script>
{% endblock %}
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