Skip to content

Commit

Permalink
More complex inline model field example for SQLAlchemy
Browse files Browse the repository at this point in the history
  • Loading branch information
mrjoes committed Jul 4, 2013
1 parent b9eb2cc commit f613262
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ venv
*.sublime-*
.coverage
__pycache__
examples/sqla-inline/static
130 changes: 130 additions & 0 deletions examples/sqla-inline/simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import os
import os.path as op

from werkzeug import secure_filename
from sqlalchemy import event

from flask import Flask, request, render_template
from flask.ext.sqlalchemy import SQLAlchemy

from wtforms import fields

from flask.ext import admin
from flask.ext.admin.form import RenderTemplateWidget
from flask.ext.admin.model.form import InlineFormAdmin
from flask.ext.admin.contrib.sqlamodel import ModelView
from flask.ext.admin.contrib.sqlamodel.form import InlineModelConverter
from flask.ext.admin.contrib.sqlamodel.fields import InlineModelFormList

# Create application
app = Flask(__name__)

# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'

# Create in-memory database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.sqlite'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)

# Figure out base upload path
base_path = op.join(op.dirname(__file__), 'static')


# Create models
class Location(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(64))


class LocationImage(db.Model):
id = db.Column(db.Integer, primary_key=True)
alt = db.Column(db.Unicode(128))
path = db.Column(db.String(64))

location_id = db.Column(db.Integer, db.ForeignKey(Location.id))
location = db.relation(Location, backref='images')


# Register after_delete handler which will delete image file after model gets deleted
@event.listens_for(LocationImage, 'after_delete')
def _handle_image_delete(mapper, conn, target):
try:
if target.path:
os.remove(op.join(base_path, target.path))
except:
pass


# This widget uses custom template for inline field list
class CustomInlineFieldListWidget(RenderTemplateWidget):
def __init__(self):
super(CustomInlineFieldListWidget, self).__init__('field_list.html')


# This InlineModelFormList will use our custom widget
class CustomInlineModelFormList(InlineModelFormList):
widget = CustomInlineFieldListWidget()


# Create custom InlineModelConverter and tell it to use our InlineModelFormList
class CustomInlineModelConverter(InlineModelConverter):
inline_field_list_type = CustomInlineModelFormList


# Customized inline form handler
class InlineModelForm(InlineFormAdmin):
form_excluded_columns = ('path',)

form_label = 'Image'

def __init__(self):
return super(InlineModelForm, self).__init__(LocationImage)

def postprocess_form(self, form_class):
form_class.upload = fields.FileField('Image')
return form_class

def on_model_change(self, form, model):
file_data = request.files.get(form.upload.name)

if file_data:
model.path = secure_filename(file_data.filename)
file_data.save(op.join(base_path, model.path))


# Administrative class
class LocationAdmin(ModelView):
inline_model_form_converter = CustomInlineModelConverter

inline_models = (InlineModelForm(),)

def __init__(self):
super(LocationAdmin, self).__init__(Location, db.session, name='Locations')


# Simple page to show images
@app.route('/')
def index():
locations = db.session.query(Location).all()
return render_template('locations.html', locations=locations)


if __name__ == '__main__':
# Create upload directory
try:
os.mkdir(base_path)
except OSError:
pass

# Create admin
admin = admin.Admin(app, 'Inline Fun')

# Add views
admin.add_view(LocationAdmin())

# Create DB
db.create_all()

# Start app
app.run(debug=True)
13 changes: 13 additions & 0 deletions examples/sqla-inline/templates/field_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% import 'admin/model/inline_list_base.html' as base with context %}

{% macro render_field(field) %}
{% set model = field.object_data %}
{% if model and model.path %}
{{ field.form.id }}
<img src="{{ url_for('static', filename=model.path) }}" style="max-width: 300px;"></img>
{% else %}
{{ field }}
{% endif %}
{% endmacro %}

{{ base.render_inline_fields(field, template, render_field) }}
12 changes: 12 additions & 0 deletions examples/sqla-inline/templates/locations.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>
<body>
{% for loc in locations %}
<h2>{{ loc.name }}</h2>
{% for img in loc.images %}
<img src="{{ url_for('static', filename=img.path) }}" alt="{{ img.alt }}" style="max-width: 300px"></img>
{% endfor %}
<hr/>
{% endfor %}
<a href="/admin">Open admin to upload some images.</a>
</body>
</html>
17 changes: 10 additions & 7 deletions flask_admin/contrib/peeweemodel/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,16 @@ def contribute(self, converter, model, form_class, inline_model):
exclude = ignore

# Create field
child_form = model_form(info.model,
base_class=form.BaseForm,
only=info.form_columns,
exclude=exclude,
field_args=info.form_args,
allow_pk=True,
converter=converter)
child_form = info.get_form()

if child_form is None:
child_form = model_form(info.model,
base_class=form.BaseForm,
only=info.form_columns,
exclude=exclude,
field_args=info.form_args,
allow_pk=True,
converter=converter)

prop_name = 'fa_%s' % model.__name__

Expand Down
28 changes: 16 additions & 12 deletions flask_admin/contrib/sqlamodel/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,12 @@ def _resolve_prop(prop):

# Get list of fields and generate form
def get_form(model, converter,
base_class=form.BaseForm,
only=None, exclude=None,
field_args=None,
hidden_pk=False,
ignore_hidden=True):
base_class=form.BaseForm,
only=None,
exclude=None,
field_args=None,
hidden_pk=False,
ignore_hidden=True):
"""
Generate form from the model.
Expand Down Expand Up @@ -473,17 +474,20 @@ def contribute(self, converter, model, form_class, inline_model):
ignore = [reverse_prop.key]

if info.form_excluded_columns:
exclude = ignore + info.form_excluded_columns
exclude = ignore + list(info.form_excluded_columns)
else:
exclude = ignore

# Create form
child_form = get_form(info.model,
converter,
only=info.form_columns,
exclude=exclude,
field_args=info.form_args,
hidden_pk=True)
child_form = info.get_form()

if child_form is None:
child_form = get_form(info.model,
converter,
only=info.form_columns,
exclude=exclude,
field_args=info.form_args,
hidden_pk=True)

# Post-process form
child_form = info.postprocess_form(child_form)
Expand Down
12 changes: 6 additions & 6 deletions flask_admin/contrib/sqlamodel/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,9 @@ def scaffold_form(self):
"""
converter = self.model_form_converter(self.session, self)
form_class = form.get_form(self.model, converter,
only=self.form_columns,
exclude=self.form_excluded_columns,
field_args=self.form_args)
only=self.form_columns,
exclude=self.form_excluded_columns,
field_args=self.form_args)

if self.inline_models:
form_class = self.scaffold_inline_form_models(form_class)
Expand All @@ -534,9 +534,9 @@ def scaffold_inline_form_models(self, form_class):

for m in self.inline_models:
form_class = inline_converter.contribute(converter,
self.model,
form_class,
m)
self.model,
form_class,
m)

return form_class

Expand Down
7 changes: 7 additions & 0 deletions flask_admin/model/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ def __init__(self, model, **kwargs):
for k, v in iteritems(kwargs):
setattr(self, k, v)

def get_form(self):
"""
If you want to use completely custom form for inline field, you can override
Flask-Admin form generation logic by overriding this method and returning your form.
"""
return None

def postprocess_form(self, form_class):
"""
Post process form. Use this to contribute fields.
Expand Down

0 comments on commit f613262

Please sign in to comment.