Commit 7a8433ae authored by bryhoyt's avatar bryhoyt

Merge pull request #5 from mrjoes/master

Merge from central repo into bryhoyt's fork
parents 48ebb0fc 0b81e59d
......@@ -84,6 +84,10 @@ if __name__ == '__main__':
admin.add_link(NotAuthenticatedMenuLink(name='Login',
endpoint='login_view'))
# Add links with categories
admin.add_link(MenuLink(name='Google', category='Links', url='http://www.google.com/'))
admin.add_link(MenuLink(name='Mozilla', category='Links', url='http://mozilla.org/'))
# Add logout link by endpoint
admin.add_link(AuthenticatedMenuLink(name='Logout',
endpoint='logout_view'))
......
from functools import wraps
from re import sub
from flask import Blueprint, render_template, url_for, abort, g
from flask import Blueprint, render_template, abort, g
from flask.ext.admin import babel
from flask.ext.admin._compat import with_metaclass
from flask.ext.admin import helpers as h
# For compatibility reasons import MenuLink
from flask.ext.admin.menu import MenuCategory, MenuView, MenuLink
def expose(url='/', methods=('GET',)):
"""
......@@ -211,7 +213,7 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
# If name is not povided, use capitalized endpoint name
if self.name is None:
self.name = self._prettify_name(self.__class__.__name__)
self.name = self._prettify_class_name(self.__class__.__name__)
# Create blueprint and register rules
self.blueprint = Blueprint(self.endpoint, __name__,
......@@ -253,14 +255,14 @@ class BaseView(with_metaclass(AdminViewMeta, BaseViewClass)):
return render_template(template, **kwargs)
def _prettify_name(self, name):
def _prettify_class_name(self, name):
"""
Prettify a class name by splitting the name on capitalized characters. So, 'MySuperClass' becomes 'My Super Class'
Split words in PascalCase string into separate words.
:param name:
String to prettify
"""
return sub(r'(?<=.)([A-Z])', r' \1', name)
return h.prettify_class_name(name)
def is_visible(self):
"""
......@@ -344,79 +346,6 @@ class AdminIndexView(BaseView):
return self.render(self._template)
class MenuItem(object):
"""
Simple menu tree hierarchy.
"""
def __init__(self, name, view=None):
self.name = name
self._view = view
self._children = []
self._children_urls = set()
self._cached_url = None
self.url = None
if view is not None:
self.url = view.url
def add_child(self, view):
self._children.append(view)
self._children_urls.add(view.url)
def get_url(self):
if self._view is None:
return None
if self._cached_url:
return self._cached_url
self._cached_url = url_for('%s.%s' % (self._view.endpoint, self._view._default_view))
return self._cached_url
def is_active(self, view):
if view == self._view:
return True
return view.url in self._children_urls
def is_visible(self):
if self._view is None:
return False
return self._view.is_visible()
def is_accessible(self):
if self._view is None:
return False
return self._view.is_accessible()
def is_category(self):
return self._view is None
def get_children(self):
return [c for c in self._children if c.is_accessible() and c.is_visible()]
class MenuLink(object):
"""
Additional menu links.
"""
def __init__(self, name, url=None, endpoint=None):
self.name = name
self.url = url
self.endpoint = endpoint
def get_url(self):
return self.url or url_for(self.endpoint)
def is_visible(self):
return True
def is_accessible(self):
return True
class Admin(object):
"""
Collection of the admin views. Also manages menu structure.
......@@ -493,7 +422,8 @@ class Admin(object):
# If app was provided in constructor, register view with Flask app
if self.app is not None:
self.app.register_blueprint(view.create_blueprint(self))
self._add_view_to_menu(view)
self._add_view_to_menu(view)
def add_link(self, link):
"""
......@@ -502,26 +432,33 @@ class Admin(object):
:param link:
Link to add.
"""
self._menu_links.append(link)
if link.category:
self._add_menu_item(link, link.category)
else:
self._menu_links.append(link)
def _add_view_to_menu(self, view):
def _add_menu_item(self, menu_item, target_category):
"""
Add a view to the menu tree
:param view:
View to add
"""
if view.category:
category = self._menu_categories.get(view.category)
if target_category:
category = self._menu_categories.get(target_category)
if category is None:
category = MenuItem(view.category)
self._menu_categories[view.category] = category
category = MenuCategory(target_category)
self._menu_categories[target_category] = category
self._menu.append(category)
category.add_child(MenuItem(view.name, view))
category.add_child(menu_item)
else:
self._menu.append(MenuItem(view.name, view))
self._menu.append(menu_item)
def _add_view_to_menu(self, view):
self._add_menu_item(MenuView(view.name, view), view.category)
def init_app(self, app):
"""
......@@ -537,7 +474,6 @@ class Admin(object):
# Register views
for view in self._views:
app.register_blueprint(view.create_blueprint(self))
self._add_view_to_menu(view)
def _init_extension(self):
if not hasattr(self.app, 'extensions'):
......
......@@ -36,14 +36,15 @@ class InlineModelFormList(InlineFieldList):
def display_row_controls(self, field):
return field.get_pk() is not None
def process(self, formdata, data=None):
if not formdata:
attr = getattr(self.model, self.prop)
data = self.model.select().where(attr == data).execute()
else:
data = None
return super(InlineModelFormList, self).process(formdata, data)
# *** bryhoyt removed def process() entirely, because I believe it was buggy
# (but worked because another part of the code had a complimentary bug)
# and I'm not sure why it was necessary anyway.
# If we want it back in, we need to fix the following bogus query:
# self.model.select().where(attr == data).execute() # `data` is not an ID, and only happened to be so because we patched it in in .contribute() below
#
# For reference:
# .process() introduced in https://github.com/mrjoes/flask-admin/commit/2845e4b28cb40b25e2bf544b327f6202dc7e5709
# Fixed, brokenly I think, in https://github.com/mrjoes/flask-admin/commit/4383eef3ce7eb01878f086928f8773adb9de79f8#diff-f87e7cd76fb9bc48c8681b24f238fb13R30
def populate_obj(self, obj, name):
pass
......@@ -234,7 +235,8 @@ class InlineModelConverter(InlineModelConverterBase):
allow_pk=True,
converter=converter)
prop_name = 'fa_%s' % model.__name__
prop_name = reverse_field.related_name
label = self.get_label(info, prop_name)
......@@ -246,10 +248,6 @@ class InlineModelConverter(InlineModelConverterBase):
info,
label=label or info.model.__name__))
setattr(field.rel_model,
prop_name,
property(lambda self: self.id))
return form_class
......
......@@ -116,7 +116,7 @@ class ModelView(BaseModelView):
class MyModelView(ModelView):
inline_models = ((Post, dict(form_label='Hello')))
2. Using target model name with `fa_` prefis:
2. Using field's related_name:
class Model1(Base):
# ...
......@@ -124,11 +124,11 @@ class ModelView(BaseModelView):
class Model2(Base):
# ...
pass
model1 = ForeignKeyField(related_name="model_twos")
class MyModel1View(Base):
inline_models = (Model2,)
column_labels = {'fa_Model2': 'Hello'}
column_labels = {'model_ones': 'Hello'}
"""
def __init__(self, model, name=None,
......
......@@ -32,6 +32,7 @@ def get_primary_key(model):
pks.append(get_column_for_current_model(p).key)
else:
pks.append(p.key)
if len(pks) == 1:
return pks[0]
elif len(pks) > 1:
......
......@@ -106,7 +106,7 @@ class ImageUploadInput(object):
if field.url_relative_path:
filename = urljoin(field.url_relative_path, filename)
return url_for(field.endpoint, filename=field.data)
return url_for(field.endpoint, filename=filename)
# Fields
......
from re import sub
from jinja2 import contextfunction
from flask import g, request
from wtforms.validators import DataRequired, InputRequired
......@@ -85,3 +86,13 @@ def get_render_ctx():
Get view template context.
"""
return getattr(g, '_admin_render_ctx', None)
def prettify_class_name(name):
"""
Split words in PascalCase string into separate words.
:param name:
String to split
"""
return sub(r'(?<=.)([A-Z])', r' \1', name)
from flask import url_for
class BaseMenu(object):
"""
Base menu item
"""
def __init__(self, name):
self.name = name
self._children = []
def add_child(self, menu):
self._children.append(menu)
def get_url(self):
raise NotImplemented()
def is_category(self):
return False
def is_active(self, view):
for c in self._children:
if c.is_active(view):
return True
return False
def is_visible(self):
return True
def is_accessible(self):
return True
def get_children(self):
return [c for c in self._children if c.is_accessible() and c.is_visible()]
class MenuCategory(BaseMenu):
"""
Menu category item.
"""
def get_url(self):
return None
def is_category(self):
return True
def is_visible(self):
for c in self._children:
if c.is_visible():
return True
return False
def is_accessible(self):
for c in self._children:
if c.is_accessible():
return True
return False
class MenuView(BaseMenu):
"""
Admin view menu item
"""
def __init__(self, name, view=None):
super(MenuView, self).__init__(name)
self._view = view
self._cached_url = None
def get_url(self):
if self._view is None:
return None
if self._cached_url:
return self._cached_url
self._cached_url = url_for('%s.%s' % (self._view.endpoint, self._view._default_view))
return self._cached_url
def is_active(self, view):
if view == self._view:
return True
return super(MenuView, self).is_active(view)
def is_visible(self):
if self._view is None:
return False
return self._view.is_visible()
def is_accessible(self):
if self._view is None:
return False
return self._view.is_accessible()
class MenuLink(BaseMenu):
"""
Link item
"""
def __init__(self, name, url=None, endpoint=None, category=None):
super(MenuLink, self).__init__(name)
self.category = category
self.url = url
self.endpoint = endpoint
def get_url(self):
return self.url or url_for(self.endpoint)
......@@ -515,7 +515,7 @@ class BaseModelView(BaseView, ActionsMixin):
# If name not provided, it is model name
if name is None:
name = '%s' % self.prettify_name(model.__name__)
name = '%s' % self._prettify_class_name(model.__name__)
# If endpoint not provided, it is model name + 'view'
if endpoint is None:
......@@ -640,7 +640,7 @@ class BaseModelView(BaseView, ActionsMixin):
if self.column_labels and field in self.column_labels:
return self.column_labels[field]
else:
return self.prettify_name(field)
return self._prettify_name(field)
def get_list_columns(self):
"""
......@@ -960,7 +960,7 @@ class BaseModelView(BaseView, ActionsMixin):
raise NotImplemented()
# Various helpers
def prettify_name(self, name):
def _prettify_name(self, name):
"""
Prettify pythonic variable name.
......
......@@ -262,11 +262,16 @@ def test_submenu():
eq_(admin._menu[1].name, 'Test')
eq_(len(admin._menu[1]._children), 2)
# Categories don't have URLs and they're not accessible
# Categories don't have URLs
eq_(admin._menu[1].get_url(), None)
eq_(admin._menu[1].is_accessible(), False)
eq_(len(admin._menu[1].get_children()), 1)
# Categories are only accessible if there is at least one accessible child
eq_(admin._menu[1].is_accessible(), True)
children = admin._menu[1].get_children()
eq_(len(children), 1)
ok_(children[0].is_accessible())
def test_delayed_init():
......@@ -354,3 +359,8 @@ def test_menu_links():
data = rv.data.decode('utf-8')
ok_('TestMenuLink1' in data)
ok_('TestMenuLink2' in data)
def check_class_name():
view = MockView()
eq_(view.name, 'Mock View')
......@@ -329,3 +329,11 @@ def test_custom_form():
eq_(view._edit_form_class, TestForm)
ok_(not hasattr(view._create_form_class, 'col1'))
def check_class_name():
class DummyView(MockModelView):
pass
view = DummyView(Model)
eq_(view.name, 'Dummy View')
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