Как стать автором
Обновить

Комментарии 41

Админка за 0 минут — запустите любой Database-клиент. Я так некоторые проекты годами веду, очень удобно, все возможности.

Restfull-админка — это неудобно, потому что элементарные сущности и так можно добавлять в базу, а что-то более сложно все равно придется допиливать.
Админкой пользуются не только люди, которые умеют с бд клиентом работать. Кроме того в таких приложениях часто присутствует какая-то логика помимо просто записей в базе и не на уровне бд.

Хранимые процедуры и вот вам логика

Во многих проектах стараются их избегать. Потому что уйдет человек, которых их писал и это очень тяжело поддерживать. И с версионированием этого всего как-то непонятно. И удобнее когда вся логика в одном месте — в приложении.
Каким образом хранимые процедуры могут, например, отправить письма или сделать https запрос?
Легко, в MS SQL есть CLR-сборки, по сути вызов функций из DLL.
Source: Работаю с биллинговой системой, построенной подобным образом.
Для Postgres есть hasura (https://hasura.io/), которая позволяет создавать graphql endpoint, который имеет в себе всё что только нужно, однако, все подобные технологии пока ещё не до конца готовы и сыроваты, ещё требуется 2-3 года до готовности, когда наконец-то монолит из данных и логики будет храниться полностью в БД.
Ты же просто хранимая процедура, имитация ЯП. Разве может хранимая процедура отправить письмо или сделать http-запрос?
Вы сами себе ответили

имитация ЯП


Может ли имитация быть лучше хорошего ЯП?
Каждый инструмент должен быть на своём месте.
НЛО прилетело и опубликовало эту надпись здесь
Джанговская админка обычно всех устраивает и там есть всё что угодно из коробки или на pypi. И кастомизируется как угодно.
НЛО прилетело и опубликовало эту надпись здесь
Какой-то абстрактный пример. В админке django modelform можно кастомизировать, какие там должны быть поля и как их потом сохранить в бд Остальное накручивается на js.
НЛО прилетело и опубликовало эту надпись здесь
Ну стоимость можно сделать readonly полем и после сохранения высчитывать её. ФИО тоже после сохранения заполнять.
Стоит добавить в проект webargs и marshmallow. С ними делать REST под flask прям хорошо и прекрасно.
Спасибо за коммент. А вроде есть reqparse в flask_restful.
https://flask-restful.readthedocs.io/en/latest/api.html#module-reqparse
Нужен ли webargs?

А по-поводу marshmallow, я к нему присматриваюсь. И раз вы здесь его упомянули хотелось бы спросить: у вас есть позитивный опыт использования? Можете что-нибудь про него рассказать хорошее?
webargs позволяет задавать параметры к запросу декоратором, что с моей точки зрения удобнее. Плюс он всеядный, если специально не указывать из какого источника брать ему можно присылать в любом виде, т.е. и query params и form-data и json. Он все обработает одинаково, главное чтобы имена совпадали.
Дополнительно он имеет отличную интеграцию с marshmallow, что позволяет прям объекты напрямую из запроса доставать.
Marshmallow я использую и это единственный на данный момент сериализатор под python который нормально из PostgreSQL жрет нативные uuid. Дополнительно там есть слой совместимости с sqlalchemy. Который мне правда не актуален, я использую PonyORM.
Мне больше нравится pydantic. У себя я превращаю аргументы функции в параметры запроса так:
from pydantic import create_model
def get_query_schema(handler):
    params = inspect.signature(handler).parameters
    query_params = {k: (p.annotation, p.default) for k, p in params.items() if k not in ('pk', 'request', 'self')}
    return create_model('query_schema', **query_params)

это можно использовать потом в декораторе или middleware
@web.middleware
async def webapi_validate_query(request, handler):
    self = handler.__closure__[0].cell_contents.__self__
    if request.method not in ('GET', 'POST', 'PUT', 'DELETE'):
        raise web.HTTPMethodNotAllowed(f'{request.method} not allowed')
    query = request.query.copy()
    if self.paginator:
        self.paginator.get_page_from_query(query)
    if self.filter_class:
        self.filter = self.filter_class(**request.query)
    validated_query = self.query_schema(**query.items()).dict()
    result = await handler(request, **request.match_info, **validated_query)
    return web.json_response(result, dumps=dumps)
Вот не лень писать столько кода? Смотрите как это выглядит в случае webargs

@bp.route('/charge', methods=['POST'])
@use_args({
    "account": fields.Int(required=True),
    "agent": fields.Int(required=True),
    "ts": fields.DateTime(),
    "unit": fields.Int(required=True),
    "service": fields.UUID(required=True),
    "amount": fields.Decimal(required=True),
    "count": fields.Decimal(missing=1),
    "note": fields.Str()
})
def add_charge(args):
    schema = ChargeMaSchema()
    if args.get('amount') < 0:
        return abort(422)
    try:
        new_charge = Charge(**args)
        commit()
        return schema.jsonify(new_charge)
    except:
        abort(422)

К примеру кусок кода из моего проекта. Аннотацию входных данных можно так же брать из схемы marshmallow.

На abort дополнительно можно довесить обработчик, в webargs если требуется так же можно его довесить.
Так это код из моей библиотеки. А в самом проекте будет просто
async def test_handler(request, query: str, page: int=1): pass

Мне нравится использовать аннотации типов, этим pydantic больше нравится. В fastapi он аналогично используется
А что делать в случае если надо обрабатывать не только query?
Обычно, в rest, data это данные модели, для которых тоже есть сериализатор. Ну или писать отдельный.
Ну в случае использования webargs это там сразу из коробки, чем оно и хорошо.
norguhtar, спасибо за наводку.
Получается, что если мы хотим использовать webargs в примере из статьи.
То писать надо примерно так:
from webargs.flaskparser import use_args                                                                                                                                 
from webargs import fields    
# Code ...
class UserLogin(Resource):                                                                                                                                               
      @use_args({'username': fields.Str(),                                                                                                                                 
                 'password': fields.Str()}, locations=['json'])                                                                                                            
      def post(self, args):                                                                                                                                                
          if args.get('username') == 'admin' and args.get('password') == 'habr': 
             # Code


Верно? Я проверил, в принципе, работает.
Да. Только еще стоит добавить:

  @use_args({
'username': fields.Str(required=True),                                                                                                                                 
'password': fields.Str(required=True)}, 
locations=['json'])    


В этом случае если параметры не обнаружены, вывалит ошибку по умолчанию. Туда можно вставить свой хендлер обработки и обрабатывать отсутствие параметров единожды. Там еще есть опция missing которая позволяет задавать умолчания, с ней надо учесть, что умолчания там задаются один раз при старте. А то я там положил раз datetime.now() :)
Допустим удалим один или несколько параметров, то мы получаем в json
{
    "error": "Invalid username and password"
}


А если есть required=True, то получим
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>422 Unprocessable Entity</title>
<h1>Unprocessable Entity</h1>
<p>The request was well-formed but was unable to be followed due to semantic errors.</p>


Видимо, надо по ситуации все же решать. Либо как-то научить его отдавать json в качестве исключения.
Это отрабатывает глобальный хендлер. Его можно переопределить и это более правильный вариант обработки ошибок. Для flask webargs.readthedocs.io/en/latest/framework_support.html#flask
Если хочется отдавать 200 то так
webargs.readthedocs.io/en/latest/advanced.html#returning-http-400-responses

И все. Обработка ошибок делается один раз и выводит то что надо.
Не, я к тому, что react выведет ошибку «Invalid username and password», а в случае с required=True выведет «Network error».
Вот что будет в браузере.
А я вам и говорю, что поведение это изменяемое. И да 422 ошибка это не Network error. Она должна обрабатываться на стороне react особенно учитывая что подразумевается REST а там кодами отличными от 200 вообще-то пользоваться надо.
norguhtar, понял, спасибо :)
Просто годная библиотека для сериализации. Кстати, я тут сделал генераторы маршмаллов из моделей peewee и алхимии, может кому пригодится
github.com/pawnhearts/aiorf/blob/master/aiorf/modelschema.py
github.com/pawnhearts/aiorf/blob/master/aiorf/saschema.py
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Ну, да. Я на это тоже посмотрел, разбираться поленился и просто добавил axios.
НЛО прилетело и опубликовало эту надпись здесь
PerlPower, Благодарю!
Довелось мне поработать с react-admin.
Это какой-то сборник антипаттернов, а не фреймворк для админок.
Сойдет, если заказчика устраивает функционал точь в точь, как в демке. Шаг в сторону, получаем проблему на проблеме.
Спасибо за инфу.
Можете порекомендовать альтернативу?
Сомневаюсь, что есть хорошие альтернативы.

Я не пробовал, но встречал следующие:
cxjs.io — выглядит довольно интересно;
pro.ant.design
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации