.. _tgadmin:

=================================
Working with the TurboGears Admin
=================================

TurboGears provides the ``tgext.admin`` extension which is powered by ``tgext.crud``
and ``sprox``. This can be used to automatically create simple administration pages
and is the toolkit powering the ``/admin`` page in newly quickstarted applications.

By default the admin will provide autogenerated access to all the models
imported in your project ``models/__init__.py``.

While the default configuration for the admin usually *just works* it doesn't provide
the best experience for users and might require some customizations.

To do so the TurboGears Admin permits to provide custom configurations throught the
``AdminController`` ``config_type`` paramter.

The default turbogears admin is created as::

    admin = AdminController(model, DBSession, config_type=TGAdminConfig)

which creates an admin for all the models with the default TurboGears admin
configuration.

Restricting Access to some Models
=================================

Restricting access to some models is possible by specifying them explicitly
instead of passing ``model`` as the first argument to the AdminController:

.. code-block:: python

    from myproject.model import User, Group

    admin = AdminController([User, Group], DBSession, config_type=TGAdminConfig)

Switching to a Custom Admin Configuration
=========================================

First step to perform if you want to switch to a customized administration
configuration is to declare your own config. This can easily be done by
subclassing ``TGAdminConfig``:

.. code-block:: python

    class CustomAdminConfig(TGAdminConfig):
        pass

if you replace ``config_type=TGAdminConfig`` with your new ``CustomAdminConfig``:

.. code-block:: python

    admin = AdminController(model, DBSession, config_type=CustomAdminConfig)

You will notice that everything works as before, as we didn't change the configuration
in any way.
The custom configuration will contains properties for the admin itself and for
each CRUD inside the admin.

Apart from configuration for each crud, there are some general admin configuration
options:

.. autoclass:: tgext.admin.config.AdminConfig

Customizing Models CRUD
=======================

The admin page can be configured using the ``TGAdminConfig`` class,
supposing we have a game with running Match and a list of Settings
we can declared ``MatchAdminController`` and ``SettingAdminController``
which inherit from ``EasyCrudRestController`` and tell TurboGears
Admin to use them for the administration of matches and settings:

.. code-block:: python

    class CustomAdminConfig(TGAdminConfig):
        class match(CrudRestControllerConfig):
            defaultCrudRestController = MatchAdminController
        class setting(CrudRestControllerConfig):
            defaultCrudRestController = SettingAdminController

    class RootController(BaseController):
        admin = AdminController([model.Match, model.Setting], DBSession,
                                config_type=CustomAdminConfig)

This will create an administration controller which uses our custom CrudRestControllers
to manage Match and Settings instances.

Each class attribute of our ``CustomAdminConfig`` that has the *lower case*
name of a Model will be used to configure the admin CRUD for that model.

This can be done by subclassing ``CrudRestControllerConfig`` with
a ``defaultCrudRestController`` class attribute that points to the
CrudRestController class to use for the related model.

Defining Custom Controllers
---------------------------

The explicit way to define models configuration is by declaring
``CrudRestControllers`` and configurations separately, but in case
the custom controllers are required only for the admin it might be
shortest to just define them together:

.. code-block:: python

    class MyModelAdminCrudConfig(CrudRestControllerConfig):
        class defaultCrudRestController(EasyCrudRestController):
            __table_options__ = {
                # options here...
            }

    class CustomAdminConfig(TGAdminConfig):
        photo = MyModelAdminCrudConfig

For a complete list of options available inside ``defaultCrudRestController``
refer to :ref:`tgext.crud.controller`.

Photos Admin Tutorial
---------------------

Now suppose we have a photo model that looks like:

.. code-block:: python

    class Photo(DeclarativeBase):
        __tablename__ = 'photos'

        uid = Column(Integer, primary_key=True)
        title = Column(Unicode(255), nullable=False)
        image = Column(LargeBinary, nullable=False)
        mimetype = Column(Unicode(64), nullable=False, default='image/jpeg')

        user_id = Column(Integer, ForeignKey('tg_user.user_id'), index=True)
        user = relationship('User', uselist=False,
                            backref=backref('photos',
                                            cascade='all, delete-orphan'))

we might want to customize our admin to use the ``GroupedBootstrapAdminLayout``
layout, put the photos inside the *Media* group and improve the table view
for the crud by removing the ``user_id`` and ``mimetype`` columns
and declare the ``image`` column as xml so that we can show the image inside:

.. code-block:: python

    import base64
    from tgext.admin import CrudRestControllerConfig
    from tgext.admin.tgadminconfig import BootstrapTGAdminConfig as TGAdminConfig
    from tgext.admin.layouts import GroupedBootstrapAdminLayout
    from tgext.admin.controller import AdminController as TGAdminController
    from tgext.crud import EasyCrudRestController


    class PhotoAdminCrudConfig(CrudRestControllerConfig):
        icon_class = 'glyphicon-picture'
        admin_group = 'Media'

        class defaultCrudRestController(EasyCrudRestController):
            __table_options__ = {
                '__omit_fields__': ['user_id', 'mimetype'],
                '__xml_fields__': ['image'],

                'image': lambda filler, row: '<img src="data:%s;base64,%s" width="256"/>' % (
                    row.mimetype,
                    base64.b64encode(row.image).decode('ascii')
                )
            }


    class CustomAdminConfig(TGAdminConfig):
        layout = GroupedBootstrapAdminLayout

        photo = PhotoAdminCrudConfig

Now our admin works as expected and uses the ``PhotoAdminCrudConfig`` to
manage photos. There is still an open issue, when uploading photos we
have to manually provide the photo ``user`` and ``mimetype``.

To solve this issue we will customize the form hiding the two values
from the form and forcing them when data is submitted:

.. code-block:: python

    import base64, imghdr
    from tg import expose, request
    from tgext.admin import CrudRestControllerConfig
    from tgext.admin.tgadminconfig import BootstrapTGAdminConfig as TGAdminConfig
    from tgext.admin.layouts import GroupedBootstrapAdminLayout
    from tgext.admin.controller import AdminController as TGAdminController
    from tgext.crud import EasyCrudRestController


    class PhotoAdminCrudConfig(CrudRestControllerConfig):
        icon_class = 'glyphicon-picture'
        admin_group = 'Media'

        class defaultCrudRestController(EasyCrudRestController):
            __table_options__ = {
                '__omit_fields__': ['user_id', 'mimetype'],
                '__xml_fields__': ['image'],

                'image': lambda filler, row: '<img src="data:%s;base64,%s" width="256"/>' % (
                    row.mimetype,
                    base64.b64encode(row.image).decode('ascii')
                )
            }

            __form_options__ = {
                '__hide_fields__': ['user', 'mimetype']
            }

            @expose(inherit=True)
            def post(self, *args, **kw):
                kw['user'] = request.identity['user'].user_id
                kw['mimetype'] = 'image/%s' % imghdr.what(kw['image'].file)
                return EasyCrudRestController.post(self, *args, **kw)

            @expose(inherit=True)
            def put(self, *args, **kw):
                image = kw.get('image')
                if image is not None and image.filename:
                    kw['mimetype'] = 'image/%s' % imghdr.what(kw['image'].file)
                return EasyCrudRestController.put(self, *args, **kw)


    class CustomAdminConfig(TGAdminConfig):
        layout = GroupedBootstrapAdminLayout

        photo = PhotoAdminCrudConfig