Django Admin Action Function Failing - отправка электронной почты нескольким выбранным пользователям (если «apply» в request.POST)
В моей реализации используется модель CustomUser. Я создаю действие в админке Django, которое позволяет мне составлять и отправлять электронные письма нескольким пользователям. Все на сайте ведет себя так, как будто все работает. После нажатия кнопки «Отправить письмо» я перенаправляюсь обратно в админку, но не вижу никаких консольных выводов, указывающих на то, что письмо было отправлено, и self.message_user никогда не вызывается и не отправляется в админку.
Email Backend
Сброс пароля работает со ссылкой на сброс, отправленной в консоль, так что бэкэнд электронной почты работает.
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
admin.py
if «apply» in request.POST: <-- Эта строка никогда не срабатывает!
class CustomUserAdmin(ExportMixin, UserAdmin):
@admin.action(description="Email selected users")
def send_email_action(self, request, queryset):
print(request.POST)
print(request.POST.getlist("apply"))
input("HALT")
if "apply" in request.POST:
print("apply")
form = EmailForm(request.POST)
if form.is_valid():
print("form valid")
subject = form.cleaned_data["subject"]
message = form.cleaned_data["message"]
recipients = [user.email for user in queryset]
send_mail(subject, message, "your_email@example.com", recipients)
self.message_user(request, f"{len(recipients)} emails were sent.")
return HttpResponseRedirect(request.get_full_path())
else:
print("no apply")
form = EmailForm()
return render(request, "email/admin_send.html", {"form": form})
forms.py
Работает, как и ожидалось.
class EmailForm(forms.Form):
subject = forms.CharField(max_length=256)
message = forms.CharField(widget=forms.Textarea)
admin_send.html
Либо:
- Функция send_email_action вызывается после нажатия кнопки «Отправить письмо», но форма не отправляет обратно данные «apply», поэтому она терпит неудачу: if «apply in request.POST:
- --ИЛИ--
- Функция send_email_action даже не вызывается снова, потому что я не знаю, как это сделать, возможно, это имеет какое-то отношение к этому: form action=«»
Q1: Нужно ли мне определять form action=«» , которая сейчас пуста? Я спрашиваю, потому что мне кажется, что когда я нажимаю кнопку «Отправить электронную почту», она может не вызывать функцию send_email_action снова. Если мне нужно определить это, я не знаю, что сюда вставить, потому что обычно это URL, но мне нужно, чтобы это была функция действия администратора. Я считаю, что это основная проблема, потому что если функция вызывается, то я должен увидеть результаты print(request.POST), выведенные в консоль, но я этого не делаю (т.е. QueryDict).
Q2: Обратите внимание на input name=«apply». Функция send_email_action явно ищет эти данные, но они не возвращаются из формы, а если и возвращаются, то я не могу найти их в объекте запроса. Я полагаю, что это также может быть проблемой, но мы сможем решить ее только после того, как решим первую проблему выше.
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block title %}
Email Users
{% endblock %}
{% block content %}
<br>
<br>
<br>
<h2>Email Users</h2>
<form action="" method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="apply" value="Send Email">
</form>
{% endblock content %}
Шаг 1) В админке я выбрал несколько пользователей в таблице CustomUser, выбрал «Email selected users» из выпадающего меню действий, нажал GO:
console
<QueryDict: {'csrfmiddlewaretoken': ['zHlbmtRaNIFwrdNmCFxwyiVLi1IaCFKHoJAmZWKttwTMN97gC1JhWSryO9POuaDK'], 'action': ['send_email_action'], 'select_across': ['0'], 'index': ['0'], '_selected_action': ['6', '1', '7', '8']}>
[]
HALT
Шаг2) Я нажимаю enter в консоли, чтобы продолжить, появляется форма. Я ввожу тему и сообщение.
console
no apply
[23/Nov/2024 23:58:25] "POST /admin/app_accounts/customuser/?company=Cisco HTTP/1.1" 200 10317
Шаг 3) Я нажимаю кнопку «Отправить письмо».
console
[24/Nov/2024 00:02:07] "POST /admin/app_accounts/customuser/?company=Cisco HTTP/1.1" 200 19098
[24/Nov/2024 00:02:07] "GET /admin/jsi18n/ HTTP/1.1" 200 3342
ОК, я понял, как заставить это работать. Ключевым моментом для меня было просто осознание того, что form action=«» на самом деле должна быть заполнена. Чем? Я не знал, как заполнить ее чем-то, что позволило бы мне вернуться к моей функции действия администратора <- это был мой ментальный блок. ДУХ! Мне нужно было заполнить его обычным представлением, а не функцией действий администратора! Мне нужно было разделить эти части кода. Поэтому я создал URL, по которому можно было отправлять POST-данные формы, придумал, как прозрачно передать данные из моей функции действия администратора через промежуточную форму в обычное представление для последующей обработки (немного преобразования данных), затем я построил свое представление, используя части кода из моего оригинального файла admin.py в разделе if «apply» in request.POST:. Затем я упростил свою функцию действия администратора.
Единственное, что я не смог заставить работать, - это сообщение о подтверждении в интерфейс администратора... см. комментарий # в предпоследней строке моего файла views.py. Это привело к ошибке: ModelAdmin.message_user() missing 1 required positional argument: 'message'. Хотя ясно, что я указал сообщение. Я не могу объяснить, почему это происходит.
Это решение кажется мне немного грязным, потому что прозрачная передача данных, кажется, передается в виде параметров в URL, но ничего конфиденциального не передается, так что я думаю, что это не так уж важно, и это никогда не станет известно конечному пользователю, пока код работает правильно.
Если кто-нибудь прочитает это, я надеюсь, что это поможет. Также, если вы можете придумать лучший, более простой, более эффективный способ решения этой проблемы, дайте мне знать! Я довольно новичок в Django, поэтому мне приятно, что у меня вообще получилось это сделать, но я задаюсь вопросом, нет ли лучшего способа.
admin.py
class CustomUserAdmin(ExportMixin, UserAdmin):
resource_class = CustomUserResource
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
actions = ["send_email_action"]
@admin.action(description="Email selected users")
def send_email_action(self, request, queryset):
form = EmailForm()
recipients = "|".join([item.email for item in queryset])
return render(
request,
"email/admin_send.html",
{"form": form, "recipients": recipients},
forms.py
class EmailForm(forms.Form):
subject = forms.CharField(max_length=256)
message = forms.CharField(widget=forms.Textarea)
admin_send.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block title %}
Email Users
{% endblock %}
{% block content %}
<br>
<br>
<br>
<h2>Email Users</h2>
<form action="{% url 'process_email' recipients %}" method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" value="Send Email">
</form>
{% endblock content %}
urls.py
from django.urls import path
from .views import process_email_view
urlpatterns = [
path(
"process_email/<str:recipients>/",
process_email_view,
name="process_email",
),
]
views.py
def process_email_view(request, recipients, modeladmin):
form = EmailForm(request.POST)
if form.is_valid():
subject = form.cleaned_data["subject"]
message = form.cleaned_data["message"]
recipients = recipients.split("|")
for recipient in recipients:
send_mail(subject, message, "your_email@example.com", [recipient])
# CustomUserAdmin.message_user(request, f"{len(recipients)} emails were sent.")
return HttpResponseRedirect("/admin/app_accounts/customuser/")