Это прямое продолжение третьей части серии Написание Instagram на Python, поэтому, возможно, будет полезно прочитать последние три, но если у вас нет, давайте не будем терять время и тем не менее погрузимся в нее.

Как обычно, всякий раз, когда мы создаем часть проекта Django, которую можно разделить на другое приложение, мы запускаем другое приложение. В этом случае нам нужно решить проблему фолловеров и фолловеров, которые на самом деле являются лишь частью генерации фида. Итак, мы запустим приложение feed.

$ ./manage.py startapp feed

Прежде чем мы снова погрузимся в подробности, давайте на мгновение задумаемся о дизайне. Как лучше всего определить отношения, охватывающие нескольких пользователей, следующих за несколькими пользователями? Я думаю, что ответ начинается прямо перед нами. Рекурсивная ManyToManyField связь для нашей User модели. Затем нам нужно будет учесть наши настройки конфиденциальности для принятия запросов на отслеживание. В этом сценарии, если у пользователя есть личный профиль, он будет получать запросы на отслеживание. В противном случае подписчик будет просто добавлен в следующий список. Таким образом, нам также необходимо вести таблицу для последующих запросов. Пока все довольно просто. И правда в том, что это все, что нужно сделать.

Вернемся к нашему user/models.py и добавим поле и несколько вспомогательных функций -

Давайте посмотрим, как это будет работать на практике. Допустим, у нас есть три пользователя - admin, mentix02 и rgreene. Посмотрите, как они будут работать в интерактивной оболочке -

>>> from user.models import User
>>> admin, rgreene, mentix02 = User.objects.all()
<QuerySet [<User: admin>, <User: rgreene>, <User: mentix02>]>
>>> mentix02.following
<QuerySet []>

Сейчас пользователь mentix02 ни на кого не подписывается. Заставим его подписаться на rgreene -

>>> mentix02.follow(rgreene)
>>> mentix02.following
<QuerySet [<User: rgreene>]>

Вуаля. Давайте сделаем так, чтобы mentix02 подписывалась на администратора сейчас и наоборот.

>>> mentix02.follow(admin)
>>> admin.follow(mentix02
>>> admin.following
<QuerySet [<User: mentix02>]>
>>> mentix02.followers
<QuerySet [<User: admin>]>

Наконец, если мы хотим проверить, следует ли пользователь за другим пользователем, мы должны выполнить операцию «in». Посмотрим, следует ли Mentix02 за rgreene -

>>> rgreene in mentix02.following
True
>>> # Check if admin follows rgreene
>>> rgreene in admin.following
False

И в этом волшебство поля ManyToManyField - оно делает жизнь намного проще, но что на самом деле происходит под капотом? Если вы хотите пропустить эту часть, вы можете, но поверьте мне, это очень поможет вам понять, как отношения обрабатываются на уровне базы данных.

Django обрабатывает ManyToMany отношения, создавая под ней совершенно новую таблицу (user_user_follows), которая содержит два столбца - to_user_id и from_user_id (имена могут измениться в будущем). В этом случае столбец from_user_id является подписчиком, а столбец to_user_id - подписчиком. Каждая строка является «связующим звеном» между подписчиком и пользователем, за которым она следует. Кажется немного странным обдумывать это, но как только вы это сделаете, все обретет смысл. Теперь все, что осталось, - это создать FollowRequest модель, в которой хранятся запросы на отслеживание для usre, чья учетная запись установлена ​​как приватная.

Откройте feed/models.py и введите следующее -

Глядя на это, это кажется ужасно похожим на отношения ManyToMany, которые мы описали между разными пользователями… и вы были бы правы. Это. Мы могли бы пойти ManyToManyField путем с самими пользователями, но это сделало бы User модели слишком сложными для меня. Я стараюсь ограничивать количество моделей в одном приложении двумя, потому что позже нам придется писать представления, тесты, URL-адреса и иметь огромное models.py отношение ко всем классам моделей.

Еще одно преимущество использования самоописываемой таблицы отношений для таблицы последующих запросов состоит в том, что у нас есть более четкая ментальная модель того, как представлен запрос, и мы можем описывать правильно названные вспомогательные функции в истинном ООП-стиле, имитирующем реальный мир. Наконец, последнее преимущество этого - действительно показать, как ManyToManyField работает изнутри, и развенчать его кажущееся «волшебство».

Давайте посмотрим на общий случай. Скажем, у mentix02 есть личный профиль, и администратор делает запрос, чтобы подписаться на него - будет создан экземпляр FollowRequest, заполняющий поле requester указанного экземпляра администратором, а поле to_follow - Mentix02. Для этого нам придется переписать follow метод, который мы только что написали в модели User, следующим образом:

def follow(self, user: User, force: bool = False) -> None:
    """ Helper function to add user to a follower list. """

    if user.id == self.id:
        return
    if force:
        self._follows.add(user)
        return

    if user.private:
        FollowRequest.objects.create(requester=self, to_follow=user)
    else:
        self._follows.add(user)

Необходимо добавить необязательный логический аргумент force, чтобы метод accept в экземпляре FollowRequest мог добавить пользователя to_follow в список _follows requester.

Немного сложно осмыслить концептуально - нет никакой «реальной» защиты со стороны профиля пользователя с частной учетной записью, скорее есть лишь поверхностное ограничение методом подписки запрашивающего и, таким образом, теоретически, возможность подписаться на чью-то учетную запись полностью находится в руках отправителя запроса, а не его учетной записи, независимо от настроек конфиденциальности. И поэтому мы должны убедиться, что мы только используем эти обновленные вспомогательные функции, определенные внутри User, для отслеживания других пользователей.

Это отличное время, чтобы отстаивать частные и общедоступные переменные-члены и отсутствие их поддержки в Python. Поле _follows нельзя трогать, и к нему следует обращаться только в четко определенных пределах удобных общедоступных методов. Таким образом, мы помещаем подчеркивание, чтобы обозначить, что это «псевдо» частное поле - это означает, что его может распознать только программист, но для Python это все то же самое, что и любая другая функция-член или переменная.

И я считаю, что мы почти закончили с более теоретической частью внедрения системы отслеживания запросов для наших пользователей. Единственное, что касается этой статьи, - это получение FollowRequest экземпляров по запросам пользователя. Как и в случае с большинством вещей здесь, сделать проще, чем сказать. Создайте feed/serializers.py и заполните его стандартной ModelSerializer настройкой -

from rest_framework import serializers

from feed.models import FollowRequest


class FollowRequestSerializer(serializers.ModelSerializer):
    class Meta:
        model = FollowRequest
        fields = '__all__'

Теперь, чтобы на самом деле настроить конечные точки, чтобы пользователи могли запрашивать подписку на других пользователей по протоколу HTTP. Внутри feed/views.py войдите в это представление -

Опять же, мы не делаем ничего сложного - мы просто получаем to_follow_id из тела POST, проверяем существование пользователя и позволяем нашему удобному методу follow делать всю тяжелую работу, что является еще одним ключевым шаблоном, которому следует следовать при разработке веб-приложений. используя шаблон MVC -

Тяжелые модели, легкие виды.

Давайте посмотрим, как пользователь может принять (или отклонить) действительный запрос на отслеживание -

Пройдемся по нему шаг за шагом - здесь особо нечего сказать. Нам требуются два аргумента в теле POST: действие, в котором строка «1» означает принятие, а все остальное (обычно «0») означает отклонение, и действительный FollowRequest идентификатор, который может быть получен клиентом внешнего интерфейса с помощью FollowRequestListAPIView , к которому мы скоро вернемся. А остальное делается с помощью вспомогательного метода accept и reject модели FollowRequest.

Последняя и самая простая в реализации часть - FollowRequestListAPIView, где пользователь, которому поступают запросы, может получить список всех пользователей, которые попросили подписаться на них. Безусловно, самый простой - просто использовать rest_framework.generic.ListAPIView -

class FollowRequestListAPIView(ListAPIView):
    permission_classes = (IsAuthenticated,)
    serializer_class = FollowRequestSerializer

    def get_queryset(self) -> QuerySet:
        return self.request.user.requests.all()

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

И все это было довольно просто, так как мы все хорошо разбираемся в дизайне баз данных и вместо того, чтобы обезьяны исправлять вещи, у нас был твердый план того, как все будет работать, и поэтому нам нужно было только посвятить время. к планированию - реализация прошла довольно легко.

Следующую часть читайте здесь.