Commit 48ebb0fc authored by bryhoyt's avatar bryhoyt

Merge pull request #4 from mrjoes/master

Merge latest main repo into my local
parents ee750e5c 9a87a0be
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
</ul> </ul>
<a href="http://github.com/mrjoes/flask-admin"><img style="position: fixed; top: 0; right: 0; border: 0;" <a href="http://github.com/mrjoes/flask-admin"><img style="position: fixed; top: 0; right: 0; border: 0;"
src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a> src="//s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
{{ f }} {{ f }}
{% if f.errors %} {% if f.errors %}
<ul> <ul>
{% for e in f.errrors %} {% for e in f.errors %}
<li>{{ e }}</li> <li>{{ e }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
......
import os import os
import os.path as op
from flask import Flask from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.sqlalchemy import SQLAlchemy
......
...@@ -7,7 +7,7 @@ import shutil ...@@ -7,7 +7,7 @@ import shutil
from operator import itemgetter from operator import itemgetter
from werkzeug import secure_filename from werkzeug import secure_filename
from flask import flash, url_for, redirect, abort, request from flask import flash, url_for, redirect, abort, request, send_file
from wtforms import fields, validators from wtforms import fields, validators
...@@ -64,13 +64,12 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -64,13 +64,12 @@ class FileAdmin(BaseView, ActionsMixin):
""" """
Simple file-management interface. Simple file-management interface.
Requires two parameters:
:param path: :param path:
Path to the directory which will be managed Path to the directory which will be managed
:param url: :param base_url:
Base URL for the directory. Will be used to generate Optional base URL for the directory. Will be used to generate
static links to the files. static links to the files. If not defined, a route will be created
to serve uploaded files.
Sample usage:: Sample usage::
...@@ -86,6 +85,11 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -86,6 +85,11 @@ class FileAdmin(BaseView, ActionsMixin):
Is file upload allowed. Is file upload allowed.
""" """
can_download = True
"""
Is file download allowed.
"""
can_delete = True can_delete = True
""" """
Is file deletion allowed. Is file deletion allowed.
...@@ -151,7 +155,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -151,7 +155,7 @@ class FileAdmin(BaseView, ActionsMixin):
Edit template Edit template
""" """
def __init__(self, base_path, base_url, def __init__(self, base_path, base_url=None,
name=None, category=None, endpoint=None, url=None, name=None, category=None, endpoint=None, url=None,
verify_path=True): verify_path=True):
""" """
...@@ -310,10 +314,10 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -310,10 +314,10 @@ class FileAdmin(BaseView, ActionsMixin):
Static file path Static file path
""" """
if self.is_file_editable(path): if self.is_file_editable(path):
return url_for(".edit", path=path) route = '.edit'
else: else:
base_url = self.get_base_url() route = '.download'
return urljoin(base_url, path) return url_for(route, path=path)
def _normalize_path(self, path): def _normalize_path(self, path):
""" """
...@@ -503,6 +507,27 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -503,6 +507,27 @@ class FileAdmin(BaseView, ActionsMixin):
return self.render(self.upload_template, form=form) return self.render(self.upload_template, form=form)
@expose('/download/<path:path>')
def download(self, path=None):
"""
Download view method.
:param path:
File path.
"""
if not self.can_download:
abort(404)
base_path, directory, path = self._normalize_path(path)
# backward compatibility with base_url
base_url = self.get_base_url()
if base_url:
base_url = urljoin(url_for('.index'), base_url)
return redirect(urljoin(base_url, path))
return send_file(directory)
@expose('/mkdir/', methods=('GET', 'POST')) @expose('/mkdir/', methods=('GET', 'POST'))
@expose('/mkdir/<path:path>', methods=('GET', 'POST')) @expose('/mkdir/<path:path>', methods=('GET', 'POST'))
def mkdir(self, path=None): def mkdir(self, path=None):
......
...@@ -107,7 +107,7 @@ class FilterConverter(filters.BaseFilterConverter): ...@@ -107,7 +107,7 @@ class FilterConverter(filters.BaseFilterConverter):
return None return None
@filters.convert('StringField') @filters.convert('StringField', 'EmailField')
def conv_string(self, column, name): def conv_string(self, column, name):
return [f(column, name) for f in self.strings] return [f(column, name) for f in self.strings]
......
from flask.ext.admin._compat import iteritems from flask.ext.admin._compat import iteritems
from flask.ext.admin.form import rules
from flask.ext.admin.model.form import InlineBaseFormAdmin from flask.ext.admin.model.form import InlineBaseFormAdmin
...@@ -9,13 +8,6 @@ class EmbeddedForm(InlineBaseFormAdmin): ...@@ -9,13 +8,6 @@ class EmbeddedForm(InlineBaseFormAdmin):
self._form_subdocuments = convert_subdocuments(getattr(self, 'form_subdocuments', {})) self._form_subdocuments = convert_subdocuments(getattr(self, 'form_subdocuments', {}))
form_rules = getattr(self, 'form_rules', None)
if form_rules:
self._form_rules = rules.RuleSet(self, form_rules)
else:
self._form_rules = None
def convert_subdocuments(values): def convert_subdocuments(values):
result = {} result = {}
......
...@@ -9,7 +9,9 @@ from wtforms.validators import ValidationError ...@@ -9,7 +9,9 @@ from wtforms.validators import ValidationError
from .tools import get_primary_key from .tools import get_primary_key
from flask.ext.admin._compat import text_type, string_types from flask.ext.admin._compat import text_type, string_types
from flask.ext.admin.form import FormOpts
from flask.ext.admin.model.fields import InlineFieldList, InlineModelFormField from flask.ext.admin.model.fields import InlineFieldList, InlineModelFormField
from flask.ext.admin.model.widgets import InlineFormWidget
try: try:
...@@ -186,7 +188,7 @@ class InlineModelFormList(InlineFieldList): ...@@ -186,7 +188,7 @@ class InlineModelFormList(InlineFieldList):
Form field type. Override to use custom field for each inline form Form field type. Override to use custom field for each inline form
""" """
def __init__(self, form, session, model, prop, inline_view, **kwargs): def __init__(self, form, session, model, prop, inline_view, form_widget=None, **kwargs):
""" """
Default constructor. Default constructor.
...@@ -209,7 +211,16 @@ class InlineModelFormList(InlineFieldList): ...@@ -209,7 +211,16 @@ class InlineModelFormList(InlineFieldList):
self._pk = get_primary_key(model) self._pk = get_primary_key(model)
super(InlineModelFormList, self).__init__(self.form_field_type(form, self._pk), **kwargs) # Generate inline form field
if form_widget is None:
form_opts = FormOpts(widget_args=getattr(inline_view, 'form_widget_args', None),
form_rules=inline_view._form_rules)
form_widget = InlineFormWidget(form_opts)
form_field = self.form_field_type(form, self._pk, widget=form_widget)
super(InlineModelFormList, self).__init__(form_field, **kwargs)
def display_row_controls(self, field): def display_row_controls(self, field):
return field.get_pk() is not None return field.get_pk() is not None
......
...@@ -31,7 +31,7 @@ def get_primary_key(model): ...@@ -31,7 +31,7 @@ def get_primary_key(model):
if is_inherited_primary_key(p): if is_inherited_primary_key(p):
pks.append(get_column_for_current_model(p).key) pks.append(get_column_for_current_model(p).key)
else: else:
pks.append(p.columns[0].key) pks.append(p.key)
if len(pks) == 1: if len(pks) == 1:
return pks[0] return pks[0]
elif len(pks) > 1: elif len(pks) > 1:
......
import inspect import inspect
from flask.ext.admin.form import BaseForm from flask.ext.admin.form import BaseForm, rules
from flask.ext.admin._compat import iteritems from flask.ext.admin._compat import iteritems
...@@ -37,6 +37,14 @@ class InlineBaseFormAdmin(object): ...@@ -37,6 +37,14 @@ class InlineBaseFormAdmin(object):
for k, v in iteritems(kwargs): for k, v in iteritems(kwargs):
setattr(self, k, v) setattr(self, k, v)
# Convert form rules
form_rules = getattr(self, 'form_rules', None)
if form_rules:
self._form_rules = rules.RuleSet(self, form_rules)
else:
self._form_rules = None
def get_form(self): def get_form(self):
""" """
If you want to use completely custom form for inline field, you can override If you want to use completely custom form for inline field, you can override
......
...@@ -10,7 +10,7 @@ var AdminFilters = function(element, filters_element, filters_by_group) { ...@@ -10,7 +10,7 @@ var AdminFilters = function(element, filters_element, filters_by_group) {
function changeOperation() { function changeOperation() {
var $row = $(this).closest('tr'); var $row = $(this).closest('tr');
var $el = $('.filter-val', $row); var $el = $('.filter-val:input', $row);
var count = getCount($el.attr('name')); var count = getCount($el.attr('name'));
$el.attr('name', 'flt' + count + '_' + $(this).val()); $el.attr('name', 'flt' + count + '_' + $(this).val());
$('button', $root).show(); $('button', $root).show();
......
...@@ -83,7 +83,11 @@ ...@@ -83,7 +83,11 @@
</td> </td>
{% else %} {% else %}
<td> <td>
{% if admin_view.can_download %}
<a href="{{ get_file_url(path)|safe }}">{{ name }}</a> <a href="{{ get_file_url(path)|safe }}">{{ name }}</a>
{% else %}
{{ name }}
{% endif %}
</td> </td>
<td> <td>
{{ size }} {{ size }}
......
...@@ -79,12 +79,13 @@ ...@@ -79,12 +79,13 @@
{% set direct_error = h.is_field_error(field.errors) %} {% set direct_error = h.is_field_error(field.errors) %}
<div class="control-group{{ ' error' if direct_error else '' }}"> <div class="control-group{{ ' error' if direct_error else '' }}">
<div class="control-label"> <div class="control-label">
{{ field.label.text }} <label for="{{ field.id }}">{{ field.label.text }}
{% if h.is_required_form_field(field) %} {% if h.is_required_form_field(field) %}
<strong style="color: red">&#42;</strong> <strong style="color: red">&#42;</strong>
{% else %} {%- else -%}
&nbsp; &nbsp;
{% endif %} {%- endif %}
</label>
</div> </div>
<div class="controls"> <div class="controls">
<div> <div>
......
...@@ -4,10 +4,16 @@ ...@@ -4,10 +4,16 @@
{% for subfield in field %} {% for subfield in field %}
<div id="{{ subfield.id }}" class="fa-inline-field"> <div id="{{ subfield.id }}" class="fa-inline-field">
{%- if not check or check(subfield) %} {%- if not check or check(subfield) %}
<div class="fa-inline-field-control"> {% if subfield.get_pk and subfield.get_pk() %}
<input type="checkbox" name="del-{{ subfield.id }}" id="del-{{ subfield.id }}" /> <div class="fa-inline-field-control">
<label for="del-{{ subfield.id }}" style="display: inline">{{ _gettext('Delete?') }}</label> <input type="checkbox" name="del-{{ subfield.id }}" id="del-{{ subfield.id }}" />
</div> <label for="del-{{ subfield.id }}" style="display: inline">{{ _gettext('Delete?') }}</label>
</div>
{% else %}
<div class="fa-inline-field-control">
<a href="javascript:void(0)" class="fa-remove-field"><i class="icon-remove"></i></a>
</div>
{% endif %}
{%- endif -%} {%- endif -%}
{{ render(subfield) }} {{ render(subfield) }}
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
</li> </li>
{% if admin_view.can_create %} {% if admin_view.can_create %}
<li> <li>
<a href="{{ url_for('.create_view', url=return_url) }}">{{ _gettext('Create') }}</a> <a href="{{ url_for('.create_view', url=return_url) }}" title="{{ _gettext('Create new record') }}">{{ _gettext('Create') }}</a>
</li> </li>
{% endif %} {% endif %}
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
{% block list_header scoped %} {% block list_header scoped %}
{% if actions %} {% if actions %}
<th class="span1"> <th class="span1">
<input type="checkbox" name="rowtoggle" class="action-rowtoggle" /> <input type="checkbox" name="rowtoggle" class="action-rowtoggle" title="{{ _gettext('Select all records') }}" />
</th> </th>
{% endif %} {% endif %}
{% block list_row_actions_header %} {% block list_row_actions_header %}
...@@ -62,10 +62,10 @@ ...@@ -62,10 +62,10 @@
{% endblock %} {% endblock %}
{% set column = 0 %} {% set column = 0 %}
{% for c, name in list_columns %} {% for c, name in list_columns %}
<th> <th class="column-header">
{% if admin_view.is_sortable(c) %} {% if admin_view.is_sortable(c) %}
{% if sort_column == column %} {% if sort_column == column %}
<a href="{{ sort_url(column, True) }}"> <a href="{{ sort_url(column, True) }}" title="{{ _gettext('Sort by %(name)s', name=name) }}">
{{ name }} {{ name }}
{% if sort_desc %} {% if sort_desc %}
<i class="icon-chevron-up"></i> <i class="icon-chevron-up"></i>
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
{% endif %} {% endif %}
</a> </a>
{% else %} {% else %}
<a href="{{ sort_url(column) }}">{{ name }}</a> <a href="{{ sort_url(column) }}" title="{{ _gettext('Sort by %(name)s', name=name) }}">{{ name }}</a>
{% endif %} {% endif %}
{% else %} {% else %}
{{ name }} {{ name }}
...@@ -96,13 +96,13 @@ ...@@ -96,13 +96,13 @@
{% block list_row scoped %} {% block list_row scoped %}
{% if actions %} {% if actions %}
<td> <td>
<input type="checkbox" name="rowid" class="action-checkbox" value="{{ get_pk_value(row) }}" /> <input type="checkbox" name="rowid" class="action-checkbox" value="{{ get_pk_value(row) }}" title="{{ _gettext('Select record') }}" />
</td> </td>
{% endif %} {% endif %}
<td> <td>
{% block list_row_actions scoped %} {% block list_row_actions scoped %}
{%- if admin_view.can_edit -%} {%- if admin_view.can_edit -%}
<a class="icon" href="{{ url_for('.edit_view', id=get_pk_value(row), url=return_url) }}"> <a class="icon" href="{{ url_for('.edit_view', id=get_pk_value(row), url=return_url) }}" title="Edit record">
<i class="icon-pencil"></i> <i class="icon-pencil"></i>
</a> </a>
{%- endif -%} {%- endif -%}
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
{% if csrf_token %} {% if csrf_token %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% endif %} {% endif %}
<button onclick="return confirm('{{ _gettext('You sure you want to delete this item?') }}');"> <button onclick="return confirm('{{ _gettext('You sure you want to delete this item?') }}');" title="Delete record">
<i class="icon-trash"></i> <i class="icon-trash"></i>
</button> </button>
</form> </form>
......
...@@ -139,3 +139,22 @@ def test_rule_inlinefieldlist(): ...@@ -139,3 +139,22 @@ def test_rule_inlinefieldlist():
rv = client.get('/admin/model1view/new/') rv = client.get('/admin/model1view/new/')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
def test_inline_model_rules():
app, db, admin = setup()
Model1, Model2 = create_models(db)
db.create_all()
view = CustomModelView(Model1, db.session,
inline_models=[(Model2, dict(form_rules=('string_field', 'bool_field')))])
admin.add_view(view)
client = app.test_client()
rv = client.get('/admin/model1view/new/')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('int_field' not in data)
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