.. meta::
:description: The aiohttp admin for sqlalchemy and umongo.
:keywords: admin, aiohttp, admin-dashboard, admin-panel, aiohttp-admin, umongo, python, sqlalchemy, asyncio
Usage aiohttp admin
===================
.. image:: /images/overview_header.png
For the beginning let's make a simple overview of architecture and main
components. The `aiohttp_admin` has small list of main components which
responsible for different function of admin interface:
- **resource** - this component implement all method (get/delete/update etc)
that need to communicate with databases. So, if you want to add support of
database which is not exist right now on `aiohttp_admin` then can just create
your resource object for that and all other components of admin interface will
work with it togather without any problems
- **controller** - in this component allocate business logic related with some
model. What are fields we need to show on list view? How many items do we need
to show on each list page? What is order we need to use by default? What do
we need to do before/after create/update instance? How do models related? All
these question about controller. Controller use `resource` to get access
to database and `mapper` for validate input data from user.
- **mapper** - this component responsible for validation and convert input
data from user which will use for update or create instance.
- **view** - this component responsible for represent result for user via some
async web framework (now we use aiohttp but you can to implement views for
other web framework and use other all components of aiohttp admin without
any problems).
Let's consider a simple example of books library. We have table of authors and
books. Each book has one or more authors. Each author has one or more books.
Relation between author and books is many to many and implement via a separate
table. All these tables stores in the PostgreSQL but large book's files
allocated in the MongoDB.
.. image:: /images/overview.png
We can to see that relation between models implement on controller level and we
can to bind models from different storages together.
Authorization & Permissions
---------------------------
Authorization
.............
If you need an authorization to the your admin interface then you can add
custom middleware to achieve this goal. It might look like this:
.. code-block:: python
from aiohttp import web
from aiohttp_security import is_anonymous
from aiohttp_security import permits
from aiohttp_admin2 import setup_admin
@web.middleware
async def admin_access_middleware(request, handler):
if await is_anonymous(request):
raise web.HTTPFound('/')
if not await permits(request, 'admin'):
raise web.HTTPFound('/')
return await handler(request)
setup_admin(
application,
# You can specify here a list of middlewares which you want to apply
# to each request related with admin interface
middleware_list=[admin_access_middleware, ],
...
)
In the snippet above we just check that user is not anonymous and has admin
permissions. The logic of `is_anonymous` and `permits` you have to implement
by yourself because admin just guarantee the for each admin route will apply
list of middlewares which you specify in the **middleware_list** param.
The `aiohhtp_admin2` don't provide any views for login/logout logic so all of
this logic you need implement by yourself.
Permissions
...........
For organization permissions in your admin interface you have to use the
`access_hook` method in the view class. Since the `aiohhtp_admin2` instantiate
new view for each request and after that run `access_hook` method therefore
inside this method you can easy change any propery of the current view instance
to restrict access.
As an example you have a ActorView class and you want to show information
related with this view only for users who have correct rights for that.
.. code-block:: python
from aiohttp_admin2.views import ControllerView
class ActorView(ControllerView):
controller = ActorController
async def access_hook(self) -> None:
if not user_can_view(self.request, 'aсtors'):
self.has_access = False
In `access_hook` method we can to get current request so we just pass it to the
predicate function (`user_can_view`) and change property if need. If user
without right access visit any route related with current view then he gets
the `PermissionDenied` exception. If you want only hide view from aside menu
than you have to use `is_hide_view` property instead.
Let's consider case when you need to give only read right or give right to
create but without edit rights.
.. code-block:: python
from aiohttp_admin2.views import ControllerView
class ActorView(ControllerView):
controller = ActorController
async def access_hook(self) -> None:
# here we get controller instance of the current view
controller = self.get_controller()
controller.can_view = user_can_view(self.request, 'aсtors')
controller.can_edit = user_can_edit(self.request, 'aсtors')
controller.can_delete = user_can_delete(self.request, 'aсtors')
controller.can_create = user_can_create(self.request, 'aсtors')
if is_guest(self.request):
controller.inline_fields = ['id', ]
self.template_detail_name = 'aiohttp_admin/detail_view_for_guest.html'
controller.per_page = 20
We can change any property of controller even `inline_fields` or `per_page`
if we need to do that.
.. warning::
The `access_hook` method is async function so you actually can to do
request to databases inside it to check permission but it's not a good
idea because for each request the admin call this method for each view
(to check that we can show link to views in aside menu) and that can
produce n + 1 requests. The better approach is get all rights inside
`middelware` and set this info to request and inside `access_hook` method
just check that request contain right access.
Mappers
-------
Mapper is schema for validation and converting data which income from user and
use for create or update instances. You can create mapper in two ways.
Custom mappers
..............
You can create your own mapper with custom fields:
.. code-block:: python
from aiohttp_admin2.mappers import Mapper
from aiohttp_admin2.mappers import fields
class UserMapper(Mapper):
"""Mapper for user instance."""
name = fields.StringField(required=True)
age = fields.IntField(default=18)
Mappers generator
.................
If you create admin page for SQLalchemy or Umongo instances then you can
generate mapping automatically by specifying models.
.. code-block:: python
from aiohttp_admin2.mappers.generics import PostgresMapperGeneric
from aiohttp_admin2.mappers import fields
user = sa.Table('user', metadata,
sa.Column('name', sa.String(255)),
sa.Column('age', sa.Integer),
)
class UserMapper(PostgresMapperGeneric, table=user):
"""Mapper for user instance."""
pass
but if you want to rewrite some field you can do it some like that
.. code-block:: python
from aiohttp_admin2.mappers.generics import PostgresMapperGeneric
from aiohttp_admin2.mappers import fields
class UserMapper(PostgresMapperGeneric, table=user):
"""Mapper for user instance."""
age = fields.StringField(required=True)
In this case generic will generate all fields for you but will use age field
which you specify.
Fields
......
**StringField, LongStringField, UrlImageField, UrlFileField, UrlField** - field for represented string data.
- *required* - add validation for empty value if set to `True`
- *default* - replace empty value if specify
- *validators* - list of validators
- *primary_key* - `True` if current field is a primary key
**IntField, SmallIntField** - field for represented integer data.
- *required* - add validation for empty value if set to `True`
- *default* - replace empty value if specify
- *validators* - list of validators
- *primary_key* - `True` if current field is a primary key
**FloatField** - field for represented float data.
- *required* - add validation for empty value if set to `True`
- *default* - replace empty value if specify
- *validators* - list of validators
- *primary_key* - `True` if current field is a primary key
**DateTimeField, DateField** - field for represented datetime data.
- *required* - add validation for empty value if set to `True`
- *default* - replace empty value if specify (you can specify str or
datetime/date object)
- *validators* - list of validators
- *primary_key* - `True` if current field is a primary key
**BooleanField** - field for represented boolean data. If value contains '0',
'false' or 'f' than value will be parse as `False` in other case as `True`.
- *required* - add validation for empty value if set to `True`
- *default* - replace empty value if specify
- *validators* - list of validators
- *primary_key* - `True` if current field is a primary key
**ChoicesField** - add predefined values. If you have some finite list of values
and want that this list will represented like select tag you need to use
current field type.
- *required* - add validation for empty value if set to `True`
- *default* - replace empty value if specify
- *validators* - list of validators
- *field_cls* - field type which will represent selected value
- *choices* - tuple of tuple with values. It might look like this
`[('admin title of value1', 'value1'), ('admin title of value1', 'value2')])`
- *primary_key* - `True` if current field is a primary key
- *empty_value* - need to to specify string which will show if a value is not
set. By default it's `-- empty --`.
**ArrayField** - field for represented array data. Instances inside array must
to have the same type. To specify this type you have to provide `field_cls`
- *required* - add validation for empty value if set to `True`
- *default* - replace empty value if specify
- *validators* - list of validators
- *field_cls* - field type which will represent data type of items inside array
- *primary_key* - `True` if current field is a primary key
- *min_length, max_length* - add validation related with min/max length of
array
**JsonField** - field for represented data in json type format.
- *required* - add validation for empty value if set to `True`
- *default* - replace empty value if specify
- *validators* - list of validators
- *primary_key* - `True` if current field is a primary key
.. code-block:: python
from aiohttp_admin2.mappers.generics import PostgresMapperGeneric
from aiohttp_admin2.mappers import fields
class UserMapper(PostgresMapperGeneric, table=user):
"""Mapper for user instance."""
GENDER_CHOICES = (
('male', "male"),
('female', "female"),
)
gender = fields.ChoicesField(
field_cls=fields.StringField,
choices=GENDER_CHOICES,
default='male'
)
In common you do not use mappers you need to create these only for internal
usage for aiohttp admin but for a better understanding of why they are needed,
let's take a look at how they are used.
.. code-block:: python
from aiohttp_admin2.mappers import Mapper
from aiohttp_admin2.mappers import fields
class UserMapper(Mapper):
"""Mapper for user instance."""
name = fields.StringField(required=True)
age = fields.IntField(default=18)
Let's try to validate wrong data
.. code-block:: python
user_data = UserMapper({"age": '38'})
# return False because name is required
user_data.is_valid()
Now, try to check corrected data
.. code-block:: python
user_data = UserMapper({"age": '38', "name": "mike"})
# return True because all is fine
user_data.is_valid()
print(user_data.data)
# {'name': 'mike', 'age': 38}
`user_data.data` return converting data in right type. We can see that string
'38' have been successful converting to int value 38.
.. note::
The primary key is required fields for any models when we wanna update
instance but when we need to create instance we don't know it (when a
storage autoincrement it). For these purposes fields have `primary_key`
property. If this property set to True and we try to create instance then
mapper will ignore `required` errors related with current field. For that
we need just specify `skip_primary` to `True` into `is_valid` method.
.. code-block:: python
from aiohttp_admin2.mappers import Mapper
from aiohttp_admin2.mappers import fields
class UserMapper(Mapper):
"""Mapper for user instance."""
id = fields.IntField(primary_key=True, required=True)
name = fields.StringField(required=True)
# False
UserMapper({"name": "Mike", "id": None}).is_valid()
# True
UserMapper({"name": "Mike", "id": None}).is_valid(skip_primary=True)
So when you don't use generators for your models or rewrite primary key
fields then don't forget to specify `primary key` property.
Validators
..........
We also can add custom validators for some particular field. Let's consider
case when we need to validate string value and check that this value has
valid format for phone number. To do this we need to create validation function
which raise exception if value is not corrected.
.. code-block:: python
import re
from aiohttp_admin2.mappers import Mapper
from aiohttp_admin2.mappers import fields
from aiohttp_admin2.mappers.exceptions import ValidationError
PHONE_REG = re.compile(r'^[0-9]{10,14}$')
def phone_validator(value):
if not PHONE_REG.match(value):
raise ValidationError("wrong phone format")
class UserMapper(Mapper):
"""Mapper for user instance."""
name = fields.StringField(required=True)
phone = fields.StringField(validators=[phone_validator])
# return False because '1234' is not valid format for a phone number
UserMapper({'name': 'Mike', 'phone': '1234'}).is_valid()
You also can to use standard validators from the `aiohttp_admin2.mappers.validators` module.
.. code-block:: python
from aiohttp_admin2.mappers import Mapper
from aiohttp_admin2.mappers import fields
from aiohttp_admin2.mappers.validators import length
class UserMapper(Mapper):
"""Mapper for user instance."""
name = fields.StringField(validators=[length(max_value=10, min_value=3)])
Controllers
-----------
The controller is class that generate access to the your data based on some
engine (Resource). Out of the box you have engines for different storages
- PostgreSQL
- MySQL
- MongoDB (in progress)
but you actually can easy to add your own engine.
The controller is framework and database agnostic part of the admin. It's mean
that controller have not to know any about request/response, generation of
urls, templates and so on. Also it have not to know about how to
get/update/delete data from some database (this logic need to allocate
into the resource class).
For the PostgreSQL, an easier way to create a controller is to use the
`PostgresController`.
.. code-block:: python
from aiohttp_admin2.controllers.postgres_controller import PostgresController
@postgres_injector.inject
class UserController(PostgresController, table=user):
mapper = UserMapper
name = 'user'
per_page = 10
For the `MongoDB` and the `MySQL` you can use `MongoController` and
`MySQLController` apropriate.
The Controller need to have connection for engine. For this goal we need to
inject connection by `ConnectionInjector`.
.. code-block:: python
from aiohttp_admin2.connection_injectors import ConnectionInjector
postgres_injector = ConnectionInjector()
async def init_db(app):
# Context function for initialize connection to db
engine = await aiopg.sa.create_engine(
user='postgres',
database='postgres',
host='0.0.0.0',
password='postgres',
)
app['db'] = engine
# here we add connection for our injector
postgres_injector.init(engine)
After that you can user `postgres_injector` to decorate your controllers. For
`MongoController` you don't need to use `ConnectionInjector` because connection
to db exist in table instance.
.. note::
If you don't need to customize some field or add new field in mapper that
based on you model then you may don't put mapper in the controller class.
In this case controller will generate this mapper instead of you. Examples
which represented below are equals:
.. code-block:: python
from aiohttp_admin2.controllers.postgres_controller import PostgresController
from aiohttp_admin2.mappers.generics import PostgresMapperGeneric
from aiohttp_admin2.mappers import fields
class UserMapper(PostgresMapperGeneric, table=user):
"""Mapper for user instance."""
@postgres_injector.inject
class UserController(PostgresController, table=user):
# implicit specify a mapper
mapper = UserMapper
name = 'user'
per_page = 10
.. code-block:: python
@postgres_injector.inject
class UserController(PostgresController, table=user):
name = 'user'
per_page = 10
Common controller settings
..........................
**access settings**
- *can_create (default True)* - `True` if can to edit an instance
- *can_update (default True)* - `True` if can to update an instance
- *can_delete (default True)* - `True` if can to delete an instance
- *can_view (default True)* - `True` if can to show an instance
If we remove access for some user to some controller then `aiohttp admin` will
automatically hide all url to do this action from interface but if user visit
current page directly then admin show error message.
*snippet from the demo*
.. code-block:: python
class ActorController(PostgresController, table=actors):
mapper = ActorMapper
can_create = False
.. image:: /images/access_settings_result.png
**list settings**
- *inline_fields (default ['id'])* - list of fields which will show on the list
page
*snippet from the demo*
.. code-block:: python
class ActorController(PostgresController, table=actors):
mapper = ActorMapper
inline_fields = ['id', 'name', 'hash', ]
.. image:: /images/inline_fields_example.png
For user on the list page we show only three fields.
- *search_fields (default [])* - list of fields which will use for do search
(fields must be searchable)
.. code-block:: python
class ActorController(PostgresController, table=actors):
mapper = ActorMapper
search_fields = ['name', ]
.. image:: /images/search_fields_example.png
After specify current settings into admin interface you can see search input.
- *order_by (defaault `id`)* - name of field for the default sorting
- *per_page (defaault `50`)* - default count of items per page
- *list_filter (default [])* - list of fields which can to use filters
*snippet from the demo*
.. code-block:: python
class ActorController(PostgresController, table=actors):
mapper = ActorMapper
inline_fields = ['name', 'gender', ]
list_filter = ['gender', ]
.. image:: /images/filters_example.png
After specify current settings into admin interface you can see filter sidebar
with filter for corresponding field.
**detail settings**
- *read_only_fields (default [])* - list of fields which can't modify (on the
detail page u can see current fields but can't edit)
- *exclude_update_fields (default `id`)* - list of fields which can't update
(fields will be hide on update page)
- *exclude_create_fields (default `id`)* - list of fields which can't specify
during create a new instance
- *fields (default `__all__`)* - list of available fields
- *autocomplete_search_fields (default [])* - list of feilds which will use to
the autocomplete (when you update/create relation fields you just set primary
key to input. For improve user experience you can set list of fields which will
use to search suggestion items in current input.)
**common settings**
- *mapper* - a mapper for the current controller
- *relations_to_one (default [])* - list of `ToOneRelation` which describe
one-to-one relation with other controllers
- *relations_to_many (default [])* - list of `ToManyRelation` which describe
many-to-many relation with other controllers
Operations hooks
................
If you need to do some before/after create/update or delete some data you can
use hooks:
- *pre_create* - run before create instance
- *pre_delete* - run before delete instance
- *pre_update* - run before update instance
- *post_create* - run after create instance
- *post_delete* - run after delete instance
- *post_update* - run after update instance
Let's say that you need to delete key in Redis after delete user instance in
PostgeSQL. It might look like this
.. code-block:: python
from aiohttp_admin2.controllers.postgres_controller import PostgresController
from .redis import redis_client
@postgres_injector.inject
class UserController(PostgresController, table=user):
mapper = UserMapper
name = 'user'
async post_delete(self, pk):
await redis_client.delete(f'user:{pk}')
Relations
.........
**One-to-one relation**
To declare one-to-one relation in `aiohttp admin` you need to create the
`ToOneRelation` from the `aiohttp_admin2.controllers.relations` module. Created
object you need to add to `relations_to_one` list in apropriate controller.
*snippet from the demo*
.. code-block:: python
class ActorMovieController(PostgresController, table=movies_actors):
mapper = ActorMoviesMapper
relations_to_one = [
ToOneRelation(
name='movie_id',
field_name='movie_id',
controller=MoviesController,
),
]
`ToOneRelation`
- *name* - name of relation
- *field_name* - name of the field which responsible for the current relation
- *controller* - controller of related models (can be callable object)
**Many-to-many relation**
To declare many-to-many relation in aiohttp admin you need to create the
`ToManyRelation` from the `aiohttp_admin2.controllers.relations` module.
Created object you need to add to `relations_to_many` list in appropriate
controller.
*snippet from the demo*
.. code-block:: python
class MoviesController(PostgresController, table=movies):
mapper = MoviesMapper
name = 'movies'
relations_to_many = [
ToManyRelation(
name='Actors',
left_table_pk='movie_id',
relation_controller=lambda: ActorMovieController
),
]
`ToManyRelation`
- *name* - name of relation
- *left_table_pk* - name of the field which responsible for the current
relation
- *relation_controller* - controller of related models (can be callable object)
- *view_settings* - override a controller view settings
.. note::
`ToManyRelation` create a controller view for the current controller with default
settings. If you need to specify any view's settings (like `template_delete_name`,
`infinite_scroll` etc) use `view_settings` for it.
.. code-block:: python
ToManyRelation(
name='products',
left_table_pk='id',
view_settings={"infinite_scroll": True},
relation_controller=lambda: ProductController,
)
Custom fields
.............
On list page you can add custom fields or rewrite view of existing. Let's
consider case from the demo related with image representation. Each movie has
a picture url but on list page view want to show image block.
*snippet from the demo*
.. code-block:: python
from markupsafe import Markup
class MoviesController(PostgresController, table=movies):
mapper = MoviesMapper
name = 'movies'
inline_fields = ['poster', 'title', ]
async def poster_field(self, obj):
return Markup('
')\
.format(path=obj.data.poster_path)
For that into `inline_fields` we add new field `poster` and create a function
`poster_field` (_field) which receive as second argument the
current `Instance` object. Also for give access use html in field without
escaping we need to wrap our html in a `Markup` object.
To get the field value from the `Instance` object, we need to get the data
property and try to get the field which we need.
.. code-block:: python
async def poster_field(self, obj):
return obj.data.poster_path
.. image:: /images/custom_fields_example.png
Also you can to get relation instances inside custom fields, for that just use
`get_relation` method of `Instance` class to get related `Instance` object
from other controller.
.. code-block:: python
from aiohttp_admin2.controllers.relations import ToOneRelation
class ActorMovieController(PostgresController, table=movies_actors):
mapper = ActorMoviesMapper
inline_fields = ['id', 'title', ]
relations_to_one = [
ToOneRelation(
# relation name
name='movie_id',
field_name='movie_id',
controller=MoviesController,
),
ToOneRelation(
# relation name
name='actor_id',
field_name='actor_id',
controller=ActorController,
),
]
async def title_field(self, obj):
# get via relation name
actor = await obj.get_relation('actor_id')
# get via relation name
movie = await obj.get_relation('movie_id')
return actor.data.name + "|" + movie.data.title
.. image:: /images/get_relation_example.png
Custom sort
...........
To specify custom sorting we need to provide sort method into contorller class
for the current field (_sort). This function receive `is_reverse`
that mean need we return reverse sorting or not.
In example below we add custom field which from json field `data` get key and
implement sorting for this field in the `data_field_sort` method.
.. code-block:: python
@postgres_injector.inject
class UsersController(PostgresController, table=users):
mapper = UsersMapper
inline_fields = ['id', 'data', ]
async def data_field(self, obj) -> str:
if obj.data.payload and isinstance(obj.data.payload, dict):
return obj.data.data
return ''
def data_field_sort(self, is_reverse):
if is_reverse:
return sa.text("payload ->> 'data' desc")
return sa.text("payload ->> 'data'")
Views
-----
This class use for represent data on the admin interface. The simples view
which you can to create is `TemplateView`.
TemplateView
............
.. code-block:: python
from aiohttp_admin2.views import TemplateView
class NewPage(TemplateView):
title = 'new page'
template_name = 'aiohttp_admin/my_template.html'
You can change specify template for you custom view as in example above or
specify `content` variable in jinja's context.
.. code-block:: python
from aiohttp_admin2.views import TemplateView
class NewPage(TemplateView):
title = 'new page'
async def get_context(self, req):
return {
**await super().get_context(req=req),
"content": "My custom content"
}
- template_name - path to template for current page
*Dashboards* view is just subclass of `TemplateView` which you can to customize
in the same way.
Common view settings
....................
All view has properties which describe below:
- *is_hide_view* - if we don't want to show link on current views in the aside
bar then we need to set True
- *group_name* - If views have the same group name then they will organize
together into separate block in the aside bar
- *name* - This string will use as the pretty name of the current views in the
admin interface.
We can to see how below settings work together
.. code-block:: python
from aiohttp_admin2.views import TemplateView
class FirstView(TemplateView):
group_name = 'first group'
name = 'first view'
class SecondView(TemplateView):
group_name = 'first group'
name = 'second view'
class ThirdView(TemplateView):
group_name = 'second group'
name = 'third view'
class FourthView(TemplateView):
group_name = 'second group'
name = 'fourth view'
class FifthView(TemplateView):
group_name = 'second group'
name = 'fifth view'
# hide current view
is_hide_view = True
.. image:: /images/groups_example.png
We can see that first and second views concat in single group in a side menu
because common `group_name` and the same story with third and fourth views but
fifth doesn't exist in menu because the view has `is_hide_view` setting set
to `True`.
- *index_url* - The url prefix path for all routes related with the current views
- *icon* - This string set a type of icon which will use in aside bar for the
current views (full list of available icons you can
to find `here `_)
ControllerView
..............
Controller view is view for representation information related with your
models.
.. code-block:: python
from aiohttp_admin2.views import ControllerView
class UserView(ControllerView):
controller = UserController
You can specify templates which you wanna use for instead of default:
- *template_list_name* - the template for list page (with a simple pagination)
- *template_list_cursor_name* - the template for list page (with an infinite
scroll)
- *template_detail_name* - the template for detail page in read only mode
- *template_detail_edit_name* - the template for detail page in edit mode
- *template_detail_create_name* - the template for create page
- *template_delete_name* - the template for delete page
also you can to specify:
- *infinite_scroll* (True/False default False) - if set to `True` then will use
infinite scroll instead of standard pagination. It can be very helpful when
table is so large and count query (which need to generate standard pagination
bar) is so cost.
.. image:: /images/infinity_example.png
After specify current setting to `True` we can to see that standard pagination
bar has been replaced by `Next` button.
- *search_filter* (default `SearchFilter`) - filter which will use for search
(for search input at the top of list page)
- *fields_widgets* (default empty dict) - a map of field names and coresponding
widngets. It's helpful if you want to specify a some special widget for the
particular field.
- *type_widgets* (default empty dict) - a map of field type and coresponding
widngets. It's helpful if you want to specify a some special widget for the
particular type of field.
- *foreignkey_widget* (default `AutocompleteStringWidget`) - a widget which
will use for the autocomplete
View's Widgets and Filters
..........................
The widgets and filters class need only to allocated path to the template and
extra `.css` and `.js` files which need to corrected render of it. Custom widget
have to inherit from `BaseWidget`. Custom filter have to inherit from
`FilerBase`.
Custom Routes
...............
You can use `@route` decorator to add custom endpoint to your view
.. code-block:: python
from aiohttp_admin2.views import ControllerView
from aiohttp_admin2.views.aiohttp.views.utils import route
class UserView(ControllerView):
controller = UserController
@route(r'/{pk:\d+}/ban/', method='POST')
def ban_user(self, req):
# ban_user(req.match_info['pk'])
return await self.get_detail(req)
`@route` takes 2 parameters: url and method. Valid methods are: `POST`, `GET`, `PUT`, `DELETE`, `HEAD`.
The URL must always start and end with `/`.
Templates
---------
For generate pages `aiohttp_admin` use `jinja2`.
If you setup `aiohttp_jinja2` with not default `jinja_app_key` argument then
you should initialize admin interface with your `jinja_app_key` argument.
.. code-block:: python
aiohttp_admin.setup_admin(app, jinja_app_key='my_jinja_value')
Overriding jinja templates
..........................
You can rewrite native templates for `aiohttp_admin`. For that you should
create `aiohttp_admin` directory into templates's directory for the `jinja2`
and create your template with name of template witch you want to rewrite.
The full list of templates you can see below:
- aiohttp_admin/blocks/header.html - the header for base layout
- aiohttp_admin/layouts/base.html - the base layout
- aiohttp_admin/layouts/create_page.html - the content for create page
- aiohttp_admin/layouts/delete_page.html - the content for confirm delete page
- aiohttp_admin/layouts/detail_view_page.html - the content for detail page in read only mode
- aiohttp_admin/layouts/detail_edit_page.html - the content for edit page
- aiohttp_admin/layouts/custom_page.html - the content for custom page
- aiohttp_admin/layouts/custom_tab_page.html - the content for custom tab
- aiohttp_admin/layouts/list_page.html - the content for list page (with a simple pagination)
- aiohttp_admin/layouts/list_cursor_page.html - the content for list page (with an infinite scroll)
- aiohttp_admin/blocks/from/form.html - the main form for create and update
- aiohttp_admin/blocks/from/field_errors.html - the macro for form's errors
- aiohttp_admin/blocks/from/field_title.html - the macro for form's title
- aiohttp_admin/blocks/from/fields/* - the macros for different types of fields
- aiohttp_admin/blocks/filters/* - the macros for different types of filters (in the left aside bar)
- aiohttp_admin/blocks/pagination.html - the pagination block
- aiohttp_admin/blocks/cursor_pagination.html - the infinity scroll pagination block
- aiohttp_admin/blocks/list_action_buttons.html - the list actions for list page
- aiohttp_admin/blocks/list_cell.html - the macro for table cell
- aiohttp_admin/blocks/list_objects_block.html - the table for list page
- aiohttp_admin/blocks/list_objects_header_block.html - the header of table for list page
- aiohttp_admin/blocks/messages.html - the macro for message's notification bar
- aiohttp_admin/blocks/nav_aside.html - the aside with pages links
- aiohttp_admin/blocks/tabs_bar.html - the template for tabs
Overriding view templates
.........................
You also can specify template for some special `ControllerView`.
.. code-block:: python
class UserPage(ControllerView):
controller = UserController
template_list_name = 'aiohttp_admin/layouts/list_page.html'
template_list_cursor_name = 'aiohttp_admin/layouts/list_cursor_page.html'
template_detail_name = 'aiohttp_admin/layouts/detail_view_page.html'
template_detail_edit_name = 'aiohttp_admin/layouts/detail_edit_page.html'
template_detail_create_name = 'aiohttp_admin/layouts/create_page.html'
template_delete_name = 'aiohttp_admin/layouts/delete_page.html'
Resources
---------
So, we already told that `Resources` is a class which implement method to work
with some particular database. If you want to implement your own `Resources`
you need just inherit from `AbstractResource` and implement methods which
described below:
- **get_one** - Get one an instance from a storage. This method receive primary
key of an database's object and return the `Instance` if object exist else
raise the `InstanceDoesNotExist` exception.
- **get_many** - Get many instances by ids from a storage. This method will
use as a dataloader. This method mainly will use on list page in cases when
need to show field with data from related model for prevent N + 1. This
method receive list of primary keys of an database's objects and name of
primary key after that return dict where keys are primary keys and as a
values corresponding Instance objects (InstanceMapper).
- **delete** - Delete instance. This method receive primary key of instance
and delete it or raise the `InstanceDoesNotExist` exception if object
doesn't exist.
- **create** - Create instance. This method receive `Instance` object and
return it from databases after create.
- **update** - Update instance. This method receive primary key and `Instance`
object after that update an object in databases and return corresponding
`Instance` object.
- **get_list** - Get list of instances. This method will use for show list of
instances. The current method have to implement possible to pagination,
filtering and sorting.
**PostgresResource**
- **get_list_select** - In this method you can redefine query. It might helpful
when you need to use need to do join or add to response a field based on
some aggregation
Filters
.......
For filtering data resources use Filters objects. Filter object can apply
condition expressions to query. Each filter inherit from `ABCFilter` class and
provide `apply` method which will apply to query conditions.