23 |
24 |
25 |
26 |
27 | {% for item in admin_view.admin.menu() %}
28 | {% if item.is_category() %}
29 | {% set children = item.get_children() %}
30 | {% if children %}
31 | {% if item.is_active(admin_view) %}{% else %} {% endif %}
32 | {{ item.name }}
33 |
40 |
41 | {% endif %}
42 | {% else %}
43 | {% if item.is_accessible() %}
44 | {% if item.is_active(admin_view) %}{% else %} {% endif %}
45 | {{ item.name }}
46 |
47 | {% endif %}
48 | {% endif %}
49 | {% endfor %}
50 |
51 |
52 |
53 |
54 |
55 | {% with messages = get_flashed_messages(with_categories=True) %}
56 | {% if messages %}
57 | {% for category, m in messages %}
58 | {% if category == 'error' %}
59 |
60 | {% else %}
61 |
62 | {% endif %}
63 |
x
64 | {{ m }}
65 |
66 | {% endfor %}
67 | {% endif %}
68 | {% endwith %}
69 |
70 | {% block body %}{% endblock %}
71 |
72 |
73 | {% endblock %}
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | {% block tail %}
82 | {% endblock %}
83 |
84 |
85 |
--------------------------------------------------------------------------------
/flask_superadmin/model/backends/sqlalchemy/view.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.sql.expression import desc, literal_column, or_
2 |
3 | from orm import model_form, AdminModelConverter
4 |
5 | from flask_superadmin.model.base import BaseModelAdmin
6 | from sqlalchemy import schema
7 |
8 |
9 | class ModelAdmin(BaseModelAdmin):
10 | hide_backrefs = False
11 |
12 | def __init__(self, model, session=None,
13 | *args, **kwargs):
14 | super(ModelAdmin, self).__init__(model, *args, **kwargs)
15 | if session:
16 | self.session = session
17 | self._primary_key = self.pk_key
18 |
19 | @staticmethod
20 | def model_detect(model):
21 | return isinstance(getattr(model, 'metadata', None), schema.MetaData)
22 |
23 | def _get_model_iterator(self, model=None):
24 | """
25 | Return property iterator for the model
26 | """
27 | if model is None:
28 | model = self.model
29 |
30 | return model._sa_class_manager.mapper.iterate_properties
31 |
32 | @property
33 | def pk_key(self):
34 | for p in self._get_model_iterator():
35 | if hasattr(p, 'columns'):
36 | for c in p.columns:
37 | if c.primary_key:
38 | return p.key
39 |
40 | def allow_pk(self):
41 | return False
42 |
43 | def get_model_form(self):
44 | return model_form
45 |
46 | def get_converter(self):
47 | return AdminModelConverter(self)
48 |
49 | @property
50 | def query(self):
51 | return self.get_queryset() # TODO remove eventually (kept for backwards compatibility)
52 |
53 | def get_queryset(self):
54 | return self.session.query(self.model)
55 |
56 | def get_objects(self, *pks):
57 | id = self.get_pk(self.model)
58 | return self.get_queryset().filter(id.in_(pks))
59 |
60 | def get_object(self, pk):
61 | return self.get_queryset().get(pk)
62 |
63 | def get_pk(self, instance):
64 | return getattr(instance, self._primary_key)
65 |
66 | def save_model(self, instance, form, adding=False):
67 | form.populate_obj(instance)
68 | if adding:
69 | self.session.add(instance)
70 | self.session.commit()
71 | return instance
72 |
73 | def delete_models(self, *pks):
74 | objs = self.get_objects(*pks)
75 | [self.session.delete(x) for x in objs]
76 | self.session.commit()
77 | return True
78 |
79 | def construct_search(self, field_name, op=None):
80 | if op == '^':
81 | return literal_column(field_name).startswith
82 | elif op == '=':
83 | return literal_column(field_name).op('=')
84 | else:
85 | return literal_column(field_name).contains
86 |
87 | def apply_search(self, qs, search_query):
88 | or_queries = []
89 | # treat spaces as if they were OR operators
90 | for word in search_query.split():
91 | op = word[:1]
92 | if op in ['^', '=']:
93 | word = word[1:]
94 | orm_lookups = [self.construct_search(str(model_field), op)
95 | for model_field in self.search_fields]
96 | or_queries.extend([orm_lookup(word) for orm_lookup in orm_lookups])
97 | if or_queries:
98 | qs = qs.filter(or_(*or_queries))
99 | return qs
100 |
101 | def get_list(self, page=0, sort=None, sort_desc=None, execute=False, search_query=None):
102 | qs = self.get_queryset()
103 |
104 | # Filter by search query
105 | if search_query and self.search_fields:
106 | qs = self.apply_search(qs, search_query)
107 |
108 | #Calculate number of rows
109 | count = qs.count()
110 |
111 | #Order queryset
112 | if sort:
113 | if sort_desc:
114 | sort = desc(sort)
115 | qs = qs.order_by(sort)
116 |
117 | # Pagination
118 | if page is not None:
119 | qs = qs.offset(page * self.list_per_page)
120 |
121 | qs = qs.limit(self.list_per_page)
122 |
123 | if execute:
124 | qs = qs.all()
125 |
126 | return count, qs
127 |
--------------------------------------------------------------------------------
/flask_superadmin/static/js/filters.js:
--------------------------------------------------------------------------------
1 | var AdminFilters = function(element, filters_element, adminForm, operations, options, types) {
2 | var $root = $(element);
3 | var $container = $('.filters', $root);
4 | var lastCount = 0;
5 |
6 | function getCount(name) {
7 | var idx = name.indexOf('_');
8 | return parseInt(name.substr(3, idx - 3), 10);
9 | }
10 |
11 | function changeOperation() {
12 | var $parent = $(this).parent();
13 | var $el = $('.filter-val', $parent);
14 | var count = getCount($el.attr('name'));
15 | $el.attr('name', 'flt' + count + '_' + $(this).val());
16 | $('button', $root).show();
17 | }
18 |
19 | function removeFilter() {
20 | $(this).parent().remove();
21 | $('button', $root).show();
22 | }
23 |
24 | function addFilter(name, op) {
25 | var $el = $('
').appendTo($container);
26 |
27 | $('
')
28 | .append($('
× '))
29 | .append(' ')
30 | .append(name)
31 | .appendTo($el)
32 | .click(removeFilter);
33 |
34 | var $select = $('
')
35 | .appendTo($el)
36 | .change(changeOperation);
37 |
38 | $(op).each(function() {
39 | $select.append($('
').attr('value', this[0]).text(this[1]));
40 | });
41 |
42 | $select.chosen();
43 |
44 | var optId = op[0][0];
45 |
46 | var $field;
47 |
48 | if (optId in options) {
49 | $field = $('
')
50 | .attr('name', 'flt' + lastCount + '_' + optId)
51 | .appendTo($el);
52 |
53 | $(options[optId]).each(function() {
54 | $field.append($('
')
55 | .val(this[0]).text(this[1]))
56 | .appendTo($el);
57 | });
58 |
59 | $field.chosen();
60 | } else
61 | {
62 | $field = $('
')
63 | .attr('name', 'flt' + lastCount + '_' + optId)
64 | .appendTo($el);
65 | }
66 |
67 | if (optId in types) {
68 | $field.attr('data-role', types[optId]);
69 | adminForm.applyStyle($field, types[optId]);
70 | }
71 |
72 | lastCount += 1;
73 | }
74 |
75 | $('a.filter', filters_element).click(function() {
76 | var name = $(this).text().trim();
77 |
78 | addFilter(name, operations[name]);
79 |
80 | $('button', $root).show();
81 | });
82 |
83 | $('.filter-op', $root).change(changeOperation);
84 | $('.filter-val', $root).change(function() {
85 | $('button', $root).show();
86 | });
87 | $('.remove-filter', $root).click(removeFilter);
88 |
89 | $('.filter-val', $root).each(function() {
90 | var count = getCount($(this).attr('name'));
91 | if (count > lastCount)
92 | lastCount = count;
93 | });
94 |
95 | lastCount += 1;
96 | };
97 |
98 | checker = $( ':checkbox[value="all"]' );
99 | checker.click(function(){
100 | all_checkboxes.attr( 'checked', $( this ).is( ':checked' ) ).change();
101 | });
102 | all_checkboxes = $( ':checkbox[name="_selected_action"]' ).not(checker);
103 | actions = $('.actions').change(function() {this.form.submit();});
104 | actions = actions.chosen().data('chosen').container.addClass(actions.attr('class'));
105 | all_checkboxes.change(function(e) {
106 | _this = $(this);
107 | checked = $.grep(all_checkboxes, function (a) { return $(a).is( ':checked' ); });
108 | all_selected = checked.length === all_checkboxes.length;
109 | opacity = (all_selected || checked.length === 0) ? 1 : 0.5;
110 | checker.attr('checked',checked.length > 0).css('opacity', opacity);
111 | actions.toggleClass('hidden', checked.length === 0);
112 | // $('.action_delete').stop().animate({'opacity':checked.length>0?1:0},200)
113 | if (_this.is(':checked')) _this.parent().parent().addClass('checked');
114 | else _this.parent().parent().removeClass('checked');
115 | }).click(function(e) {e.stopPropagation();});
116 |
--------------------------------------------------------------------------------
/flask_superadmin/translations/admin.pot:
--------------------------------------------------------------------------------
1 | # Translations template for Flask-SuperAdmin.
2 | # Copyright (C) 2012 ORGANIZATION
3 | # This file is distributed under the same license as the Flask-SuperAdmin
4 | # project.
5 | # FIRST AUTHOR
, 2012.
6 | #
7 | #, fuzzy
8 | msgid ""
9 | msgstr ""
10 | "Project-Id-Version: Flask-SuperAdmin 1.5.1\n"
11 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
12 | "POT-Creation-Date: 2012-08-27 03:49+0800\n"
13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14 | "Last-Translator: FULL NAME \n"
15 | "Language-Team: LANGUAGE \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=utf-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Generated-By: Babel 0.9.6\n"
20 |
21 | #: flask_superadmin/form.py:81
22 | msgid "Invalid time format"
23 | msgstr ""
24 |
25 | #: flask_superadmin/contrib/fileadmin.py:32
26 | msgid "Invalid directory name"
27 | msgstr ""
28 |
29 | #: flask_superadmin/contrib/fileadmin.py:49
30 | msgid "File required."
31 | msgstr ""
32 |
33 | #: flask_superadmin/contrib/fileadmin.py:54
34 | msgid "Invalid file type."
35 | msgstr ""
36 |
37 | #: flask_superadmin/contrib/fileadmin.py:338
38 | msgid "File uploading is disabled."
39 | msgstr ""
40 |
41 | #: flask_superadmin/contrib/fileadmin.py:347
42 | #, python-format
43 | msgid "File \"%(name)s\" already exists."
44 | msgstr ""
45 |
46 | #: flask_superadmin/contrib/fileadmin.py:354
47 | #, python-format
48 | msgid "Failed to save file: %(error)s"
49 | msgstr ""
50 |
51 | #: flask_superadmin/contrib/fileadmin.py:373
52 | msgid "Directory creation is disabled."
53 | msgstr ""
54 |
55 | #: flask_superadmin/contrib/fileadmin.py:383
56 | #, python-format
57 | msgid "Failed to create directory: %(error)s"
58 | msgstr ""
59 |
60 | #: flask_superadmin/contrib/fileadmin.py:405
61 | msgid "Deletion is disabled."
62 | msgstr ""
63 |
64 | #: flask_superadmin/contrib/fileadmin.py:410
65 | msgid "Directory deletion is disabled."
66 | msgstr ""
67 |
68 | #: flask_superadmin/contrib/fileadmin.py:415
69 | #, python-format
70 | msgid "Directory \"%s\" was successfully deleted."
71 | msgstr ""
72 |
73 | #: flask_superadmin/contrib/fileadmin.py:417
74 | #, python-format
75 | msgid "Failed to delete directory: %(error)s"
76 | msgstr ""
77 |
78 | #: flask_superadmin/contrib/fileadmin.py:421
79 | #, python-format
80 | msgid "File \"%(name)s\" was successfully deleted."
81 | msgstr ""
82 |
83 | #: flask_superadmin/contrib/fileadmin.py:423
84 | #, python-format
85 | msgid "Failed to delete file: %(name)s"
86 | msgstr ""
87 |
88 | #: flask_superadmin/contrib/fileadmin.py:442
89 | msgid "Renaming is disabled."
90 | msgstr ""
91 |
92 | #: flask_superadmin/contrib/fileadmin.py:446
93 | msgid "Path does not exist."
94 | msgstr ""
95 |
96 | #: flask_superadmin/contrib/fileadmin.py:457
97 | #, python-format
98 | msgid "Successfully renamed \"%(src)s\" to \"%(dst)s\""
99 | msgstr ""
100 |
101 | #: flask_superadmin/contrib/fileadmin.py:460
102 | #, python-format
103 | msgid "Failed to rename: %(error)s"
104 | msgstr ""
105 |
106 | #: flask_superadmin/model/base.py:145
107 | #, python-format
108 | msgid "New %(model)s saved successfully"
109 | msgstr ""
110 |
111 | #: flask_superadmin/model/base.py:149
112 | #, python-format
113 | msgid "Failed to add model. %(error)s"
114 | msgstr ""
115 |
116 | #: flask_superadmin/model/base.py:197
117 | #, python-format
118 | msgid "Failed to edit model. %(error)s"
119 | msgstr ""
120 |
121 | #: flask_superadmin/model/backends/mongoengine/fields.py:115
122 | #: flask_superadmin/model/backends/mongoengine/fields.py:165
123 | #: flask_superadmin/model/backends/mongoengine/fields.py:170
124 | msgid "Not a valid choice"
125 | msgstr ""
126 |
127 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:35
128 | msgid "equals"
129 | msgstr ""
130 |
131 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:43
132 | msgid "not equal"
133 | msgstr ""
134 |
135 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:52
136 | msgid "contains"
137 | msgstr ""
138 |
139 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:61
140 | msgid "not contains"
141 | msgstr ""
142 |
143 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:69
144 | msgid "greater than"
145 | msgstr ""
146 |
147 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:77
148 | msgid "smaller than"
149 | msgstr ""
150 |
151 | #: flask_superadmin/model/backends/sqlalchemy/orm.py:38
152 | msgid "Already exists."
153 | msgstr ""
154 |
155 |
--------------------------------------------------------------------------------
/examples/auth/auth.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, url_for, redirect, render_template, request
2 | from flask.ext.sqlalchemy import SQLAlchemy
3 |
4 | from flask.ext import superadmin, login, wtf
5 | from flask.ext.superadmin.contrib import sqlamodel
6 | from wtforms.fields import TextField, PasswordField
7 | from wtforms.validators import Required, ValidationError
8 |
9 | # Create application
10 | app = Flask(__name__)
11 |
12 | # Create dummy secrey key so we can use sessions
13 | app.config['SECRET_KEY'] = '123456790'
14 |
15 | # Create in-memory database
16 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.sqlite'
17 | app.config['SQLALCHEMY_ECHO'] = True
18 | db = SQLAlchemy(app)
19 |
20 |
21 | # Create user model. For simplicity, it will store passwords in plain text.
22 | # Obviously that's not right thing to do in real world application.
23 | class User(db.Model):
24 | id = db.Column(db.Integer, primary_key=True)
25 | login = db.Column(db.String(80), unique=True)
26 | email = db.Column(db.String(120))
27 | password = db.Column(db.String(64))
28 |
29 | # Flask-Login integration
30 | def is_authenticated(self):
31 | return True
32 |
33 | def is_active(self):
34 | return True
35 |
36 | def is_anonymous(self):
37 | return False
38 |
39 | def get_id(self):
40 | return self.id
41 |
42 | # Required for administrative interface
43 | def __unicode__(self):
44 | return self.login
45 |
46 |
47 | # Define login and registration forms (for flask-login)
48 | class LoginForm(wtf.Form):
49 | login = TextField(validators=[Required()])
50 | password = PasswordField(validators=[Required()])
51 |
52 | def validate_login(self, field):
53 | user = self.get_user()
54 |
55 | if user is None:
56 | raise ValidationError('Invalid user')
57 |
58 | if user.password != self.password.data:
59 | raise ValidationError('Invalid password')
60 |
61 | def get_user(self):
62 | return db.session.query(User).filter_by(login=self.login.data).first()
63 |
64 |
65 | class RegistrationForm(wtf.Form):
66 | login = TextField(validators=[Required()])
67 | email = TextField()
68 | password = PasswordField(validators=[Required()])
69 |
70 | def validate_login(self, field):
71 | if db.session.query(User).filter_by(login=self.login.data).count() > 0:
72 | raise ValidationError('Duplicate username')
73 |
74 |
75 | # Initialize flask-login
76 | def init_login():
77 | login_manager = login.LoginManager()
78 | login_manager.setup_app(app)
79 |
80 | # Create user loader function
81 | @login_manager.user_loader
82 | def load_user(user_id):
83 | return db.session.query(User).get(user_id)
84 |
85 |
86 | # Create customized model view class
87 | class MyModelView(sqlamodel.ModelView):
88 | def is_accessible(self):
89 | return login.current_user.is_authenticated()
90 |
91 |
92 | # Create customized index view class
93 | class MyAdminIndexView(superadmin.AdminIndexView):
94 | def is_accessible(self):
95 | return login.current_user.is_authenticated()
96 |
97 |
98 | # Flask views
99 | @app.route('/')
100 | def index():
101 | return render_template('index.html', user=login.current_user)
102 |
103 |
104 | @app.route('/login/', methods=('GET', 'POST'))
105 | def login_view():
106 | form = LoginForm(request.form)
107 | if form.validate_on_submit():
108 | user = form.get_user()
109 | login.login_user(user)
110 | return redirect(url_for('index'))
111 |
112 | return render_template('form.html', form=form)
113 |
114 |
115 | @app.route('/register/', methods=('GET', 'POST'))
116 | def register_view():
117 | form = RegistrationForm(request.form)
118 | if form.validate_on_submit():
119 | user = User()
120 |
121 | form.populate_obj(user)
122 |
123 | db.session.add(user)
124 | db.session.commit()
125 |
126 | login.login_user(user)
127 | return redirect(url_for('index'))
128 |
129 | return render_template('form.html', form=form)
130 |
131 |
132 | @app.route('/logout/')
133 | def logout_view():
134 | login.logout_user()
135 | return redirect(url_for('index'))
136 |
137 | if __name__ == '__main__':
138 | # Initialize flask-login
139 | init_login()
140 |
141 | # Create admin
142 | admin = superadmin.Admin(app, 'Auth', index_view=MyAdminIndexView())
143 |
144 | # Add view
145 | admin.add_view(MyModelView(User, db.session))
146 |
147 | # Create DB
148 | db.create_all()
149 |
150 | # Start app
151 | app.debug = True
152 | app.run()
153 |
--------------------------------------------------------------------------------
/examples/auth/mongoauth.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, url_for, redirect, render_template, request
2 | try:
3 | from mongoengine import *
4 | except ImportError:
5 | exit('You must have mongoengine installed. Install it with the command:\n\t$> easy_install mongoengine')
6 |
7 | from flask.ext import superadmin, login, wtf
8 | from flask.ext.superadmin.contrib import mongoenginemodel
9 | from wtforms.fields import TextField, PasswordField
10 | from wtforms.validators import Required, ValidationError
11 |
12 | # Create application
13 | app = Flask(__name__)
14 |
15 | # Create dummy secrey key so we can use sessions
16 | app.config['SECRET_KEY'] = '123456790'
17 |
18 | # Database name for Mongo
19 | app.config['DATABASE'] = 'dummy_db'
20 |
21 |
22 | # Create user model. For simplicity, it will store passwords in plain text.
23 | # Obviously that's not right thing to do in real world application.
24 | class User(Document):
25 | id = StringField(primary_key=True)
26 | login = StringField(max_length=80, unique=True)
27 | email = EmailField(max_length=120)
28 | password = StringField(max_length=64)
29 |
30 | # Flask-Login integration
31 | def is_authenticated(self):
32 | return True
33 |
34 | def is_active(self):
35 | return True
36 |
37 | def is_anonymous(self):
38 | return False
39 |
40 | def get_id(self):
41 | return self.id
42 |
43 | # Required for administrative interface
44 | def __unicode__(self):
45 | return self.login
46 |
47 |
48 | # Define login and registration forms (for flask-login)
49 | class LoginForm(wtf.Form):
50 | login = TextField(validators=[Required()])
51 | password = PasswordField(validators=[Required()])
52 |
53 | def validate_login(self, field):
54 | user = self.get_user()
55 |
56 | if user is None:
57 | raise ValidationError('Invalid user')
58 |
59 | if user.password != self.password.data:
60 | raise ValidationError('Invalid password')
61 |
62 | def get_user(self):
63 | return User.objects.get(login=self.login)
64 |
65 |
66 | class RegistrationForm(wtf.Form):
67 | login = TextField(validators=[Required()])
68 | email = TextField()
69 | password = PasswordField(validators=[Required()])
70 |
71 | def validate_login(self, field):
72 | if len(User.objects(login=self.login.data)) > 0:
73 | raise ValidationError('Duplicate username')
74 |
75 |
76 | # Initialize flask-login
77 | def init_login():
78 | login_manager = login.LoginManager()
79 | login_manager.setup_app(app)
80 |
81 | # Create user loader function
82 | @login_manager.user_loader
83 | def load_user(user_id):
84 | return User.objects.get(id=user_id)
85 |
86 |
87 | # Create customized model view class
88 | class MyModelView(mongoenginemodel.ModelView):
89 | def is_accessible(self):
90 | return login.current_user.is_authenticated()
91 |
92 |
93 | # Create customized index view class
94 | class MyAdminIndexView(superadmin.AdminIndexView):
95 | def is_accessible(self):
96 | return login.current_user.is_authenticated()
97 |
98 |
99 | # Flask views
100 | @app.route('/')
101 | def index():
102 | return render_template('index.html', user=login.current_user)
103 |
104 |
105 | @app.route('/login/', methods=('GET', 'POST'))
106 | def login_view():
107 | form = LoginForm(request.form)
108 | if form.validate_on_submit():
109 | user = form.get_user()
110 | login.login_user(user)
111 | return redirect(url_for('index'))
112 |
113 | return render_template('form.html', form=form)
114 |
115 |
116 | @app.route('/register/', methods=('GET', 'POST'))
117 | def register_view():
118 | form = RegistrationForm(request.form)
119 | if form.validate_on_submit():
120 | user = User()
121 |
122 | form.populate_obj(user)
123 | user.id = user.login
124 | user.save()
125 | login.login_user(user)
126 |
127 | return redirect(url_for('index'))
128 |
129 | return render_template('form.html', form=form)
130 |
131 |
132 | @app.route('/logout/')
133 | def logout_view():
134 | login.logout_user()
135 | return redirect(url_for('index'))
136 |
137 | if __name__ == '__main__':
138 | # Initialize flask-login
139 | init_login()
140 |
141 | # Mongoengine connection
142 | connect(app.config['DATABASE'])
143 |
144 | # Create admin
145 | admin = superadmin.Admin(app, 'Auth', index_view=MyAdminIndexView())
146 |
147 | # Add view
148 | admin.add_view(MyModelView(User))
149 |
150 | # Start app
151 | app.debug = True
152 | app.run()
153 |
154 |
--------------------------------------------------------------------------------
/doc/_themes/flask_theme_support.py:
--------------------------------------------------------------------------------
1 | # flasky extensions. flasky pygments style based on tango style
2 | from pygments.style import Style
3 | from pygments.token import Keyword, Name, Comment, String, Error, \
4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
5 |
6 |
7 | class FlaskyStyle(Style):
8 | background_color = "#f8f8f8"
9 | default_style = ""
10 |
11 | styles = {
12 | # No corresponding class for the following:
13 | #Text: "", # class: ''
14 | Whitespace: "underline #f8f8f8", # class: 'w'
15 | Error: "#a40000 border:#ef2929", # class: 'err'
16 | Other: "#000000", # class 'x'
17 |
18 | Comment: "italic #8f5902", # class: 'c'
19 | Comment.Preproc: "noitalic", # class: 'cp'
20 |
21 | Keyword: "bold #004461", # class: 'k'
22 | Keyword.Constant: "bold #004461", # class: 'kc'
23 | Keyword.Declaration: "bold #004461", # class: 'kd'
24 | Keyword.Namespace: "bold #004461", # class: 'kn'
25 | Keyword.Pseudo: "bold #004461", # class: 'kp'
26 | Keyword.Reserved: "bold #004461", # class: 'kr'
27 | Keyword.Type: "bold #004461", # class: 'kt'
28 |
29 | Operator: "#582800", # class: 'o'
30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords
31 |
32 | Punctuation: "bold #000000", # class: 'p'
33 |
34 | # because special names such as Name.Class, Name.Function, etc.
35 | # are not recognized as such later in the parsing, we choose them
36 | # to look the same as ordinary variables.
37 | Name: "#000000", # class: 'n'
38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised
39 | Name.Builtin: "#004461", # class: 'nb'
40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
41 | Name.Class: "#000000", # class: 'nc' - to be revised
42 | Name.Constant: "#000000", # class: 'no' - to be revised
43 | Name.Decorator: "#888", # class: 'nd' - to be revised
44 | Name.Entity: "#ce5c00", # class: 'ni'
45 | Name.Exception: "bold #cc0000", # class: 'ne'
46 | Name.Function: "#000000", # class: 'nf'
47 | Name.Property: "#000000", # class: 'py'
48 | Name.Label: "#f57900", # class: 'nl'
49 | Name.Namespace: "#000000", # class: 'nn' - to be revised
50 | Name.Other: "#000000", # class: 'nx'
51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword
52 | Name.Variable: "#000000", # class: 'nv' - to be revised
53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised
54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised
55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
56 |
57 | Number: "#990000", # class: 'm'
58 |
59 | Literal: "#000000", # class: 'l'
60 | Literal.Date: "#000000", # class: 'ld'
61 |
62 | String: "#4e9a06", # class: 's'
63 | String.Backtick: "#4e9a06", # class: 'sb'
64 | String.Char: "#4e9a06", # class: 'sc'
65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment
66 | String.Double: "#4e9a06", # class: 's2'
67 | String.Escape: "#4e9a06", # class: 'se'
68 | String.Heredoc: "#4e9a06", # class: 'sh'
69 | String.Interpol: "#4e9a06", # class: 'si'
70 | String.Other: "#4e9a06", # class: 'sx'
71 | String.Regex: "#4e9a06", # class: 'sr'
72 | String.Single: "#4e9a06", # class: 's1'
73 | String.Symbol: "#4e9a06", # class: 'ss'
74 |
75 | Generic: "#000000", # class: 'g'
76 | Generic.Deleted: "#a40000", # class: 'gd'
77 | Generic.Emph: "italic #000000", # class: 'ge'
78 | Generic.Error: "#ef2929", # class: 'gr'
79 | Generic.Heading: "bold #000080", # class: 'gh'
80 | Generic.Inserted: "#00A000", # class: 'gi'
81 | Generic.Output: "#888", # class: 'go'
82 | Generic.Prompt: "#745334", # class: 'gp'
83 | Generic.Strong: "bold #000000", # class: 'gs'
84 | Generic.Subheading: "bold #800080", # class: 'gu'
85 | Generic.Traceback: "bold #a40000", # class: 'gt'
86 | }
87 |
--------------------------------------------------------------------------------
/flask_superadmin/translations/fr/LC_MESSAGES/admin.po:
--------------------------------------------------------------------------------
1 | # French translations for Flask-SuperAdmin.
2 | # Copyright (C) 2012 ORGANIZATION
3 | # This file is distributed under the same license as the Flask-SuperAdmin
4 | # project.
5 | # FIRST AUTHOR , 2012.
6 | #
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: Flask-SuperAdmin 1.5.1\n"
10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
11 | "POT-Creation-Date: 2012-08-27 03:49+0800\n"
12 | "PO-Revision-Date: 2014-01-25 17:25+0100\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: fr \n"
15 | "Plural-Forms: nplurals=2; plural=(n > 1)\n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=utf-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Generated-By: Babel 1.3\n"
20 |
21 | #: flask_superadmin/form.py:81
22 | msgid "Invalid time format"
23 | msgstr "Format d'heure invalide"
24 |
25 | #: flask_superadmin/contrib/fileadmin.py:32
26 | msgid "Invalid directory name"
27 | msgstr "Nom de répertoire invalide"
28 |
29 | #: flask_superadmin/contrib/fileadmin.py:49
30 | msgid "File required."
31 | msgstr "Fichier requis."
32 |
33 | #: flask_superadmin/contrib/fileadmin.py:54
34 | msgid "Invalid file type."
35 | msgstr "Type de fichier invalide."
36 |
37 | #: flask_superadmin/contrib/fileadmin.py:338
38 | msgid "File uploading is disabled."
39 | msgstr "L'envoi de fichiers est désactivé."
40 |
41 | #: flask_superadmin/contrib/fileadmin.py:347
42 | #, python-format
43 | msgid "File \"%(name)s\" already exists."
44 | msgstr "Le fichier \"%(name)s\" existe déjà."
45 |
46 | #: flask_superadmin/contrib/fileadmin.py:354
47 | #, python-format
48 | msgid "Failed to save file: %(error)s"
49 | msgstr "Impossible d'enregister le fichier: %(error)s"
50 |
51 | #: flask_superadmin/contrib/fileadmin.py:373
52 | msgid "Directory creation is disabled."
53 | msgstr "La création de répertoires est désactivée."
54 |
55 | #: flask_superadmin/contrib/fileadmin.py:383
56 | #, python-format
57 | msgid "Failed to create directory: %(error)s"
58 | msgstr "Impossible de créer le répertoire: %(error)s"
59 |
60 | #: flask_superadmin/contrib/fileadmin.py:405
61 | msgid "Deletion is disabled."
62 | msgstr "La suppression est désactivée."
63 |
64 | #: flask_superadmin/contrib/fileadmin.py:410
65 | msgid "Directory deletion is disabled."
66 | msgstr "La suppression de répertoires est désactivée."
67 |
68 | #: flask_superadmin/contrib/fileadmin.py:415
69 | #, python-format
70 | msgid "Directory \"%s\" was successfully deleted."
71 | msgstr "Le répertoire \"%s\" a été supprimé avec succès."
72 |
73 | #: flask_superadmin/contrib/fileadmin.py:417
74 | #, python-format
75 | msgid "Failed to delete directory: %(error)s"
76 | msgstr "Impossible de supprimer le répertoire: %(error)s"
77 |
78 | #: flask_superadmin/contrib/fileadmin.py:421
79 | #, python-format
80 | msgid "File \"%(name)s\" was successfully deleted."
81 | msgstr "Le fichier \"%(name)s\" a été supprimé avec succès."
82 |
83 | #: flask_superadmin/contrib/fileadmin.py:423
84 | #, python-format
85 | msgid "Failed to delete file: %(name)s"
86 | msgstr "Impossible de supprimer le fichier: %(name)s"
87 |
88 | #: flask_superadmin/contrib/fileadmin.py:442
89 | msgid "Renaming is disabled."
90 | msgstr "Le renommage est désactivé."
91 |
92 | #: flask_superadmin/contrib/fileadmin.py:446
93 | msgid "Path does not exist."
94 | msgstr "Le chemin n'existe pas."
95 |
96 | #: flask_superadmin/contrib/fileadmin.py:457
97 | #, python-format
98 | msgid "Successfully renamed \"%(src)s\" to \"%(dst)s\""
99 | msgstr "\"%(src)s\" a été renommé en \"%(dst)s\" avec succès"
100 |
101 | #: flask_superadmin/contrib/fileadmin.py:460
102 | #, python-format
103 | msgid "Failed to rename: %(error)s"
104 | msgstr "Impossible de renommer: %(error)s"
105 |
106 | #: flask_superadmin/model/base.py:145
107 | #, python-format
108 | msgid "New %(model)s saved successfully"
109 | msgstr "Nouveau %(model)s enregistré avec succès"
110 |
111 | #: flask_superadmin/model/base.py:149
112 | #, python-format
113 | msgid "Failed to add model. %(error)s"
114 | msgstr "Impossible d'ajouter le modèle. %(error)s"
115 |
116 | #: flask_superadmin/model/base.py:197
117 | #, python-format
118 | msgid "Failed to edit model. %(error)s"
119 | msgstr "Impossible d'éditer le modèle. %(error)s"
120 |
121 | #: flask_superadmin/model/backends/mongoengine/fields.py:115
122 | #: flask_superadmin/model/backends/mongoengine/fields.py:165
123 | #: flask_superadmin/model/backends/mongoengine/fields.py:170
124 | msgid "Not a valid choice"
125 | msgstr "Choix invalide"
126 |
127 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:35
128 | msgid "equals"
129 | msgstr "égal"
130 |
131 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:43
132 | msgid "not equal"
133 | msgstr "non égal"
134 |
135 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:52
136 | msgid "contains"
137 | msgstr "contient"
138 |
139 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:61
140 | msgid "not contains"
141 | msgstr "ne contient pas"
142 |
143 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:69
144 | msgid "greater than"
145 | msgstr "plus grand que"
146 |
147 | #: flask_superadmin/model/backends/sqlalchemy/filters.py:77
148 | msgid "smaller than"
149 | msgstr "plus petit que"
150 |
151 | #: flask_superadmin/model/backends/sqlalchemy/orm.py:38
152 | msgid "Already exists."
153 | msgstr "Existe déjà."
154 |
155 |
--------------------------------------------------------------------------------
/flask_superadmin/templates/admin/model/list.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/layout.html' %}
2 | {% import 'admin/_macros.html' as lib with context %}
3 | {% set name = admin_view.get_display_name() %}
4 |
5 | {% block head_css %}
6 |
7 |
8 | {{super()}}
9 | {% endblock %}
10 |
11 | {% block body %}
12 |
102 | {% endblock %}
103 |
104 | {% block tail %}
105 |
106 |
107 |
108 | {% endblock %}
109 |
--------------------------------------------------------------------------------
/flask_superadmin/tests/test_base.py:
--------------------------------------------------------------------------------
1 | from nose.tools import ok_, eq_, raises
2 |
3 | from flask import Flask
4 | from flask_superadmin import base
5 |
6 |
7 | class MockView(base.BaseView):
8 | # Various properties
9 | allow_call = True
10 | allow_access = True
11 |
12 | @base.expose('/')
13 | def index(self):
14 | return 'Success!'
15 |
16 | @base.expose('/test/')
17 | def test(self):
18 | return self.render('mock.html')
19 |
20 | def _handle_view(self, name, **kwargs):
21 | if self.allow_call:
22 | return super(MockView, self)._handle_view(name, **kwargs)
23 | else:
24 | return 'Failure!'
25 |
26 | def is_accessible(self):
27 | if self.allow_access:
28 | return super(MockView, self).is_accessible()
29 | else:
30 | return False
31 |
32 |
33 | def test_baseview_defaults():
34 | view = MockView()
35 | eq_(view.name, None)
36 | eq_(view.category, None)
37 | eq_(view.endpoint, None)
38 | eq_(view.url, None)
39 | eq_(view.static_folder, None)
40 | eq_(view.admin, None)
41 | eq_(view.blueprint, None)
42 |
43 |
44 | def test_base_defaults():
45 | admin = base.Admin()
46 | eq_(admin.name, 'Admin')
47 | eq_(admin.url, '/admin')
48 | eq_(admin.app, None)
49 | ok_(admin.index_view is not None)
50 |
51 | # Check if default view was added
52 | eq_(len(admin._views), 1)
53 | eq_(admin._views[0], admin.index_view)
54 |
55 |
56 | def test_base_registration():
57 | app = Flask(__name__)
58 | admin = base.Admin(app)
59 |
60 | eq_(admin.app, app)
61 | ok_(admin.index_view.blueprint is not None)
62 |
63 |
64 | def test_admin_customizations():
65 | app = Flask(__name__)
66 | admin = base.Admin(app, name='Test', url='/foobar')
67 | eq_(admin.name, 'Test')
68 | eq_(admin.url, '/foobar')
69 |
70 | client = app.test_client()
71 | rv = client.get('/foobar/')
72 | eq_(rv.status_code, 200)
73 |
74 |
75 | def test_baseview_registration():
76 | admin = base.Admin()
77 |
78 | view = MockView()
79 | bp = view.create_blueprint(admin)
80 |
81 | # Base properties
82 | eq_(view.admin, admin)
83 | ok_(view.blueprint is not None)
84 |
85 | # Calculated properties
86 | eq_(view.endpoint, 'mockview')
87 | eq_(view.url, '/admin/mockview')
88 | eq_(view.name, 'Mock View')
89 |
90 | # Verify generated blueprint properties
91 | eq_(bp.name, view.endpoint)
92 | eq_(bp.url_prefix, view.url)
93 | eq_(bp.template_folder, 'templates')
94 | eq_(bp.static_folder, view.static_folder)
95 |
96 | # Verify customizations
97 | view = MockView(name='Test', endpoint='foobar')
98 | view.create_blueprint(base.Admin())
99 |
100 | eq_(view.name, 'Test')
101 | eq_(view.endpoint, 'foobar')
102 | eq_(view.url, '/admin/foobar')
103 |
104 | view = MockView(url='test')
105 | view.create_blueprint(base.Admin())
106 | eq_(view.url, '/admin/test')
107 |
108 | view = MockView(url='/test/test')
109 | view.create_blueprint(base.Admin())
110 | eq_(view.url, '/test/test')
111 |
112 |
113 | def test_baseview_urls():
114 | app = Flask(__name__)
115 | admin = base.Admin(app)
116 |
117 | view = MockView()
118 | admin.add_view(view)
119 |
120 | eq_(len(view._urls), 2)
121 |
122 |
123 | @raises(Exception)
124 | def test_no_default():
125 | app = Flask(__name__)
126 | admin = base.Admin(app)
127 | admin.add_view(base.BaseView())
128 |
129 |
130 | def test_call():
131 | app = Flask(__name__)
132 | admin = base.Admin(app)
133 | view = MockView()
134 | admin.add_view(view)
135 | client = app.test_client()
136 |
137 | rv = client.get('/admin/')
138 | eq_(rv.status_code, 200)
139 |
140 | rv = client.get('/admin/mockview/')
141 | eq_(rv.data, 'Success!')
142 |
143 | rv = client.get('/admin/mockview/test/')
144 | eq_(rv.data, 'Success!')
145 |
146 | # Check authentication failure
147 | view.allow_call = False
148 | rv = client.get('/admin/mockview/')
149 | eq_(rv.data, 'Failure!')
150 |
151 |
152 | def test_permissions():
153 | app = Flask(__name__)
154 | admin = base.Admin(app)
155 | view = MockView()
156 | admin.add_view(view)
157 | client = app.test_client()
158 |
159 | view.allow_access = False
160 |
161 | rv = client.get('/admin/mockview/')
162 | eq_(rv.status_code, 403)
163 |
164 |
165 | def test_submenu():
166 | app = Flask(__name__)
167 | admin = base.Admin(app)
168 | admin.add_view(MockView(name='Test 1', category='Test', endpoint='test1'))
169 |
170 | # Second view is not normally accessible
171 | view = MockView(name='Test 2', category='Test', endpoint='test2')
172 | view.allow_access = False
173 | admin.add_view(view)
174 |
175 | ok_('Test' in admin._menu_categories)
176 | eq_(len(admin._menu), 2)
177 | eq_(admin._menu[1].name, 'Test')
178 | eq_(len(admin._menu[1]._children), 2)
179 |
180 | # Categories don't have URLs and they're not accessible
181 | eq_(admin._menu[1].get_url(), None)
182 | eq_(admin._menu[1].is_accessible(), False)
183 |
184 | eq_(len(admin._menu[1].get_children()), 1)
185 |
186 |
187 | def test_delayed_init():
188 | app = Flask(__name__)
189 | admin = base.Admin()
190 | admin.add_view(MockView())
191 | admin.init_app(app)
192 |
193 | client = app.test_client()
194 |
195 | rv = client.get('/admin/mockview/')
196 | eq_(rv.data, 'Success!')
197 |
198 |
199 | @raises(Exception)
200 | def test_double_init():
201 | app = Flask(__name__)
202 | admin = base.Admin(app)
203 | admin.init_app(app)
204 |
205 |
--------------------------------------------------------------------------------
/doc/_themes/flask_small/static/flasky.css_t:
--------------------------------------------------------------------------------
1 | /*
2 | * flasky.css_t
3 | * ~~~~~~~~~~~~
4 | *
5 | * Sphinx stylesheet -- flasky theme based on nature theme.
6 | *
7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
8 | * :license: BSD, see LICENSE for details.
9 | *
10 | */
11 |
12 | @import url("basic.css");
13 |
14 | /* -- page layout ----------------------------------------------------------- */
15 |
16 | body {
17 | font-family: 'Georgia', serif;
18 | font-size: 17px;
19 | color: #000;
20 | background: white;
21 | margin: 0;
22 | padding: 0;
23 | }
24 |
25 | div.documentwrapper {
26 | float: left;
27 | width: 100%;
28 | }
29 |
30 | div.bodywrapper {
31 | margin: 40px auto 0 auto;
32 | width: 700px;
33 | }
34 |
35 | hr {
36 | border: 1px solid #B1B4B6;
37 | }
38 |
39 | div.body {
40 | background-color: #ffffff;
41 | color: #3E4349;
42 | padding: 0 30px 30px 30px;
43 | }
44 |
45 | img.floatingflask {
46 | padding: 0 0 10px 10px;
47 | float: right;
48 | }
49 |
50 | div.footer {
51 | text-align: right;
52 | color: #888;
53 | padding: 10px;
54 | font-size: 14px;
55 | width: 650px;
56 | margin: 0 auto 40px auto;
57 | }
58 |
59 | div.footer a {
60 | color: #888;
61 | text-decoration: underline;
62 | }
63 |
64 | div.related {
65 | line-height: 32px;
66 | color: #888;
67 | }
68 |
69 | div.related ul {
70 | padding: 0 0 0 10px;
71 | }
72 |
73 | div.related a {
74 | color: #444;
75 | }
76 |
77 | /* -- body styles ----------------------------------------------------------- */
78 |
79 | a {
80 | color: #004B6B;
81 | text-decoration: underline;
82 | }
83 |
84 | a:hover {
85 | color: #6D4100;
86 | text-decoration: underline;
87 | }
88 |
89 | div.body {
90 | padding-bottom: 40px; /* saved for footer */
91 | }
92 |
93 | div.body h1,
94 | div.body h2,
95 | div.body h3,
96 | div.body h4,
97 | div.body h5,
98 | div.body h6 {
99 | font-family: 'Garamond', 'Georgia', serif;
100 | font-weight: normal;
101 | margin: 30px 0px 10px 0px;
102 | padding: 0;
103 | }
104 |
105 | {% if theme_index_logo %}
106 | div.indexwrapper h1 {
107 | text-indent: -999999px;
108 | background: url({{ theme_index_logo }}) no-repeat center center;
109 | height: {{ theme_index_logo_height }};
110 | }
111 | {% endif %}
112 |
113 | div.body h2 { font-size: 180%; }
114 | div.body h3 { font-size: 150%; }
115 | div.body h4 { font-size: 130%; }
116 | div.body h5 { font-size: 100%; }
117 | div.body h6 { font-size: 100%; }
118 |
119 | a.headerlink {
120 | color: white;
121 | padding: 0 4px;
122 | text-decoration: none;
123 | }
124 |
125 | a.headerlink:hover {
126 | color: #444;
127 | background: #eaeaea;
128 | }
129 |
130 | div.body p, div.body dd, div.body li {
131 | line-height: 1.4em;
132 | }
133 |
134 | div.admonition {
135 | background: #fafafa;
136 | margin: 20px -30px;
137 | padding: 10px 30px;
138 | border-top: 1px solid #ccc;
139 | border-bottom: 1px solid #ccc;
140 | }
141 |
142 | div.admonition p.admonition-title {
143 | font-family: 'Garamond', 'Georgia', serif;
144 | font-weight: normal;
145 | font-size: 24px;
146 | margin: 0 0 10px 0;
147 | padding: 0;
148 | line-height: 1;
149 | }
150 |
151 | div.admonition p.last {
152 | margin-bottom: 0;
153 | }
154 |
155 | div.highlight{
156 | background-color: white;
157 | }
158 |
159 | dt:target, .highlight {
160 | background: #FAF3E8;
161 | }
162 |
163 | div.note {
164 | background-color: #eee;
165 | border: 1px solid #ccc;
166 | }
167 |
168 | div.seealso {
169 | background-color: #ffc;
170 | border: 1px solid #ff6;
171 | }
172 |
173 | div.topic {
174 | background-color: #eee;
175 | }
176 |
177 | div.warning {
178 | background-color: #ffe4e4;
179 | border: 1px solid #f66;
180 | }
181 |
182 | p.admonition-title {
183 | display: inline;
184 | }
185 |
186 | p.admonition-title:after {
187 | content: ":";
188 | }
189 |
190 | pre, tt {
191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
192 | font-size: 0.85em;
193 | }
194 |
195 | img.screenshot {
196 | }
197 |
198 | tt.descname, tt.descclassname {
199 | font-size: 0.95em;
200 | }
201 |
202 | tt.descname {
203 | padding-right: 0.08em;
204 | }
205 |
206 | img.screenshot {
207 | -moz-box-shadow: 2px 2px 4px #eee;
208 | -webkit-box-shadow: 2px 2px 4px #eee;
209 | box-shadow: 2px 2px 4px #eee;
210 | }
211 |
212 | table.docutils {
213 | border: 1px solid #888;
214 | -moz-box-shadow: 2px 2px 4px #eee;
215 | -webkit-box-shadow: 2px 2px 4px #eee;
216 | box-shadow: 2px 2px 4px #eee;
217 | }
218 |
219 | table.docutils td, table.docutils th {
220 | border: 1px solid #888;
221 | padding: 0.25em 0.7em;
222 | }
223 |
224 | table.field-list, table.footnote {
225 | border: none;
226 | -moz-box-shadow: none;
227 | -webkit-box-shadow: none;
228 | box-shadow: none;
229 | }
230 |
231 | table.footnote {
232 | margin: 15px 0;
233 | width: 100%;
234 | border: 1px solid #eee;
235 | }
236 |
237 | table.field-list th {
238 | padding: 0 0.8em 0 0;
239 | }
240 |
241 | table.field-list td {
242 | padding: 0;
243 | }
244 |
245 | table.footnote td {
246 | padding: 0.5em;
247 | }
248 |
249 | dl {
250 | margin: 0;
251 | padding: 0;
252 | }
253 |
254 | dl dd {
255 | margin-left: 30px;
256 | }
257 |
258 | pre {
259 | padding: 0;
260 | margin: 15px -30px;
261 | padding: 8px;
262 | line-height: 1.3em;
263 | padding: 7px 30px;
264 | background: #eee;
265 | border-radius: 2px;
266 | -moz-border-radius: 2px;
267 | -webkit-border-radius: 2px;
268 | }
269 |
270 | dl pre {
271 | margin-left: -60px;
272 | padding-left: 60px;
273 | }
274 |
275 | tt {
276 | background-color: #ecf0f3;
277 | color: #222;
278 | /* padding: 1px 2px; */
279 | }
280 |
281 | tt.xref, a tt {
282 | background-color: #FBFBFB;
283 | }
284 |
285 | a:hover tt {
286 | background: #EEE;
287 | }
288 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-SuperAdmin.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-SuperAdmin.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-SuperAdmin"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-SuperAdmin"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/flask_superadmin/form.py:
--------------------------------------------------------------------------------
1 | import time
2 | import datetime
3 |
4 | from flask.ext import wtf
5 | from wtforms import fields, widgets
6 |
7 | from flask_superadmin.babel import gettext
8 | from flask import request
9 |
10 | class BaseForm(wtf.Form):
11 | """
12 | Customized form class.
13 | """
14 | def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
15 | if formdata:
16 | super(BaseForm, self).__init__(formdata, obj, prefix, **kwargs)
17 | else:
18 | super(BaseForm, self).__init__(obj=obj, prefix=prefix, **kwargs)
19 |
20 | self._obj = obj
21 |
22 | @property
23 | def has_file_field(self):
24 | """
25 | Return True if form contains at least one FileField.
26 | """
27 | # TODO: Optimize me
28 | for f in self:
29 | if isinstance(f, fields.FileField):
30 | return True
31 |
32 | return False
33 |
34 |
35 | class TimeField(fields.Field):
36 | """
37 | A text field which stores a `datetime.time` object.
38 | Accepts time string in multiple formats: 20:10, 20:10:00, 10:00 am, 9:30pm, etc.
39 | """
40 | widget = widgets.TextInput()
41 |
42 | def __init__(self, label=None, validators=None, formats=None, **kwargs):
43 | """
44 | Constructor
45 |
46 | `label`
47 | Label
48 | `validators`
49 | Field validators
50 | `formats`
51 | Supported time formats, as a enumerable.
52 | `kwargs`
53 | Any additional parameters
54 | """
55 | super(TimeField, self).__init__(label, validators, **kwargs)
56 |
57 | self.formats = formats or ('%H:%M:%S', '%H:%M',
58 | '%I:%M:%S%p', '%I:%M%p',
59 | '%I:%M:%S %p', '%I:%M %p')
60 |
61 | def _value(self):
62 | if self.raw_data:
63 | return u' '.join(self.raw_data)
64 | else:
65 | return self.data and self.data.strftime(self.formats[0]) or u''
66 |
67 | def process_formdata(self, valuelist):
68 | if valuelist:
69 | date_str = u' '.join(valuelist)
70 |
71 | for format in self.formats:
72 | try:
73 | timetuple = time.strptime(date_str, format)
74 | self.data = datetime.time(timetuple.tm_hour,
75 | timetuple.tm_min,
76 | timetuple.tm_sec)
77 | return
78 | except ValueError:
79 | pass
80 |
81 | raise ValueError(gettext('Invalid time format'))
82 |
83 |
84 | class ChosenSelectWidget(widgets.Select):
85 | """
86 | `Chosen `_ styled select widget.
87 |
88 | You must include chosen.js and form.js for styling to work.
89 | """
90 | def __call__(self, field, **kwargs):
91 | if getattr(field, 'allow_blank', False) and not self.multiple:
92 | kwargs['data-role'] = u'chosenblank'
93 | else:
94 | kwargs['data-role'] = u'chosen'
95 |
96 | return super(ChosenSelectWidget, self).__call__(field, **kwargs)
97 |
98 |
99 | class ChosenSelectField(fields.SelectField):
100 | """
101 | `Chosen `_ styled select field.
102 |
103 | You must include chosen.js and form.js for styling to work.
104 | """
105 | widget = ChosenSelectWidget
106 |
107 | class FileFieldWidget(object):
108 | # widget_file = widgets.FileInput()
109 | widget_checkbox = widgets.CheckboxInput()
110 | def __call__(self, field, **kwargs):
111 | from cgi import escape
112 | input_file = ' ' % widgets.html_params(name=field.name, type='file')
113 | return widgets.HTMLString('%s Current: %s %s Clear file '%(input_file, escape(field._value()), self.widget_checkbox(field._clear), field._clear.id))
114 |
115 | class FileField(fields.FileField):
116 | widget = FileFieldWidget()
117 | def __init__(self,*args,**kwargs):
118 | self.clearable = kwargs.pop('clearable', True)
119 | super(FileField, self).__init__(*args, **kwargs)
120 | self._prefix = kwargs.get('_prefix', '')
121 | self.clear_field = fields.BooleanField(default=False)
122 | if self.clearable:
123 | self._clear_name = '%s-clear'%self.short_name
124 | self._clear_id = '%s-clear'%self.id
125 | self._clear = self.clear_field.bind(form=None, name=self._clear_name, prefix=self._prefix, id=self._clear_id)
126 |
127 | def process(self, formdata, data=fields._unset_value):
128 | super(FileField, self).process(formdata, data)
129 | if self.clearable:
130 | self._clear.process(formdata, data)
131 | self._clear.checked = False
132 |
133 | @property
134 | def clear(self):
135 | return (not self.clearable) or self._clear.data
136 |
137 | @property
138 | def data(self):
139 | data = self._data
140 | if data is not None:
141 | data.clear = self.clear
142 | return data
143 |
144 | @data.setter
145 | def data(self, data):
146 | self._data = data
147 |
148 |
149 | class DatePickerWidget(widgets.TextInput):
150 | """
151 | Date picker widget.
152 |
153 | You must include bootstrap-datepicker.js and form.js for styling to work.
154 | """
155 | def __call__(self, field, **kwargs):
156 | kwargs['data-role'] = u'datepicker'
157 | return super(DatePickerWidget, self).__call__(field, **kwargs)
158 |
159 |
160 | class DateTimePickerWidget(widgets.TextInput):
161 | """
162 | Datetime picker widget.
163 |
164 | You must include bootstrap-datepicker.js and form.js for styling to work.
165 | """
166 | def __call__(self, field, **kwargs):
167 | kwargs['data-role'] = u'datetimepicker'
168 | return super(DateTimePickerWidget, self).__call__(field, **kwargs)
169 |
170 | # def format_form(form):
171 | # for field in form:
172 | # if isinstance(field,fields.SelectField):
173 | # field.widget = ChosenSelectWidget(multiple=field.widget.multiple)
174 | # elif isinstance(field, fields.DateTimeField):
175 | # field.widget = DatePickerWidget()
176 | # elif isinstance(field, fields.FormField):
177 | # format_form(field.form)
178 | # return form
179 | # # elif isinstance(field, fields.FieldList):
180 | # # for f in field.entries: format_form
181 |
--------------------------------------------------------------------------------
/babel/admin.pot:
--------------------------------------------------------------------------------
1 | # Translations template for Flask-SuperAdminEx.
2 | # Copyright (C) 2012 ORGANIZATION
3 | # This file is distributed under the same license as the Flask-SuperAdminEx
4 | # project.
5 | # FIRST AUTHOR , 2012.
6 | #
7 | #, fuzzy
8 | msgid ""
9 | msgstr ""
10 | "Project-Id-Version: Flask-SuperAdminEx VERSION\n"
11 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
12 | "POT-Creation-Date: 2012-04-11 18:47+0300\n"
13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14 | "Last-Translator: FULL NAME \n"
15 | "Language-Team: LANGUAGE \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=utf-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Generated-By: Babel 0.9.6\n"
20 |
21 | #: ../flask_superadminex/base.py:216
22 | msgid "Home"
23 | msgstr ""
24 |
25 | #: ../flask_superadminex/form.py:81
26 | msgid "Invalid time format"
27 | msgstr ""
28 |
29 | #: ../flask_superadminex/ext/fileadmin.py:32
30 | msgid "Invalid directory name"
31 | msgstr ""
32 |
33 | #: ../flask_superadminex/ext/fileadmin.py:40
34 | msgid "File to upload"
35 | msgstr ""
36 |
37 | #: ../flask_superadminex/ext/fileadmin.py:49
38 | msgid "File required."
39 | msgstr ""
40 |
41 | #: ../flask_superadminex/ext/fileadmin.py:54
42 | msgid "Invalid file type."
43 | msgstr ""
44 |
45 | #: ../flask_superadminex/ext/fileadmin.py:335
46 | msgid "File uploading is disabled."
47 | msgstr ""
48 |
49 | #: ../flask_superadminex/ext/fileadmin.py:344
50 | #, python-format
51 | msgid "File \"%(name)s\" already exists."
52 | msgstr ""
53 |
54 | #: ../flask_superadminex/ext/fileadmin.py:351
55 | #, python-format
56 | msgid "Failed to save file: %(error)s"
57 | msgstr ""
58 |
59 | #: ../flask_superadminex/ext/fileadmin.py:370
60 | msgid "Directory creation is disabled."
61 | msgstr ""
62 |
63 | #: ../flask_superadminex/ext/fileadmin.py:380
64 | #, python-format
65 | msgid "Failed to create directory: %(error)s"
66 | msgstr ""
67 |
68 | #: ../flask_superadminex/ext/fileadmin.py:402
69 | msgid "Deletion is disabled."
70 | msgstr ""
71 |
72 | #: ../flask_superadminex/ext/fileadmin.py:407
73 | msgid "Directory deletion is disabled."
74 | msgstr ""
75 |
76 | #: ../flask_superadminex/ext/fileadmin.py:412
77 | #, python-format
78 | msgid "Directory \"%s\" was successfully deleted."
79 | msgstr ""
80 |
81 | #: ../flask_superadminex/ext/fileadmin.py:414
82 | #, python-format
83 | msgid "Failed to delete directory: %(error)s"
84 | msgstr ""
85 |
86 | #: ../flask_superadminex/ext/fileadmin.py:418
87 | #, python-format
88 | msgid "File \"%(name)s\" was successfully deleted."
89 | msgstr ""
90 |
91 | #: ../flask_superadminex/ext/fileadmin.py:420
92 | #, python-format
93 | msgid "Failed to delete file: %(name)s"
94 | msgstr ""
95 |
96 | #: ../flask_superadminex/ext/fileadmin.py:439
97 | msgid "Renaming is disabled."
98 | msgstr ""
99 |
100 | #: ../flask_superadminex/ext/fileadmin.py:443
101 | msgid "Path does not exist."
102 | msgstr ""
103 |
104 | #: ../flask_superadminex/ext/fileadmin.py:454
105 | #, python-format
106 | msgid "Successfully renamed \"%(src)s\" to \"%(dst)s\""
107 | msgstr ""
108 |
109 | #: ../flask_superadminex/ext/fileadmin.py:457
110 | #, python-format
111 | msgid "Failed to rename: %(error)s"
112 | msgstr ""
113 |
114 | #: ../flask_superadminex/ext/sqlamodel/filters.py:35
115 | msgid "equals"
116 | msgstr ""
117 |
118 | #: ../flask_superadminex/ext/sqlamodel/filters.py:43
119 | msgid "not equal"
120 | msgstr ""
121 |
122 | #: ../flask_superadminex/ext/sqlamodel/filters.py:52
123 | msgid "contains"
124 | msgstr ""
125 |
126 | #: ../flask_superadminex/ext/sqlamodel/filters.py:61
127 | msgid "not contains"
128 | msgstr ""
129 |
130 | #: ../flask_superadminex/ext/sqlamodel/filters.py:69
131 | msgid "greater than"
132 | msgstr ""
133 |
134 | #: ../flask_superadminex/ext/sqlamodel/filters.py:77
135 | msgid "smaller than"
136 | msgstr ""
137 |
138 | #: ../flask_superadminex/ext/sqlamodel/form.py:37
139 | msgid "Already exists."
140 | msgstr ""
141 |
142 | #: ../flask_superadminex/ext/sqlamodel/view.py:504
143 | #, python-format
144 | msgid "Failed to create model. %(error)s"
145 | msgstr ""
146 |
147 | #: ../flask_superadminex/ext/sqlamodel/view.py:519
148 | #, python-format
149 | msgid "Failed to update model. %(error)s"
150 | msgstr ""
151 |
152 | #: ../flask_superadminex/ext/sqlamodel/view.py:534
153 | #, python-format
154 | msgid "Failed to delete model. %(error)s"
155 | msgstr ""
156 |
157 | #: ../flask_superadminex/model/base.py:742
158 | msgid "Model was successfully created."
159 | msgstr ""
160 |
161 | #: ../flask_superadminex/model/filters.py:82
162 | msgid "Yes"
163 | msgstr ""
164 |
165 | #: ../flask_superadminex/model/filters.py:83
166 | msgid "No"
167 | msgstr ""
168 |
169 | #: ../flask_superadminex/templates/admin/lib.html:105
170 | msgid "Submit"
171 | msgstr ""
172 |
173 | #: ../flask_superadminex/templates/admin/lib.html:110
174 | msgid "Cancel"
175 | msgstr ""
176 |
177 | #: ../flask_superadminex/templates/admin/file/list.html:7
178 | msgid "Root"
179 | msgstr ""
180 |
181 | #: ../flask_superadminex/templates/admin/file/list.html:42
182 | #, python-format
183 | msgid "Are you sure you want to delete \\'%(name)s\\' recursively?"
184 | msgstr ""
185 |
186 | #: ../flask_superadminex/templates/admin/file/list.html:50
187 | #, python-format
188 | msgid "Are you sure you want to delete \\'%(name)s\\'?"
189 | msgstr ""
190 |
191 | #: ../flask_superadminex/templates/admin/file/list.html:75
192 | msgid "Upload File"
193 | msgstr ""
194 |
195 | #: ../flask_superadminex/templates/admin/file/list.html:78
196 | msgid "Create Directory"
197 | msgstr ""
198 |
199 | #: ../flask_superadminex/templates/admin/file/rename.html:5
200 | #, python-format
201 | msgid "Please provide new name for %(name)s"
202 | msgstr ""
203 |
204 | #: ../flask_superadminex/templates/admin/model/create.html:11
205 | msgid "Save and Add"
206 | msgstr ""
207 |
208 | #: ../flask_superadminex/templates/admin/model/create.html:16
209 | #: ../flask_superadminex/templates/admin/model/list.html:12
210 | msgid "List"
211 | msgstr ""
212 |
213 | #: ../flask_superadminex/templates/admin/model/create.html:19
214 | #: ../flask_superadminex/templates/admin/model/list.html:16
215 | msgid "Create"
216 | msgstr ""
217 |
218 | #: ../flask_superadminex/templates/admin/model/list.html:23
219 | msgid "Add Filter"
220 | msgstr ""
221 |
222 | #: ../flask_superadminex/templates/admin/model/list.html:44
223 | msgid "Search"
224 | msgstr ""
225 |
226 | #: ../flask_superadminex/templates/admin/model/list.html:57
227 | msgid "Apply"
228 | msgstr ""
229 |
230 | #: ../flask_superadminex/templates/admin/model/list.html:59
231 | msgid "Reset Filters"
232 | msgstr ""
233 |
234 | #: ../flask_superadminex/templates/admin/model/list.html:67
235 | msgid "Remove Filter"
236 | msgstr ""
237 |
238 | #: ../flask_superadminex/templates/admin/model/list.html:128
239 | msgid "You sure you want to delete this item?"
240 | msgstr ""
241 |
242 |
--------------------------------------------------------------------------------
/flask_superadmin/translations/zh_CN/LC_MESSAGES/admin.po:
--------------------------------------------------------------------------------
1 | # Translations template for Flask-SuperAdminEx.
2 | # Copyright (C) 2012 ORGANIZATION
3 | # This file is distributed under the same license as the Flask-SuperAdminEx
4 | # project.
5 | # FIRST AUTHOR , 2012.
6 | #
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: Flask-SuperAdminEx\n"
10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
11 | "POT-Creation-Date: 2012-04-11 18:47+0300\n"
12 | "PO-Revision-Date: 2012-04-11 18:48+0200\n"
13 | "Last-Translator: Jiangge Zhang \n"
14 | "Language-Team: \n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Generated-By: Babel 0.9.6\n"
19 | "X-Poedit-Language: Simplified Chinese\n"
20 | "X-Poedit-Country: Chinese\n"
21 | "X-Poedit-SourceCharset: utf-8\n"
22 |
23 | #: ../flask_superadminex/base.py:216
24 | msgid "Home"
25 | msgstr "主页"
26 |
27 | #: ../flask_superadminex/form.py:81
28 | msgid "Invalid time format"
29 | msgstr "无效的时间格式"
30 |
31 | #: ../flask_superadminex/ext/fileadmin.py:32
32 | msgid "Invalid directory name"
33 | msgstr "无效的目录名"
34 |
35 | #: ../flask_superadminex/ext/fileadmin.py:40
36 | msgid "File to upload"
37 | msgstr "上传的文件"
38 |
39 | #: ../flask_superadminex/ext/fileadmin.py:49
40 | msgid "File required."
41 | msgstr "需要上传文件。"
42 |
43 | #: ../flask_superadminex/ext/fileadmin.py:54
44 | msgid "Invalid file type."
45 | msgstr "无效的文件格式。"
46 |
47 | #: ../flask_superadminex/ext/fileadmin.py:335
48 | msgid "File uploading is disabled."
49 | msgstr "文件上传功能没有启用。"
50 |
51 | #: ../flask_superadminex/ext/fileadmin.py:344
52 | #, python-format
53 | msgid "File \"%(name)s\" already exists."
54 | msgstr "文件 \"%(name)s\" 已经存在。"
55 |
56 | #: ../flask_superadminex/ext/fileadmin.py:351
57 | #, python-format
58 | msgid "Failed to save file: %(error)s"
59 | msgstr "保存文件失败: %(error)s"
60 |
61 | #: ../flask_superadminex/ext/fileadmin.py:370
62 | msgid "Directory creation is disabled."
63 | msgstr "目录创建功能没有启用。"
64 |
65 | #: ../flask_superadminex/ext/fileadmin.py:380
66 | #, python-format
67 | msgid "Failed to create directory: %(error)s"
68 | msgstr "创建目录失败: %(error)s"
69 |
70 | #: ../flask_superadminex/ext/fileadmin.py:402
71 | msgid "Deletion is disabled."
72 | msgstr "删除功能没有启用。"
73 |
74 | #: ../flask_superadminex/ext/fileadmin.py:407
75 | msgid "Directory deletion is disabled."
76 | msgstr "删除目录功能没有启用。"
77 |
78 | #: ../flask_superadminex/ext/fileadmin.py:412
79 | #, python-format
80 | msgid "Directory \"%s\" was successfully deleted."
81 | msgstr "目录 \"%s\" 已被成功地删除。"
82 |
83 | #: ../flask_superadminex/ext/fileadmin.py:414
84 | #, python-format
85 | msgid "Failed to delete directory: %(error)s"
86 | msgstr "删除目录失败: %(error)s"
87 |
88 | #: ../flask_superadminex/ext/fileadmin.py:418
89 | #, python-format
90 | msgid "File \"%(name)s\" was successfully deleted."
91 | msgstr "文件 \"%(name)s\" 已被成功地删除。"
92 |
93 | #: ../flask_superadminex/ext/fileadmin.py:420
94 | #, python-format
95 | msgid "Failed to delete file: %(name)s"
96 | msgstr "删除文件失败: %(name)s"
97 |
98 | #: ../flask_superadminex/ext/fileadmin.py:439
99 | msgid "Renaming is disabled."
100 | msgstr "重命名功能没有启用。"
101 |
102 | #: ../flask_superadminex/ext/fileadmin.py:443
103 | msgid "Path does not exist."
104 | msgstr "路径不存在。"
105 |
106 | #: ../flask_superadminex/ext/fileadmin.py:454
107 | #, python-format
108 | msgid "Successfully renamed \"%(src)s\" to \"%(dst)s\""
109 | msgstr "\"%(src)s\" 已被成功地重命名为 \"%(dst)s\""
110 |
111 | #: ../flask_superadminex/ext/fileadmin.py:457
112 | #, python-format
113 | msgid "Failed to rename: %(error)s"
114 | msgstr "重命名失败: %(error)s"
115 |
116 | #: ../flask_superadminex/ext/sqlamodel/filters.py:35
117 | msgid "equals"
118 | msgstr "相同"
119 |
120 | #: ../flask_superadminex/ext/sqlamodel/filters.py:43
121 | msgid "not equal"
122 | msgstr "不相同"
123 |
124 | #: ../flask_superadminex/ext/sqlamodel/filters.py:52
125 | msgid "contains"
126 | msgstr "包含"
127 |
128 | #: ../flask_superadminex/ext/sqlamodel/filters.py:61
129 | msgid "not contains"
130 | msgstr "不包含"
131 |
132 | #: ../flask_superadminex/ext/sqlamodel/filters.py:69
133 | msgid "greater than"
134 | msgstr "大于"
135 |
136 | #: ../flask_superadminex/ext/sqlamodel/filters.py:77
137 | msgid "smaller than"
138 | msgstr "小于"
139 |
140 | #: ../flask_superadminex/ext/sqlamodel/form.py:37
141 | msgid "Already exists."
142 | msgstr "已经存在."
143 |
144 | #: ../flask_superadminex/ext/sqlamodel/view.py:504
145 | #, python-format
146 | msgid "Failed to create model. %(error)s"
147 | msgstr "创建模型失败: %(error)s"
148 |
149 | #: ../flask_superadminex/ext/sqlamodel/view.py:519
150 | #, python-format
151 | msgid "Failed to update model. %(error)s"
152 | msgstr "更新模型失败: %(error)s"
153 |
154 | #: ../flask_superadminex/ext/sqlamodel/view.py:534
155 | #, python-format
156 | msgid "Failed to delete model. %(error)s"
157 | msgstr "删除模型失败: %(error)s"
158 |
159 | #: ../flask_superadminex/model/base.py:742
160 | msgid "Model was successfully created."
161 | msgstr "模型已被成功地创建。"
162 |
163 | #: ../flask_superadminex/model/filters.py:82
164 | msgid "Yes"
165 | msgstr "是"
166 |
167 | #: ../flask_superadminex/model/filters.py:83
168 | msgid "No"
169 | msgstr "否"
170 |
171 | #: ../flask_superadminex/templates/admin/lib.html:105
172 | msgid "Submit"
173 | msgstr "提交"
174 |
175 | #: ../flask_superadminex/templates/admin/lib.html:110
176 | msgid "Cancel"
177 | msgstr "取消"
178 |
179 | #: ../flask_superadminex/templates/admin/file/list.html:7
180 | msgid "Root"
181 | msgstr "根目录"
182 |
183 | #: ../flask_superadminex/templates/admin/file/list.html:42
184 | #, python-format
185 | msgid "Are you sure you want to delete \\'%(name)s\\' recursively?"
186 | msgstr "确定要递归删除 \\'%(name)s\\' 吗?"
187 |
188 | #: ../flask_superadminex/templates/admin/file/list.html:50
189 | #, python-format
190 | msgid "Are you sure you want to delete \\'%(name)s\\'?"
191 | msgstr "确定要删除 \\'%(name)s\\' 吗?"
192 |
193 | #: ../flask_superadminex/templates/admin/file/list.html:75
194 | msgid "Upload File"
195 | msgstr "上传文件"
196 |
197 | #: ../flask_superadminex/templates/admin/file/list.html:78
198 | msgid "Create Directory"
199 | msgstr "创建目录"
200 |
201 | #: ../flask_superadminex/templates/admin/file/rename.html:5
202 | #, python-format
203 | msgid "Please provide new name for %(name)s"
204 | msgstr "请为 %(name)s 提供新名字"
205 |
206 | #: ../flask_superadminex/templates/admin/model/create.html:11
207 | msgid "Save and Add"
208 | msgstr "保存并添加"
209 |
210 | #: ../flask_superadminex/templates/admin/model/create.html:16
211 | #: ../flask_superadminex/templates/admin/model/list.html:12
212 | msgid "List"
213 | msgstr "列表"
214 |
215 | #: ../flask_superadminex/templates/admin/model/create.html:19
216 | #: ../flask_superadminex/templates/admin/model/list.html:16
217 | msgid "Create"
218 | msgstr "创建"
219 |
220 | #: ../flask_superadminex/templates/admin/model/list.html:23
221 | msgid "Add Filter"
222 | msgstr "添加过滤规则"
223 |
224 | #: ../flask_superadminex/templates/admin/model/list.html:44
225 | msgid "Search"
226 | msgstr "搜索"
227 |
228 | #: ../flask_superadminex/templates/admin/model/list.html:57
229 | msgid "Apply"
230 | msgstr "应用"
231 |
232 | #: ../flask_superadminex/templates/admin/model/list.html:59
233 | msgid "Reset Filters"
234 | msgstr "重置所有过滤规则"
235 |
236 | #: ../flask_superadminex/templates/admin/model/list.html:67
237 | msgid "Remove Filter"
238 | msgstr "删除过滤规则"
239 |
240 | #: ../flask_superadminex/templates/admin/model/list.html:128
241 | msgid "You sure you want to delete this item?"
242 | msgstr "确定要删除该项吗?"
243 |
244 |
--------------------------------------------------------------------------------
/flask_superadmin/model/backends/sqlalchemy/orm.py:
--------------------------------------------------------------------------------
1 | """
2 | Tools for generating forms based on SQLAlchemy Model schemas.
3 | """
4 |
5 | from sqlalchemy import Column
6 | from sqlalchemy.orm.exc import NoResultFound
7 |
8 | from wtforms import Form, ValidationError, fields, validators
9 | from wtforms.ext.sqlalchemy.orm import converts, ModelConverter, model_form as original_model_form
10 | from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
11 |
12 | from flask.ext.superadmin import form
13 |
14 |
15 | class Unique(object):
16 | """Checks field value unicity against specified table field.
17 |
18 | :param get_session:
19 | A function that return a SQAlchemy Session.
20 | :param model:
21 | The model to check unicity against.
22 | :param column:
23 | The unique column.
24 | :param message:
25 | The error message.
26 | """
27 | field_flags = ('unique', )
28 |
29 | def __init__(self, db_session, model, column, message=None):
30 | self.db_session = db_session
31 | self.model = model
32 | self.column = column
33 | self.message = message
34 |
35 | def __call__(self, form, field):
36 | try:
37 | obj = (self.db_session.query(self.model)
38 | .filter(self.column == field.data).one())
39 |
40 | if not hasattr(form, '_obj') or not form._obj == obj:
41 | if self.message is None:
42 | self.message = field.gettext(u'Already exists.')
43 | raise ValidationError(self.message)
44 | except NoResultFound:
45 | pass
46 |
47 |
48 | class AdminModelConverter(ModelConverter):
49 | """
50 | SQLAlchemy model to form converter
51 | """
52 | def __init__(self, view):
53 | super(AdminModelConverter, self).__init__()
54 |
55 | self.view = view
56 |
57 | def _get_label(self, name, field_args):
58 | if 'label' in field_args:
59 | return field_args['label']
60 |
61 | # if self.view.rename_columns:
62 | # return self.view.rename_columns.get(name)
63 |
64 | return None
65 |
66 | def _get_field_override(self, name):
67 | if self.view.field_overrides:
68 | return self.view.field_overrides.get(name)
69 |
70 | def convert(self, model, mapper, prop, field_args, *args):
71 | kwargs = {
72 | 'validators': [],
73 | 'filters': []
74 | }
75 |
76 | if field_args:
77 | kwargs.update(field_args)
78 |
79 | if hasattr(prop, 'direction'):
80 | remote_model = prop.mapper.class_
81 | local_column = prop.local_remote_pairs[0][0]
82 |
83 | kwargs.update({
84 | 'allow_blank': local_column.nullable,
85 | 'label': self._get_label(prop.key, kwargs),
86 | 'query_factory': lambda: self.view.session.query(remote_model)
87 | })
88 | if local_column.nullable:
89 | kwargs['validators'].append(validators.Optional())
90 | elif prop.direction.name not in ('MANYTOMANY', 'ONETOMANY'):
91 | kwargs['validators'].append(validators.Required())
92 |
93 | # Override field type if necessary
94 | override = self._get_field_override(prop.key)
95 | if override:
96 | return override(**kwargs)
97 |
98 | if prop.direction.name == 'MANYTOONE':
99 | return QuerySelectField(widget=form.ChosenSelectWidget(),
100 | **kwargs)
101 | elif prop.direction.name == 'ONETOMANY':
102 | # Skip backrefs
103 | if not local_column.foreign_keys and self.view.hide_backrefs:
104 | return None
105 |
106 | return QuerySelectMultipleField(
107 | widget=form.ChosenSelectWidget(multiple=True),
108 | **kwargs)
109 | elif prop.direction.name == 'MANYTOMANY':
110 | return QuerySelectMultipleField(
111 | widget=form.ChosenSelectWidget(multiple=True),
112 | **kwargs)
113 | else:
114 | # Ignore pk/fk
115 | if hasattr(prop, 'columns'):
116 | column = prop.columns[0]
117 |
118 | # Column can be SQL expressions
119 | # WTForms cannot convert them
120 | if not isinstance(column, Column):
121 | return None
122 |
123 | # Do not display foreign keys - use relations
124 | if column.foreign_keys:
125 | return None
126 |
127 | unique = False
128 |
129 | if column.primary_key:
130 | # By default, don't show primary keys either
131 | if self.view.fields is None:
132 | return None
133 |
134 | # If PK is not explicitly allowed, ignore it
135 | if prop.key not in self.view.fields:
136 | return None
137 |
138 | kwargs['validators'].append(Unique(self.view.session,
139 | model,
140 | column))
141 | unique = True
142 |
143 | # If field is unique, validate it
144 | if column.unique and not unique:
145 | kwargs['validators'].append(Unique(self.view.session,
146 | model,
147 | column))
148 |
149 | if column.nullable:
150 | kwargs['validators'].append(validators.Optional())
151 | else:
152 | kwargs['validators'].append(validators.Required())
153 |
154 | # Apply label
155 | kwargs['label'] = self._get_label(prop.key, kwargs)
156 |
157 | # Override field type if necessary
158 | override = self._get_field_override(prop.key)
159 | if override:
160 | return override(**kwargs)
161 |
162 | return super(AdminModelConverter, self).convert(model, mapper,
163 | prop, kwargs)
164 |
165 | @converts('Date')
166 | def convert_date(self, field_args, **extra):
167 | field_args['widget'] = form.DatePickerWidget()
168 | return fields.DateField(**field_args)
169 |
170 | @converts('DateTime')
171 | def convert_datetime(self, field_args, **extra):
172 | field_args['widget'] = form.DateTimePickerWidget()
173 | return fields.DateTimeField(**field_args)
174 |
175 | @converts('Time')
176 | def convert_time(self, field_args, **extra):
177 | return form.TimeField(**field_args)
178 |
179 | @converts('Text')
180 | def conv_Text_fix(self, field_args, **extra):
181 | return self.conv_Text(field_args, **extra)
182 |
183 |
184 | def model_form(model, base_class=Form, fields=None, readonly_fields=None,
185 | exclude=None, field_args=None, converter=None):
186 | only = tuple(set(fields or []) - set(readonly_fields or []))
187 | return original_model_form(model, base_class=base_class, only=only,
188 | exclude=exclude, field_args=field_args,
189 | converter=converter)
190 |
--------------------------------------------------------------------------------
/flask_superadmin/translations/ru/LC_MESSAGES/admin.po:
--------------------------------------------------------------------------------
1 | # Translations template for Flask-SuperAdminEx.
2 | # Copyright (C) 2012 ORGANIZATION
3 | # This file is distributed under the same license as the Flask-SuperAdminEx
4 | # project.
5 | # FIRST AUTHOR , 2012.
6 | #
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: Flask-SuperAdminEx\n"
10 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
11 | "POT-Creation-Date: 2012-04-11 18:47+0300\n"
12 | "PO-Revision-Date: 2012-04-11 18:48+0200\n"
13 | "Last-Translator: Serge S. Koval \n"
14 | "Language-Team: \n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Generated-By: Babel 0.9.6\n"
19 | "X-Poedit-Language: Russian\n"
20 | "X-Poedit-Country: RUSSIAN FEDERATION\n"
21 | "X-Poedit-SourceCharset: utf-8\n"
22 |
23 | #: ../flask_superadminex/base.py:216
24 | msgid "Home"
25 | msgstr "Главная"
26 |
27 | #: ../flask_superadminex/form.py:81
28 | msgid "Invalid time format"
29 | msgstr "Неправильный формат времени."
30 |
31 | #: ../flask_superadminex/ext/fileadmin.py:32
32 | msgid "Invalid directory name"
33 | msgstr "Недопустимое имя директории"
34 |
35 | #: ../flask_superadminex/ext/fileadmin.py:40
36 | msgid "File to upload"
37 | msgstr "Файл"
38 |
39 | #: ../flask_superadminex/ext/fileadmin.py:49
40 | msgid "File required."
41 | msgstr "Необходимо выбрать файл"
42 |
43 | #: ../flask_superadminex/ext/fileadmin.py:54
44 | msgid "Invalid file type."
45 | msgstr "Недопустимый тип файла."
46 |
47 | #: ../flask_superadminex/ext/fileadmin.py:335
48 | msgid "File uploading is disabled."
49 | msgstr "Заливка файлов запрещена."
50 |
51 | #: ../flask_superadminex/ext/fileadmin.py:344
52 | #, python-format
53 | msgid "File \"%(name)s\" already exists."
54 | msgstr "Файл с именем \"%(name)s\" уже существует."
55 |
56 | #: ../flask_superadminex/ext/fileadmin.py:351
57 | #, python-format
58 | msgid "Failed to save file: %(error)s"
59 | msgstr "Ошибка сохранения файла: %(error)s"
60 |
61 | #: ../flask_superadminex/ext/fileadmin.py:370
62 | msgid "Directory creation is disabled."
63 | msgstr "Создание новых директорий запрещено."
64 |
65 | #: ../flask_superadminex/ext/fileadmin.py:380
66 | #, python-format
67 | msgid "Failed to create directory: %(error)s"
68 | msgstr "Ошибка создания директории: %(error)s"
69 |
70 | #: ../flask_superadminex/ext/fileadmin.py:402
71 | msgid "Deletion is disabled."
72 | msgstr "Удаление запрещено."
73 |
74 | #: ../flask_superadminex/ext/fileadmin.py:407
75 | msgid "Directory deletion is disabled."
76 | msgstr "Удаление директорий запрещено."
77 |
78 | #: ../flask_superadminex/ext/fileadmin.py:412
79 | #, python-format
80 | msgid "Directory \"%s\" was successfully deleted."
81 | msgstr "Директория \"%s\" была удалена."
82 |
83 | #: ../flask_superadminex/ext/fileadmin.py:414
84 | #, python-format
85 | msgid "Failed to delete directory: %(error)s"
86 | msgstr "Ошибка удаления директории: %(error)s"
87 |
88 | #: ../flask_superadminex/ext/fileadmin.py:418
89 | #, python-format
90 | msgid "File \"%(name)s\" was successfully deleted."
91 | msgstr "Файл \"%(name)s\" был удален."
92 |
93 | #: ../flask_superadminex/ext/fileadmin.py:420
94 | #, python-format
95 | msgid "Failed to delete file: %(name)s"
96 | msgstr "Ошибка удаления файла: %(name)s"
97 |
98 | #: ../flask_superadminex/ext/fileadmin.py:439
99 | msgid "Renaming is disabled."
100 | msgstr "Переименование запрещено."
101 |
102 | #: ../flask_superadminex/ext/fileadmin.py:443
103 | msgid "Path does not exist."
104 | msgstr "Путь не существует."
105 |
106 | #: ../flask_superadminex/ext/fileadmin.py:454
107 | #, python-format
108 | msgid "Successfully renamed \"%(src)s\" to \"%(dst)s\""
109 | msgstr "\"%(src)s\" был переименован в \"%(dst)s\""
110 |
111 | #: ../flask_superadminex/ext/fileadmin.py:457
112 | #, python-format
113 | msgid "Failed to rename: %(error)s"
114 | msgstr "Ошибка переименования: %(error)s"
115 |
116 | #: ../flask_superadminex/ext/sqlamodel/filters.py:35
117 | msgid "equals"
118 | msgstr "равно"
119 |
120 | #: ../flask_superadminex/ext/sqlamodel/filters.py:43
121 | msgid "not equal"
122 | msgstr "не равно"
123 |
124 | #: ../flask_superadminex/ext/sqlamodel/filters.py:52
125 | msgid "contains"
126 | msgstr "содержит"
127 |
128 | #: ../flask_superadminex/ext/sqlamodel/filters.py:61
129 | msgid "not contains"
130 | msgstr "не содержит"
131 |
132 | #: ../flask_superadminex/ext/sqlamodel/filters.py:69
133 | msgid "greater than"
134 | msgstr "больше чем"
135 |
136 | #: ../flask_superadminex/ext/sqlamodel/filters.py:77
137 | msgid "smaller than"
138 | msgstr "меньше чем"
139 |
140 | #: ../flask_superadminex/ext/sqlamodel/form.py:37
141 | msgid "Already exists."
142 | msgstr "Уже существует."
143 |
144 | #: ../flask_superadminex/ext/sqlamodel/view.py:504
145 | #, python-format
146 | msgid "Failed to create model. %(error)s"
147 | msgstr "Ошибка создания записи: %(error)s"
148 |
149 | #: ../flask_superadminex/ext/sqlamodel/view.py:519
150 | #, python-format
151 | msgid "Failed to update model. %(error)s"
152 | msgstr "Ошибка обновления записи: %(error)s"
153 |
154 | #: ../flask_superadminex/ext/sqlamodel/view.py:534
155 | #, python-format
156 | msgid "Failed to delete model. %(error)s"
157 | msgstr "Ошибка удаления записи: %(error)s"
158 |
159 | #: ../flask_superadminex/model/base.py:742
160 | msgid "Model was successfully created."
161 | msgstr "Запись была создана."
162 |
163 | #: ../flask_superadminex/model/filters.py:82
164 | msgid "Yes"
165 | msgstr "Да"
166 |
167 | #: ../flask_superadminex/model/filters.py:83
168 | msgid "No"
169 | msgstr "Нет"
170 |
171 | #: ../flask_superadminex/templates/admin/lib.html:105
172 | msgid "Submit"
173 | msgstr "Отправить"
174 |
175 | #: ../flask_superadminex/templates/admin/lib.html:110
176 | msgid "Cancel"
177 | msgstr "Отмена"
178 |
179 | #: ../flask_superadminex/templates/admin/file/list.html:7
180 | msgid "Root"
181 | msgstr "Корень"
182 |
183 | #: ../flask_superadminex/templates/admin/file/list.html:42
184 | #, python-format
185 | msgid "Are you sure you want to delete \\'%(name)s\\' recursively?"
186 | msgstr "Вы уверены что хотите рекурсивно удалить \\'%(name)s\\'?"
187 |
188 | #: ../flask_superadminex/templates/admin/file/list.html:50
189 | #, python-format
190 | msgid "Are you sure you want to delete \\'%(name)s\\'?"
191 | msgstr "Вы уверены что хотите удалить \\'%(name)s\\'?"
192 |
193 | #: ../flask_superadminex/templates/admin/file/list.html:75
194 | msgid "Upload File"
195 | msgstr "Залить файл"
196 |
197 | #: ../flask_superadminex/templates/admin/file/list.html:78
198 | msgid "Create Directory"
199 | msgstr "Создать директорию"
200 |
201 | #: ../flask_superadminex/templates/admin/file/rename.html:5
202 | #, python-format
203 | msgid "Please provide new name for %(name)s"
204 | msgstr "Введите новое имя для %(name)s"
205 |
206 | #: ../flask_superadminex/templates/admin/model/create.html:11
207 | msgid "Save and Add"
208 | msgstr "Сохранить и Добавить"
209 |
210 | #: ../flask_superadminex/templates/admin/model/create.html:16
211 | #: ../flask_superadminex/templates/admin/model/list.html:12
212 | msgid "List"
213 | msgstr "Список"
214 |
215 | #: ../flask_superadminex/templates/admin/model/create.html:19
216 | #: ../flask_superadminex/templates/admin/model/list.html:16
217 | msgid "Create"
218 | msgstr "Создать"
219 |
220 | #: ../flask_superadminex/templates/admin/model/list.html:23
221 | msgid "Add Filter"
222 | msgstr "Добавить Фильтр"
223 |
224 | #: ../flask_superadminex/templates/admin/model/list.html:44
225 | msgid "Search"
226 | msgstr "Поиск"
227 |
228 | #: ../flask_superadminex/templates/admin/model/list.html:57
229 | msgid "Apply"
230 | msgstr "Применить"
231 |
232 | #: ../flask_superadminex/templates/admin/model/list.html:59
233 | msgid "Reset Filters"
234 | msgstr "Сброс Фильтров"
235 |
236 | #: ../flask_superadminex/templates/admin/model/list.html:67
237 | msgid "Remove Filter"
238 | msgstr "Убрать Фильтр"
239 |
240 | #: ../flask_superadminex/templates/admin/model/list.html:128
241 | msgid "You sure you want to delete this item?"
242 | msgstr "Вы уверены что хотите удалить эту запись?"
243 |
244 |
--------------------------------------------------------------------------------
/flask_superadmin/model/backends/django/orm.py:
--------------------------------------------------------------------------------
1 | """
2 | Tools for generating forms based on Django Model schemas.
3 | """
4 |
5 | from wtforms import fields as f
6 | from wtforms import Form
7 | from wtforms import validators
8 | from wtforms.ext.django.fields import ModelSelectField
9 |
10 | from flask_superadmin import form
11 |
12 | __all__ = (
13 | 'AdminModelConverter', 'model_fields', 'model_form'
14 | )
15 |
16 |
17 | class ModelConverterBase(object):
18 | def __init__(self, converters):
19 | self.converters = converters
20 |
21 | def convert(self, model, field, field_args):
22 | kwargs = {
23 | 'label': field.verbose_name,
24 | 'description': field.help_text,
25 | 'validators': [],
26 | 'filters': [],
27 | 'default': field.default,
28 | }
29 | if field_args:
30 | kwargs.update(field_args)
31 |
32 | if field.blank:
33 | kwargs['validators'].append(validators.Optional())
34 | if field.max_length is not None and field.max_length > 0:
35 | kwargs['validators'].append(validators.Length(max=field.max_length))
36 |
37 | ftype = type(field).__name__
38 | if field.choices:
39 | kwargs['choices'] = field.choices
40 | return f.SelectField(widget=form.ChosenSelectWidget(), **kwargs)
41 | elif ftype in self.converters:
42 | return self.converters[ftype](model, field, kwargs)
43 | else:
44 | converter = getattr(self, 'conv_%s' % ftype, None)
45 | if converter is not None:
46 | return converter(model, field, kwargs)
47 |
48 |
49 | class AdminModelConverter(ModelConverterBase):
50 | DEFAULT_SIMPLE_CONVERSIONS = {
51 | f.IntegerField: ['AutoField', 'IntegerField', 'SmallIntegerField',
52 | 'PositiveIntegerField', 'PositiveSmallIntegerField'],
53 | f.DecimalField: ['DecimalField', 'FloatField'],
54 | f.FileField: ['FileField', 'FilePathField', 'ImageField'],
55 | f.BooleanField: ['BooleanField'],
56 | f.TextField: ['CharField', 'PhoneNumberField', 'SlugField'],
57 | f.TextAreaField: ['TextField', 'XMLField'],
58 | }
59 |
60 | def __init__(self, extra_converters=None, simple_conversions=None):
61 | converters = {}
62 | if simple_conversions is None:
63 | simple_conversions = self.DEFAULT_SIMPLE_CONVERSIONS
64 | for field_type, django_fields in simple_conversions.iteritems():
65 | converter = self.make_simple_converter(field_type)
66 | for name in django_fields:
67 | converters[name] = converter
68 |
69 | if extra_converters:
70 | converters.update(extra_converters)
71 | super(AdminModelConverter, self).__init__(converters)
72 |
73 | def make_simple_converter(self, field_type):
74 | def _converter(model, field, kwargs):
75 | return field_type(**kwargs)
76 | return _converter
77 |
78 | def conv_ForeignKey(self, model, field, kwargs):
79 | return ModelSelectField(widget=form.ChosenSelectWidget(),
80 | model=field.rel.to, **kwargs)
81 |
82 | def conv_TimeField(self, model, field, kwargs):
83 | def time_only(obj):
84 | try:
85 | return obj.time()
86 | except AttributeError:
87 | return obj
88 | kwargs['filters'].append(time_only)
89 | return f.DateTimeField(widget=form.DateTimePickerWidget(),
90 | format='%H:%M:%S', **kwargs)
91 |
92 | def conv_DateTimeField(self, model, field, kwargs):
93 | def time_only(obj):
94 | try:
95 | return obj.time()
96 | except AttributeError:
97 | return obj
98 | kwargs['filters'].append(time_only)
99 | return f.DateTimeField(widget=form.DateTimePickerWidget(),
100 | format='%H:%M:%S', **kwargs)
101 |
102 | def conv_DateField(self, model, field, kwargs):
103 | def time_only(obj):
104 | try:
105 | return obj.date()
106 | except AttributeError:
107 | return obj
108 | kwargs['filters'].append(time_only)
109 | return f.DateField(widget=form.DatePickerWidget(), **kwargs)
110 |
111 | def conv_EmailField(self, model, field, kwargs):
112 | kwargs['validators'].append(validators.email())
113 | return f.TextField(**kwargs)
114 |
115 | def conv_IPAddressField(self, model, field, kwargs):
116 | kwargs['validators'].append(validators.ip_address())
117 | return f.TextField(**kwargs)
118 |
119 | def conv_URLField(self, model, field, kwargs):
120 | kwargs['validators'].append(validators.url())
121 | return f.TextField(**kwargs)
122 |
123 | def conv_USStateField(self, model, field, kwargs):
124 | try:
125 | from django.contrib.localflavor.us.us_states import STATE_CHOICES
126 | except ImportError:
127 | STATE_CHOICES = []
128 |
129 | return f.SelectField(choices=STATE_CHOICES, **kwargs)
130 |
131 | def conv_NullBooleanField(self, model, field, kwargs):
132 | def coerce_nullbool(value):
133 | d = {'None': None, None: None, 'True': True, 'False': False}
134 | if value in d:
135 | return d[value]
136 | else:
137 | return bool(int(value))
138 |
139 | choices = ((None, 'Unknown'), (True, 'Yes'), (False, 'No'))
140 | return f.SelectField(choices=choices, coerce=coerce_nullbool, **kwargs)
141 |
142 |
143 | def model_fields(model, fields=None, readonly_fields=None, exclude=None,
144 | field_args=None, converter=None):
145 | """
146 | Generate a dictionary of fields for a given Django model.
147 |
148 | See `model_form` docstring for description of parameters.
149 | """
150 | converter = converter or ModelConverter()
151 | field_args = field_args or {}
152 |
153 | model_fields = ((f.name, f) for f in model._meta.fields)
154 | if fields:
155 | model_fields = (x for x in model_fields if x[0] in fields)
156 | elif exclude:
157 | model_fields = (x for x in model_fields if x[0] not in exclude)
158 |
159 | field_dict = {}
160 | for name, model_field in model_fields:
161 | field = converter.convert(model, model_field, field_args.get(name))
162 | if field is not None:
163 | field_dict[name] = field
164 |
165 | return field_dict
166 |
167 |
168 | def model_form(model, base_class=Form, fields=None, readonly_fields=None,
169 | exclude=None, field_args=None, converter=None):
170 | """
171 | Create a wtforms Form for a given Django model class::
172 |
173 | from wtforms.ext.django.orm import model_form
174 | from myproject.myapp.models import User
175 | UserForm = model_form(User)
176 |
177 | :param model:
178 | A Django ORM model class
179 | :param base_class:
180 | Base form class to extend from. Must be a ``wtforms.Form`` subclass.
181 | :param fields:
182 | An optional iterable with the property names that should be included
183 | in the form. Only these properties will have fields. It also
184 | determines the order of the fields.
185 | :param exclude:
186 | An optional iterable with the property names that should be excluded
187 | from the form. All other properties will have fields.
188 | :param field_args:
189 | An optional dictionary of field names mapping to keyword arguments
190 | used to construct each field object.
191 | :param converter:
192 | A converter to generate the fields based on the model properties. If
193 | not set, ``ModelConverter`` is used.
194 | """
195 | exclude = ([f for f in exclude] if exclude else []) + ['id']
196 | field_dict = model_fields(model, fields, readonly_fields, exclude,
197 | field_args, converter)
198 | return type(model._meta.object_name + 'Form', (base_class, ), field_dict)
199 |
200 |
--------------------------------------------------------------------------------
/flask_superadmin/templates/admin/_macros.html:
--------------------------------------------------------------------------------
1 | {% macro pager(page, pages, generator) -%}
2 | {% if pages > 1 %}
3 |
73 | {% endif %}
74 | {%- endmacro %}
75 |
76 | {% macro render_field(field, show_error_list=True) %}
77 |
78 | {{ field.label }}
79 | {{ field(**kwargs)|safe }}
80 | {% if show_error_list and field.errors %}
81 |
82 | {% for error in field.errors %}{{ error }}{% endfor %}
83 |
84 | {% endif %}
85 |
86 | {% endmacro %}
87 |
88 | {% macro render_ff (ff, delete) %}
89 |
90 | {% if ff.type == "FormField" %}
91 |
92 | {% if delete %}
93 | Delete
94 | {% else %}
95 | {{ admin_view.field_name(ff.short_name) }}
96 | {% endif %}
97 |
98 | {{ render_formfield(ff.form) }}
99 | {% elif ff.type == "ListField" %}
100 | {{ admin_view.field_name(ff.short_name) }}
101 | {% if delete %}
102 | Delete
103 | {% endif %}
104 | {% set a = ff.new_generic() %}
105 |
106 |
{{ render_ff(a, True) }}
107 | {% for field in ff %}
108 | {{ render_ff(field, admin_view.can_edit or not instance) }}
109 | {% endfor %}
110 | {% if admin_view.can_edit or not instance %}
111 |
+
112 | {% endif %}
113 |
114 |
115 | {% else %}
116 |
117 | {% if delete %}
118 | Delete
119 | {% else %}
120 | {{ admin_view.field_name(ff.label.text) }}
121 | {% endif %}
122 |
123 |
124 | {% set class='' %}
125 | {% if ff.type == "DateTimeField" %}
126 | {% set data_type="datetimepicker" %}
127 | {% endif %}
128 |
129 | {% if ff.errors|length>0 %} {% set class=class+' error' %}{% endif %}
130 |
131 | {% if not admin_view.can_edit and instance %}
132 |
133 | {% with reference = admin_view.get_reference(ff.data) %}
134 | {% if reference %}
135 |
136 | {% else %}
137 |
{{ ff.data }}
138 | {% endif %}
139 | {% endwith %}
140 |
141 | {% else %}
142 | {{ ff(class=class) }}
143 | {% endif %}
144 |
145 | {% for error in ff.errors %}
146 |
{{ error }}
147 | {% endfor %}
148 |
149 | {% if ff.description %}
150 |
{{ ff.description }}
151 | {% endif %}
152 |
153 | {% endif %}
154 |
155 | {% endmacro %}
156 |
157 | {% macro render_formfield(form) %}
158 |
159 | {% set readonly_fields = admin_view.get_readonly_fields(instance) %}
160 | {% set field_names = admin_view.fields or form._fields.keys() %}
161 |
162 | {% for field_name in field_names %}
163 | {% if field_name in readonly_fields %}
164 | {% with f = readonly_fields[field_name] %}
165 |
166 | {{ f.label }}
167 |
168 |
169 | {% if f.url %}
170 |
{{ f.value }}
171 | {% else %}
172 | {{ f.value }}
173 | {% endif %}
174 |
175 |
176 |
177 | {% endwith %}
178 | {% else %}
179 | {% if field_name != 'csrf_token' and field_name != 'csrf' %}
180 | {{ render_ff(form._fields[field_name]) }}
181 | {% endif %}
182 | {% endif %}
183 | {% endfor %}
184 |
185 |
186 | {% endmacro %}
187 |
188 | {% macro render_form(form, extra=None, can_edit=True, can_delete=True) -%}
189 |
206 | {% endmacro %}
207 |
208 |
--------------------------------------------------------------------------------
/doc/_themes/flask/static/flasky.css_t:
--------------------------------------------------------------------------------
1 | /*
2 | * flasky.css_t
3 | * ~~~~~~~~~~~~
4 | *
5 | * :copyright: Copyright 2010 by Armin Ronacher.
6 | * :license: Flask Design License, see LICENSE for details.
7 | */
8 |
9 | {% set page_width = '940px' %}
10 | {% set sidebar_width = '220px' %}
11 |
12 | @import url("basic.css");
13 |
14 | /* -- page layout ----------------------------------------------------------- */
15 |
16 | body {
17 | font-family: 'Georgia', serif;
18 | font-size: 17px;
19 | background-color: white;
20 | color: #000;
21 | margin: 0;
22 | padding: 0;
23 | }
24 |
25 | div.document {
26 | width: {{ page_width }};
27 | margin: 30px auto 0 auto;
28 | }
29 |
30 | div.documentwrapper {
31 | float: left;
32 | width: 100%;
33 | }
34 |
35 | div.bodywrapper {
36 | margin: 0 0 0 {{ sidebar_width }};
37 | }
38 |
39 | div.sphinxsidebar {
40 | width: {{ sidebar_width }};
41 | }
42 |
43 | hr {
44 | border: 1px solid #B1B4B6;
45 | }
46 |
47 | div.body {
48 | background-color: #ffffff;
49 | color: #3E4349;
50 | padding: 0 30px 0 30px;
51 | }
52 |
53 | img.floatingflask {
54 | padding: 0 0 10px 10px;
55 | float: right;
56 | }
57 |
58 | div.footer {
59 | width: {{ page_width }};
60 | margin: 20px auto 30px auto;
61 | font-size: 14px;
62 | color: #888;
63 | text-align: right;
64 | }
65 |
66 | div.footer a {
67 | color: #888;
68 | }
69 |
70 | div.related {
71 | display: none;
72 | }
73 |
74 | div.sphinxsidebar a {
75 | color: #444;
76 | text-decoration: none;
77 | border-bottom: 1px dotted #999;
78 | }
79 |
80 | div.sphinxsidebar a:hover {
81 | border-bottom: 1px solid #999;
82 | }
83 |
84 | div.sphinxsidebar {
85 | font-size: 14px;
86 | line-height: 1.5;
87 | }
88 |
89 | div.sphinxsidebarwrapper {
90 | padding: 18px 10px;
91 | }
92 |
93 | div.sphinxsidebarwrapper p.logo {
94 | padding: 0 0 20px 0;
95 | margin: 0;
96 | text-align: center;
97 | }
98 |
99 | div.sphinxsidebar h3,
100 | div.sphinxsidebar h4 {
101 | font-family: 'Garamond', 'Georgia', serif;
102 | color: #444;
103 | font-size: 24px;
104 | font-weight: normal;
105 | margin: 0 0 5px 0;
106 | padding: 0;
107 | }
108 |
109 | div.sphinxsidebar h4 {
110 | font-size: 20px;
111 | }
112 |
113 | div.sphinxsidebar h3 a {
114 | color: #444;
115 | }
116 |
117 | div.sphinxsidebar p.logo a,
118 | div.sphinxsidebar h3 a,
119 | div.sphinxsidebar p.logo a:hover,
120 | div.sphinxsidebar h3 a:hover {
121 | border: none;
122 | }
123 |
124 | div.sphinxsidebar p {
125 | color: #555;
126 | margin: 10px 0;
127 | }
128 |
129 | div.sphinxsidebar ul {
130 | margin: 10px 0;
131 | padding: 0;
132 | color: #000;
133 | }
134 |
135 | div.sphinxsidebar input {
136 | border: 1px solid #ccc;
137 | font-family: 'Georgia', serif;
138 | font-size: 1em;
139 | }
140 |
141 | /* -- body styles ----------------------------------------------------------- */
142 |
143 | a {
144 | color: #004B6B;
145 | text-decoration: underline;
146 | }
147 |
148 | a:hover {
149 | color: #6D4100;
150 | text-decoration: underline;
151 | }
152 |
153 | div.body h1,
154 | div.body h2,
155 | div.body h3,
156 | div.body h4,
157 | div.body h5,
158 | div.body h6 {
159 | font-family: 'Garamond', 'Georgia', serif;
160 | font-weight: normal;
161 | margin: 30px 0px 10px 0px;
162 | padding: 0;
163 | }
164 |
165 | {% if theme_index_logo %}
166 | div.indexwrapper h1 {
167 | text-indent: -999999px;
168 | background: url({{ theme_index_logo }}) no-repeat center center;
169 | height: {{ theme_index_logo_height }};
170 | }
171 | {% endif %}
172 |
173 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
174 | div.body h2 { font-size: 180%; }
175 | div.body h3 { font-size: 150%; }
176 | div.body h4 { font-size: 130%; }
177 | div.body h5 { font-size: 100%; }
178 | div.body h6 { font-size: 100%; }
179 |
180 | a.headerlink {
181 | color: #ddd;
182 | padding: 0 4px;
183 | text-decoration: none;
184 | }
185 |
186 | a.headerlink:hover {
187 | color: #444;
188 | background: #eaeaea;
189 | }
190 |
191 | div.body p, div.body dd, div.body li {
192 | line-height: 1.4em;
193 | }
194 |
195 | div.admonition {
196 | background: #fafafa;
197 | margin: 20px -30px;
198 | padding: 10px 30px;
199 | border-top: 1px solid #ccc;
200 | border-bottom: 1px solid #ccc;
201 | }
202 |
203 | div.admonition tt.xref, div.admonition a tt {
204 | border-bottom: 1px solid #fafafa;
205 | }
206 |
207 | dd div.admonition {
208 | margin-left: -60px;
209 | padding-left: 60px;
210 | }
211 |
212 | div.admonition p.admonition-title {
213 | font-family: 'Garamond', 'Georgia', serif;
214 | font-weight: normal;
215 | font-size: 24px;
216 | margin: 0 0 10px 0;
217 | padding: 0;
218 | line-height: 1;
219 | }
220 |
221 | div.admonition p.last {
222 | margin-bottom: 0;
223 | }
224 |
225 | div.highlight {
226 | background-color: white;
227 | }
228 |
229 | dt:target, .highlight {
230 | background: #FAF3E8;
231 | }
232 |
233 | div.note {
234 | background-color: #eee;
235 | border: 1px solid #ccc;
236 | }
237 |
238 | div.seealso {
239 | background-color: #ffc;
240 | border: 1px solid #ff6;
241 | }
242 |
243 | div.topic {
244 | background-color: #eee;
245 | }
246 |
247 | p.admonition-title {
248 | display: inline;
249 | }
250 |
251 | p.admonition-title:after {
252 | content: ":";
253 | }
254 |
255 | pre, tt {
256 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
257 | font-size: 0.9em;
258 | }
259 |
260 | img.screenshot {
261 | }
262 |
263 | tt.descname, tt.descclassname {
264 | font-size: 0.95em;
265 | }
266 |
267 | tt.descname {
268 | padding-right: 0.08em;
269 | }
270 |
271 | img.screenshot {
272 | -moz-box-shadow: 2px 2px 4px #eee;
273 | -webkit-box-shadow: 2px 2px 4px #eee;
274 | box-shadow: 2px 2px 4px #eee;
275 | }
276 |
277 | table.docutils {
278 | border: 1px solid #888;
279 | -moz-box-shadow: 2px 2px 4px #eee;
280 | -webkit-box-shadow: 2px 2px 4px #eee;
281 | box-shadow: 2px 2px 4px #eee;
282 | }
283 |
284 | table.docutils td, table.docutils th {
285 | border: 1px solid #888;
286 | padding: 0.25em 0.7em;
287 | }
288 |
289 | table.field-list, table.footnote {
290 | border: none;
291 | -moz-box-shadow: none;
292 | -webkit-box-shadow: none;
293 | box-shadow: none;
294 | }
295 |
296 | table.footnote {
297 | margin: 15px 0;
298 | width: 100%;
299 | border: 1px solid #eee;
300 | background: #fdfdfd;
301 | font-size: 0.9em;
302 | }
303 |
304 | table.footnote + table.footnote {
305 | margin-top: -15px;
306 | border-top: none;
307 | }
308 |
309 | table.field-list th {
310 | padding: 0 0.8em 0 0;
311 | }
312 |
313 | table.field-list td {
314 | padding: 0;
315 | }
316 |
317 | table.footnote td.label {
318 | width: 0px;
319 | padding: 0.3em 0 0.3em 0.5em;
320 | }
321 |
322 | table.footnote td {
323 | padding: 0.3em 0.5em;
324 | }
325 |
326 | dl {
327 | margin: 0;
328 | padding: 0;
329 | }
330 |
331 | dl dd {
332 | margin-left: 30px;
333 | }
334 |
335 | blockquote {
336 | margin: 0 0 0 30px;
337 | padding: 0;
338 | }
339 |
340 | ul, ol {
341 | margin: 10px 0 10px 30px;
342 | padding: 0;
343 | }
344 |
345 | pre {
346 | background: #eee;
347 | padding: 7px 30px;
348 | margin: 15px -30px;
349 | line-height: 1.3em;
350 | }
351 |
352 | dl pre, blockquote pre, li pre {
353 | margin-left: -60px;
354 | padding-left: 60px;
355 | }
356 |
357 | dl dl pre {
358 | margin-left: -90px;
359 | padding-left: 90px;
360 | }
361 |
362 | tt {
363 | background-color: #ecf0f3;
364 | color: #222;
365 | /* padding: 1px 2px; */
366 | }
367 |
368 | tt.xref, a tt {
369 | background-color: #FBFBFB;
370 | border-bottom: 1px solid white;
371 | }
372 |
373 | a.reference {
374 | text-decoration: none;
375 | border-bottom: 1px dotted #004B6B;
376 | }
377 |
378 | a.reference:hover {
379 | border-bottom: 1px solid #6D4100;
380 | }
381 |
382 | a.footnote-reference {
383 | text-decoration: none;
384 | font-size: 0.7em;
385 | vertical-align: top;
386 | border-bottom: 1px dotted #004B6B;
387 | }
388 |
389 | a.footnote-reference:hover {
390 | border-bottom: 1px solid #6D4100;
391 | }
392 |
393 | a:hover tt {
394 | background: #EEE;
395 | }
396 |
--------------------------------------------------------------------------------
/flask_superadmin/static/css/admin.css:
--------------------------------------------------------------------------------
1 | /* Global styles */
2 | body
3 | {
4 | padding-top:20px;
5 | background: #F6F6F6 url(../img/background.png);
6 | font-family:'Open Sans',sans-serif;
7 | }
8 | #main-nav a {
9 | font-size:15px;
10 | color: #999;
11 | font-weight: 400;
12 | text-shadow: 0 1px rgba(255, 255, 255, .75);
13 | }
14 | @media (min-width: 767px) {
15 | body {
16 | padding-top:48px;
17 | padding-bottom:36px;
18 | }
19 | }
20 | input {
21 | line-height: 1em;
22 | }
23 | #main-nav > .active > a, #main-nav > .active > a:hover {
24 | background: none;
25 | color:#717E93;
26 | border-color:#717E93;
27 | border-radius: 0;
28 | /* text-shadow: none;
29 | background-color:#717E93;
30 | color:white;
31 | border-radius: 4px;
32 | */ }
33 | #main-nav a {
34 | /* border-left:2px solid transparent;;
35 | *//* border-top:2px solid transparent;
36 | border-bottom:2px solid transparent;
37 | */ font-size:13px;
38 | line-height: 1.3em;
39 | font-weight: 600;
40 | padding:8px 8px;
41 | margin:0;
42 | text-transform: uppercase;;
43 | }
44 | #main-nav li {
45 | border-top: 1px solid rgba(113, 126, 147, 0.1);
46 | line-height: 1em;
47 | }
48 | #main-nav {
49 | border-bottom: 1px solid rgba(113, 126, 147, 0.1);
50 |
51 | }
52 | /* #main-nav > .active > a, #main-nav > .active > a:hover {
53 | background-color:#717E93;
54 | border-radius:3px;
55 | text-shadow: white 0 1px 0 rgba(0, 0, 0, .1);
56 | color:white;
57 | }
58 | #main-nav a:hover {
59 | color:#717E93;
60 | }*/
61 |
62 | .navbar-inner {
63 | background: none;
64 | box-shadow: none;
65 | }
66 | #content {
67 | border-radius: 2px;
68 | background: white;
69 | border: 1px solid #CCC;
70 | padding: 22px 32px;
71 | box-sizing: border-box;
72 | -moz-box-sizing:border-box;
73 | -webkit-box-sizing: border-box;
74 | }
75 | .model-check {
76 | width:15px;
77 | }
78 | .table tbody tr:hover td,
79 | .table tbody tr:hover th {
80 | background: #F9F9F9;
81 | }
82 | .table tbody tr.checked td{
83 | background:rgb(255, 255, 230)!important;
84 | }
85 | .table th, .table td {
86 | border-top-color:#F2F2F2;
87 | }
88 | a {
89 | color:#3F6EC2;
90 | text-decoration: none;
91 | }
92 | a:hover {
93 | color:#717E93;
94 | }
95 |
96 | #main-title {
97 | float: left;
98 | }
99 |
100 | a.btn-title,input.btn-title, .btn-title .btn {
101 | padding: 8px 16px;
102 | text-transform: uppercase;
103 | }
104 |
105 | .btn-title.chzn-container-single .chzn-single {
106 | padding: 8px 16px 3px;
107 | text-transform: uppercase;
108 | }
109 |
110 | .btn-title.btn-group,a.btn-title,input.btn-title, .btn-title {
111 | float: right;
112 | margin-right:0;
113 | margin-left:10px;
114 | }
115 |
116 | .actions {
117 | width:160px;
118 | }
119 |
120 | form > .form-buttons > .btn.btn-primary {
121 | margin: 0;
122 | margin-left: 10px;
123 | float: right;
124 | }
125 |
126 | hr {
127 | clear:both;
128 | border-top:1px solid #CCC;
129 | margin-top:12px;
130 | margin-bottom:5px;
131 | }
132 |
133 | .page-content {
134 | margin-top: 30px;
135 | }
136 |
137 | h1,h2,h3,h4,h5,h6{
138 | font-weight: 300;
139 | }
140 |
141 | .btn,.chzn-container-single .chzn-single,.chzn-container-multi .chzn-choices .search-choice {
142 | border: 1px solid rgba(0, 0, 0, 0.25);
143 | border-radius: 1px;
144 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
145 | inset 0 1px 2px rgba(255, 255, 255, 0.75);
146 | font: inherit;
147 | margin: 0;
148 | }
149 |
150 | .btn-group .btn:first-child {
151 | border-bottom-left-radius:1px;
152 | border-top-left-radius:1px;
153 | }
154 |
155 | .btn-group .btn:last-child {
156 | border-bottom-right-radius:1px;
157 | border-top-right-radius:1px;
158 | }
159 |
160 | .chzn-container-multi .chzn-choices .search-choice {
161 | margin: 3px 0 0 3px;
162 | padding: 1px 19px 0 4px;
163 | }
164 |
165 | .chzn-container-single .chzn-single {
166 | padding-top:4px;
167 | }
168 |
169 | .datepicker {
170 | border:1px solid #AAA;
171 | }
172 |
173 | .datepicker .nav {
174 | box-shadow: none;
175 | border-radius: 3px 3px 0 0;
176 | }
177 |
178 | .chzn-container-single .chzn-search {
179 | background: #e6e6e6;
180 | border-bottom: 1px solid rgba(0, 0, 0, 0.25);
181 | }
182 |
183 | .chzn-container .chzn-results {
184 | margin: 0;
185 | padding: 0;
186 | }
187 |
188 | .chzn-container-single .chzn-search input:focus {
189 | box-shadow: none;
190 | }
191 |
192 | .btn:hover {
193 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12),
194 | inset 0 1px 2px rgba(255, 255, 255, 0.95);
195 | color: black;
196 | }
197 |
198 | .btn-primary, .btn-danger {
199 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08);
200 | text-shadow: 0 1px 0 #717E93;
201 | }
202 |
203 | .btn-primary:hover, .btn-danger:hover {
204 | color:white;
205 | box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12);
206 | }
207 |
208 | /* Form customizations */
209 | form.icon {
210 | display: inline;
211 | }
212 |
213 | form.icon button {
214 | border: none;
215 | background: transparent;
216 | text-decoration: none;
217 | padding: 0;
218 | line-height: normal;
219 | }
220 |
221 | a.icon {
222 | text-decoration: none;
223 | }
224 |
225 | /* Model search form */
226 | form.search-form {
227 | margin: 4px 0 0 0;
228 | }
229 |
230 | form.search-form a.clear i {
231 | margin: 2px 0 0 0;
232 | }
233 |
234 | /* Filters */
235 | .filter-row {
236 | margin: 4px;
237 | }
238 |
239 | .filter-row a, .filter-row select {
240 | margin-right: 4px;
241 | }
242 |
243 | .filter-row input
244 | {
245 | margin-bottom: 0px;
246 | width: 208px;
247 | }
248 |
249 | .filter-row .remove-filter
250 | {
251 | vertical-align: middle;
252 | }
253 |
254 | .filter-row .remove-filter .close-icon
255 | {
256 | font-size: 16px;
257 | }
258 |
259 | .filter-row .remove-filter .close-icon:hover
260 | {
261 | color: black;
262 | opacity: 0.4;
263 | }
264 |
265 | .dropdown-menu input {
266 | width:100%;
267 | background: none;
268 | text-align: left;
269 | border: none;
270 | box-shadow:none;
271 | }
272 |
273 | .dropdown-menu li > a:hover,
274 | .dropdown-menu .active > a,
275 | .dropdown-menu .active > a:hover,
276 | .dropdown-menu li > input:hover,
277 | .dropdown-menu .active > input,
278 | .dropdown-menu .active > input:hover {
279 | color: #ffffff;
280 | text-decoration: none;
281 | background-color: #3875D7;
282 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3875d7', endColorstr='#2a62bc', GradientType=0 );
283 | background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(20%, #3875D7), color-stop(90%, #2A62BC));
284 | background-image: -webkit-linear-gradient(top, #3875D7 20%, #2A62BC 90%);
285 | background-image: -moz-linear-gradient(top, #3875D7 20%, #2A62BC 90%);
286 | background-image: -o-linear-gradient(top, #3875D7 20%, #2A62BC 90%);
287 | background-image: linear-gradient(#3875D7 20%, #2A62BC 90%);
288 | color: white;
289 | }
290 |
291 | .search {
292 | position: relative;
293 | }
294 |
295 | .search .search-input {
296 | width: 250px;
297 | margin-bottom: 10px;
298 | padding: 4px 12px 4px 25px;
299 | border: solid 1px #999;
300 | background: #fff url("../img/search-input-icon.png") 10px 10px no-repeat;
301 | resize: none;
302 | -webkit-border-radius: 16px;
303 | -moz-border-radius: 16px;
304 | border-radius: 16px;
305 | -webkit-background-clip: padding-box;
306 | -moz-background-clip: padding;
307 | background-clip: padding-box;
308 | -webkit-box-sizing: border-box;
309 | -moz-box-sizing: border-box;
310 | box-sizing: border-box;
311 | height: 30px;
312 | }
313 |
314 | .search .clear-btn {
315 | position: absolute;
316 | right: auto;
317 | left: 225px;
318 | top: 7px;
319 | background: #ddd;
320 | border-radius: 16px;
321 | width: 16px;
322 | height: 16px;
323 | color: white;
324 | line-height: 0;
325 | cursor: pointer;
326 | -webkit-touch-callout: none;
327 | -webkit-user-select: none;
328 | -moz-user-select: none;
329 | -ms-user-select: none;
330 | -o-user-select: none;
331 | user-select: none;
332 | }
333 |
334 | .search .clear-btn:before {
335 | content: '×';
336 | padding: 9px 0 0 3px;
337 | display: inline-block;
338 | font-weight: bold;
339 | font-size: 17px;
340 | font-family: arial;
341 | }
342 |
343 |
--------------------------------------------------------------------------------