Usage aiohttp admin

_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.

_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:

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.

from aiohttp_admin2.view 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.

from aiohttp_admin2.view 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:

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.

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

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
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.

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

user_data = UserMapper({"age": '38'})

# return False because name is required
user_data.is_valid()

Now, try to check corrected data

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.

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.

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.

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.

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.

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:

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
@postgres_injector.inject
class UserController(PostgresController, table=user):
    name = 'user'
    per_page = 10

Common 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

class ActorController(PostgresController, table=actors):
    mapper = ActorMapper

    can_create = False
_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

class ActorController(PostgresController, table=actors):
    mapper = ActorMapper

    inline_fields = ['id', 'name', 'hash', ]
_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)
class ActorController(PostgresController, table=actors):
    mapper = ActorMapper

    search_fields = ['name', ]
_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

class ActorController(PostgresController, table=actors):
    mapper = ActorMapper

    inline_fields = ['name', 'gender', ]
    list_filter = ['gender', ]
_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

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

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 apropriate controller.

snippet from the demo

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)

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

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('<img src="{path}" width="100">')\
            .format(path=obj.data.poster_path)

For that into inline_fields we add new field poster and create a function poster_field (<field_name>_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.

async def poster_field(self, obj):
    return obj.data.poster_path
_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.

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
_images/get_relation_example.png

Custom sort

To specify custom sorting we need to provide sort method into contorller class for the current field (<field_name>_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.

@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

from aiohttp_admin2.view 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.

from aiohttp_admin2.view 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 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

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
_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.

from aiohttp_admin2.view 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.
_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.

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.

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.

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.