"Видеоурок"

Код на Гитхабе

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

Добавление ссылки на профиль в списки сообщений

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

социальные/шаблоны/социальные/post_list.html

{% for post in post_list %}
        <div class="row justify-content-center mt-3">
            <div class="col-md-5 col-sm-12 border-bottom position-relative">
                <p><a style="text-decoration: none;" class="text-primary" href="{% url 'profile' post.author.profile.pk %}">@{{ post.author }}</a> {{ post.created_on }}</p>
                <div class="position-relative">
                    <p>{{ post.body }}</p>
                    <a class="stretched-link" href="{% url 'post-detail' post.pk %}"></a>
                </div>
            </div>
        </div>
        {% endfor %}

социальные/шаблоны/социальные/профиль.html

{% for post in posts %}
    <div class="row justify-content-center mt-5">
        <div class="col-md-5 col-sm-12 border-bottom position-relative">
            <p><a style="text-decoration: none;" class="text-primary" href="{% url 'profile' post.author.profile.pk %}">@{{ post.author }}</a> {{ post.created_on }}</p>
            <div class="position-relative">
                <p>{{ post.body }}</p>
                <a class="stretched-link" href="{% url 'post-detail' post.pk %}"></a>
            </div>
        </div>
    </div>
    {% endfor %}

Обновление нашего файла Models.py

Сначала нам нужно добавить отношение «многие ко многим» в наш профиль пользователя, чтобы удерживать всех подписчиков для этого пользователя. В Django есть поле ManyToMany, которое мы можем использовать для этого. Нам нужно убедиться, что мы используем модель пользователя в качестве того, что будет храниться в этом поле, мы также хотим убедиться, что его можно оставить пустым, если у пользователя нет подписчиков. Обязательно перенесите изменения с помощью makemigrations и выполните миграцию после изменения файла models.py.

социальные/models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User, primary_key=True, verbose_name='user', related_name='profile', on_delete=models.CASCADE)
    name = models.CharField(max_length=30, blank=True, null=True)
    bio = models.TextField(max_length=500, blank=True)
    birth_date = models.DateField(null=True, blank=True)
    location = models.CharField(max_length=100, blank=True, null=True)
    picture = models.ImageField(upload_to='uploads/profile_pictures/', default='uploads/profile_pictures/default.png', blank=True)
    followers = models.ManyToManyField(User, blank=True, related_name='followers')

Обновление профилей для отображения количества подписчиков

Теперь, когда у нас все настроено, мы можем отобразить количество подписчиков для каждого пользователя, пока оно будет равно нулю, пока мы не реализуем остальную часть логики. Давайте сначала добавим логику подсчета, чтобы позже мы могли легко увидеть, все ли работает. Сначала нам нужно получить количество объектов в поле подписчиков, а затем мы можем отобразить это в нашем шаблоне. Мы также хотим добавить логическое значение, чтобы проверить, подписан ли пользователь на другого пользователя, мы будем использовать его, чтобы решить, показывать ли кнопку подписки или отмены подписки в профиле.

социальные сети/views.py

class ProfileView(View):
    def get(self, request, pk, *args, **kwargs):
        profile = UserProfile.objects.get(pk=pk)
        user = profile.user
        posts = Post.objects.filter(author=user).order_by('-created_on')
        followers = profile.followers.all()
        for follower in followers:
            if follower == request.user:
                is_following = True
                break
            else:
                is_following = False
        number_of_followers = len(followers)
        context = {
            'user': user,
            'profile': profile,
            'posts': posts,
            'is_following': is_following,
            'number_of_followers': number_of_followers,
        }
        return render(request, 'social/profile.html', context)

социальные/шаблоны/социальные/профиль.html

<div class="row justify-content-center mt-5">
        <div class="card col-md-8 col-sm-12 shadow-sm px-5 pt-3">
                <img src="{{ profile.picture.url }}" class="rounded-circle" width="100" height="100" />
                {% if profile.name %}
                <h3 class="py-4">{{ profile.name }}
                    <span>
                        {% if request.user == user %}
                        <a href="{% url 'profile-edit' profile.pk %}" style="color: #333;"><i class="far fa-edit"></i></a>
                        {% endif %}
                    </span></h3>
                {% endif %}
            <div>
                {% if profile.location %}
                <p>{{ profile.location }}</p>
                {% endif %}
                {% if profile.birth_date %}
                <p>{{ profile.birth_date }}</p>
                {% endif %}
                {% if profile.bio %}
                <p>{{ profile.bio }}</p>
                {% endif %}
            </div>
            <div class="mb-3">
                <p>Followers: {{ number_of_followers }}</p>
            </div>
        </div>
    </div>

После просмотра и URL-пути

Теперь давайте добавим представление и URL-адрес для отслеживания пользователя, это представление примет запрос на публикацию и добавит его в поле ManyToMany, которое мы создали ранее, но вместо рендеринга шаблона оно перенаправит обратно на URL-адрес профиля, на котором мы только что были. В Django есть метод добавления для полей ManyToMany, поэтому мы можем использовать его, чтобы легко добавлять к нему. Мы будем использовать наш общий класс View и добавим для этого метод post.

социальные сети/views.py

class AddFollower(LoginRequiredMixin, View):
    def post(self, request, pk, *args, **kwargs):
        profile = UserProfile.objects.get(pk=pk)
        profile.followers.add(request.user)
        return redirect('profile', pk=profile.pk)

социальные/urls.py

from django.urls import path
from .views import PostListView, PostDetailView, PostEditView, PostDeleteView, CommentDeleteView, ProfileView, ProfileEditView, AddFollower, RemoveFollower
urlpatterns = [
    path('', PostListView.as_view(), name='post-list'),
    path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
    path('post/edit/<int:pk>/', PostEditView.as_view(), name='post-edit'),
    path('post/delete/<int:pk>/', PostDeleteView.as_view(), name='post-delete'),
    path('post/<int:post_pk>/comment/delete/<int:pk>/', CommentDeleteView.as_view(), name='comment-delete'),
    path('profile/<int:pk>/', ProfileView.as_view(), name='profile'),
    path('profile/edit/<int:pk>/', ProfileEditView.as_view(), name='profile-edit'),
    path('profile/<int:pk>/followers/add', AddFollower.as_view(), name='add-follower'),
]

Отмена просмотра и URL-адреса

Теперь мы сделаем то же самое для вида unfollow, это будет очень похоже, мы можем сделать почти то же самое, за исключением того, что мы можем использовать метод удаления в поле ManyToMany, чтобы удалить из него объект. Затем мы можем перенаправить обратно в профиль. Мы снова будем использовать общий класс представления с методом post.

социальные сети/views.py

class RemoveFollower(LoginRequiredMixin, View):
    def post(self, request, pk, *args, **kwargs):
        profile = UserProfile.objects.get(pk=pk)
        profile.followers.remove(request.user)
        return redirect('profile', pk=profile.pk)

социальные/urls.py

from django.urls import path
from .views import PostListView, PostDetailView, PostEditView, PostDeleteView, CommentDeleteView, ProfileView, ProfileEditView, AddFollower, RemoveFollower
urlpatterns = [
    path('', PostListView.as_view(), name='post-list'),
    path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
    path('post/edit/<int:pk>/', PostEditView.as_view(), name='post-edit'),
    path('post/delete/<int:pk>/', PostDeleteView.as_view(), name='post-delete'),
    path('post/<int:post_pk>/comment/delete/<int:pk>/', CommentDeleteView.as_view(), name='comment-delete'),
    path('profile/<int:pk>/', ProfileView.as_view(), name='profile'),
    path('profile/edit/<int:pk>/', ProfileEditView.as_view(), name='profile-edit'),
    path('profile/<int:pk>/followers/add', AddFollower.as_view(), name='add-follower'),
    path('profile/<int:pk>/followers/remove', RemoveFollower.as_view(), name='remove-follower'),
]

Добавление кнопки «Подписаться» или «Отписаться» в профилях

Наконец, давайте добавим кнопку для отправки запроса на публикацию в одно из этих двух представлений, в зависимости от того, подписан ли пользователь на пользователя или нет. Мы уже проверили это в нашем представлении, поэтому у нас должен быть доступ к логическому значению is_following. Мы можем создать две формы с кнопкой, которая отправляет запрос на публикацию, и показать только одну на основе логического значения. Вот окончательная версия этой строки profile.html, которая содержит информацию о подписчиках.

социальные/шаблоны/социальные/профиль.html

<div class="row justify-content-center mt-5">
        <div class="card col-md-8 col-sm-12 shadow-sm px-5 pt-3">
                <img src="{{ profile.picture.url }}" class="rounded-circle" width="100" height="100" />
                {% if profile.name %}
                <h3 class="py-4">{{ profile.name }}
                    <span>
                        {% if request.user == user %}
                        <a href="{% url 'profile-edit' profile.pk %}" style="color: #333;"><i class="far fa-edit"></i></a>
                        {% endif %}
                    </span></h3>
                {% endif %}
            <div>
                {% if profile.location %}
                <p>{{ profile.location }}</p>
                {% endif %}
                {% if profile.birth_date %}
                <p>{{ profile.birth_date }}</p>
                {% endif %}
                {% if profile.bio %}
                <p>{{ profile.bio }}</p>
                {% endif %}
            </div>
            <div class="mb-3">
                <p>Followers: {{ number_of_followers }}</p>
                {% if user == request.user %}
                {% else %}
                {% if is_following %}
                
                <form method="POST" action="{% url 'remove-follower' profile.pk %}">
                    {% csrf_token %}
                    <button class="btn btn-outline-danger" type=submit>UnFollow</button>
                </form>
                {% else %}
                <form method="POST" action="{% url 'add-follower' profile.pk %}">
                    {% csrf_token %}
                    <button class="btn btn-outline-success" type=submit>Follow</button>
                </form>
                {% endif %}
                {% endif %}
            </div>
        </div>
    </div>

Это завершает этот урок, нам еще есть что добавить, поэтому мы добавим больше в это приложение в будущем.