Рубрики
Без рубрики

Создание клона Hacker News в Django – Часть 4 (AJAX и Mixin)

Автор оригинала: Arun Ravindran.

Вы читаете пост из серии руководств, состоящей из четырех частей.

  • Часть 1
  • Часть 2
  • Часть 3
  • Часть 4

Да, все хорошее когда-нибудь заканчивается. Становится еще лучше, когда хороший финал. Steel Rumors – это проект, который помогал новичкам в Django перейти на следующий уровень с базовых руководств. В нем были элементы, которые были бы полезны для большинства практических сайтов, таких как регистрация пользователей и создание просмотров CRUD.

Честно говоря, я сам не люблю большинство видеоуроков, так как на их просмотр нужно много времени. Однако, если они приходят с полной расшифровкой, я могу бегло просмотреть текст и решить, есть ли в нем советы, которые стоит посмотреть на видео. Иногда стоит посмотреть на дополнительный комментарий, объясняющий контекст.

Итак, я решил создать Steel Rumors как нечто, что всем, включая меня, понравилось бы смотреть. Но оказывается, сделать стенограмму намного сложнее, чем записать быстрое видео. На самом деле, иногда это становится однообразным (я очень сочувствую тем, кто работает в Medical Transcription!).

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

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

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

Я бы рекомендовал просмотреть все предыдущие части перед просмотром этого видео.

Скринкаст

Вы узнали много нового из этой серии видео? Тогда вам следует подписаться на мою готовящуюся к выпуску книгу «Создание сайта социальных новостей на Django» . В нем объясняется в стиле учиться у друга , как создаются веб-сайты, и постепенно рассматриваются сложные темы, такие как тестирование, безопасность, миграция баз данных и отладка.

Пошаговая инструкция

Это расшифровка видео. В части 3 мы создали сайт социальных новостей, где пользователи могут публиковать и комментировать слухи о “Человеке из стали” , но не может голосовать.

План части 4 скринкаста таков:

  • Голосование с помощью FormView
  • Голосование через AJAX
  • Миксины
  • Показать статус голосования
  • Алгоритм ранжирования
  • Фоновые задачи

Голосование с помощью FormView

Мы добавим кнопку голосования (со знаком плюс) к каждому заголовку. Нажатие на нее переключает статус пользователя “проголосовал” для ссылки, т.е. проголосовал или не голосовал. Самый безопасный способ реализовать это – использовать ModelForm для нашей модели Vote .

Добавьте новую форму в links/forms.py :

    from .models import Vote
    ...

    class VoteForm(forms.ModelForm):
        class Meta:
            model  Vote

Мы будем использовать другое общее представление под названием FormView для обработки части представления этой формы. Добавьте эти строки в links/views.py

    from django.shortcuts import redirect
    from django.shortcuts import get_object_or_404
    from django.views.generic.edit import FormView
    from .forms import VoteForm
    from .models import Vote
    ...

    class VoteFormView(FormView):
        form_class  VoteForm

        def form_valid(self, form):
            link  get_object_or_404(Link, pkform.data["link"])
            user  self.request.user
            prev_votes  Vote.objects.filter(voteruser, linklink)
            has_voted  (prev_votes.count() > 0)

            if not has_voted:
                # add vote
                Vote.objects.create(voteruser, linklink)
                print("voted")
            else:
                # delete vote
                prev_votes[0].delete()
                print("unvoted")

            return redirect("home")

        def form_invalid(self, form):
            print("invalid")
            return redirect("home")

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

Отредактируйте шаблон домашней страницы, чтобы добавить форму голосования для каждого заголовка. Добавьте строки со знаком ‘+’ (убрав знак ‘+’) в steelrumors/templates/links/link_list.html :

    {% for link in object_list %}
    + 
{% url 'vote' %}">
  • [{{ link.votes }}] + {% csrf_token %} + {{ link.pk }}" /> + {{ user.pk }}" /> + {{ link.title }}
  • +

    Добавьте это представление в steelrumours/urls.py :

        from links.views import VoteFormView
    
        url(r'^vote/$', auth(VoteFormView.as_view()), name"vote"),  
    

    Обновите браузер, чтобы увидеть кнопки «+» на каждом заголовке. Вы также можете проголосовать за них. Но узнать статус голосования можно только с консоли.

    Голосование с помощью AJAX

    Вы уже скопировали статическую папку из набора вкусностей в предыдущей части. Но если вы этого не сделали, выполните этот шаг.

    Создайте папку с именем ‘js’ в steelrumors/static наших файлах javascript. Скопируйте в эту папку jquery и vote.js из пакета вкусностей.

        mkdir steelrumors/static/js
        cp /tmp/sr-goodies-master/static/js/* ~/proj/steelrumors/steelrumors/static/js/
    

    Добавьте эти строки в steelrumors/templates/base.html внутри блока :

          Steel Rumors
          
        +  
        +  
        
        
    

    В views.py удалите весь класс VoteFormView и замените этими тремя классами. Мы используем миксин для реализации ответа JSON на наши запросы AJAX:

        import json
        from django.http import HttpResponse
        ...
    
        class JSONFormMixin(object):
            def create_response(self, vdictdict(), valid_formTrue):
                response  HttpResponse(json.dumps(vdict), content_type'application/json')
                response.status  200 if valid_form else 500
                return response
    
        class VoteFormBaseView(FormView):
            form_class  VoteForm
    
            def create_response(self, vdictdict(), valid_formTrue):
                response  HttpResponse(json.dumps(vdict))
                response.status  200 if valid_form else 500
                return response
    
            def form_valid(self, form):
                link  get_object_or_404(Link, pkform.data["link"])
                user  self.request.user
                prev_votes  Vote.objects.filter(voteruser, linklink)
                has_voted  (len(prev_votes) > 0)
    
                ret  {"success": 1}
                if not has_voted:
                    # add vote
                    v  Vote.objects.create(voteruser, linklink)
                    ret["voteobj"]  v.id
                else:
                    # delete vote
                    prev_votes[0].delete()
                    ret["unvoted"]  1
                return self.create_response(ret, True)
    
            def form_invalid(self, form):
                ret  {"success": 0, "form_errors": form.errors }
                return self.create_response(ret, False)
    
        class VoteFormView(JSONFormMixin, VoteFormBaseView):
            pass
    

    Отображение состояния “Проголосовано”

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

    Добавьте это в класс LinkListView в links/views.py :

        class LinkListView(ListView):
        ...
    
            def get_context_data(self, **kwargs):
                context  super(LinkListView, self).get_context_data(**kwargs)
                if self.request.user.is_authenticated():
                    voted  Vote.objects.filter(voterself.request.user)
                    links_in_page  [link.id for link in context["object_list"]]
                    voted  voted.filter(link_id__inlinks_in_page)
                    voted  voted.values_list('link_id', flatTrue)
                    context["voted"]  voted
                return context
    

    Снова измените шаблон домашней страницы. Добавьте строки со знаком ‘+’ (убрав знак ‘+’) в steelrumors/templates/links/link_list.html :

            {{ user.pk }}" />
          + {% if not user.is_authenticated %}
          + 
          + {% elif link.pk not in voted %}
            
          + {% else %}
          + 
          + {% endif %}
            
    

    Теперь кнопка меняется в зависимости от проголосованного состояния заголовка. Попробуйте это в своем браузере с разными учетными записями пользователей.

    Расчет рейтинга

    Мы собираемся изменить порядок сортировки ссылок с наивысшей оценки на самую высокую. Добавьте новую функцию в models.py для вычисления рейтинга:

        from django.utils.timezone import now
        ...
    
        class Link(models.Model):
        ...
    
            def set_rank(self):
                # Based on HN ranking algo at http://amix.dk/blog/post/19574
                SECS_IN_HOUR  float(60*60)
                GRAVITY  1.2
    
                delta  now() - self.submitted_on
                item_hour_age  delta.total_seconds() // SECS_IN_HOUR
                votes  self.votes - 1
                self.rank_score  votes / pow((item_hour_age+2), GRAVITY)
                self.save()
    

    В том же файле измените критерии сортировки в классе LinkVoteCountManager . Измененная строка отмечена знаком «+».

        class LinkVoteCountManager(models.Manager):
            def get_query_set(self):
                return super(LinkVoteCountManager, self).get_query_set().annotate(
        +             votesCount('vote')).order_by('-rank_score', '-votes')
    

    Рейтинг вакансий

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

    #!/usr/bin/env python
    import os
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "steelrumors.settings")
    from links.models import Link
    
    def rank_all():
        for link in Link.with_votes.all():
            link.set_rank()
    
    import time
    
    def show_all():
        print "\n".join("%10s %0.2f" % (l.title, l.rank_score,
                             ) for l in Link.with_votes.all())
        print "----\n\n\n"
    
    if __name__"__main__":
        while 1:
            print "---"
            rank_all()
            show_all()
            time.sleep(5)
    

    Это выполняется каждые 5 секунд на переднем плане.

    Превратите это в фоновую работу

    (nohup python -u rerank.py&)
    tail -f nohup.out
    

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

    Просмотр новостей

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

    Чтобы увидеть более резкое изменение рейтингов, измените константу SECS_IN_HOUR на небольшое значение, например 5.0. Теперь отправьте новую ссылку и смотрите, как очки падают, как камень!

    Заключительные комментарии

    Steel Rumors – далеко не полный клон Hacker News. Но он поддерживает голосование, отправку ссылок и регистрацию пользователей. Фактически, на данный момент это вполне пригодно.

    Посмотрите сами демонстрацию Steel Rumors .

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

    Ресурсы