Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: mrjoes/flask-admin
base: 33b471534b
...
head fork: mrjoes/flask-admin
compare: 2845e4b28c
Checking mergeability… Don't worry, you can still create the pull request.
  • 2 commits
  • 14 files changed
  • 0 commit comments
  • 1 contributor
View
21 examples/peewee/simple.py
@@ -25,6 +25,16 @@ def __unicode__(self):
return self.username
+class UserInfo(BaseModel):
+ key = peewee.CharField(max_length=64)
+ value = peewee.CharField(max_length=64)
+
+ user = peewee.ForeignKeyField(User)
+
+ def __unicode__(self):
+ return '%s - %s' % (self.key, self.value)
+
+
class Post(BaseModel):
title = peewee.CharField(max_length=120)
text = peewee.TextField(null=False)
@@ -36,6 +46,10 @@ def __unicode__(self):
return self.title
+class UserAdmin(peeweemodel.ModelView):
+ inline_models = (UserInfo,)
+
+
class PostAdmin(peeweemodel.ModelView):
# Visible columns in the list view
#list_columns = ('title', 'user')
@@ -60,13 +74,18 @@ def index():
if __name__ == '__main__':
+ import logging
+ logging.basicConfig()
+ logging.getLogger().setLevel(logging.DEBUG)
+
admin = admin.Admin(app, 'Peewee Models')
- admin.add_view(peeweemodel.ModelView(User))
+ admin.add_view(UserAdmin(User))
admin.add_view(PostAdmin(Post))
try:
User.create_table()
+ UserInfo.create_table()
Post.create_table()
except:
pass
View
5 examples/sqla/simple.py
@@ -64,9 +64,6 @@ class UserInfo(db.Model):
key = db.Column(db.String(64), nullable=False)
value = db.Column(db.String(64))
- tag_id = db.Column(db.Integer(), db.ForeignKey(Tag.id), nullable=False)
- tags = db.relationship(Tag, backref='userinfos')
-
user_id = db.Column(db.Integer(), db.ForeignKey(User.id))
user = db.relationship(User, backref='info')
@@ -82,7 +79,7 @@ def index():
# Customized User model admin
class UserAdmin(sqlamodel.ModelView):
- inline_models = ('info',)
+ inline_models = (UserInfo,)
# Customized Post model admin
View
129 flask_admin/contrib/peeweemodel/form.py
@@ -1,10 +1,70 @@
from wtforms import fields
-from peewee import DateTimeField, DateField, TimeField
+from peewee import DateTimeField, DateField, TimeField, BaseModel, ForeignKeyField
-from wtfpeewee.orm import ModelConverter
+from wtfpeewee.orm import ModelConverter, model_form
from flask.ext.admin import form
+from flask.ext.admin.model.form import InlineFormAdmin
+from flask.ext.admin.model.fields import InlineModelFormField
+from flask.ext.admin.model.widgets import InlineFormListWidget
+
+from .tools import get_primary_key
+
+
+class InlineModelFormList(fields.FieldList):
+ widget = InlineFormListWidget()
+
+ def __init__(self, form, model, prop, **kwargs):
+ self.form = form
+ self.model = model
+ self.prop = prop
+
+ # TODO: Fix me
+ self._pk = get_primary_key(model)
+
+ super(InlineModelFormList, self).__init__(InlineModelFormField(form, self._pk), **kwargs)
+
+ def __call__(self, **kwargs):
+ return self.widget(self, template=self.form(), **kwargs)
+
+ def process(self, formdata, data=None):
+ if not formdata:
+ data = self.model.select().where(user=data).execute()
+ else:
+ data = None
+
+ return super(InlineModelFormList, self).process(formdata, data)
+
+ def populate_obj(self, obj, name):
+ pass
+
+ def save_related(self, obj):
+ model_id = getattr(obj, self._pk)
+
+ values = self.model.select().where(user=model_id).execute()
+
+ pk_map = dict((str(getattr(v, self._pk)), v) for v in values)
+
+ # Handle request data
+ for field in self.entries:
+ field_id = field.get_pk()
+
+ if field_id in pk_map:
+ model = pk_map[field_id]
+
+ if field.should_delete():
+ model.delete_instance(recursive=True)
+ continue
+ else:
+ model = self.model()
+
+ field.populate_obj(model, None)
+
+ # Force relation
+ setattr(model, self.prop, model_id)
+
+ model.save()
class CustomModelConverter(ModelConverter):
@@ -24,3 +84,68 @@ def handle_datetime(self, model, field, **kwargs):
def handle_time(self, model, field, **kwargs):
return field.name, form.TimeField(**kwargs)
+
+
+def contribute_inline(model, form_class, inline_models):
+ # Contribute columns
+ for p in inline_models:
+ # Figure out settings
+ if isinstance(p, tuple):
+ info = InlineFormAdmin(p[0], **p[1])
+ elif isinstance(p, InlineFormAdmin):
+ info = p
+ elif isinstance(p, BaseModel):
+ info = InlineFormAdmin(p)
+ else:
+ raise Exception('Unknown inline model admin: %s' % repr(p))
+
+ # Find property from target model to current model
+ reverse_field = None
+
+ for field in info.model._meta.get_fields():
+ field_type = type(field)
+
+ if field_type == ForeignKeyField:
+ if field.to == model:
+ reverse_field = field
+ break
+ else:
+ raise Exception('Cannot find reverse relation for model %s' % info.model)
+
+ # Remove reverse property from the list
+ ignore = [reverse_field.name]
+
+ if info.exclude:
+ exclude = ignore + info.exclude
+ else:
+ exclude = ignore
+
+ # Create field
+ converter = CustomModelConverter()
+ child_form = model_form(info.model,
+ base_class=form.BaseForm,
+ only=info.include,
+ exclude=exclude,
+ allow_pk=True,
+ converter=converter)
+
+ prop_name = 'fa_%s' % model.__name__
+
+ setattr(form_class,
+ prop_name,
+ InlineModelFormList(child_form,
+ info.model,
+ reverse_field.name,
+ label=info.model.__name__))
+
+ setattr(field.to,
+ prop_name,
+ property(lambda self: self.id))
+
+ return form_class
+
+
+def save_inline(form, model):
+ for _, f in form._fields.iteritems():
+ if f.type == 'InlineModelFormList':
+ f.save_related(model)
View
7 flask_admin/contrib/peeweemodel/tools.py
@@ -0,0 +1,7 @@
+from peewee import PrimaryKeyField
+
+
+def get_primary_key(model):
+ for n, f in model._meta.get_sorted_fields():
+ if type(f) == PrimaryKeyField:
+ return n
View
42 flask_admin/contrib/peeweemodel/view.py
@@ -9,7 +9,8 @@
from flask.ext.admin.actions import action
from flask.ext.admin.contrib.peeweemodel import filters
-from .form import CustomModelConverter
+from .form import CustomModelConverter, contribute_inline, save_inline
+from .tools import get_primary_key
class ModelView(BaseModelView):
@@ -49,6 +50,14 @@ class MyModelView(BaseModelView):
for your model.
"""
+ inline_models = None
+ """
+ Inline related-model editing for parent to child relation::
+
+ class MyModelView(ModelView):
+ inline_models = (Post,)
+ """
+
def __init__(self, model, name=None,
category=None, endpoint=None, url=None):
self._search_fields = []
@@ -64,11 +73,7 @@ def _get_model_fields(self, model=None):
return model._meta.get_sorted_fields()
def scaffold_pk(self):
- for n, f in self._get_model_fields():
- if type(f) == PrimaryKeyField:
- return n
-
- return None
+ return get_primary_key(self.model)
def get_pk_value(self, model):
return getattr(model, self._primary_key)
@@ -149,12 +154,17 @@ def is_valid_filter(self, filter):
return isinstance(filter, filters.BasePeeweeFilter)
def scaffold_form(self):
- return model_form(self.model,
- base_class=form.BaseForm,
- only=self.form_columns,
- exclude=self.excluded_form_columns,
- field_args=self.form_args,
- converter=CustomModelConverter())
+ form_class = model_form(self.model,
+ base_class=form.BaseForm,
+ only=self.form_columns,
+ exclude=self.excluded_form_columns,
+ field_args=self.form_args,
+ converter=CustomModelConverter())
+
+ if self.inline_models:
+ form_class = contribute_inline(self.model, form_class, self.inline_models)
+
+ return form_class
def _handle_join(self, query, field, joins):
if field.model != self.model:
@@ -237,6 +247,10 @@ def create_model(self, form):
model = self.model()
form.populate_obj(model)
model.save()
+
+ # For peewee have to save inline forms after model was saved
+ save_inline(form, model)
+
return True
except Exception, ex:
flash(gettext('Failed to create model. %(error)s', error=str(ex)), 'error')
@@ -252,6 +266,10 @@ def update_model(self, form, model):
try:
form.populate_obj(model)
model.save()
+
+ # For peewee have to save inline forms after model was saved
+ save_inline(form, model)
+
return True
except Exception, ex:
flash(gettext('Failed to update model. %(error)s', error=str(ex)), 'error')
View
44 flask_admin/contrib/sqlamodel/fields.py
@@ -4,14 +4,13 @@
import operator
from wtforms import widgets
-from wtforms.fields import SelectFieldBase, FormField, FieldList
+from wtforms.fields import SelectFieldBase, FieldList
from wtforms.validators import ValidationError
from .tools import get_primary_key
+from flask.ext.admin.model.fields import InlineModelFormField
from flask.ext.admin.model.widgets import InlineFormListWidget
-from flask import request
-
try:
from sqlalchemy.orm.util import identity_key
has_identity_key = True
@@ -181,46 +180,18 @@ def pre_validate(self, form):
raise ValidationError(self.gettext('Not a valid choice'))
-class InlineModelFormField(FormField):
- def __init__(self, form, model, **kwargs):
- super(InlineModelFormField, self).__init__(form, **kwargs)
-
- self.model = model
- self._pk = get_primary_key(model)
-
- self._should_delete = False
-
- def process(self, formdata, data=None):
- super(InlineModelFormField, self).process(formdata, data)
-
- # Grab delete key
- key = 'del-%s' % self.id
- if key in request.form:
- self._should_delete = True
-
- def should_delete(self):
- return self._should_delete
-
- def get_pk(self):
- return getattr(self.form, self._pk).data
-
- def populate_obj(self, obj, name):
- for name, field in self.form._fields.iteritems():
- if name != self._pk:
- field.populate_obj(obj, name)
-
-
class InlineModelFormList(FieldList):
widget = InlineFormListWidget()
- def __init__(self, form, session, model, **kwargs):
+ def __init__(self, form, session, model, prop, **kwargs):
self.form = form
self.session = session
self.model = model
+ self.prop = prop
self._pk = get_primary_key(model)
- super(InlineModelFormList, self).__init__(InlineModelFormField(form, model), **kwargs)
+ super(InlineModelFormList, self).__init__(InlineModelFormField(form, self._pk), **kwargs)
def __call__(self, **kwargs):
return self.widget(self, template=self.form(), **kwargs)
@@ -234,7 +205,7 @@ def populate_obj(self, obj, name):
# Create primary key map
pk_map = dict((str(getattr(v, self._pk)), v) for v in values)
- # Create fake object to work around wtforms limitations
+ # Handle request data
for field in self.entries:
field_id = field.get_pk()
@@ -251,9 +222,10 @@ def populate_obj(self, obj, name):
field.populate_obj(model, None)
# Force relation
- model.user = obj
+ setattr(self.model, self.prop, obj)
def get_pk_from_identity(obj):
+ # TODO: Remove me
cls, key = identity_key(instance=obj)
return u':'.join(unicode(x) for x in key)
View
56 flask_admin/contrib/sqlamodel/form.py
@@ -268,34 +268,42 @@ def contribute_inline(session, model, form_class, inline_models):
# Contribute columns
for p in inline_models:
- # Figure out
- if isinstance(p, basestring):
- info = InlineFormAdmin(p)
- elif isinstance(p, tuple):
+ # Figure out settings
+ if isinstance(p, tuple):
info = InlineFormAdmin(p[0], **p[1])
elif isinstance(p, InlineFormAdmin):
info = p
+ elif hasattr(p, '_sa_class_manager'):
+ info = InlineFormAdmin(p)
else:
raise Exception('Unknown inline model admin: %s' % repr(p))
- prop = mapper.get_property(info.field)
- if prop is None:
- raise Exception('Inline form property %s.%s was not found' % (model.__name__,
- info.field))
+ # Find property from target model to current model
+ target_mapper = info.model._sa_class_manager.mapper
+
+ reverse_prop = None
- if not hasattr(prop, 'direction'):
- raise Exception('Failed to convert inline admin %s - only one-to-many relations are supported' % info.field)
+ for prop in target_mapper.iterate_properties:
+ if hasattr(prop, 'direction') and prop.direction.name == 'MANYTOONE':
+ if prop.mapper.class_ == model:
+ reverse_prop = prop
+ break
+ else:
+ raise Exception('Cannot find reverse relation for model %s' % info.model)
- if prop.direction.name != 'ONETOMANY':
- raise Exception('Failed to convert inline admin %s - only one-to-many relations are supported' % info.field)
+ # Find forward property
+ forward_prop = None
- # Find reverse relationship (to exlude from the list)
- ignore = []
+ for prop in mapper.iterate_properties:
+ if hasattr(prop, 'direction') and prop.direction.name == 'ONETOMANY':
+ if prop.mapper.class_ == target_mapper.class_:
+ forward_prop = prop
+ break
+ else:
+ raise Exception('Cannot find forward relation for model %s' % info.model)
- for remote_prop in prop.mapper.iterate_properties:
- if hasattr(remote_prop, 'direction') and remote_prop.direction.name == 'MANYTOONE':
- if remote_prop.mapper.class_ == prop.parent.class_:
- ignore.append(remote_prop.key)
+ # Remove reverse property from the list
+ ignore = [reverse_prop.key]
if info.exclude:
exclude = ignore + info.exclude
@@ -303,14 +311,18 @@ def contribute_inline(session, model, form_class, inline_models):
exclude = ignore
# Create field
- remote_model = prop.mapper.class_
-
converter = AdminModelConverter(session, info)
- child_form = get_form(remote_model, converter,
+ child_form = get_form(info.model,
+ converter,
only=info.include,
exclude=exclude,
hidden_pk=True)
- setattr(form_class, p, InlineModelFormList(child_form, session, remote_model))
+ setattr(form_class,
+ forward_prop.key,
+ InlineModelFormList(child_form,
+ session,
+ info.model,
+ forward_prop.key))
return form_class
View
9 flask_admin/contrib/sqlamodel/view.py
@@ -126,13 +126,10 @@ class MyModelView(BaseModelView):
inline_models = None
"""
- Inline related-model editing for parent to child relation.
+ Inline related-model editing for parent to child relation::
- If you have child relation with name 'posts', you can generate inline
- administration interface by using this code::
-
- class MyModelView(BaseModelView):
- inline_models = ('posts',)
+ class MyModelView(ModelView):
+ inline_models = (Post,)
"""
def __init__(self, model, session,
View
16 flask_admin/form.py
@@ -1,6 +1,8 @@
import time
import datetime
+from flask.globals import _request_ctx_stack
+
from flask.ext import wtf
from wtforms import fields, widgets
@@ -125,3 +127,17 @@ class DateTimePickerWidget(widgets.TextInput):
def __call__(self, field, **kwargs):
kwargs['data-role'] = u'datetimepicker'
return super(DateTimePickerWidget, self).__call__(field, **kwargs)
+
+
+class RenderTemplateWidget(object):
+ def __init__(self, template):
+ self.template = template
+
+ def __call__(self, field, **kwargs):
+ ctx = _request_ctx_stack.top
+ jinja_env = ctx.app.jinja_env
+
+ kwargs['field'] = field
+
+ template = jinja_env.get_template(self.template)
+ return template.render(kwargs)
View
29 flask_admin/model/fields.py
@@ -0,0 +1,29 @@
+from wtforms.fields import FormField
+
+
+class InlineModelFormField(FormField):
+ def __init__(self, form, pk, **kwargs):
+ super(InlineModelFormField, self).__init__(form, **kwargs)
+
+ self._pk = pk
+ self._should_delete = False
+
+ def process(self, formdata, data=None):
+ super(InlineModelFormField, self).process(formdata, data)
+
+ # Grab delete key
+ if formdata:
+ key = 'del-%s' % self.id
+ if key in formdata:
+ self._should_delete = True
+
+ def should_delete(self):
+ return self._should_delete
+
+ def get_pk(self):
+ return getattr(self.form, self._pk).data
+
+ def populate_obj(self, obj, name):
+ for name, field in self.form._fields.iteritems():
+ if name != self._pk:
+ field.populate_obj(obj, name)
View
4 flask_admin/model/form.py
@@ -11,8 +11,8 @@ def _inner(func):
class InlineFormAdmin(object):
- def __init__(self, field, **kwargs):
- self.field = field
+ def __init__(self, model, **kwargs):
+ self.model = model
defaults = dict(include=None,
exclude=None)
View
18 flask_admin/model/widgets.py
@@ -1,20 +1,4 @@
-from flask.globals import _request_ctx_stack
-
-
-class RenderTemplateWidget(object):
- def __init__(self, template):
- self.template = template
-
- def __call__(self, field, **kwargs):
- ctx = _request_ctx_stack.top
- jinja_env = ctx.app.jinja_env
-
- print kwargs
-
- kwargs['field'] = field
-
- template = jinja_env.get_template(self.template)
- return template.render(kwargs)
+from flask.ext.admin.form import RenderTemplateWidget
class InlineFormListWidget(RenderTemplateWidget):
View
2  flask_admin/templates/admin/lib.html
@@ -74,7 +74,7 @@
{%- endmacro %}
{% macro render_form_fields(form, focus_set=False) %}
- {{ form.hidden_tag() }}
+ {{ form.hidden_tag() if form.hidden_tag is defined }}
{% for f in form if f.type != 'HiddenField' and f.type != 'CSRFTokenField' %}
<div class="control-group{% if f.errors %} error{% endif %}">
View
2  flask_admin/templates/admin/model/inline_form_list.html
@@ -29,5 +29,5 @@
</div>
{% endfor %}
</div>
- <a href="#" class="btn" onclick="faForm.addInlineModel('{{ field.id }}', '#{{ field.id }}-forms', {{ render_template(template)|tojson }});">Add {{ field.name }}</a>
+ <a href="#" class="btn" onclick="faForm.addInlineModel('{{ field.id }}', '#{{ field.id }}-forms', {{ render_template(template)|tojson }});">Add {{ field.label.text }}</a>
</div>

No commit comments for this range

Something went wrong with that request. Please try again.