# Создание интерфейса пользователя --- ## Модель пользователя ---- Стандартная модель пользователя user, предлагаемая стандартным же приложением django.contrib.auth, не подходит, поскольку нам нужно хранить дополнительные данные о пользователе. ---- Наша модель будет носить название AdvUser. Список полей, которые мы объявим в ней, приведен в таблице ![](https://i.imgur.com/kDm7SAV.png) ---- Код, объявляющий эту модель: ``` 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().
{"metaMigratedAt":"2023-06-16T14:54:31.215Z","metaMigratedFrom":"Content","title":"Создание интерфейса пользователя","breaks":true,"contributors":"[{\"id\":\"0d39d5a3-691d-488c-8f1e-1a0fb0be4f13\",\"add\":30235,\"del\":1063}]"}
    651 views