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 @@
</ul>
<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 @@
{{ f }}
{% if f.errors %}
<ul>
{% for e in f.errrors %}
{% for e in f.errors %}
<li>{{ e }}</li>
{% endfor %}
</ul>
......
import os
import os.path as op
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
......
......@@ -7,7 +7,7 @@ import shutil
from operator import itemgetter
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
......@@ -64,13 +64,12 @@ class FileAdmin(BaseView, ActionsMixin):
"""
Simple file-management interface.
Requires two parameters:
:param path:
Path to the directory which will be managed
:param url:
Base URL for the directory. Will be used to generate
static links to the files.
:param base_url:
Optional base URL for the directory. Will be used to generate
static links to the files. If not defined, a route will be created
to serve uploaded files.
Sample usage::
......@@ -86,6 +85,11 @@ class FileAdmin(BaseView, ActionsMixin):
Is file upload allowed.
"""
can_download = True
"""
Is file download allowed.
"""
can_delete = True
"""
Is file deletion allowed.
......@@ -151,7 +155,7 @@ class FileAdmin(BaseView, ActionsMixin):
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,
verify_path=True):
"""
......@@ -310,10 +314,10 @@ class FileAdmin(BaseView, ActionsMixin):
Static file path
"""
if self.is_file_editable(path):
return url_for(".edit", path=path)
route = '.edit'
else:
base_url = self.get_base_url()
return urljoin(base_url, path)
route = '.download'
return url_for(route, path=path)
def _normalize_path(self, path):
"""
......@@ -503,6 +507,27 @@ class FileAdmin(BaseView, ActionsMixin):
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/<path:path>', methods=('GET', 'POST'))
def mkdir(self, path=None):
......
......@@ -107,7 +107,7 @@ class FilterConverter(filters.BaseFilterConverter):
return None
@filters.convert('StringField')
@filters.convert('StringField', 'EmailField')
def conv_string(self, column, name):
return [f(column, name) for f in self.strings]
......
from flask.ext.admin._compat import iteritems
from flask.ext.admin.form import rules
from flask.ext.admin.model.form import InlineBaseFormAdmin
......@@ -9,13 +8,6 @@ class EmbeddedForm(InlineBaseFormAdmin):
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):
result = {}
......
......@@ -9,7 +9,9 @@ from wtforms.validators import ValidationError
from .tools import get_primary_key
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.widgets import InlineFormWidget
try:
......@@ -186,7 +188,7 @@ class InlineModelFormList(InlineFieldList):
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.
......@@ -209,7 +211,16 @@ class InlineModelFormList(InlineFieldList):
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):
return field.get_pk() is not None
......
......@@ -31,7 +31,7 @@ def get_primary_key(model):
if is_inherited_primary_key(p):
pks.append(get_column_for_current_model(p).key)
else:
pks.append(p.columns[0].key)
pks.append(p.key)
if len(pks) == 1:
return pks[0]
elif len(pks) > 1:
......
import inspect
from flask.ext.admin.form import BaseForm
from flask.ext.admin.form import BaseForm, rules
from flask.ext.admin._compat import iteritems
......@@ -37,6 +37,14 @@ class InlineBaseFormAdmin(object):
for k, v in iteritems(kwargs):
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):
"""
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) {
function changeOperation() {
var $row = $(this).closest('tr');
var $el = $('.filter-val', $row);
var $el = $('.filter-val:input', $row);
var count = getCount($el.attr('name'));
$el.attr('name', 'flt' + count + '_' + $(this).val());
$('button', $root).show();
......
......@@ -83,7 +83,11 @@
</td>
{% else %}
<td>
{% if admin_view.can_download %}
<a href="{{ get_file_url(path)|safe }}">{{ name }}</a>
{% else %}
{{ name }}
{% endif %}
</td>
<td>
{{ size }}
......
......@@ -79,12 +79,13 @@
{% set direct_error = h.is_field_error(field.errors) %}
<div class="control-group{{ ' error' if direct_error else '' }}">
<div class="control-label">
{{ field.label.text }}
<label for="{{ field.id }}">{{ field.label.text }}
{% if h.is_required_form_field(field) %}
<strong style="color: red">&#42;</strong>
{% else %}
{%- else -%}
&nbsp;
{% endif %}
{%- endif %}
</label>
</div>
<div class="controls">
<div>
......
......@@ -4,10 +4,16 @@
{% for subfield in field %}
<div id="{{ subfield.id }}" class="fa-inline-field">
{%- if not check or check(subfield) %}
{% if subfield.get_pk and subfield.get_pk() %}
<div class="fa-inline-field-control">
<input type="checkbox" name="del-{{ subfield.id }}" id="del-{{ subfield.id }}" />
<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 -%}
{{ render(subfield) }}
......
......@@ -18,7 +18,7 @@
</li>
{% if admin_view.can_create %}
<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>
{% endif %}
......@@ -54,7 +54,7 @@
{% block list_header scoped %}
{% if actions %}
<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>
{% endif %}
{% block list_row_actions_header %}
......@@ -62,10 +62,10 @@
{% endblock %}
{% set column = 0 %}
{% for c, name in list_columns %}
<th>
<th class="column-header">
{% if admin_view.is_sortable(c) %}
{% 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 }}
{% if sort_desc %}
<i class="icon-chevron-up"></i>
......@@ -74,7 +74,7 @@
{% endif %}
</a>
{% else %}
<a href="{{ sort_url(column) }}">{{ name }}</a>
<a href="{{ sort_url(column) }}" title="{{ _gettext('Sort by %(name)s', name=name) }}">{{ name }}</a>
{% endif %}
{% else %}
{{ name }}
......@@ -96,13 +96,13 @@
{% block list_row scoped %}
{% if actions %}
<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>
{% endif %}
<td>
{% block list_row_actions scoped %}
{%- 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>
</a>
{%- endif -%}
......@@ -111,7 +111,7 @@
{% if csrf_token %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% 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>
</button>
</form>
......
......@@ -139,3 +139,22 @@ def test_rule_inlinefieldlist():
rv = client.get('/admin/model1view/new/')
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