diff options
-rw-r--r-- | ipalib/crud.py | 112 | ||||
-rw-r--r-- | tests/test_ipalib/test_crud.py | 8 |
2 files changed, 114 insertions, 6 deletions
diff --git a/ipalib/crud.py b/ipalib/crud.py index 173fefc72..77c97f3f4 100644 --- a/ipalib/crud.py +++ b/ipalib/crud.py @@ -16,14 +16,114 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + """ Base classes for standard CRUD operations. + +These base classes are for `Method` plugins that provide standard +Create, Retrieve, Updated, and Delete operations (CRUD) for their corresponding +`Object` plugin. In particuar, these base classes provide logic to +automatically create the plugin args and options by inspecting the params on +their corresponding `Object` plugin. This provides a single point of definition +for LDAP attributes and enforces a simple, consistent API for CRUD operations. + +For example, say we want CRUD operations on a hypothetical "user" entry. First +we need an `Object` plugin: + +>>> from ipalib import Object, Str +>>> class user(Object): +... takes_params = ( +... Str('login', primary_key=True), +... Str('first'), +... Str('last'), +... Str('ipauniqueid', flags=['no_create', 'no_update']), +... ) +... + +Next we need `Create`, `Retrieve`, `Updated`, and `Delete` plugins, and +optionally a `Search` plugin. For brevity, we'll just define `Create` and +`Retrieve` plugins: + +>>> from ipalib import crud +>>> class user_add(crud.Create): +... pass +... +>>> class user_show(crud.Retrieve): +... pass +... + +Now we'll register the plugins and finalize the `plugable.API` instance: + +>>> from ipalib import create_api +>>> api = create_api() +>>> api.register(user) +>>> api.register(user_add) +>>> api.register(user_show) +>>> api.finalize() + +First, notice that our ``user`` `Object` has the params we defined with the +``takes_params`` tuple: + +>>> list(api.Object.user.params) +['login', 'first', 'last', 'ipauniqueid'] +>>> api.Object.user.params.login +Str('login', primary_key=True) + +Although we defined neither ``takes_args`` nor ``takes_options`` for our +``user_add`` plugin, the `Create` base class automatically generated them for +us: + +>>> list(api.Command.user_add.args) +['login'] +>>> list(api.Command.user_add.options) +['first', 'last'] + +Notice that ``'ipauniqueid'`` isn't included in the options for our ``user_add`` +plugin. This is because of the ``'no_create'`` flag we used when defining the +``ipauniqueid`` param. Often times there are LDAP attributes that are +automatically created by the server and therefor should not be supplied as an +option to the `Create` plugin. Often these same attributes shouldn't be +update-able either, in which case you can also supply the ``'no_update'`` flag, +as we did with our ``ipauniqueid`` param. Lastly, you can also use the ``'no_search'`` flag for attributes that shouldn't be search-able (because, for +example, the attribute isn't indexed). + +As with our ``user_add` plugin, we defined neither ``takes_args`` nor +``takes_options`` for our ``user_show`` plugin; instead the `Retrieve` base +class created them for us: + +>>> list(api.Command.user_show.args) +['login'] +>>> list(api.Command.user_show.options) +[] + +As you can see, `Retrieve` plugins take a single argument (the primary key) and +no options. If needed, you can still specify options for your `Retrieve` plugin +with a ``takes_options`` tuple. + +Flags like ``'no_create'`` remove LDAP attributes from those that can be +supplied as *input* to a `Method`, but they don't effect the attributes that can +be returned as *output*. Regardless of what flags have been used, the output +entry (or list of entries) can contain all the attributes defined on the +`Object` plugin (in our case, the above ``user.params``). + +For example, compare ``user.params`` with ``user_add.output_params`` and +``user_show.output_params``: + +>>> list(api.Object.user.params) +['login', 'first', 'last', 'ipauniqueid'] +>>> list(api.Command.user_add.output_params) +['login', 'first', 'last', 'ipauniqueid'] +>>> list(api.Command.user_show.output_params) +['login', 'first', 'last', 'ipauniqueid'] + +Note that the above are all equal. """ +from frontend import Method, Object import backend, frontend, parameters, output -class Create(frontend.Method): +class Create(Method): """ Create a new entry. """ @@ -39,13 +139,15 @@ class Create(frontend.Method): for option in super(Create, self).get_options(): yield option for option in self.obj.params_minus(self.args): + if 'no_create' in option.flags: + continue yield option.clone(attribute=True) if not self.extra_options_first: for option in super(Create, self).get_options(): yield option -class PKQuery(frontend.Method): +class PKQuery(Method): """ Base class for `Retrieve`, `Update`, and `Delete`. """ @@ -75,6 +177,8 @@ class Update(PKQuery): for option in super(Update, self).get_options(): yield option for option in self.obj.params_minus_pk(): + if 'no_update' in option.flags: + continue yield option.clone(attribute=True, required=False, autofill=False) if not self.extra_options_first: for option in super(Update, self).get_options(): @@ -89,7 +193,7 @@ class Delete(PKQuery): has_output = output.standard_delete -class Search(frontend.Method): +class Search(Method): """ Retrieve all entries that match a given search criteria. """ @@ -104,6 +208,8 @@ class Search(frontend.Method): for option in super(Search, self).get_options(): yield option for option in self.obj.params_minus(self.args): + if 'no_search' in option.flags: + continue yield option.clone( attribute=True, query=True, required=False, autofill=False ) diff --git a/tests/test_ipalib/test_crud.py b/tests/test_ipalib/test_crud.py index 47d51c5dc..969fb4fd1 100644 --- a/tests/test_ipalib/test_crud.py +++ b/tests/test_ipalib/test_crud.py @@ -23,6 +23,7 @@ Test the `ipalib.crud` module. from tests.util import read_only, raises, get_api, ClassChecker from ipalib import crud, frontend, plugable, config +from ipalib.parameters import Str class CrudChecker(ClassChecker): @@ -38,9 +39,10 @@ class CrudChecker(ClassChecker): class user(frontend.Object): takes_params = ( 'givenname', - 'sn', - frontend.Param('uid', primary_key=True), + Str('sn', flags='no_update'), + Str('uid', primary_key=True), 'initials', + Str('uidnumber', flags=['no_create', 'no_search']) ) class user_verb(self.cls): takes_args = args @@ -102,7 +104,7 @@ class test_Update(CrudChecker): """ api = self.get_api() assert list(api.Method.user_verb.options) == \ - ['givenname', 'sn', 'initials'] + ['givenname', 'initials', 'uidnumber'] for param in api.Method.user_verb.options(): assert param.required is False |