owned this note
owned this note
Published
Linked with GitHub
# Создание интерфейса пользователя
---
## Модель пользователя
----
Стандартная модель пользователя user, предлагаемая стандартным же приложением django.contrib.auth, не подходит, поскольку нам нужно хранить дополнительные данные о пользователе.
----
Наша модель будет носить название AdvUser. Список полей, которые мы объявим в ней, приведен в таблице

----
Код, объявляющий эту модель:
```
from django.db import models
from django.contrib.auth.models import AbstractUser
class AdvUser(AbstractUser):
is_activated = models.BooleanField(default=True, db_index=True, verbose_name='Прошел активацию?')
send_messages = models.BooleanField(default=True, verbose_name='Слать оповещения о новых комментариях?')
class Meta(AbstractUser.Meta):
pass
```
----
Сразу же укажем ее как модель пользователя, используемую подсистемой разграничения доступа Django. Для этого откроем модуль settings.py пакета конфигурации и добавим в него строку:
`AUTH_USER_MODEL = 'main.AdvUser'`
----
Остановим отладочный веб-сервер Django и зададим в командной строке команду сначала на создание миграций:
`python manage.py makemigrations`
----
а потом — на их выполнение:
`python manage.py migrate`
----
Как только миграции будут выполнены, создадим суперпользователя, подав команду:
`python manage.py createsuperuser`
----
апоследок откроем модуль admin.ру пакета приложения, в котором объявляются классы-редакторы и регистрируются модели в административном сайте. Зарегистрируем нашу модель пользователя, добавив в этот модуль код:
```
from .models import AdvUser
admin.site.register(AdvUser)
```
---
## Страница входа
----
Далее ради простоты мы будем, по возможности, следовать установленным Django соглашениям.
----
Код контроллера-класса, выполняющего вход и носящего имя BBLoginView. Добавим его в модуль `views.py` пакета приложения.
```
from django.contrib.auth.views import LoginView
class BBLoginView(LoginView):
template_name = 'main/login.html'
```
----
Шаблон страницы входа login.html мы поместили в папку templates\main — туда же, где находятся все остальные шаблоны. Поскольку наш сайт включает относительно немного страниц, будем хранить их в одной папке, чтобы упростить сопровождение сайта.
----
Запишем новый маршрут, указывающий на контроллер BBLoginView, в списке уровня приложения. Откроем модуль `urls.py` пакета приложения и добавим в него код:
```
from .views import BBLoginView
...
urlpatterns = [
path ('accounts/login/', BBLoginView.as_view(), name='login'),
...
]
```
----
В маршруте мы указали шаблонный путь accounts/login/. По умолчанию именно по нему Django выполняет перенаправление при попытке гостя получить доступ к закрытой от него странице.
----
Напишем шаблон страницы входа templates\main\login.html.
```
{% extends "layout/basic.html" %}
{% load bootstrap4 %}
{% block title %}Вход{% endblock %}
{% block content %}
<h2>Вход</h2>
{% if user.is_authenticated %}
<р>Вы уже выполнили вход.</р>
{% else %}
<form method="post">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' %}
<input type="hidden" name="next" value="{{ next }}">
{% buttons submit='Войти' %}{% endbuttons %}
</form>
{% endif %}
{% endblock %}
```
----
Напоследок внесем правки в базовый шаблон templates\layout\basic.html. Найдем в нем фрагмент кода, создающего пункт Вход и пункт с раскрывающимся меню Профиль горизонтальной полосы навигации, и исправим его следующим образом:
```
<ul class="col nav justify-content-end border">
<li class="nav-item"><a ....>Регистрация</a></li>
{% if user.is_authenticated %}
<li class="nav-item dropdown">
...
</li>
{% else %}
<li class="nav-item"><a ... >Вход</a></li>
{% endif %}
</ul>
```
----
Запишем в тег <а>, создающий гиперссылку Вход, интернет-адрес страницы входа:
`<а . . . href="{% url 'main:login' %}">Вход</а>`
----
Но пока не будем выполнять вход, иначе возникнет ошибка. Сначала сделаем страницы профиля и выхода
---
## Страница профиля
----
Контроллер, выводящий страницу пользовательского профиля, реализуем в виде функции и назовем profile(). Его код чрезвычайно прост.
```
from django.contrib.auth.decorators import login_required
@login_required
def profile(request):
return render(request, 'main/profile.html')
```
----
Добавим в список маршрутов уровня приложения (не забываем, что он хранитсяв модуле `urls.py` пакета приложения) маршрут на контроллер profile ():
```
from .views import profile
urlpatterns = [
path ('accounts/profile/', profile, name='profile'),
...
]
```
----
Код шаблона templates\main\profile.html, формирующего страницу профиля.
```
{% extends "layout/basic.html" %}
{% block title %}Профиль пользователя{% endblock %}
{% block content %}
<h2>Профиль пользователя {{ user.username }}</h2>
{% if user.first_name and user.last_name %}
<p>3дравствуйте, {{ user.first_name }} {{ user.last_name }}!</p>
{% else %}
<p>3дравствуите!</p>
{% endif %}
<h3>Ваши объявления</h3>
{% endblock %}
```
----
В шаблоне templates/layout/basic.html найдем тег <а>, выводящий гиперссылку Мои объявления, и вставим в него интернет-адрес страницы профиля:
`<a . . . href="{% url 'main:profile' %}">Мои объявления</a>`
---
## Страница выхода
----
Контроллер выхода реализуем в виде класса BBLogoutview, производного от класса LogoutView.
```
from django.contrib.auth.views import LogoutView
from django.contrib.auth.mixins import LoginRequiredMixin
class BBLogoutView(LoginRequiredMixin, LogoutView):
template_name = 'main/logout.html'
```
----
Страница выхода должна быть доступна только зарегистрированным пользователям, выполнившим вход. Поэтому мы добавили в число суперклассов контроллера- класса `BBLogoutView` примесь `LoginRequiredMixin`.
----
В списке маршрутов уровня приложения запишем маршрут, ведущий на этот контроллер:
```
from .views import BBLogoutView
urlpatterns = [
path('accounts/logout/', BBLogoutView.as_view(), name='logout'),
]
```
----
Напишем шаблон страницы выхода templates\main\logout.html.
```
{% extends "layout/basic.html" %}
{% block title %}Выход{% endblock %}
{% block content %}
<h2>Выход</h2>
<p>Вы успешно вышли с сайта.</p>
{% endblock %}
```
----
В шаблоне templates/layout/basic.html найдем тег `<a>`, выводящий гиперссылку Выход, и поместим в него интернет-адрес страницы выхода:
`<a . . . href="{% url 'main:logout' %}">Выйти</a>`
---
## Страница редактирования
----
Сначала объявим форму ChangeUserlnfoForm, связанную с моделью AdvUser и предназначенную для ввода основных данных. Его мы запишем во вновь созданный модуль forms.py пакета приложения.
```
from django import forms
from .models import AdvUser
class ChangeUserInfoForm (forms.ModelForm):
email = forms.EmailField(required=True, label='Адрес электронной почты')
class Meta:
model = AdvUser
fields = ('username', 'email', 'first_name', 'last_name', 'send_messages')
```
----
Контроллер страницы основных данных должен выполнять правку записи модели, так что мы можем написать его на базе высокоуровневого класса UpdateView.
```
from django.views.generic.edit import UpdateView
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404
from .models import AdvUser
from .forms import ChangeUserInfoForm
class ChangeUserInfoView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
model = AdvUser
template_name = 'main/change_user_info.html'
form_class = ChangeUserInfoForm
success_url = reverse_lazy('main:profile')
success_message = 'Данные пользователя изменены'
def setup(self, request, *args, **kwargs):
self.user_id = request.user.pk
return super().setup(request, *args, **kwargs)
def get_object(self, queryset=None):
if not queryset:
queryset = self.get_queryset()
return get_object_or_404(queryset, pk=self.user_id)
```
----
В процессе работы этот контроллер должен извлечь из модели AdvUser запись, представляющую текущего пользователя, для чего ему нужно предварительно получить ключ текущего пользователя. Получить его можно из объекта текущего пользователя, хранящегося в атрибуте user объекта запроса.
----
Вероятно, наилучшее место для получения ключа текущего пользователя — метод setup(), наследуемый всеми контроллерами-классами от их общего суперкласса view.
----
Этот метод выполняется в самом начале исполнения контроллера-класса и получает объект запроса в качестве одного из параметров. В переопределенном методе setup () мы извлечем ключ пользователя и сохраним его в атрибуте user_id.
----
Извлечение исправляемой записи выполняем в методе get_object(), который контроллер-класс унаследовал от примеси SingieObjectMixin.
----
В качестве одного из суперклассов этого контроллера-класса мы указали примесь LoginRequiredMixin, запрещающую доступ к контроллеру гостям, и примесь SuccessMessageMixin, которая применяется для вывода всплывающих сообщений об успешном выполнении операции.
----
В список маршрутов уровня приложения добавим соответствующий маршрут:
```
from .views import ChangeUserInfoView
...
urlpatterns = [
path('accounts/profile/change/', ChangeUserInfoView.as_view(), name='profile_change'),
path('accounts/profile/', profile, name='profile'),
...
]
```
----
Код шаблона templates\main\change_user_info.html, создающего страницу для правки основных данных.
```
{% extends "layout/basic.html" %}
{% load bootstrap4 %}
{% block title %}Правка личных данных{% endblock %}
{% block content %}
<h2>Правка личных данных пользователя {{ user.username }}</h2>
<form method="post">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' %}
{% buttons submit='Сохранить' %}{% endbuttons %}
</form>
{% endblock %}
```
----
В шаблоне templates\layout\basic.html отыщем тег <а>, выводящий гиперссылку Изменить личные данные, и вставим в него интернет-адрес страницы правки основных данных:
`<a . . . href="{% url 'main:profile_change'%}">Изменить личные данные</a>`
---
## Страница изменения пароля
----
```
from django.contrib.auth.views import PasswordChangeView
class BBPasswordChangeView(SuccessMessageMixin, LoginRequiredMixin, PasswordChangeView):
template_name = 'main/password_change.html'
success_url = reverse_lazy('main:profile')
success_message = 'Пароль пользователя изменен'
```
----
Добавим в список маршрутов уровня приложения маршрут, который укажет на новый контроллер:
```
...
urlpatterns = [
path('accounts/logout/', BBLogoutView.as_view(), name='logout'),
path ('accounts/password/change/', BBPasswordChangeView.as_view(), name='password_change'),
...
]
```
----
Код шаблона страницы для смены пароля templates\main\password_change.html.
```
{% extends "layout/basic.html" %}
{% load bootstrap4 %}
{% block title %}Смена пароля{% endblock %}
{% block content %}
<h2>Смена пароля пользователя {{ user.username }}</h2>
<form method="post">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' %}
{% buttons submit='Сменить пароль' %}{% endbuttons %}
</form>
{% endblock %}
```
----
Осталось в коде шаблона templates\layout\basic.html найти код, создающий гиперссылку Изменить пароль, и поместить в нее правильный интернет-адрес:
`<a . . . href="{% url 'main:password_change' %}">Изменить пароль</a>`
---
## Регистрация нового пользователя
----
Мы напишем форму для ввода сведений о новом пользователе, контроллеры и шаблоны для страниц непосредственно регистрации и уведомления об успешной регистрации.
----
Для отправки письма о необходимости активации мы объявим свой сигнал. Называться он будет user_registered и получит в качестве единственного параметра instance объект вновь созданного пользователя.
----
Сигнал мы объявим в модуле apps.py пакета приложения. Этот модуль выполняется непосредственно при инициализации приложения и, таким образом, является идеальным местом для записи кода, объявляющего сигналы.
----
Код класса формы RegisterUserForm запишем в модуль forms.py пакета приложения.
```
from django.contrib.auth import password_validation
from django.core.exceptions import ValidationError
from .apps import user_registered
class RegisterUserForm(forms.ModelForm):
email = forms.EmailField(required=True, label='Адрес электронной почты')
password1 = forms.CharField(label='Пароль', widget=forms.PasswordInput, help_text=password_validation.password_validators_help_text_html())
password2 = forms.CharField(label='Пароль (повторно)',
widget = forms.PasswordInput,
help_text='Введите тот же самый пароль еще раз для проверки')
def clean_passwordl(self):
password1 = self.cleaned_data['password1']
if password1:
password_validation.validate_password(password1)
return password1
def clean(self):
super().clean()
password1 = self.cleaned_data['password1']
password2 = self.cleaned_data['password2']
if password1 and password2 and password1 != password2:
errors = {'password2': ValidationError('Введенные пароли не совпадают', code='password_mismatch')}
raise ValidationError(errors)
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data['password1'])
user.is_active = False
user.is_activated = False
if commit:
user.save()
user_registered.send(RegisterUserForm, instance=user)
return user
class Meta:
model = AdvUser
fields = ('username', 'email', 'password1', 'password2', 'first_name', 'last_name', 'send_messages')
```
----
Здесь мы также комбинируем быстрое и полное объявление полей. Полное объявление используем для создания полей электронной почты (поскольку хотим сделать его обязательным для заполнения) и обоих полей для занесения пароля.
----
В качестве дополнительного поясняющего текста у первого поля пароля указываем объединенный текст с требованиями к вводимому паролю, предоставленный всеми доступными в системе валидаторами, — там новый пользователь сразу поймет, какие требования предъявляются к паролю.
----
В методе clean_password1() выполняем валидацию пароля, введенного в первое поле, с применением доступных в системе валидаторов пароля.
----
В переопределенном методе clean() проверяем, совпадают ли оба введенных пароля. Эта проверка будет проведена после валидации пароля из первого поля.
----
В переопределенном методе save() при сохранении нового пользователя заносим значения False в поля is_active (признак, является ли пользователь активным) и is_activated (признак, выполнил ли пользователь процедуру активации), тем самым сообщая фреймворку, что этот пользователь еще не может выполнять вход на сайт.
----
Далее сохраняем в записи закодированный пароль и отправляем сигнал user registered, чтобы отослать пользователю письмо с требованием активации.
----
Контроллер-класс, регистрирующий пользователя, мы назовем RegisterUserView и сделаем производным от класса CreateView.
----
```
from django.views.generic.edit import CreateView
from .forms import RegisterUserForm
class RegisterUserView(CreateView):
model = AdvUser
template_name = 'main/register_user.html'
form_class = RegisterUserForm
success_url = reverse_lazy('main:register_done')
```
----
Контроллер, который выведет сообщение об успешной регистрации, будет называться RegisterDoneView и, в силу его исключительной простоты, станет производным от класса Templateview.
```
from django.views.generic.base import TemplateView
class RegisterDoneView(TemplateView):
template_name = 'main/register_done.html'
```
----
В список маршрутов уровня приложения добавим два маршрута, ведущие на только что написанные нами контроллеры:
```
from .views import RegisterUserView, RegisterDoneView
...
urlpatterns = [
path('acoounts/register/done/', RegisterDoneView.as_view(), name='register_done'),
path('acoounts/register/', RegisterUserView.as_view(), name='register'),
path('accounts/login', BBLoginView.as_view(), name='login'),
...
]
```
----
Код шаблонa templates\main\register_user.html
```
{% extends "layout/basic.html" %}
{% load bootstrap4 %}
{% block title %}Регистрация{% endblock %}
{% block content %}
<h2>Регистрация нового пользователя</h2>
<form method="post">
{% csrf_token %}
{% bootstrap_form form layout='horizontal' %}
{% buttons submit='Зарегистрироваться' %}{% endbuttons %}
</form>
{% endblock %}
```
----
Код шаблонa templates\main\register_done.html
```
{% extends "layout/basic.html" %}
{% block title %}Регистрация завершена{% endblock %}
{% block content %}
<h2>Регистрация</h2>
<p>Регистрация пользователя завершена.</p>
<p>На адрес электронной почты, указанный пользователем, выслано письмо для активации.</p>
{% endblock %}
```
----
В шаблоне templates\layout\basic.html найдем фрагмент, создающий гиперссылку Регистрация, и вставим в нее интернет-адрес страницы регистрации:
`<а . . . href="{% url 'main:register' %}">Регистрация</а>`
---
## Валидация аккаунта через письмо подтверждение
Откроем модуль `apps.py` пакета приложения и запишем в него код, который объявит сигнал user registered и привяжет к нему обработчик:
```
from django.dispatch import Signal
from .utilities import send_activation_notification
user_registered = Signal(providing_args=['instance'])
def user_registered_dispatcher(sender, **kwargs):
send_activation_notification(kwargs['instance'])
user_registered.connect(user_registered_dispatcher)
```
----
Создадим в пакете приложения модуль `utilities.py`. Занесем в него объявление функции send_activation_notification()
```
from django.template.loader import render_to_string
from django.core.signing import Signer
from bboard.settings import ALLOWED_HOSTS
signer = Signer()
def send_activation_notification(user):
if ALLOWED_HOSTS:
host = 'http://' + ALLOWED_HOSTS[0]
else:
host = 'http://localhost:8000'
context = {'user': user, 'host': host, 'sign': signer.sign(user.username)}
subject = render_to_string('email/activation_letter_subject.txt', context)
body_text = render_to_string('email/activation_letter_body.txt', context)
user.email_user(subject, body_text)
```
----
Чтобы сформировать интернет-адрес, ведущий на страницу подтверждения активации, понадобится, во-первых, домен, на котором находится наш сайт, а во-вторых, некоторое значение, уникально идентифицирующее только что зарегистрированного пользователя и при этом устойчивое к попыткам его подделать.
----
Домен мы можем извлечь из списка разрешенных доменов, который записан в параметре allowed_hosts настроек проекта. Выберем самый первый домен, присутствующий в списке.
----
В качестве уникального и стойкого к подделке идентификатора пользователя применяем его имя, защищенное цифровой подписью. Создание цифровой подписи выполняем посредством класса signer.
----
Текст темы и тела письма формируем с применением шаблонов templates\email\activation_letter_subject.txt и templates\email\activation_letter_body.txt соответственно.
----
templates\email\activation_letter_subject.txt
`Активация пользователя {{ user.username }}`
----
templates\email\activation_letter_body.txt
```
Уважаемый пользователь {{ user.username }}!
Вы зарегистрировались на сайте "Доска объявлений".
Вам необходимо выполнить активацию, чтобы подтвердить свою личность.
Для этого пройдите, пожалуйста, по ссылке
{{ host }}{% url 'main:register_activate' sign=sign %}
До свидания!
С уважением, администрация сайта "Доска объявлений".
```
----
Чтобы реализовать активацию нового пользователя, мы напишем один контроллер и целых три шаблона. Они создадут страницы с сообщением об успешной активации, о том, что активация была выполнена ранее, и о том, что цифровая подпись у идентификатора пользователя, полученного в составе интернет-адреса, скомпрометирована.
----
Контроллер мы реализуем в виде функции user_activate().
```
from django.core.signing import BadSignature
from .utilities import signer
def user_activate(request, sign):
try:
username = signer.unsign(sign)
except BadSignature:
return render(request, 'main/bad_signature.html')
user = get_object_or_404(AdvUser, username=username)
if user.is_activated:
template = 'main/user_is_activated.html'
else:
template = 'main/activation_done.html'
user.is_active = True
user.is_activated = True
user.save()
return render(request, template)
```
----
Подписанный идентификатор пользователя, передаваемый в составе интернет-адреса, получаем с параметром sign. Далее извлекаем из него имя пользователя, ищем пользователя с этим именем, делаем его активным, присвоив значения True полям is active и is activated модели, и выводим страницу с сообщением об успешной активации.
----
Если цифровая подпись оказалась скомпрометированной, выводим страницу с сообщением о неуспехе активации, а если пользователь был активирован ранее (поле is activated уже хранит значение True) — страницу с сообщением, что активация уже произошла.
----
Для обработки подписанного значения используем экземпляр класса signer, созданный в модуле utilities.py и хранящийся в переменной signer. Так мы сэкономим оперативную память.
----
В списке маршрутов уровня приложения запишем маршрут, ведущий на контроллер user_activate():
```
from .views import user_activate
...
urlpatterns = [
path('accounts/register/асtivate/<str:sign>/', user_activate, name='register_activate'),
path('accounts/register/done/', RegisterDoneView.as_view(), name='register_done'),
...
]
```
----
templates\main\activation_done.html
```
{% extends "layout/basic.html" %}
{% block title %}Активация выполнена{% endblock %}
{% block content %}
<h2>Активация</h2>
<p>Пользователь с таким именем успешно активирован.</p>
<p><a href="{% url 'main:login' %}">Войти на сайт</a></p>
{% endblock %}
```
----
templates\main\bad_signature.html
```
{% extends "layout/basic.html" %}
{% block title %}Ошибка при активации{% endblock %}
{% block content %}
<h2>Активация</h2>
<p>Активация пользователя с таким именем прошла неудачно.</p>
<p><a href="{% url 'main:register' %}">Зарегистрироваться повторно</a></p>
{% endblock %}
```
----
templates\main\user_is_activated.html
```
{% extends "layout/basic.html" %}
{% block title %}Пользователь уже активирован{% endblock %}
{% block content %}
<h2 >Активация</h2>
<p>Пользователь с таким именем был активирован ранее.</p>
<p><а href="{% url 'main:login' %}">Войти на сайт</а></p>
{% endblock %}
```
----
Чтобы протестировать отправку электронных писем, воспользуемся отладочным SMTP-сервером.
Simple Mail Transfer Protocol (SMTP) — простой протокол связи, применяемый с целью пересылки электронных писем с сервера отправителя на сервер получателя.
----
Добавим в модуль settings.py пакета конфигурации выражение, задающее TCP-порт № 1025, который используется этим сервером по умолчанию:
`EMAIL_PORT = 1025`
----
Запустим еще один экземпляр командной строки и отдадим команду, запускающую отладочный SMTP-сервер с использованием TCP-порта № 1025:
`python -m smtpd -n -c DebuggingServer localhost:1025`
---
## Удаление пользователя
----
Контроллер-класс DeleteUserView, удаляющий текущего пользователя, сделаем производным от класса DeleteView.
```
from django.views.generic.edit import DeleteView
from django.contrib.auth import logout
from django.contrib import messages
class DeleteUserView(LoginRequiredMixin, DeleteView):
model = AdvUser
template_name = 'main/delete_user.html'
success_url = reverse_lazy('main:index')
def setup(self, request, *args, **kwargs):
self.user_id = request.user.pk
return super().setup(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
logout(request)
messages.add_message(request, messages.SUCCESS, 'Пользователь удален')
return super().post(request, *args, **kwargs)
def get_object(self, queryset=None):
if not queryset:
queryset = self.get_queryset()
return get_object_or_404(queryset, pk=self.user_id)
```
----
Здесь мы использовали те же программные приемы, что и в контроллере ChangeUserInfoView. В переопределенном методе setup() сохранили ключ текущего пользователя, а в переопределенном методе get_object() отыскали по этому ключу пользователя, подлежащего удалению.
----
Перед удалением текущего пользователя необходимо выполнить выход, что мы и сделали в переопределенном методе post(). В том же методе post() мы создали всплывающее сообщение об успешном удалении пользователя.
----
В списке маршрутов уровня приложения запишем код, который добавит новый маршрут:
```
from .views import DeleteUserView
...
urlpatterns = [
path ('accounts/profile/delete/' , DeleteUserView.as_view (), name='profile_delete'),
path('accounts/profile/change/', ChangeUserlnfoView.as_view(), name='profile_change'),
...
]
```
----
Напишем шаблон templates\main\delete_user.html страницы для удаления пользователя.
```
{% extends "layout/basic.html" %}
{% load bootstrap4 %}
{% block title %}Удаление пользователя!{% endblock %}
{% block content %}
<h2>Удаление пользователя {{ object.username }}</h2>
<form method="post">
{% csrf_token %}
{% buttons submit='Удалить' %}{% endbuttons %}
</form>
{% endblock %}
```
----
Осталось записать в шаблоне templates\layout\basic.html интернет-адрес, ведущий на страницу удаления пользователя:
`<а . . . href="{% url 'main:profile_delete' %}">Удалить</а>`
---
## Редактор для административной панели
----
Полный код класса редактора AdvUserAdmin, вспомогательного класса. Этот код следует занести в модуль admin.ру пакета приложения, заменив им имеющийся там код.
```
from django.contrib import admin
import datetime
from .models import AdvUser
from .utilities import send_activation_notification
def send_activation_notifications(modeladmin, request, queryset):
for rec in queryset:
if not rec.is_activated:
send_activation_notification(rec)
modeladmin.message_user(request, 'Письма с требованиями отправлены')
send_activation_notifications.short_description = \
'Отправка писем с требованиями активации'
class NonactivatedFilter(admin.SimpleListFilter):
title = 'Прошли активацию?'
parameter_name = 'actstate'
def lookups(self, request, model_admin):
return (
('activated', 'Прошли'),
('threedays', 'He прошли более 3 дней'),
('week', 'He прошли более недели'),
)
def queryset(self, request, queryset):
val = self.value()
if val == 'activated':
return queryset.filter(is_active=True, is_activated=True)
elif val == 'threedays':
d = datetime.date.today() - datetime.timedelta(days=3)
return queryset.filter(is_active=False, is_activated=False, date_joined__date__lt=d)
elif val == 'week':
d = datetime.date.today() - datetime.timedelta(weeks=l)
return queryset.filter(is_active=False, is_activated=False, date_joined__date__lt=d)
class AdvUserAdmin (admin.ModelAdmin) :
list_display = ('is_activated', 'date_joined')
search_fields = ('username', 'email', 'first_name', 'last_name')
list_filter = (NonactivatedFilter,)
fields = (('username', 'email'), ('first_name', 'last_name'),
('send_messages', 'is_active', 'is_activated'),
('is_staff', 'is_superuser'),
'groups', 'user_permissions',
('last_login', 'date_joined'))
readonly_fields = ('last_login', 'date_joined')
actions = (send_activation_notifications,)
admin.site.register(AdvUser, AdvUserAdmin)
```
----
В списке записей указываем выводить строковое представление записи (имя пользователя — как реализовано в модели Abstractuser, от которой наследует наша модель), поле признака, выполнил ли пользователь активацию, временную отметку его регистрации.
----
Также разрешаем выполнять фильтрацию по полям имени, адреса электронной почты, настоящих имени и фамилии.
----
Для выполнения фильтрации пользователей, выполнивших активацию, не выполнивших ее в течение трех дней и недели, используем класс NonactivatedFilter.
----
Мы явно указываем список полей, которые должны выводиться в формах для правки пользователей, чтобы выстроить их в удобном для работы порядке.
----
Поля даты регистрации пользователя и последнего его входа на сайт делаем доступными только для чтения.
----
Наконец, регистрируем действие, которое разошлет пользователям письма с предписаниямивыполнить активацию. Это действие реализовано функцией send_activation_notifications().