Commit aafb9889 authored by Serge S. Koval's avatar Serge S. Koval

Merge branch 'master' of github.com:mrjoes/flask-admin

parents b7f53eb9 551aad3c
...@@ -8,15 +8,14 @@ Flask-Admin ...@@ -8,15 +8,14 @@ Flask-Admin
Introduction Introduction
------------ ------------
This is library for building adminstrative interface on top of Flask framework. This is a library for building an administrative interface on top of the Flask framework.
Flask-Admin comes with few batteries out of the box: model scaffolding for `SQLAlchemy <http://www.sqlalchemy.org/>`_, Flask-Admin comes with a few batteries included: model scaffolding for `SQLAlchemy <http://www.sqlalchemy.org/>`_,
`MongoEngine <http://mongoengine.org/>`_ and `Peewee <https://github.com/coleifer/peewee>`_ ORMs and `MongoEngine <http://mongoengine.org/>`_ and `Peewee <https://github.com/coleifer/peewee>`_ ORMs as well as a simple file management interface.
simple file management interface.
However, you're not limited by provided functionality - instead of providing simple scaffolding for the ORM You're not limited by the default functionality - instead of providing simple scaffolding for the ORM
models, Flask-Admin provides tools that can be used to build adminstrative interface of any complexity, models, Flask-Admin provides tools that can be used to construct administrative interfaces of any complexity,
using consistent look and feel. using a consistent look and feel.
Documentation Documentation
------------- -------------
...@@ -26,7 +25,7 @@ Flask-Admin is extensively documented, you can find `documentation here <http:// ...@@ -26,7 +25,7 @@ Flask-Admin is extensively documented, you can find `documentation here <http://
3rd Party Stuff 3rd Party Stuff
--------------- ---------------
Flask-Admin is built with help of `Twitter Bootstrap <http://twitter.github.com/bootstrap/>`_ and `Select2 <https://github.com/ivaynberg/select2>`_. Flask-Admin is built with the help of `Twitter Bootstrap <http://twitter.github.com/bootstrap/>`_ and `Select2 <https://github.com/ivaynberg/select2>`_.
Kudos Kudos
----- -----
...@@ -36,4 +35,4 @@ Some ideas were taken from the `Flask-Admin <https://github.com/wilsaj/flask-adm ...@@ -36,4 +35,4 @@ Some ideas were taken from the `Flask-Admin <https://github.com/wilsaj/flask-adm
Examples Examples
-------- --------
Library comes with few examples, you can find them in `examples` directory. The library comes with a few examples, you can find them in the `examples` directory.
...@@ -29,10 +29,10 @@ class ActionsMixin(object): ...@@ -29,10 +29,10 @@ class ActionsMixin(object):
Actions mixin. Actions mixin.
In some cases, you might work with more than one "entity" (model, file, etc) in In some cases, you might work with more than one "entity" (model, file, etc) in
your admin view and will want to perform actions on group of entities at once. your admin view and will want to perform actions on a group of entities simultaneously.
In this case, you can add this functionality by doing this: In this case, you can add this functionality by doing this:
1. Add mixin to your administrative view class 1. Add this mixin to your administrative view class
2. Call `init_actions` in your class constructor 2. Call `init_actions` in your class constructor
3. Expose actions view 3. Expose actions view
4. Import `actions.html` library and add call library macros in your template 4. Import `actions.html` library and add call library macros in your template
...@@ -76,7 +76,7 @@ class ActionsMixin(object): ...@@ -76,7 +76,7 @@ class ActionsMixin(object):
def get_actions_list(self): def get_actions_list(self):
""" """
Return list and a dictionary of allowed actions. Return a list and a dictionary of allowed actions.
""" """
actions = [] actions = []
actions_confirmation = {} actions_confirmation = {}
......
...@@ -107,7 +107,7 @@ class BaseView(object): ...@@ -107,7 +107,7 @@ class BaseView(object):
arguments you want to pass to the template and call parent view. arguments you want to pass to the template and call parent view.
These arguments are local for this request and will be discarded These arguments are local for this request and will be discarded
in next request. in the next request.
Any value passed through ``_template_args`` will override whatever Any value passed through ``_template_args`` will override whatever
parent view function passed to the template. parent view function passed to the template.
...@@ -133,17 +133,17 @@ class BaseView(object): ...@@ -133,17 +133,17 @@ class BaseView(object):
Constructor. Constructor.
:param name: :param name:
Name of this view. If not provided, will be defaulted to the class name. Name of this view. If not provided, will default to the class name.
:param category: :param category:
View category. If not provided, will be shown as a top-level menu item. Otherwise, will View category. If not provided, this view will be shown as a top-level menu item. Otherwise, it will
be in a submenu. be in a submenu.
:param endpoint: :param endpoint:
Base endpoint name for the view. For example, if there's view method called "index" and Base endpoint name for the view. For example, if there's a view method called "index" and
endpoint was set to "myadmin", you can use `url_for('myadmin.index')` to get URL to the endpoint is set to "myadmin", you can use `url_for('myadmin.index')` to get the URL to the
view method. By default, equals to the class name in lower case. view method. Defaults to the class name in lower case.
:param url: :param url:
Base URL. If provided, affects how URLs are generated. For example, if url parameter Base URL. If provided, affects how URLs are generated. For example, if the url parameter
equals to "test", resulting URL will look like "/admin/test/". If not provided, will is "test", the resulting URL will look like "/admin/test/". If not provided, will
use endpoint as a base url. However, if URL starts with '/', absolute path is assumed use endpoint as a base url. However, if URL starts with '/', absolute path is assumed
and '/admin/' prefix won't be applied. and '/admin/' prefix won't be applied.
""" """
...@@ -230,7 +230,7 @@ class BaseView(object): ...@@ -230,7 +230,7 @@ class BaseView(object):
def _prettify_name(self, name): def _prettify_name(self, name):
""" """
Prettify class name by splitting name by capital characters. So, 'MySuperClass' will look like 'My Super Class' Prettify a class name by splitting the name on capitalized characters. So, 'MySuperClass' becomes 'My Super Class'
:param name: :param name:
String to prettify String to prettify
...@@ -241,10 +241,10 @@ class BaseView(object): ...@@ -241,10 +241,10 @@ class BaseView(object):
""" """
Override this method to add permission checks. Override this method to add permission checks.
Flask-Admin does not make any assumptions about authentication system used in your application, so it is Flask-Admin does not make any assumptions about the authentication system used in your application, so it is
up for you to implement it. up to you to implement it.
By default, it will allow access for the everyone. By default, it will allow access for everyone.
""" """
return True return True
...@@ -252,7 +252,7 @@ class BaseView(object): ...@@ -252,7 +252,7 @@ class BaseView(object):
""" """
This method will be executed before calling any view method. This method will be executed before calling any view method.
By default, it will check if admin class is accessible and if it is not - will By default, it will check if the admin class is accessible and if it is not it will
throw HTTP 404 error. throw HTTP 404 error.
:param name: :param name:
...@@ -278,10 +278,10 @@ class AdminIndexView(BaseView): ...@@ -278,10 +278,10 @@ class AdminIndexView(BaseView):
admin = Admin(index_view=MyHomeView()) admin = Admin(index_view=MyHomeView())
Default values for the index page are following: Default values for the index page are:
* If name is not provided, 'Home' will be used. * If a name is not provided, 'Home' will be used.
* If endpoint is not provided, will use ``admin`` * If an endpoint is not provided, will default to ``admin``
* Default URL route is ``/admin``. * Default URL route is ``/admin``.
* Automatically associates with static folder. * Automatically associates with static folder.
* Default template is ``admin/index.html`` * Default template is ``admin/index.html``
...@@ -351,7 +351,7 @@ class MenuItem(object): ...@@ -351,7 +351,7 @@ class MenuItem(object):
class Admin(object): class Admin(object):
""" """
Collection of the views. Also manages menu structure. Collection of the admin views. Also manages menu structure.
""" """
def __init__(self, app=None, name=None, def __init__(self, app=None, name=None,
url=None, subdomain=None, url=None, subdomain=None,
...@@ -364,19 +364,19 @@ class Admin(object): ...@@ -364,19 +364,19 @@ class Admin(object):
:param app: :param app:
Flask application object Flask application object
:param name: :param name:
Application name. Will be displayed in main menu and as a page title. If not provided, defaulted to "Admin" Application name. Will be displayed in the main menu and as a page title. Defaults to "Admin"
:param url: :param url:
Base URL Base URL
:param subdomain: :param subdomain:
Subdomain to use Subdomain to use
:param index_view: :param index_view:
Home page view to use. If not provided, will use `AdminIndexView`. Home page view to use. Defaults to `AdminIndexView`.
:param translations_path: :param translations_path:
Location of the translation message catalogs. By default will use translations Location of the translation message catalogs. By default will use the translations
shipped with the Flask-Admin. shipped with Flask-Admin.
:param endpoint: :param endpoint:
Base endpoint name for index view. If you use multiple instances of `Admin` class with Base endpoint name for index view. If you use multiple instances of the `Admin` class with
one Flask application, you have to set unique endpoint name for each instance. a single Flask application, you have to set a unique endpoint name for each instance.
""" """
self.app = app self.app = app
...@@ -407,7 +407,7 @@ class Admin(object): ...@@ -407,7 +407,7 @@ class Admin(object):
def add_view(self, view): def add_view(self, view):
""" """
Add view to the collection. Add a view to the collection.
:param view: :param view:
View to add. View to add.
...@@ -422,7 +422,7 @@ class Admin(object): ...@@ -422,7 +422,7 @@ class Admin(object):
def locale_selector(self, f): def locale_selector(self, f):
""" """
Installs locale selector for current ``Admin`` instance. Installs a locale selector for the current ``Admin`` instance.
Example:: Example::
...@@ -453,7 +453,7 @@ class Admin(object): ...@@ -453,7 +453,7 @@ class Admin(object):
def _add_view_to_menu(self, view): def _add_view_to_menu(self, view):
""" """
Add view to the menu tree Add a view to the menu tree
:param view: :param view:
View to add View to add
...@@ -472,7 +472,7 @@ class Admin(object): ...@@ -472,7 +472,7 @@ class Admin(object):
def init_app(self, app): def init_app(self, app):
""" """
Register all views with Flask application. Register all views with the Flask application.
:param app: :param app:
Flask application instance Flask application instance
...@@ -506,6 +506,6 @@ class Admin(object): ...@@ -506,6 +506,6 @@ class Admin(object):
def menu(self): def menu(self):
""" """
Return menu hierarchy. Return the menu hierarchy.
""" """
return self._menu return self._menu
...@@ -162,7 +162,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -162,7 +162,7 @@ class FileAdmin(BaseView, ActionsMixin):
:param base_url: :param base_url:
Base URL for the files Base URL for the files
:param name: :param name:
Name of this view. If not provided, will be defaulted to the class name. Name of this view. If not provided, will default to the class name.
:param category: :param category:
View category View category
:param endpoint: :param endpoint:
...@@ -171,7 +171,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -171,7 +171,7 @@ class FileAdmin(BaseView, ActionsMixin):
URL for view URL for view
:param verify_path: :param verify_path:
Verify if path exists. If set to `True` and path does not exist Verify if path exists. If set to `True` and path does not exist
will throw exception. will raise an exception.
""" """
self.base_path = base_path self.base_path = base_path
self.base_url = base_url self.base_url = base_url
...@@ -198,7 +198,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -198,7 +198,7 @@ class FileAdmin(BaseView, ActionsMixin):
def is_accessible_path(self, path): def is_accessible_path(self, path):
""" """
Verify if path is accessible for current user. Verify if the provided path is accessible for the current user.
Override to customize behavior. Override to customize behavior.
...@@ -242,7 +242,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -242,7 +242,7 @@ class FileAdmin(BaseView, ActionsMixin):
def is_file_editable(self, filename): def is_file_editable(self, filename):
""" """
Verify if file can be edited. Determine if the file can be edited.
Override to customize behavior. Override to customize behavior.
...@@ -261,7 +261,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -261,7 +261,7 @@ class FileAdmin(BaseView, ActionsMixin):
def is_in_folder(self, base_path, directory): def is_in_folder(self, base_path, directory):
""" """
Verify if `directory` is in `base_path` folder Verify that `directory` is in `base_path` folder
:param base_path: :param base_path:
Base directory path Base directory path
...@@ -319,9 +319,9 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -319,9 +319,9 @@ class FileAdmin(BaseView, ActionsMixin):
""" """
Verify and normalize path. Verify and normalize path.
If path is not relative to the base directory, will throw 404 exception. If the path is not relative to the base directory, will raise a 404 exception.
If path does not exist, will also throw 404 exception. If the path does not exist, this will also raise a 404 exception.
""" """
base_path = self.get_base_path() base_path = self.get_base_path()
...@@ -353,7 +353,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -353,7 +353,7 @@ class FileAdmin(BaseView, ActionsMixin):
Index view method Index view method
:param path: :param path:
Optional directory path. If not provided, will use base directory Optional directory path. If not provided, will use the base directory
""" """
# Get path and verify if it is valid # Get path and verify if it is valid
base_path, directory, path = self._normalize_path(path) base_path, directory, path = self._normalize_path(path)
...@@ -406,7 +406,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -406,7 +406,7 @@ class FileAdmin(BaseView, ActionsMixin):
Upload view method Upload view method
:param path: :param path:
Optional directory path. If not provided, will use base directory Optional directory path. If not provided, will use the base directory
""" """
# Get path and verify if it is valid # Get path and verify if it is valid
base_path, directory, path = self._normalize_path(path) base_path, directory, path = self._normalize_path(path)
...@@ -439,7 +439,7 @@ class FileAdmin(BaseView, ActionsMixin): ...@@ -439,7 +439,7 @@ class FileAdmin(BaseView, ActionsMixin):
Directory creation view method Directory creation view method
:param path: :param path:
Optional directory path. If not provided, will use base directory Optional directory path. If not provided, will use the base directory
""" """
# Get path and verify if it is valid # Get path and verify if it is valid
base_path, directory, path = self._normalize_path(path) base_path, directory, path = self._normalize_path(path)
......
...@@ -75,7 +75,7 @@ class ModelView(BaseModelView): ...@@ -75,7 +75,7 @@ class ModelView(BaseModelView):
""" """
Field to filter converter. Field to filter converter.
Override this attribute to use non-default converter. Override this attribute to use a non-default converter.
""" """
column_type_formatters = DEFAULT_FORMATTERS column_type_formatters = DEFAULT_FORMATTERS
...@@ -123,7 +123,7 @@ class ModelView(BaseModelView): ...@@ -123,7 +123,7 @@ class ModelView(BaseModelView):
def get_pk_value(self, model): def get_pk_value(self, model):
""" """
Return primary key value from the model instance Return the primary key value from the model instance
:param model: :param model:
Model instance Model instance
...@@ -154,7 +154,7 @@ class ModelView(BaseModelView): ...@@ -154,7 +154,7 @@ class ModelView(BaseModelView):
def scaffold_sortable_columns(self): def scaffold_sortable_columns(self):
""" """
Return sortable columns dictionary (name, field) Return a dictionary of sortable columns (name, field)
""" """
columns = {} columns = {}
...@@ -222,7 +222,7 @@ class ModelView(BaseModelView): ...@@ -222,7 +222,7 @@ class ModelView(BaseModelView):
def is_valid_filter(self, filter): def is_valid_filter(self, filter):
""" """
Validate if it is valid MongoEngine filter Validate if the provided filter is a valid MongoEngine filter
:param filter: :param filter:
Filter object Filter object
...@@ -254,7 +254,7 @@ class ModelView(BaseModelView): ...@@ -254,7 +254,7 @@ class ModelView(BaseModelView):
:param search: :param search:
Search criteria Search criteria
:param filters: :param filters:
List of applied fiters List of applied filters
:param execute: :param execute:
Run query immediately or not Run query immediately or not
""" """
...@@ -307,7 +307,7 @@ class ModelView(BaseModelView): ...@@ -307,7 +307,7 @@ class ModelView(BaseModelView):
def get_one(self, id): def get_one(self, id):
""" """
Return single model instance by ID Return a single model instance by its ID
:param id: :param id:
Model ID Model ID
......
...@@ -3,7 +3,7 @@ from peewee import PrimaryKeyField ...@@ -3,7 +3,7 @@ from peewee import PrimaryKeyField
def get_primary_key(model): def get_primary_key(model):
for n, f in model._meta.get_sorted_fields(): for n, f in model._meta.get_sorted_fields():
if type(f) == PrimaryKeyField: if type(f) == PrimaryKeyField or f.primary_key:
return n return n
......
...@@ -18,7 +18,7 @@ from .typefmt import DEFAULT_FORMATTERS ...@@ -18,7 +18,7 @@ from .typefmt import DEFAULT_FORMATTERS
class ModelView(BaseModelView): class ModelView(BaseModelView):
""" """
SQLALchemy model view SQLAlchemy model view
Usage sample:: Usage sample::
...@@ -90,7 +90,7 @@ class ModelView(BaseModelView): ...@@ -90,7 +90,7 @@ class ModelView(BaseModelView):
class MyModelView(ModelView): class MyModelView(ModelView):
column_searchable_list = (User.name, User.email) column_searchable_list = (User.name, User.email)
Following search rules apply: The following search rules apply:
- If you enter *ZZZ* in the UI search field, it will generate *ILIKE '%ZZZ%'* - If you enter *ZZZ* in the UI search field, it will generate *ILIKE '%ZZZ%'*
statement against searchable columns. statement against searchable columns.
...@@ -103,8 +103,8 @@ class ModelView(BaseModelView): ...@@ -103,8 +103,8 @@ class ModelView(BaseModelView):
- If you prefix your search term with ^, it will find all rows - If you prefix your search term with ^, it will find all rows
that start with ^. So, if you entered *^ZZZ*, *ILIKE 'ZZZ%'* will be used. that start with ^. So, if you entered *^ZZZ*, *ILIKE 'ZZZ%'* will be used.
- If you prefix your search term with =, it will do exact match. - If you prefix your search term with =, it will perform an exact match.
For example, if you entered *=ZZZ*, *ILIKE 'ZZZ'* statement will be used. For example, if you entered *=ZZZ*, the statement *ILIKE 'ZZZ'* will be used.
""" """
column_filters = None column_filters = None
...@@ -163,11 +163,11 @@ class ModelView(BaseModelView): ...@@ -163,11 +163,11 @@ class ModelView(BaseModelView):
""" """
If set to `False` and user deletes more than one model using built in action, If set to `False` and user deletes more than one model using built in action,
all models will be read from the database and then deleted one by one all models will be read from the database and then deleted one by one
giving SQLAlchemy chance to manually cleanup any dependencies (many-to-many giving SQLAlchemy a chance to manually cleanup any dependencies (many-to-many
relationships, etc). relationships, etc).
If set to `True`, will run `DELETE` statement which is somewhat faster, If set to `True`, will run a `DELETE` statement which is somewhat faster,
but might leave corrupted data if you forget to configure `DELETE but may leave corrupted data if you forget to configure `DELETE
CASCADE` for your model. CASCADE` for your model.
""" """
...@@ -195,9 +195,9 @@ class ModelView(BaseModelView): ...@@ -195,9 +195,9 @@ class ModelView(BaseModelView):
class MyModelView(ModelView): class MyModelView(ModelView):
inline_models = (MyInlineModelForm(MyInlineModel),) inline_models = (MyInlineModelForm(MyInlineModel),)
You can customize generated field name by: You can customize the generated field name by:
1. Using `form_name` property as option: 1. Using the `form_name` property as a key to the options dictionary:
class MyModelView(ModelView): class MyModelView(ModelView):
inline_models = ((Post, dict(form_label='Hello'))) inline_models = ((Post, dict(form_label='Hello')))
...@@ -226,15 +226,15 @@ class ModelView(BaseModelView): ...@@ -226,15 +226,15 @@ class ModelView(BaseModelView):
:param model: :param model:
Model class Model class
:param session: :param session:
SQLALchemy session SQLAlchemy session
:param name: :param name:
View name. If not set, will default to model name View name. If not set, defaults to the model name
:param category: :param category:
Category name Category name
:param endpoint: :param endpoint:
Endpoint name. If not set, will default to model name Endpoint name. If not set, defaults to the model name
:param url: :param url:
Base URL. If not set, will default to '/admin/' + endpoint Base URL. If not set, defaults to '/admin/' + endpoint
""" """
self.session = session self.session = session
...@@ -270,19 +270,19 @@ class ModelView(BaseModelView): ...@@ -270,19 +270,19 @@ class ModelView(BaseModelView):
# Scaffolding # Scaffolding
def scaffold_pk(self): def scaffold_pk(self):
""" """
Return primary key name from a model Return the primary key name from a model
""" """
return tools.get_primary_key(self.model) return tools.get_primary_key(self.model)
def get_pk_value(self, model): def get_pk_value(self, model):
""" """
Return PK value from a model object. Return the PK value from a model object.
""" """
return getattr(model, self._primary_key) return getattr(model, self._primary_key)
def scaffold_list_columns(self): def scaffold_list_columns(self):
""" """
Return list of columns from the model. Return a list of columns from the model.
""" """
columns = [] columns = []
...@@ -307,7 +307,7 @@ class ModelView(BaseModelView): ...@@ -307,7 +307,7 @@ class ModelView(BaseModelView):
def scaffold_sortable_columns(self): def scaffold_sortable_columns(self):
""" """
Return dictionary of sortable columns. Return a dictionary of sortable columns.
Key is column name, value is sort column/field. Key is column name, value is sort column/field.
""" """
columns = dict() columns = dict()
...@@ -321,7 +321,7 @@ class ModelView(BaseModelView): ...@@ -321,7 +321,7 @@ class ModelView(BaseModelView):
column = p.columns[0] column = p.columns[0]
# Can't sort by on primary and foreign keys by default # Can't sort on primary or foreign keys by default
if column.foreign_keys: if column.foreign_keys:
continue continue
...@@ -382,7 +382,7 @@ class ModelView(BaseModelView): ...@@ -382,7 +382,7 @@ class ModelView(BaseModelView):
def is_text_column_type(self, name): def is_text_column_type(self, name):
""" """
Verify if column type is text-based. Verify if the provided column type is text-based.
:returns: :returns:
``True`` for ``String``, ``Unicode``, ``Text``, ``UnicodeText`` ``True`` for ``String``, ``Unicode``, ``Text``, ``UnicodeText``
...@@ -478,7 +478,7 @@ class ModelView(BaseModelView): ...@@ -478,7 +478,7 @@ class ModelView(BaseModelView):
def is_valid_filter(self, filter): def is_valid_filter(self, filter):
""" """
Verify that provided filter object is derived from the Verify that the provided filter object is derived from the
SQLAlchemy-compatible filter class. SQLAlchemy-compatible filter class.
:param filter: :param filter:
...@@ -521,7 +521,7 @@ class ModelView(BaseModelView): ...@@ -521,7 +521,7 @@ class ModelView(BaseModelView):
def scaffold_auto_joins(self): def scaffold_auto_joins(self):
""" """
Return list of joined tables by going through the Return a list of joined tables by going through the
displayed columns. displayed columns.
""" """
if not self.column_auto_select_related: if not self.column_auto_select_related:
...@@ -668,7 +668,7 @@ class ModelView(BaseModelView): ...@@ -668,7 +668,7 @@ class ModelView(BaseModelView):
def get_one(self, id): def get_one(self, id):
""" """
Return one model by its id. Return a single model by its id.
:param id: :param id:
Model id Model id
......
This diff is collapsed.
...@@ -30,7 +30,7 @@ def import_attribute(name): ...@@ -30,7 +30,7 @@ def import_attribute(name):
:param name: :param name:
String reference. String reference.
Throws ImportError or AttributeError if module or attribute do not exist. Raises ImportError or AttributeError if module or attribute do not exist.
Example:: Example::
...@@ -80,7 +80,7 @@ def rec_getattr(obj, attr, default=None): ...@@ -80,7 +80,7 @@ def rec_getattr(obj, attr, default=None):
def get_dict_attr(obj, attr, default=None): def get_dict_attr(obj, attr, default=None):
""" """
Get attibute of the object without triggering its __getattr__. Get attribute of the object without triggering its __getattr__.
:param obj: :param obj:
Object Object
...@@ -98,7 +98,7 @@ def get_dict_attr(obj, attr, default=None): ...@@ -98,7 +98,7 @@ def get_dict_attr(obj, attr, default=None):
def get_property(obj, name, old_name, default=None): def get_property(obj, name, old_name, default=None):
""" """
Check if old property name exists and if it is - show warning message Check if old property name exists and if it does - show warning message
and return value. and return value.
Otherwise, return new property value Otherwise, return new property value
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment