Является ли это правильным способом обработки всех CRUD-операций в одном представлении (конечной точке) с помощью HTMX?
Я новичок в использовании HTMX с Django и ищу совета. Я нахожусь в процессе редизайна большого CRUD-приложения, стремясь интегрировать HTMX для улучшения взаимодействия. Учитывая сложность приложения и его взаимодействие с базой данных через различные модели, я хочу быть уверен, что начну с правильной ноги, приняв лучшие практики HTMX на раннем этапе.
Я видел во многих руководствах, что все следуют общепринятой практике создания отдельных представлений и URL-путей для каждой транзакции базы данных (такой как создание, редактирование, удаление, обновление). Поскольку я считаю, что это может привести к значительному количеству дополнительного кода и временным затратам, я бы предпочел поместить всю логику приложения в одно представление (или конечную точку) для каждой модели.
Можете ли вы, пожалуйста, подсказать мне, правильный ли это поток и правильный способ сделать это:
def movie(request, template_name="movies.html"):
movies = Movie.objects.all()
movie_form = MovieForm(request.POST or None)
if request.method == 'POST':
action = request.POST.get('action')
# 1) CREATE
if action == 'add_movie':
if movie_form.is_valid():
movie_form.save()
content = render(request, 'partials/movie-table.html', {'movies': movies})
return HttpResponse(content, status=201,
headers={
'HX-Trigger': json.dumps({'create': movie_form.cleaned_data['name']})}
)
else:
content = render(request, 'error.html', {'error': movie_form.errors})
return HttpResponse(content, status=202)
# 2) DELETE
elif action == 'delete_movie':
Movie.objects.get(id=request.POST.get('movie_id')).delete()
content = render(request, 'movie.html', {'movies': movies})
return HttpResponse(content, status=201,
headers={
'HX-Trigger': json.dumps({'delete: movie_form.cleaned_data['name']})}
)
# 3) UPDATE (two steps are necessary)
# first step is used to get the necessary movie data that will populate the form
elif action == 'load_movie':
movie_id = request.POST.get('movie_id')
movie_form = MovieForm(instance = Movie.objects.get(id=request.POST.get('movie_id')))
movie_form.id = movie_id
return render(request, 'edit-movie.html', {'movie_form': movie_form})
# UPDATE (second step, actual edit)
elif action == 'edit_movie':
movie_form = MovieForm(request.POST, instance = Movie.objects.get(id=request.POST.get('movie_id')))
if movie_form.is_valid():
movie_form.save()
content = render(request, 'movie.html', {'movies': movies})
return HttpResponse(content, status=201,
headers={
'HX-Trigger': json.dumps({'update': movie_form.cleaned_data['name']})}
)
else:
return HttpResponse(status=400)
return render(request, template_name, {'movies' : movies, 'movie_form' : movie_form})
Вот пояснение к приведенному выше коду - пользовательский интерфейс состоит из трех отдельных кнопок: Добавить, Редактировать и Удалить. Каждая кнопка вызывает свое действие, передаваемое в бэкенд с помощью аргумента hx-vals:
<div class="ui button" id="add_movie__button" name="add_movie__button" type="submit" hx-post="{% url 'movie' %}" hx-vals='{"action": "add_movie"}' hx-target="#table_movies">
На основании значения hx-vals (action) бэкенд определяет, как обрабатывать размещенные параметры. После завершения процесса добавления, редактирования или удаления бэкэнд отправляет соответствующий HX-триггер (например, 'update') на JavaScript. Этот триггер помогает определить последующие действия, такие как обновление модала и отображение соответствующих сообщений для пользователя.
Хотя использование одной конечной точки имеет свои преимущества, такие как оптимизация разработки, оно также создает проблемы, особенно в процедуре Update. В этом сценарии бэкэнд сначала должен получить идентификатор фильма, который пользователь хочет отредактировать. Затем он извлекает соответствующий фильм из базы данных, отправляет его обратно, чтобы заполнить форму, и ожидает дальнейшего ввода пользователем. После отправки формы на бэкэнд отправляется еще одно действие для обновления данных фильма, которые затем сохраняются в базе данных.
На всякий случай, если кто-то захочет проверить, я включил соответствующие части шаблонов:
modals.html
<!-- Delete button -->
...
<div class="button" id="delete_movie__button" name="delete_movie__button" type="button" hx-post="{% url 'movie' %}" hx-target="#table_movies">Delete<div>
...
<!-- Edit button, that is part of the edit form, that is part of the modal -->
...
<div class="button" id="edit_movie__button" name="edit_movie__button" type="submit" hx-post="{% url 'movie' %}" hx-target="#table_movies" hx-vals='{"action": "edit_movie", "movie_id": "{{ movie_form.id }}"}'>
<i class="add icon"></i> Edit
...
main_template.html
...
function deleteMovieModal(){
// opens delete movie modal
$('#delete_movie__modal').modal('show');
}
function editMovieModal(){
// opens edit movie modal
$('#edit_movie__modal').modal('show');
}
function deleteMovieId(movie_id){
// adds hx-vals argument to the delete button in delete movie modal
document.getElementById("delete_movie__button").setAttribute("hx-vals",`{"action": "delete_movie", "movie_id": "${movie_id}"}`);
}
...
partials/table_movies.html
...
<td>
<!-- clicking this button opens delete movie modal and calls the function that passes an movie id argument to the delete button in delete movie modal -->
<div class="button" onclick="deleteMovieModal();deleteMovieId({{ movie.id }});">Delete</div>
</td>
<td>
<!-- clicking this button opens edit movie modal and sends htmx request that populates form data with choosen movie, in the edit movie modal -->
<div class="button" onclick="editMovieModal();editMovieId({{ movie.id }});" hx-post="{% url 'movie' %}" hx-vals='{"action": "load_movie", "movie_id": {{ movie.id }} }' hx-target="#edit_movie__modal_content" >Edit </div>
</td>
...
Можете ли вы поделиться своими мыслями о таком подходе? Любые мысли/идеи/предложения будут оценены по достоинству.