Comments 11
Вы могли заметить небольшое дублирование:
get_object_or_404
вызывается и вget
, и вpost
. «Книжный» способ решить это при использовании базового классаView
— переопределить методdispatch
. Он выполняется до вызоваget
илиpost
, поэтому логично поместить туда подготовительную логику
Для этого есть отдельный метод setup
Согласен, setup классный. Только он вызывается до вызова dispatch, и если у Kevin Renskers стоит какой-нибудь dispatch декоратор через method_decorator то может произойти вызов объекта раньше чем это вообще надо... хотя о чем это я, Kevin не такой... ну я про то, что он не стал бы использовать декораторы...
@python_leader спасибо за перевод.
Перевожу мой комментарий к оригиналу статьи:
Забавная статья. Автор не хочет запоминать другие «магические» методы и по-прежнему использует магию, такую как get_object_or_404, redirect или что-то еще.
А если автор хочет избежать дублирования кода, он предложил поместить код в «dispatch». Как он предлагает изменить dispatch? Используя тот же код, что и в методе get_object из SingleObjectMixin.
Субъективно, но однострочный миксин в определении класса все же проще, чем import + переопределение метода + ...
Но в общем подход автора показывает, что он, вероятно, не понимает важную идею GCBV: декларативный подход.
Декларативный подход означает меньше кода.
Декларативный подход означает меньше тестирования.
Декларативный подход означает меньшую сложность (например, можно использовать метрики Халстеда).
Декларативный подход означает меньшую цикломатическую сложность.
Декларативный подход означает меньше документации.
Я согласен с этой статьей, если автор зарабатывает деньги в зависимости от количества строк кода. В этом случае Black-formatter также может помочь получить гораздо больше строк.
P.s. Kevin Renskers ссылается на туториал от Luke Plant, контрибьютора в те самые DGBV. Люк сожалеет (I hate it when that happens…), что попал в список соавторов django.views.generic. Однако, я пробежал по комиттам и не нашел ни одной написанной им строчки кода, мне это показалось очень странным. Хочу верить, что я просто ошибаюсь.
Лично для меня любая декларативность наоборот повышает сложность, так как приходится тратить дополнительные усилия на то, чтобы понять, как же оно там реально работает внутри. А с императивностью всё просто сразу на виду без лишних абстракций
однострочный миксин в определении класса все же проще
Нет, явный вызов конкретной функции всегда проще и понятнее, чем прятать всё за миксином, который переопределяет непонятно что непонятно в каком порядке (я не желаю гулять по всей иерархии классов, чтобы выяснить, да что же блин происходит в моём коде, и поэтому не использую CBV в своих проектах, кстати удачи с ромбовидным наследованием)
Но код, написанный автором, мне тоже не нравится: автор зачем-то использовал класс, продолбал аннотации типов, разделил фактически идентичные get и post и из-за этого оказался вынужден обмазаться функциями-хелперами, от которых можно было бы спокойно избавиться:
def comment_form(request: HttpRequest, post_id: int) -> HttpResponse:
post = get_object_or_404(Post, pk=post_id)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect(post)
else:
form = CommentForm()
return render(request, "form.html", {"post": post, "form": form})
Это паттерн, который я использую в своих проектах и который даже совпадает с тем, что написано в документации Django — код не только в два раза короче, но и, по моему субъективному мнению, НАМНОГО проще для понимания и дальнейшей поддержки. Не надо скакать по иерархии классов, не надо даже скакать по функциям — всё на виду и максимально очевидно
Я слишком долго работаю с чистой Django... и устал от всего этого 💩 (как от FBV так и от GCBV.
По мне:
class CommentView (UpdateView, DetailView):
form = CommentFrom
success_url = "something" # because redirect(post) from previous example is wrong
все же короче и не требует тестирования.
Как минимум — вы куда-то потеряли рендеринг поста и возвращение обратно к странице с постом, как максимум — как вы огранизуете более сложную логику вроде проверок доступа?
@andreymal вот пример, который рендерит форму на GET, сохраняет на POST и переводит к рендеру объекта. Кстати, в примере с FBV return
redirect(post)
сработает только если str(post) выдает url, что, по мне, странно.
@method_decorator(permission_required('blog.change_post'), name='post')
class CommentEditView(UpdateView, CommentView):
fields = '__all__'
для проверки репо. Возврат к рендеру объекта присутствует. Логика вроде проверок прав пользователя только на одном методе присутствует. Все еще проще и не требует юнит тестирования
Можно отправлять коммент если можно менять пост? То есть не может комментировать никто кроме автора поста и админа? Очень странно
Но даже без этого вы как-то удобненько выбрали встроенную в Django проверку, а если надо разрешить комментировать например всё кроме чужих черновиков или запретить комментировать тем кого заблокировал автор поста? (Примеры реальные, с одного из поддерживаемых мной проектов правда этот проект на PHP но не суть)
redirect(post)
сработает только если str(post) выдает url
Вы забыли про get_absolute_url
Про get_absolute_url в resolve_url забыл, действительно сработает.
В моем примере для GCBV есть пример проверки прав доступа. В примере FBV этого нет. @andreymal прошу пример, как будет работать проверка прав доступа на FBV.
Я могу поменять permission_required
на user_passes_test
и выполнить проверку на разрешение комментировать всё кроме чужих черновиков или запретить комментировать тем кого заблокировал автор поста. Прошу так же пример, как будет работать такая проверка на FBV.
def comment_form(request: HttpRequest, post_id: int) -> HttpResponse:
post = get_object_or_404(Post, pk=post_id)
can_comment = post_can_be_commented_by(post, request.user)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid() and can_comment:
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect(post.get_absolute_url()) + f"#comment{comment.id}"
else:
form = CommentForm()
return render(request, "form.html", {"post": post, "form": form, "can_comment": can_comment})
Обратите внимание на три вещи:
вьюха специально в любом случае возвращает статус 200 и рендерит страницу, позволяя адекватно информировать пользователя о том, что он не может комментировать — а вы так сможете?
я специально добавил проверку
can_comment
в одной строке сif form.is_valid()
: это позволяет вернуть обратно текст комментария, если пользователь был заблокирован в момент набора этого комментария, и таким образом улучшает пользовательский опыт — а вы так сможете? (При желании можно ещё добавитьelse: form.add_error(...)
но это уже вкусовщина)и мой, и ваш варианты необходимо тестировать — мне нужно убедиться, что я не забыл учесть
can_comment
перед созданием комментария, а вам нужно убедиться, что вы не забыли использоватьuser_passes_test
(и что вы использовали правильныйuser_passes_test
и ничего не перепутали)
(Ещё бонусом добавил прокрутку к свежесозданному комментарию тоже для улучшения пользовательского опыта)
@danilovmy спасибо за развёрнутый комментарий!
Меньше магии, больше кода: мой способ писать Django views