Введение в Django

Чтение и комментирование

Показывать лекцию целиком

Цель лекции: реализовать возможность чтения твитов других пользователей, используя специальную кнопку "Читать"; рассмотреть воз-можность прекратить чтение твитов выбранного пользователя; создать модель пользователя для чтения твитов пользователями; создание страницы входа и выхода из системы.

Ключевые термины: твит, django, python, модель, шаблон, пользователь, читатель, данные, html, class, url база, файл, счетчик, профиль.

Разрешить пользователям читать других пользователей.

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

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

Стратегия для внедрения этой особенности состоит в следующем:

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

    Во-первых, мы добавим счетчик ретвитов для каждого твита и отслеживание всех твитов, которые отметил пользователь. Для реализации этого, мы используем модель данных UserModel.

    Модель данных UserModel

    Когда пользователь читает другого пользователя, нам нужно хранить следующую информацию в базу данных:

  • Данные о пользователе, которого начали читать. Нам нужно этого для того, чтобы определить пользователя, у которого больше всего читателей за выбранный период времени.
  • Число читателей у пользователя
  • Список пользователей, кто читает нашего пользователя
  • Это нужно, чтобы предотвратить двойное добавление для чтения на одного и того же пользователя.

    Для этой цели, Мы создадим новую модель данных, назовем ее UserFollowers. Откроем файл user_profile/model.py и добавим в него следующий класс:

    class UserFollower(models.Model):
        user = models.ForeignKey(User, unique=True)
        date = models.DateTimeField(auto_now_add=True)
        count = models.IntegerField(default=1)
    followers= models.ManyToManyField(User,related_name='followers')
        def __unicode__(self):
            return self.user.username
    

    Эта модель данных выполняет очень важные функции, мы рассмотрим ее поля шаг за шагом.

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

    Поле date имеет тип models.DateTimeField. Как можно догадаться по имени поля, оно нужно для хранения значения даты/времени. Аргумент auto_now_add указывает Django на то, чтобы автоматически устанавливалась текущая дата/время в это поле, когда объект этих данных будет создан в первый раз.

    Поле count имеет тип models.IntegerField. Это поле хранит целочисленное значение. Используя параметр default=1, мы указываем Django на то, чтобы установить 1 в это поле, когда объект этих данных будет создан в первый раз.

    Параметр followers типа models.ManyToManyField содержит список пользователей, которых читает данный пользователь.

    Здесь параметр related_name=`followers` должен быть вторым парметром. И пользователь и читатель относятся к классу user, которые, если обратиться к по относительному имени, выдаст ошибку, такую как получающий доступ к полю user сталкивается с относительным m2m полем User.userfollowers_set.

    После введения кода модели данных в файл user_profile/model.py, выполните следующую команду, чтобы создать его корреспондирующие таблицы в базе данных:

    python manage.py syncdb

    С этой командой мы сможем сохранить всю информацию, которая нам нужна для поддержки читателей.

    Далее, мы создадим представление, в котором пользователи читают других пользователей, нажимая кнопку "Читать" по имени профиля.

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

    Давайте отредактируем текущий профиль пользователя, profile.html

    Добавим напротив имени пользователя иконку пользователя, Мы можем использовать глиф-иконки Bootstrap. Это набор иконок, поставляемых по умолчанию с Bootstrap.

    <p class="navbar-text navbar-right">
            <span class="glyphicon glyphicon-user"></span> 
    {{ user.username }}
        </p>
    {% endblock %}
    

    Мы также сделаем дизайн нового текстового поля публикации твита на странице профиля. Обновленный файл profile.html будет выглядеть так:

    {% extends "base.html" %}
    {% block navbar %}
         <p class="navbar-text navbar-left">
            <span class="glyphicon glyphicon-user"> 
    </span>  {{ profile.username } </p>
    
    {% endblock %}
    {% block content %}
    
        <div class="row clearfix">
    
            <div class="col-md-6 col-md-offset-3 column">
                <form id="search-form" action="post/" method="POST">{% csrf_token %}
                    <div class="input-group">
                        {{ form.text.errors }}
                        {{ form.text }}
                        {{ form.country.as_hidden }}
    
          <span class="input-group-btn">
            <button class="btn btn-default" type="submit">Post</button>
          </span>
                    </div>
                    <!-- /input-group -->
                </form>
            </div>
            <h1> </h1>
            <div class="col-md-12 column">
                {% for tweet in tweets %}
                    <div class="well">
                        <span>{{ tweet.text }}</span>
                    </div>
                {% endfor %}
            </div>
        </div>
    {% endblock %}
    

    Обновите файл forms.py для отрисовки новой формы:

    class TweetForm(forms.Form):
        text = forms.CharField(widget=forms.Textarea(attrs={'rows': 1, 'cols': 85, 'class':'form-control post-tweet', 'placeholder': 'Опубликовать новый твит'}), max_length=160)
        country = forms.CharField(widget=forms.HiddenInput())
    

    Обновленный элемент интерфейса будет выглядеть примерно так:

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

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

    Модели входа пользователя

    Для осуществления входа пользователя в систему, мы должны добавить URL-адреса по умолчанию для входа и регистрации. Мы будем добавлять следующие шаблоны URL-адресов в файл urls.py:

    url(r'^login/$', 'django.contrib.auth.views.login'),
    url(r'^logout/$', 'django.contrib.auth.views.logout'),
    

    Теперь наш файл urls.py выглядит следующим образом:

    from django.conf.urls import patterns, include, url
    
    from django.contrib import admin
    
    from tweet.views import Index, Profile, PostTweet, HashTagCloud, Search, SearchHashTag, HashTagJson 
    
    admin.autodiscover()
    
    urlpatterns = patterns('',
        url(r'^$', Index.as_view()),
        url(r'^user/(\w+)/$', Profile.as_view()),
        url(r'^admin/', include(admin.site.urls)),
        url(r'^user/(\w+)/post/$', PostTweet.as_view()),
        url(r'^hashTag/(\w+)/$', HashTagCloud.as_view()),
        url(r'^search/$', Search.as_view()),
        url(r'^search/hashTag$',SearchHashTag.as_view())),
        url(r'^hashtag.json$', HashTagJson.as_view()),
        url(r'^login/$', 'django.contrib.auth.views.login'),
        url(r'^logout/$', 'django.contrib.auth.views.logout'),
    )
    

    У представлений входа в систему и выхода из нее имена шаблонов по умолчанию, registration/login.html и registration/login_out.html соответственно. Поскольку эти представления специфичны для пользователя и не очень специфичны для повторного использования нашего приложения, мы создадим новый каталог шаблона/регистрации внутри проекта mytweets, используя следующую команду:

    mkdir –p mytweets/templates/registration

    Затем создадим простые страницы входа в систему и выхода из нее. Оформим следующий фрагмент кода в файл login.html.

    {% extends "base.html" %}
    
    {% block content %}
    
        {% if form.errors %}
            <p> Ваши логин и пароль не совпадают. Попробуйте еще. </p>
        {% endif %}
    
        <form method="post" action="{% url 'django.contrib.auth.views.login' %}">
            {% csrf_token %}
            <table>
                <tr>
                    <td>{{ form.username.label_tag }}</td>
                    <td>{{ form.username }}</td>
                </tr>
                <tr>
                    <td>{{ form.password.label_tag }}</td>
                    <td>{{ form.password }}</td>
                </tr>
            </table>
    
            <input type="submit" value="login"/>
            <input type="hidden" name="next" value="{{ next }}"/>
        </form>
    
    {% endblock %}
    

    Оформим следующий фрагмент кода в файл logout.html:

    {% extends "base.html" %}
    
    {% block content %}
    Вы вышли из системы!
    {% endblock %}
    

    Мы просто активируем систему аутентификации Django по умолчанию. Так как это базовая аутентификация, то требуется предопределить URL для правильного перенаправления. Например мы уже знаем, что что /login принимает пользователя от html-страницы /registration/login. Аналогично, после того, как пользователь прошел проверку подлинности, перенаправляются на URL accounts/profile. В нашем проекте у нас есть пользовательские URL для каждого пользователя. Мы обновим эти записи в файле settings.py:

    LOGIN_REDIRECT_URL = '/profile'
    LOGIN_URL = 'django.contrib.auth.views.login'
    

    Для сохранения простоты, мы просто создаем представление, которое будет забирать пользователя, прошедшего аутентификацию из профиля и перенаправляющего этого пользователя на страницу его профиля. Главным образом, мы создаем параметр имени пользователя после его аутентификации. Иными словами, /profile | /profile/<username> генерируется в отдельное представление класса. Для этого нам нужно создать нижеследующую URL-запись:

    url(r'^profile/$', UserRedirect.as_view()),

    А перенаправляющий класс Profile с методом get() будет выглядеть как:

    class UserRedirect(View):
        def get(self, request):
        return HttpResponseRedirect('/user/'+request.user.username)
    

    Вот так. Теперь каждый пользователь, входящий в систему пользователь будет перенаправляться на его страницу профиля.

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

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

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

    {% extends "base.html" %}
    {% load staticfiles %}
    {% block navbar %}
         <p class="navbar-text navbar-left">
            <span class="glyphicon glyphicon-user"> </span>  {{ profile.username }}'s Profile Page
             {% if profile.username != user.username %}
                  <span class="btn btn-xs btn-default follow-btn" title="Click to follow {{ profile.username }}" value="{{ following }}" username="{{ profile.username }}">
                 <span class="glyphicon glyphicon-plus"></span><span class="follow-text">
                     {{ following|yesno:"Unfollow,Follow" }}
                 </span></span>
             {% endif %}
        </p>
        <p class="navbar-text navbar-right">
            <span class="glyphicon glyphicon-user"></span> {{ user.username }}
        </p>
    {% endblock %}
    {% block content %}
    
        <div class="row clearfix">
            <div class="col-md-6 col-md-offset-3 column">
                <form id="search-form" action="post/" method="POST">{% csrf_token %}
                    <div class="input-group">
                        {{ form.text.errors }}
                        {{ form.text }}
                        {{ form.country.as_hidden }}
    
          <span class="input-group-btn">
            <button class="btn btn-default" type="submit">Post</button>
          </span>
                    </div>
                    <!-- /input-group -->
                </form>
            </div>
    
            <h1> </h1>
    
            <div class="col-md-12 column">
                {% for tweet in tweets %}
                    <div class="well">
                        <span>{{ tweet.text }}</span>
                    </div>
                {% endfor %}
            </div>
        </div>
    {% endblock %}
    

    Следующий код проверяет, просматривает ли пользователь свой собственный профиль; Если это так, кнопка чтения не отображается. Он также проверяет находится ли пользователь вошедший в систему в профиле пользователя, который тот читает; если это так, будет показана кнопка прекращения чтения, а если нет, будет показана кнопка чтения.

    {% if profile.username != user.username %}
                  <span class="btn btn-xs btn-default follow-btn" title="Click to follow {{ profile.username }}" value="{{ following }}" username="{{ profile.username }}">
                 <span class="glyphicon glyphicon-plus"></span><span class="follow-text">
                     {{ following|yesno:"Unfollow,Follow" }}
                 </span></span>
             {% endif %}
    

    Для отрисовки обновленного содержимого, обновим класс Profile() соответственно:

    class Profile(LoginRequiredMixin, View):
        """User Profile page reachable from /user/<username> URL"""
        def get(self, request, username):
            params = dict()
            userProfile = User.objects.get(username=username)
                userFollower = UserFollower.objects.get(user=userProfile)
                if userFollower.followers.filter(username=request.user.username).exists():
                    params["following"] = True
                else:
                    params["following"] = False
            except:
                userFollower = []
    
            form = TweetForm(initial={'country': 'Global'})
            search_form = SearchForm()
            tweets = Tweet.objects.filter(user=userProfile).order_by('-created_date')
            params["tweets"] = tweets
            params["profile"] = userProfile
            params["form"] = form
            params["search"] = search_form
            return render(request, 'profile.html', params)
    

    Следующий код проверяет, является ли пользователь, вошедший в систему, читателем пользователя, страницу которого он посетил:

    if userFollower.followers.filter
    (username=request.user.username).exists()
    

    Добавление и удаление читателей

    Давайте создадим метод post() для профиля, чтобы добавлять и удалять читателей, основано на параметрах:

    def post(self, request, username):
        follow = request.POST['follow']
        user = User.objects.get(username= request.user.username)
        userProfile = User.objects.get(username=username)
      userFollower,status=UserFollower.objects.get_or_create(user=userProfile)
        userFollower.count += 1
        userFollower.save()
        if follow == 'true':
            #follow user
            userFollower.followers.add(user)
        else:
            #unfollow user
            userFollower.followers.remove(user)
        return HttpResponse(json.dumps(""), 
    content_type="application/json")
    

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

    Часть кнопки подписки в файле profile.html должны быть обновлены с именами классов, так, что мы можем переключать функциональность событий JavaScript, как показано ниже:

    <p class="navbar-text navbar-left">
            <span class="glyphicon glyphicon-user"> </span>  {{ profile.username }}'s Profile Page
             {% if profile.username != user.username %}
                  <span class="btn btn-xs btn-default follow-btn" title="Click 
    to follow {{ profile.username }}" value="{{ following }}" 
    username="{{ profile.username }}">
                 <span class="glyphicon glyphicon-plus"></span><span class="follow-text">
                     {{ following|yesno:"Unfollow,Follow" }}
                 </span></span>
             {% endif %}
        </p>
    

    Наконец, создадим файл Javascript c именем profile.js, который вызывает метод post () в зависимости от нажатой кнопки "Читать"/ "Перестать читать":

    $(".follow-btn").click(function () {
        var username = $(this).attr('username');
        var follow = $(this).attr('value') != "True";
        $.ajax({
            type: "POST",
            url:  "/user/"+username+"/",
            data: { username: username , follow : follow  },
            success: function () {
                window.location.reload();
            },
            error: function () {
                alert("ERROR !!");
            }
        })
    });
    

    Не забудьте добавить этот файл JavaScript в файл profile.html в конец страницы, как показано следующим кодом:

    {% block js %}
        <script src="{% static 'js/profile.js' %}"></script>
    {% endblock %}
    

    Отображение пользователя, на которого чаще всего подписываются

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

    Основные компоненты для дизайна этой страницы будут:

  • Представление: файл users.html
  • Представление: файл users.html
  • Отображение URL
  • Добавьте следующее содержимое в файл users.html:

    {% extends "base.html" %}
    {% load staticfiles %}
    {% block navbar %}
    <p class="navbar-text navbar-right">
    <span class="glyphicon glyphicon-user"x/span>
    {{ user.username }}
    </p>
    {% endblock %}
    {% block content %}
    <div class="row clearfix">
    <div class="col-md-12 column">
    {% for userFollower in UserFollowers %}
    <div class="well">
    <span class="username">{{ userFollower.user.username }}</span>
    <span class="count text-muted"> ({{ userFollower.count }} followers)</span>
    </div>
    {% endfor %}
    </div>
    </div>
    {% endblock %}
    

    И следующий класс в контроллер:

    class MostFollowedUsers(View):
        def get(self, request):
            userFollowers = UserFollower.objects.order_by('-count')[:2]
            params = dict()
            params['userFollowers'] = userFollowers
            return render(request, 'users.html', params)
    

    Следующая строка сортирует пользователей по количеству читателей:

    userFollowers = UserFollower.objects.order_by('-count1)

    Нам нужно обновить URL-представление, как показано ниже:

    url(r’^mostFollowed/$', MostFollowedUsers.as_view()),

    Вот и все! Мы сделали страницу со списком пользователей по числу читателей. Если подсчет затягивается, можно ограничить список с помощью базового синтаксиса списка Python:

    userFollowers = UserFollower.objects.order_by('-count1) [: 10]

    Будет выведен топ-10 пользователей.

    Контрольные вопросы

  • Для чего требуется проверка, читает ли пользователь того пользователя, профиль которого он посетил?
  • Какую информацию необходимо сохранить в случае, когда пользователь читает другого пользователя?
  • Какие компоненты необходимы для страницы, где отображается список пользователей, которых наиболее часто читают?
  • Что требуется для базовой аутентификации?
  • Как вывести 10 самых читаемых пользователей?
  • Упражнения

    Упражнение 1.

    Поменяйте иконку пользователя

    Упражнение 2.

    Доработайте систему комментирования

    Упражнение 3.

    Добавьте возможность отображения общего количества твитов для конкретного пользователя

    Упражнение 4.

    Доработайте страницу входа пользователя с тем, чтобы была возможность проверки

    Список тем курсовых работ

  • Создание прототипа системы модерации твитов
  • Разработка прототипа системы регистрации пользователей
  • Создание продвинутой системы комментирования
  • Разработка продвинутого профиля пользователя
  • Разработка прототипа системы ведения пользовательских блогов
  • Разработка прототипа сервиса мгновенного обмена собщениями
  • Разработка прототипа веб-сервиса для хранения изображений
  • Разработка прототипа веб-сервиса электронной почты
  • Разработка прототипа сервиса для ведения списка ежедневных дел
  • Разработка прототипа сервиса для учета рабочего времени
  • Краткие итоги

  • Создали модель данных UserModel
  • Создали страницу входа в систему
  • Создали страницу входа в систему
  • Рассмотрели стратегии реализации возможности чтения твитов других пользователей
  • Реализовали возможность чтения твитов других пользователей.
  • Реализовали возможность добавления и удаления читателей
  • Реализовали модель входа пользователя
  • Узнали, как можно отобразить пользователя, который имеет наибольшее количество читателей
  • Разработали страницу, на которой отображается наибольшее количество комментариев
  • Реализовали возможность комментирования твитов
  • Вернуться к учебному плану