Django 5 для начинающих

Прогресс по курсу:  9/1004

6.4 Рекомендация постов по электронной почте
4 из 7 шагов пройдено
0 из 9 баллов  получено

Отправка электронных писем в представлениях

Отредактируйте представление post_share в файле views.py приложения blog, как показано ниже:

from django.core.mail import send_mail
from django.conf import settings


def post_share(request, post_id):
    # Извлечь пост по его идентификатору id
    post = get_object_or_404(Post,
                             id=post_id,
                             status=Post.Status.PUBLISHED)
    sent = False

    if request.method == 'POST':
        # Форма была передана на обработку
        form = EmailPostForm(request.POST)
        if form.is_valid():
            # Поля формы успешно прошли валидацию
            cd = form.cleaned_data
            post_url = request.build_absolute_uri(
                post.get_absolute_url())
            subject = f"{cd['name']} recommends you read " \
                      f"{post.title}"
            message = f"Read {post.title} at {post_url}\n\n" \
                      f"{cd['name']}\'s ({cd['email']}) comments: {cd['comments']}"
            send_mail(subject, message, settings.EMAIL_HOST_USER,
                      [cd['to']])
            sent = True
    else:
        form = EmailPostForm()
    return render(request, 'blog/post/share.html', {'post': post,
                                                    'form': form,
                                                    'sent': sent})

Если вы используете SMTP-сервер, а не почтовый бэкенд console.EmailBackend, то замените your_account@gmail.com своей реальной учетной записью электронной почты.

В приведенном выше исходном коде мы объявили переменную sent с изначальным значением False. Мы задаем этой переменной значение True после отправки электронного письма. Позже мы будем использовать переменную sent в шаблоне отображения сообщения об успехе при успешной передаче формы.

Поскольку ссылка на пост должна вставляться в электронное письмо, мы получаем абсолютный путь к посту, используя его метод get_absolute_url().

Мы используем этот путь на входе в метод request.build_absolute_uri(), чтобы сформировать полный URL-адрес, включая HTTP-схему и хост-имя (hostname).

Мы создаем тему и текст сообщения электронного письма, используя очищенные данные валидированной формы.

Наконец, мы отправляем электронное письмо на адрес электронной почты, указанный в поле to(Кому) формы.

Теперь, когда представление post_share завершено, для него необходимо добавить новый шаблон URL-адреса.

Откройте файл urls.py приложения blog и добавьте шаблон URL-адреса post_share, как показано ниже:

from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    # представления поста
    # path('', views.post_list, name='post_list'),
    path('', views.PostListView.as_view(), name='post_list'),
    path('<int:year>/<int:month>/<int:day>/<slug:post>/',
         views.post_detail,
         name='post_detail'),
    path('<int:post_id>/share/',
         views.post_share, name='post_share'),
]

 

Прорисовка форм в шаблонах

После того как была создана форма, запрограммировано представление и добавлен шаблон URL-адреса, не хватает только одного – шаблона представления.

Внутри каталога /templates/blog/post/ создайте новый файл и назовите его share.html.
Добавьте следующий ниже исходный код в новый шаблон share.html:

{% extends "blog/base.html" %}

{% block title %}Share a post{% endblock %}

{% block content %}
    {% if sent %}
        <h1>E-mail successfully sent</h1>
        <p>
            "{{ post.title }}" was successfully sent
            to {{ form.cleaned_data.to }}.
        </p>
    {% else %}
        <h1>Share "{{ post.title }}" by e-mail</h1>
        <form method="post">
            {{ form.as_p }}
            {% csrf_token %}
            <input type="submit" value="Send e-mail">
        </form>
    {% endif %}
{% endblock %}

Это шаблон, который используется для отображения формы, служащей для того, чтобы делиться постом по электронной почте, и для отображения успешного сообщения после отправки электронного письма. Различие между обоими случаями проводится с помощью тега {% if sent %}.

Для того чтобы отобразить форму, мы определили HTML-элемент form, указав, что форма должна быть передана методом POST

<form method="post">

Экземпляр формы вставлен с помощью тега {{ form.as_p }}. При этом веб-фреймворку Django сообщается, что нужно прорисовывать поля формы, используя абзацные HTML-элементы <p> с применением метода as_p.

Кроме того, форму можно было бы прорисовывать в виде неупорядоченного списка с использованием метода as_ul или в виде HTML-таблицы с применением метода as_table, а также в виде div контейнеров с помощью тега {{ form.as_div }}

Еще одним вариантом является прорисовка каждого поля путем прокручивания полей формы в цикле, как в следующем ниже примере:

{% for field in form %}
   <div>
      {{ field.errors }}
      {{ field.label_tag }} {{ field }}
   </div>
{% endfor %}

Здесь добавлен шаблонный тег {% csrf_token %}. Указанный тег вводит скрытое поле с автоматически сгенерированным токеном во избежание атак по подделке межсайтовых запросов (CSRF). Такие атаки заключаются в том, что вредоносный веб-сайт или программа выполняют нежелательные для пользователя действия на сайте.

Более подробная информация о подделке межсайтовых запросов находится на странице https://owasp.org/www-community/attacks/csrf.

По умолчанию Django проверяет наличие токена CSRF во всех запросах методом POST. Тег csrf_token следует вставлять во все формы, передаваемые на обработку методом POST.

Шаблонный тег {% csrf_token %} генерирует скрытое поле, которое прорисовывается следующим образом:

<input type='hidden' name='csrfmiddlewaretoken'
value='26JjKo2lcEtYkGoV9z4XmJIEHLXN5LDR' />

Отредактируйте шаблон blog/post/detail.html, придав ему следующий вид:

{% extends "blog/base.html" %}

{% block title %}{{ post.title }}{% endblock %}

{% block content %}
    <h1>{{ post.title }}</h1>
    <p class="date">
        Published {{ post.publish }} by {{ post.author }}
    </p>
    {{ post.body|linebreaks }}
    <p>
        <a href="{% url 'blog:post_share' post.id %}">
            Share this post
        </a>
    </p>
{% endblock %}

Здесь была добавлена ссылка на URL-адрес post_share. URL-адрес формируется динамически с помощью предоставляемого веб-фреймворком Django шаблонного тега {% url %}. При этом используются именное пространство blog и URL-адрес post_share.

Для того чтобы сформировать URL-адрес, id поста передается в качестве параметра. Откройте приглашение командной оболочки и исполните следующую ниже команду, чтобы запустить сервер разработки и пройдите по URL-адресу http://127.0.0.1:8000/blog/ в своем браузере и кликните по заголовку любого поста, чтобы просмотреть страницу детальной информации о посте.

Под телом поста вы должны увидеть ссылку, которую вы только что добавили:

Кликните по Share this post(Поделиться этим постом), и вы должны увидеть страницу, включая форму, позволяющую делиться этим постом по электронной почте, как показано ниже:

Стили CSS формы включены в пример исходного кода и находятся в файле static/css/blog.css. При нажатии кнопки SEND E-MAIL(Отправить электронное письмо) форма передается на обработку и затем валидируется.

Если все поля содержат валидные данные, то вы получите сообщение об успехе, как показано ниже:

Отправьте пост на свой собственный адрес электронной почты и проверьте свой почтовый ящик. Полученное вами электронное письмо должно выглядеть следующим образом:

Если передать форму на обработку с не валидными данными, то форма будет прорисована снова, выводя все ошибки валидации.

Большинство современных браузеров не позволят передавать форму на обработку с пустыми или ошибочными полями. Это вызвано тем, что перед передачей формы на обработку браузер проверяет поля на основе их атрибутов. В этом случае форма не будет передана на обработку, и браузер отобразит сообщение об ошибке у неправильных полей.

Для того чтобы протестировать валидацию формы Django с использованием современного браузера, можно пропустить валидацию формы браузером, добавив атрибут novalidate в элемент HTML <form>.

Например, <form method="post" novalidate>. Этот атрибут можно добавлять, чтобы запрещать браузеру валидировать поля и тестировать свою собственную валидацию формы. После завершения тестирования удалите атрибут novalidate, чтобы вернуть валидацию формы браузером.

Теперь функциональность, позволяющая делиться постами по электронной почте, завершена.

Более подробная информация о работе с формами находится на странице https://docs.djangoproject.com/en/5.0/topics/forms/.


  • Комментариев
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий
   
Здесь имеется в виду my_site/templates/blog/post наверное?
И ещё вопрос по поводу request.build_absolute_uri()...url возможно?

@Павел_Васильев, По поводу первого вопроса, верно, сейчас убрали из пути название проекта. По поводу второго вопроса, в коде все правильно написано: build_absolute_uri() возвращает абсолютный путь URL. Можете почитать https://docs.djangoproject.com/en/4.2/ref/request-response/#django.http.HttpRequest.build_absolute_uri

Изменен Илья Перминов

Здравствуйте, подскажите, зачем в данном варианте отправки писем явно указывается поле 'email' в форме, если отправка производится с адреса, указанного как EMAIL_HOST_USER в настройках проекта?

@Олег_Якушев, Очень хороший вопрос, на самом деле это наша не доработка. В планах было следующее.

Форма содержит следующие ниже поля:

  • name: имя человека, отправляющего пост;

  • email: адрес электронной почты человека, отправившего рекомендуемый пост;

  • to: адрес электронной почты получателя;

  • comments: используется для комментариев, которые будут вставляться в электронное письмо с рекомендуемым постом

А вот отправителя мы забыли совсем добавить в письмо. Для этого нужно изменить:

            message = f"Read {post.title} at {post_url}\n\n" \
                      f"{cd['name']}\'s comments: {cd['comments']}"

На следующий код: 

            message = f"Read {post.title} at {post_url}\n\n" \
                      f"{cd['name']}\'s ({cd['email']}) comments: {cd['comments']}"

В лекции код изменили, еще раз спасибо за внимательность! 

Небольшой дискомфорт от того, что при отправке на несуществующий адрес мы все равно сообщаем пользователю об успехе. Надеюсь, документации хватит, чтобы разобраться. Давно хотела научиться отправлять письма из программы) Для отправки смс и сообщений в месенджеры наверняка тоже есть удобный функционал?

@Ольга_Миронова, думаю да, для этого есть специальные библиотеки. В крайнем случае есть сервисы, к которым можно будет общаться через API. Одна из них - https://smsc.ru

@Илья_Перминов, Спасибо! Теперь в телеграм тоже умею, а смски - ну их, они платные. Хорошо, что урок с почтой дал мне пинок, а то я со своей ленью еще бы лет 10 собиралась.

Не очень понятно для чего в пред. степах мы в settings.py  указывали почту в поле 

EMAIL_HOST_USER = '****@gmail.com'

И тут в представлении мы тоже указываем это мыло

...

send_mail(subject, message, 'your_account@gmail.com',
                      [cd['to']])
...

@Никита_Ильин, Спасибо, тоже поправили.

кавычки надо исправить

Изменен Никита Ильин

@Никита_Ильин, Спасибо, поправили, правда мой PyCharm не считает это за ошибку.

Не подскажете, почему не подключились стили нормально, что могло пойти не так? Спасибо.

@Dmitrii_Novozhilov, можете прислать свой проект мне на почту perminoff-ilya@yandex.ru, я посмотрю.

@Илья_Перминов, о каких стилях тут речь? Можете скинуть пример как должно было получиться? У меня видать тоже не подключились.

У меня всё работает, сообщения отправляются, но после отправки страница с полями остается без изменений, то есть нет сообщения E-mail successfully sent

@Дмитрий_Чекмасов, в первую очередь проверьте код представления post_share():

def post_share(request, post_id):
    ...
        if form.is_valid():
            # Поля формы успешно прошли валидацию
            ...
            sent = True
    else:
        ...
    return render(request, 'blog/post/share.html', {'post': post,
                                                    'form': form,
                                                    'sent': sent})

Для переменной sent должно устанавливаться значение True при валидной форме, так-же она должна передаваться в шаблон.

Кроме представления необходимо проверить шаблон share.html на наличие данного кода:

{% extends "blog/base.html" %}

{% block title %}Share a post{% endblock %}

{% block content %}
    {% if sent %}
        <h1>E-mail successfully sent</h1>
        <p>
            "{{ post.title }}" was successfully sent
            to {{ form.cleaned_data.to }}.
        </p>
    {% else %}
        <h1>Share "{{ post.title }}" by e-mail</h1>
        <form method="post">
            {{ form.as_p }}
            {% csrf_token %}
            <input type="submit" value="Send e-mail">
        </form>
    {% endif %}
{% endblock %}

@Дмитрий_Селезнев, да, спасибо, пропустил 'sent': sent, поэтому не отображалось

Подскажите пожалуйста, письмо пришло, но не в такой форме, как на примере и без ссылки. Что не так?

Это письмо из прошлого шага, которое через шелл отправляли вроде.

@Илья_Перминов, возможно(
Но делаю то я этот шаг и когда жму отправить письмо - мне приходит такое(

@Илья_Перминов, вопрос снят) письма в спам попадали, поэтому я их не видел)