Реклама

Django - Урок 022. Добавление системы закладок (избранное) на сайте

РуководствоDjangoDjango, jQuery, AJAX, bookmark, favorite606

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

Для того, чтобы реализовать систему закладок, необходимо:

  • Добавить таблицу, которая реализует отношение Many-to-Many между пользователем и статьёй или комментарием.
  • Добавить view, который будет обрабатывать данный запрос.
  • Добавить url для обработки запроса на добавление или исключение объекта из избранного.
  • Написать html-код, который будет отвечать за отображение счётчика добавленного в закладки.
  • Добавить javascript обработчик, который будет вызывать AJAX-запрос.

На данном сайте в качестве иконки счётчика используется иконка звезды из Bootstrap.

Many-to-Many таблица для закладок

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

class BookmarkBase(models.Model):
    class Meta:
        abstract = True

    user = models.ForeignKey(User, verbose_name="Пользователь")

    def __str__(self):
        return self.user.username

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

Здесь добавим поле obj , которое будет отвечать за внешний ключ на таблицу контента: Article или Comment. Важно, чтобы поле называлось одинаково в обеих моделях. Тогда можно будет написать один view для всех таблиц закладок.

class BookmarkArticle(BookmarkBase):
    class Meta:
        db_table = "bookmark_article"

    obj = models.ForeignKey(Article, verbose_name="Статья")


class BookmarkComment(BookmarkBase):
    class Meta:
        db_table = "bookmark_comment"

    obj = models.ForeignKey(Comment, verbose_name="Комментарий")

views.py

Для обработки запроса на добавление в закладки или удаление из закладок создадим view, которое сможет работать с любой таблицей закладок, которая будет удовлетворять общему виду, представленному выше.

# -*- coding: utf-8 -*-

import json

from django.contrib import auth
from django.http import HttpResponse
from django.views import View


class BookmarkView(View):
    # в данную переменную будет устанавливаться модель закладок, которую необходимо обработать
    model = None

    def post(self, request, pk):
        # нам потребуется пользователь
        user = auth.get_user(request)
        # пытаемся получить закладку из таблицы, или создать новую
        bookmark, created = self.model.objects.get_or_create(user=user, obj_id=pk)
        # если не была создана новая закладка, 
        # то считаем, что запрос был на удаление закладки
        if not created:
            bookmark.delete()

        return HttpResponse(
            json.dumps({
                "result": created,
                "count": self.model.objects.filter(obj_id=pk).count()
            }),
            content_type="application/json"
        )

Заметьте, что в данном коде использовалось сравнение obj_id=pk , что означает, что мы пытаемся найти запись в таблице закладок по id объекта. Поскольку во всех моделях - это поле одинаковое, то проблем возникнуть не должно с подобным синтаксисом.

urls.py

А теперь посмотрим, как будут выглядеть url для обработки запросов на добавление контента в закладки.

# -*- coding: utf-8 -*-

from django.conf.urls import url
from django.contrib.auth.decorators import login_required

from . import views
from users.models import BookmarkArticle, BookmarkComment

app_name = 'ajax'
urlpatterns = [
    url(r'^article/(?P<pk>\d+)/bookmark/$',
        login_required(views.BookmarkView.as_view(model=BookmarkArticle)),
        name='article_bookmark'),
    url(r'^comment/(?P<pk>\d+)/bookmark/$',
        login_required(views.BookmarkView.as_view(model=BookmarkComment)),
        name='comment_bookmark'),
]

Чтобы выполнить этот запрос пользователь должен быть авторизован, за что отвечает декоратор login_required .

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

html

В моём случае html-код выглядит так:

<div data-id="{{ like_obj.id }}" data-type="article" data-action="bookmark" title="Избранное">
    <span class="glyphicon glyphicon-star"></span>
    <span data-count="bookmark">{{ like_obj.get_bookmark_count }}</span>
</div>

Здесь имеется несколько кастомных атрибутов:

  • data-id - отвечает за pk контента, который можно добавлять в закладки.
  • data-type - тип контента, это же название фигурирует и в url.
  • data-action - действие, которое нужно совершить, в данном случае добавление в закладки
  • data-count - счётчик, показывающий сколько пользователей добавили контент в закладки

Что касается следующего кода like_obj.get_bookmark_count, то это тоже единообразный метод, которая добавляется в модели контента, например для статей будет выглядеть следующим образом:

def get_bookmark_count(self):
    return self.bookmarkarticle_set().all().count()

javascript

AJAX-запросы для данного функционала создаются с помощью библиотеки jQuery .

При работе с AJAX необходимо учитывать несколько нюансов:

  1. Если у вас мультиязычный сайт, у которого различаются url по текущему языку, то лучше сделать отдельное api для AJAX, которое будет независимо от языка, иначе нужно будет учитывать язык в url при создании AJAX-запроса. Если не учитывать язык, то будет производится редирект AJAX-запроса на текущий url с учётом языка, и запрос не будет срабатывать. То есть редиректов быть не должно.
  2. Django не примет AJAX-запрос, если он не будет настроен на использование CSRF токена, который служит для борьбы с подделкой межсайтовых запросов. При каждом запросе страницы Django подмешивает CSRF токен в Cookies, оттуда его и можно будет взять.

Настройка AJAX на использование CSRF токена

Следующий код можно включить в скрипт на каждой странице сайта, где нужно использоваться AJAX. Он автоматически будет настраивать AJAX на использование CSRF Токена.

// Получение переменной cookie по имени
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

// Настройка AJAX
$(function () {
    $.ajaxSetup({
        headers: { "X-CSRFToken": getCookie("csrftoken") }
    });
});

Обработчики добавления в закладки и их подключение

В ниже следующем коде формируется AJAX-запрос на вызов добавления или удаления из закладок контента. В данном случае код будет универсален как для статей, так и для комментариев.

В url запроса можно наблюдать следующую строку "/api/" + type + "/" + pk + "/" + action + "/", которая означает, что модуль для AJAX запросах висит на префиксе /api/ , то есть на этот url он подключён в основном urls.py файле проекта, далее идёт тип контента, его первичный ключ и действие которое нужно совершить. Поскольку все эти данные забираются из атрибутов data, то итоговый url будет выглядеть следующим образом:

  • /api/article/112/bookmark/ - для статей
  • /api/comment/14/bookmark/ - для комментариев

В обработчике успешного результата можно добавить подсвечивание звёздочки закладки для текущего пользователя и т.д.

function to_bookmarks()
{
    var current = $(this);
    var type = current.data('type');
    var pk = current.data('id');
    var action = current.data('action');

    $.ajax({
        url : "/api/" + type + "/" + pk + "/" + action + "/",
        type : 'POST',
        data : { 'obj' : pk },

        success : function (json) {
            current.find("[data-count='" + action + "']").text(json.count);
        }
    });

    return false;
}

// Подключение обработчика
$(function() {
    $('[data-action="bookmark"]').click(to_bookmarks);
});
Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
Последние комментарии
  • EVILEG
  • 23 мая 2017 г. 12:26

Qt/C++ - Урок 005. QSqlRelationalTableModel - Работаем со связными таблицами

В классе DataBase указывается путь к базе данных. В данном случае C:/example/ и т.д. Так вот, у вас есть каталог example ?

  • EVILEG
  • 23 мая 2017 г. 12:19

Qt/C++ - Урок 029. Изображение в базе данных в Qt – Сохранение и Восстановление

Нет. не верно. Ошибка вот в этой строке: QPixmap inixmap = fileName; // Сохраняем его в изображение объекта QPixmap; Выше я показывал, как что нужно путь передавать в качестве аргумент...

Qt/C++ - Урок 029. Изображение в базе данных в Qt – Сохранение и Восстановление

QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "/home", tr("Images (*.png *.xpm *.jpg)")); QPixmap inixmap = fileName; // Сохраняем его в и...

Qt/C++ - Урок 005. QSqlRelationalTableModel - Работаем со связными таблицами

https://www.dropbox.com/sh/vhxcx0iyq0j4578/AACwgWPnZwNqGBndKESiXfFqa?dl=0

  • tetta
  • 22 мая 2017 г. 1:32

Qt/C++ - Урок 052. Кастомизация Qt Аудио плеера в стиле AIMP

Как сделать так, что бы только когда верхнюю полосу зажимаешь, то перетаскивалось окно и что бы оно оставалось на месте?

Сейчас обсуждают на форуме
  • tetta
  • 23 мая 2017 г. 17:59

Создание кнопки "new", "save" и "open". MDI. toolBar

void MainWindow::open(){ QString openFile = QFileDialog::getOpenFileName(this, tr("Open File"), "C:/", ...

Проверка наличия записи в БД при выполнении запроса

Код форм про которые говорил прикрепил. regrdit форма в которой отображается информация отбираемая из базы. editobject форма для редактирования

  • Arrow
  • 23 мая 2017 г. 13:41

qmake

Странно по сообщениям выдает, что у меня компилирутся 32 bit версия (x86 build). Компилятор опознан правильно и в ABI сам определилил x86-windows-msys-pre-64bit и исполняемый файл создае...

WinApi CBTProc

Да всех активных, тоесть через CBT.