На сайте добавлена возможность помечать статьи, комментарии, темы и ответы на форуме как избранное. При этом пометка в качестве избранного не предусматривает перезагрузку страницы, поскольку для этих действий используется механизм 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 необходимо учитывать несколько нюансов:
- Если у вас мультиязычный сайт, у которого различаются url по текущему языку, то лучше сделать отдельное api для AJAX, которое будет независимо от языка, иначе нужно будет учитывать язык в url при создании AJAX-запроса. Если не учитывать язык, то будет производится редирект AJAX-запроса на текущий url с учётом языка, и запрос не будет срабатывать. То есть редиректов быть не должно.
- 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); });
Для Django рекомендую VDS-сервера хостера Timeweb .
Доброго времени суток Евгений. Не подскажете как сделать иконки (лайк-дизлайк) кликабельными?
Доброго времени суток Евгений. Не ясен метод:
Не могли бы вы растолковать откуда моя модель статьи знает о этой функции?:
Когда применяю данный метод то выскакивает ошибка:
И второй вопрос каким образом можно подсвечивать закладку (допустим менять ее цвет когда она добавлена пользователем, ну как у вас на сайте)?
Добрый день, Игорь!
Вообще это стандартное поведение в моделях, если на данную модель какая-то иная модель имеет внешний ключ (ForeignKey)
В данном конкретном случае есть модель Article и BookmarkArticle , поскольку BookmarkArticle имеет внешний ключ на Article, то модель Article автоматически получает метод bookmarkarticle_set, который возвращает query set всех закладок, которые имеют внешний ключ на конкретный объект статьи.
Здесь я по ходу допустил ошибку, напишите так
Для подсветки я использую фильтр в шаблоне, которые ищет пользователя в наборе
а вот его применение
Да, так все работает! Спасибо вам огромное.
Еще один вопрос:
Я подключил фильтр:
в templatetags/movie_extras.py
в шаблоне:
При этом ошибка:
что я делаю не так Евгений?
мне кажется, вы неправильно записали это
Как у вас называется модель закладок для фильмов? BookmarkArticle? BookmarkMovie?
Я вам дал код из сайта, а здесь уже на десять раз всё переписано и выглядит иначе. У вас скорее всего как-то так должно быть.
Или что-то типо того
Все верно.
Так все работает. Только у меня:
Фильтр к сожалению не работает.
так перепишите его так
Все работет Евгений. Спасибо вам.
Доброго времени суток Евгений. Можно поинтересоваться каким образом выводите закладки в профиле пользователя?
Добрый день, Игорь.
Во вьшке находите в базе данных пользователя, а потом забираете все закладки аналогично, как сделали для фильмов. К пользователю также добавляются неявно методы соответствующих моделей данных, которые имеют внешний ключ на пользователя.
user.bookmarkmovie_set.all()
Спасибо Евгений, буду пробовать.
Ну с выводом кол-ва закладок пользователя разобрался успешно, а вот с выводом содержимого закладок почему то не могу сообразить. Прошу помощи Евгений.
Покажите шаблон, где выводите закладки, но без контента
хм.. для вывода тогда должно быть как-то так
Думаю, что догадаетесь как вывести нужную информацию о фильме
bookmark.movie - означает, что вы берёте поле внешнего ключа на фильм, которое должно быть в закладке. полагаю, что онго должно называться movie
Ага цикл. Спасибо за разъяснение Евгений.
здраствуйте. У меня почему-то выходит так
здраствуйте. У меня почему-то выходит так
url или не подключили, или подключили неправильно
Подскажите пожалуйста, что значит
в urls.py ??
Это надо новое приложение создать?
Да, для такого функционала лучше создать отдельное приложение. Причина предельно простая. Обычно, когда делают сайт на Django, то очень часто делают его мультиязычным. Поэтому всё, что касается ajax взаимодействия, лучше пускать мимо дистпечеризации urls с поддержкой мультиязычности. Такая проблема возникает вследствие того, что производится переадресация на url, который зависит от языка. Из-за переадресации неправильно работает js.
Тут два решения проблемы:
ок, понял, спасибо
А в новом приложении строку app_name = 'ajax' указывать в урл?
Да, нужно будет добавить файл urls, поскольку обычно при использование команды startapp это файл не создаётся, и потом в нём обязательно добавить app_name, можете присовить своё имя для него. app_name отвечает за контекс генерирования url
подскажите еще пж, я правильно понимаю, что функцию:
Нужно добавить в модель статьи?
И еще второй вопрос, вроде всё сделал, при клике на звездочку (кстати у меня она не отображется, просто пустое место, но при клике что-то срабатывает) ничего не происходит, в консоле: Not Found: /api/article//bookmark/
Так что ищите косяк где-то в js
Ок, а посмотреть добавленные закладки на каком урл можно будет после добавления?
на том, который сами наваяете, если на пользовательском фронтенде, а вообще добавляйте модель закладок в панель администрирования в файле admin.py, тогда сможете увидеть в админке, добавилось там что-нибудь или нет.
Не получилось у меня сделать с ajax, по всей видимости пробелма с получением ID поста. Попробовал без ajax, по сути таже ошбика(какие-то проблемы с id). Создал новый вопрос: https://evileg.com/ru/forum/topic/1329/ , не знаю как пофиксить.
Подскажите, а дейтсвие:
В какой части кода описываем?
Вообщем решил сделать не по id, а по slug, что в итоге вышло:
urls.py
в .js
В шаблоне:
В итоге при клике на иконку в консоле получаю:
Счётчик при клике на иконку никак не обновляется 0 всегда, также в БД таблица bookmark_article пустая. Может быть с урлами что-то?
Также попробовал нажать на иконку для неавторизованного пользователя, аналогично:
То есть @login_required не срабатывает
Евгений, можете подсказать почему может быть так?
А вы js код где подключаете?
файл js с to_bookmarks можно загрузить в head теге, а вот этот код
должен вызываться в конце страницы, когда html загружен
Загрузил в конце страницы - ситуация аналогичная. Клик нажимается, в консоле
Но счетчик не обновляется и в базе нет записи. У меня подозрения, что не срабатаывает url.
urls.py
Урл нигде не перподключал, создал новое app "ajax" в нём добавил urls.py и в него добавил код выше. Может быть надо где-то include url вставить?
И еще немного симптомов, если я убираю во views.py (ajax app) класс BookmarkView(View) , то ничего не меняется (никаких ошибок нет). Но в коносле POST запрос ("POST /novayastatya/ HTTP/1.1" 200 7405) при клике всё равно пишет. Получается урл не срабатывает или view, а может и с моделями что-то...
А вы эти urls вообще подключили в самом главном файле urls, который обычно находится рядом с файлом settings.py?
нет, а как?)
также, как подключили сами статьи в файле urls.py, который лежит рядом с settings.py. Статьи же у вас работают...
я не понимаю, как нужно подкллючить url из ajax app. Сатьи у меня лежат в главном приложении
Статьи я подключил вот так:
А как подключить url'ы приложения ajax app ?
боюсь, что вам это слабо поможет, но обычно url из других app подключают подобным образом
Постарайтесь разобраться с этим, а то у меня возникает ощущение, что вас за руку водить нужно. Поэтому я склоняюсь к тому, что вы рано начали решать свою задачу. Вам нужно поднабраться опыта с Django. Поэтому переключитесь на какие-нибудь другие задачи, а добавление в избранное реализуете позже.
За неделю я разобрался и нашел где была ошибка, сейчас закладки добавляются успешно.
Сейчас новый вопрос, как можно их вывывести?
Пытаюсь вывести в шаблоне:
Ничего не выводится. Вьюху не создавал, не имею понятия как. Подскажите как вывести закладки юзера?
Добрый день.
То, что вы добавили как статью, следовало добавить как вопрос на форуме. ЭТо не статья, вы описали, что сделали и спрашивайте, что делать дальше. Поэтому задайте именно вопрос на форуме, в противном случае я не могу пропустить ту статью, чтобы другие пользователи имели к ней доступ. Она не соответсвует наименованию статьи.
Но суть вашей проблемы в том случае заключается в том, что вы не забираете список постов в соответствии с пользователем.
Поскольку никак не передаёте информацию о пользователе во view
ок, немного промазал при создании)
создал вопрос к статье: https://evileg.com/ru/forum/topic/1345/
Вы можете показать пример как это сделать?
всё, разобрался
Оставлю для тех, у кого возникнет подобный вопрос. Посты выводим циклом:
Так как мы ссылаемся на обьект статьи в модели BookmarkArticle через переменную obj , то и доступ к статье получаем через неё:
Как я могу вывести добавленные посты в обратном порядке? сейчас выводятся новодобавленные в конец. Пыатюсь добавить order_by('-created_on') :
views.py
Ошибка:
Может так?
да, спасибо)