Pull to refresh

Comments 41

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

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

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

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

имитация ЯП


Может ли имитация быть лучше хорошего ЯП?
Каждый инструмент должен быть на своём месте.
UFO just landed and posted this here
Джанговская админка обычно всех устраивает и там есть всё что угодно из коробки или на pypi. И кастомизируется как угодно.
UFO just landed and posted this here
Какой-то абстрактный пример. В админке django modelform можно кастомизировать, какие там должны быть поля и как их потом сохранить в бд Остальное накручивается на js.
UFO just landed and posted this here
Ну стоимость можно сделать 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 вообще-то пользоваться надо.
UFO just landed and posted this here
UFO just landed and posted this here
Ну, да. Я на это тоже посмотрел, разбираться поленился и просто добавил axios.
UFO just landed and posted this here
Довелось мне поработать с react-admin.
Это какой-то сборник антипаттернов, а не фреймворк для админок.
Сойдет, если заказчика устраивает функционал точь в точь, как в демке. Шаг в сторону, получаем проблему на проблеме.
Спасибо за инфу.
Можете порекомендовать альтернативу?
Сомневаюсь, что есть хорошие альтернативы.

Я не пробовал, но встречал следующие:
cxjs.io — выглядит довольно интересно;
pro.ant.design
Sign up to leave a comment.

Articles