Im Artikel zum Erstellen eines Lesezeichensystems auf Django wurde ein Beispiel für die Verwendung eines abstrakten Modells für mehrere Arten von Lesezeichen betrachtet, nämlich für Artikel und Kommentare zu Artikeln . Es wurde auch betont, dass die Felder von Modellen, die Fremdschlüssel zu unterschiedlichen Modellen hatten, dieselben Namen haben sollten, um die Möglichkeit zu bewahren, eine einzige Schnittstelle zum Hinzufügen von Lesezeichen zu erstellen. Möglich wird dies durch das sogenannte "duck typing" (Ententypisierung) , das impliziert, dass Objekte, die keine einzige Vererbungshierarchie haben, im selben Szenario verwendet werden können, wenn es Schnittstellen (Methoden) gibt, die eine haben dieselbe Signatur.
Das Prinzip der Ententypisierung ist buchstäblich wie folgt:
> Wenn es aussieht wie eine Ente, schwimmt wie eine Ente und quakt wie eine Ente, dann könnte es eine Ente sein.
>
> Wenn es aussieht wie eine Ente, schwimmt wie eine Ente und quakt wie eine Ente, dann ist es wahrscheinlich eine Ente.
>
>
Das heißt, wenn wir Methoden mit derselben Signatur haben, können wir Objekte verwenden, die nicht durch eine Vererbungshierarchie im selben Kontext verbunden sind.
In diesem Artikel werden wir die Option in Betracht ziehen, wenn zum Erstellen eines Like Dislike-Systems nicht zwei verschiedene Tabellen für Artikel und Kommentare verwendet werden und nicht einmal eine, die einen Fremdschlüssel für einen Artikel oder Kommentar enthält (dh zwei Spalten , und nur eine der Spalten, je nachdem, zu welcher Art von Inhalt die Aktivität des Benutzers gehört), und eine Tabelle, die Folgendes enthält:
- content_type – die Art des Inhalts, zu dem der Beitrag gehört
- object_id - Datensatz-ID
- content_object - generierter Fremdschlüssel zum Schreiben, eigentlich ein Inhaltsobjekt
- weitere Zusatzfelder
LikeDislike-Datenmodell mit GenericForeignKey
Schauen wir uns nun genauer an, wie ein Datenmodell aussehen könnte, das mit jeder Art von Inhalt funktionieren kann.
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey class LikeDislike(models.Model): LIKE = 1 DISLIKE = -1 VOTES = ( (DISLIKE, 'Не нравится'), (LIKE, 'Нравится') ) vote = models.SmallIntegerField(verbose_name=_("Голос"), choices=VOTES) user = models.ForeignKey(User, verbose_name=_("Пользователь")) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey() objects = LikeDislikeManager()
Dieser Code enthält sowohl Pflichtfelder für die Verwendung polymorpher Links, als auch zusätzliche Felder, die bereits die Art der Benutzeraktivität charakterisieren.
Das System Gefällt mir nicht basiert in diesem Fall auf dem Prinzip +1/-1, das für die Abstimmung mit der Berechnung der Gesamtbewertung verwendet werden kann. Es gibt auch einen Fremdschlüssel für den Benutzer, der für den Artikel oder Kommentar gestimmt hat.
Um den Inhaltstyp zu berücksichtigen, wird das ContentType -Modell verwendet, das im Django-Admin-Panel verwendet wird und Protokolle generiert. Tatsächlich wird beim Erstellen von Datenmodellen automatisch ein ContentType -Eintrag für dieses Modell erstellt. Somit werden alle Tabellen im ContentType -Modell berücksichtigt. Dies ist die Grundlage des Verwaltungsprotokollierungssystems von Django. Dieser Fremdschlüssel wird dem Feld content_type. zugeordnet.
object_id enthält die Primärschlüssel-ID der Modellinstanz, für die die Beziehung erstellt wird.
content_object enthält ein Feld, das mit einem beliebigen Modell verknüpft werden kann, und ist eine GenericForeignKey -Klasse. Wenn die beiden vorherigen Felder andere Namen als content_type und object_id haben, müssen sie als Argumente an GenericForeignKey übergeben werden. Wenn sie sich nicht unterscheiden, bestimmt GenericForeignKey sie unabhängig und verwendet sie, um polymorphe Beziehungen zu erstellen.
Das Modell enthält auch das Objektfeld, dem ein spezieller Modellmanager zugeordnet ist, der die Arbeit erleichtert, Like- und Dislike-Zähler getrennt sowie deren Gesamtbewertung zu erhalten.
LikeDislikeManager
Mit diesem Modellmanager können Sie separat Gefällt mir - und Gefällt mir nicht -Einträge für den aktuellen Artikel oder Kommentar gefällt mir nicht abrufen.
from django.db import models from django.db.models import Sum class LikeDislikeManager(models.Manager): use_for_related_fields = True def likes(self): # Забираем queryset с записями больше 0 return self.get_queryset().filter(vote__gt=0) def dislikes(self): # Забираем queryset с записями меньше 0 return self.get_queryset().filter(vote__lt=0) def sum_rating(self): # Забираем суммарный рейтинг return self.get_queryset().aggregate(Sum('vote')).get('vote__sum') or 0
Hinzufügen eines Links zu Artikel- und Kommentardatenmodellen
Beziehungen werden mithilfe der Klasse GenericRelation hinzugefügt, die im Gegensatz zu den Feldern content_type , object_id , GenericForeignKey keine zusätzlichen Datenbankmigrationen erstellt.
from django.db import models from django.contrib.contenttypes.fields import GenericRelation class Article(models.Model): votes = GenericRelation(LikeDislike, related_query_name='articles') class Comment(models.Model): votes = GenericRelation(LikeDislike, related_query_name='comments')
GenericRelation hat zwei Argumente übergeben:
- Ein Modell mit polymorphen Verknüpfungen, wie Sie sehen können, ist es für beide Modelle gleich.
- related_query_name – der Name des Modells, anhand dessen umgekehrte Auswahlen getroffen werden können. Der Standard GenericForeignKey kann dies nicht tun.
Das bedeutet, dass es bei Vorhandensein von related_query_name möglich sein wird, die Stimmen eines bestimmten Benutzers zu Artikeln oder Kommentaren zu sortieren. Und der Einfachheit halber können Sie dies in LikeDislikeManager implementieren, indem Sie zwei weitere Methoden hinzufügen.
def articles(self): return self.get_queryset().filter(content_type__model='article').order_by('-articles__pub_date') def comments(self): return self.get_queryset().filter(content_type__model='comment').order_by('-comments__pub_date')
Achten Sie auf das Argument order_by. Ohne die Angabe von related_query_name würde ein solches Argument einen Fehler 500 erzeugen, ansonsten können Sie alle Likes sortiert nach dem Veröffentlichungsdatum von Artikeln oder Kommentaren nehmen. Für ein Like-Dislike -System ist dies nicht wirklich notwendig, kann aber für Lesezeichen nützlich sein.
views.py
Stellen Sie sich nun eine Ansicht vor, die die Stimme des Benutzers zu einem Artikel oder Kommentar hinzufügt oder daraus entfernt. Die Abstimmungsimplementierung erfolgt mithilfe von AJAX-Anforderungen.
Das Fazit ist, dass wir in der Datei urls.py das Datenmodell festlegen, für das wir für diese Ansicht stimmen, sowie den Abstimmungstyp „Gefällt mir“ oder „Gefällt mir nicht“.
Wenn Sie eine Anfrage senden, werden wir versuchen, den Datensatz abzurufen, und falls vorhanden, dann entweder den Sprachtyp auf das Gegenteil setzen oder die Stimme löschen. Wenn der Eintrag nicht vorhanden ist, fügen Sie einen Spracheintrag mit dem aktuellen Like- oder Dislike-Typ hinzu.
import json from django.http import HttpResponse from django.views import View from django.contrib.contenttypes.models import ContentType from ecore.models import LikeDislike class VotesView(View): model = None # Модель данных - Статьи или Комментарии vote_type = None # Тип комментария Like/Dislike def post(self, request, pk): obj = self.model.objects.get(pk=pk) # GenericForeignKey не поддерживает метод get_or_create try: likedislike = LikeDislike.objects.get(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id, user=request.user) if likedislike.vote is not self.vote_type: likedislike.vote = self.vote_type likedislike.save(update_fields=['vote']) result = True else: likedislike.delete() result = False except LikeDislike.DoesNotExist: obj.votes.create(user=request.user, vote=self.vote_type) result = True return HttpResponse( json.dumps({ "result": result, "like_count": obj.votes.likes().count(), "dislike_count": obj.votes.dislikes().count(), "sum_rating": obj.votes.sum_rating() }), content_type="application/json" )
urls.py
Der reguläre Ausdruckseintrag für die URL definiert die Art des Inhalts, für den wir abstimmen, seinen Primärschlüssel und die Art der Abstimmung. Somit wird eine URL vom Typ 4 erhalten.
from django.conf.urls import url from django.contrib.auth.decorators import login_required from . import views from .models import LikeDislike from knowledge.models import Article, Comment app_name = 'ajax' urlpatterns = [ url(r'^article/(?P<pk>\d+)/like/$', login_required(views.VotesView.as_view(model=Article, vote_type=LikeDislike.LIKE)), name='article_like'), url(r'^article/(?P<pk>\d+)/dislike/$', login_required(views.VotesView.as_view(model=Article, vote_type=LikeDislike.DISLIKE)), name='article_dislike'), url(r'^comment/(?P<pk>\d+)/like/$', login_required(views.VotesView.as_view(model=Comment, vote_type=LikeDislike.LIKE)), name='comment_like'), url(r'^comment/(?P<pk>\d+)/dislike/$', login_required(views.VotesView.as_view(model=Comment, vote_type=LikeDislike.DISLIKE)), name='comment_dislike'), ]
html
HTML-Code sieht in meinem Fall so aus:
<ul> <li data-id="{{ like_obj.id }}" data-type="article" data-action="like" title="Нравится"> <span class="glyphicon glyphicon-thumbs-up"></span> <span data-count="like">{{ like_obj.votes.likes.count }}</span> </li> <li data-id="{{ like_obj.id }}" data-type="article" data-action="dislike" title="Не нравится"> <span class="glyphicon glyphicon-thumbs-down"></span> <span data-count="dislike">{{ like_obj.votes.dislikes.count }}</span> </li> </ul>
Im vorherigen Artikel wurde das gleiche Prinzip der Generierung von Zählern angewendet.
- data-id - ist verantwortlich für pk-Inhalte, die mit einem Lesezeichen versehen werden können.
- Datentyp - Inhaltstyp, derselbe Name erscheint in der URL.
- data-action - die auszuführende Aktion, in diesem Fall Lesezeichen
- data-count - Zähler, der anzeigt, wie viele Benutzer Inhalte mit Lesezeichen versehen haben
Der Unterschied liegt darin, dass zum Ermitteln der Anzahl von Like und Dislike nicht die Methoden der Datenmodelle von Artikeln und Kommentaren verwendet werden, sondern die Methoden des Modellmanagers LikeDislikeManager , was die Weiterentwicklung weiter vereinfacht, da nicht mehr nachverfolgt werden muss, ob alle Modelle über genügend Methoden verfügen. Es reicht aus, das Feld GenericRelation hinzuzufügen.
JavaScript
Im vorherigen Artikel zu den Lesezeichen auf der Seite habe ich bereits betont, dass es notwendig ist, das CSRF-Token für AJAX-Anfragen zu verarbeiten. Daher werde ich keine Informationen duplizieren.
Ich werde nur die AJAX-Request-Handler selbst zeigen. Es wird 2 davon geben, einen für Like, den zweiten für Dislike.
function like() { var like = $(this); var type = like.data('type'); var pk = like.data('id'); var action = like.data('action'); var dislike = like.next(); $.ajax({ url : "/api/" + type +"/" + pk + "/" + action + "/", type : 'POST', data : { 'obj' : pk }, success : function (json) { like.find("[data-count='like']").text(json.like_count); dislike.find("[data-count='dislike']").text(json.dislike_count); } }); return false; } function dislike() { var dislike = $(this); var type = dislike.data('type'); var pk = dislike.data('id'); var action = dislike.data('action'); var like = dislike.prev(); $.ajax({ url : "/api/" + type +"/" + pk + "/" + action + "/", type : 'POST', data : { 'obj' : pk }, success : function (json) { dislike.find("[data-count='dislike']").text(json.dislike_count); like.find("[data-count='like']").text(json.like_count); } }); return false; } // Подключение обработчиков $(function() { $('[data-action="like"]').click(like); $('[data-action="dislike"]').click(dislike); });
Für Django empfehle ich Timeweb-Hoster-VDS-Server .
Доброго времени суток Евгений. Не подскажете как сделать иконки (лайк-дизлайк) кликабельными?
День добрый. А в каком смысле кликабельными? Вот у меня на сайте они кликаются например. Что конкретно у вас не работает?
В прямом, иконки не кликабельные... просто как декор висят.
А что в консоли отладчика браузера? Может у вас JavaSсript с ошибками отрабатывает?
Дико извиняюсь. Действительно в консоли посмотрел, они кликабельные. Только сыпятся ошбки при клике на $.ajax
really nice work .thanks.
Hi, works perfect, thank you.
I try to retrieve list of users who like eg. Post, but it don't work. How to do this?
I know how to retrieve list of user names but not name with avatar like on evileg.
I think, you made some mistake. Do you have likes or dislikes in admin panel? Or some more information?
Because, I don`t know, what you did in your code.
I suggest you create theme on forum in django section , and we can discuss your problem.
Здравствуйте. Ваша система очень хорошо работает.Спасибо.
Но у меня есть вопрос. Допустим, проголосовал пользователь или нет, можно проверить по условию "if post.votes.user == request.user". А как опредилить какой голос пользователь поставил (лайк или дизлайк)?
Добрый день.
Если у вас уже выбран объект голоса для конкретного пользователя, то можно проверить так.
Ну или проверить, лайкнул ли пользователь из всех лайков так
Спасибо. А не подскажите как это в шаблоне проверить?
Лучше я, наверное, более подробно опишу. Есть несколько постов на странице и мне нужно проверить какой голос поставил пользователь. Если стоит лайк, то закрасить кнопку лайк, а если дизлайк, то кнопку дизлайк. Как это решить?
Я понял. Я вам позже отвечу. У меня это реализовано, но нужно посмотреть исходники сайта.
Пока что, мне пришло в голову такое решение.
Я добавил в LikeDislikeManager следующий метод:
И в шаблоне проверяю таким образом: {% if request.user in post.votes.likers %}.
я написал template tag, фильтр, через который делаю проверку прямо в шаблоне
Для этого подгружаю модуль в шаблоне и проверяю наличие пользователя в queryset
Плюс в том, что могу делать такую проверку для какой угодно модели данных, у которой поле пользователя называется user. И нет никакой зависимости на ModelManager
Спасибо большое.
Снова здравствуйте). Есть ещё вопрос. Как сохранить объект LikeDislike, при удалении статьи? Пробовал
Не помогло.
Добрый день!
А зачем его вообще сохранять, если статья удалена? Вообще объект LikeDislike как раз сохраняется... в этом-то и проблема связей GenericForeignKey, я бы как раз удалял это добро. А content_type - это внешний ключ на модель ContentType, то есть это будет влиять только при удалении самого типа контента статей, а не конкретной статьи.
Спасибо за помощь.
-"А зачем его вообще сохранять, если статья удалена?".
Мне нужно знать сколько лайков/дизлайков получал пользователь за все свои статьи, даже если он удалил какую-нибудь статью.
вообще GenericForeignKey не удаляется, если только вы там что-то не нахимичили. Проблема, как раз в обратном, чтобы его удалять и не было битых отношений.
Просто удалите статью с лайком и посмотрите, что там в админ панели, только учтите, что если имеете обращение к самой статье через лайк, то получите ошибку 500. Нужно обрабатывать эту проблему дополнительно.
Я бы ещё добавил тогда поле target_user, куда будет добавляться пользователь, который получил лайк, тогда при удалении статьи лайк будет соотноситься с сами пользователем.
-"вообще GenericForeignKey не удаляется, если только вы там что-то не нахимичили."
Если использовать ваш код, то при удалении статьи объект LikeDislike не будет удалятся?
Я правильно понял?
Просто, если это так, то очень странно. Потому что у меня LikeDislike удаляется.
да? я перепроверю. Но у меня вроде бы были проблемы с этим делом. Во всяком случае кое-какие объекты, например, теги оставляют битый мусор. Возможно в последних верссиях Джанго дело обстоит иначе уже, я начинал с Джанго 1.10.
Видимо всё из-за GenericRelation. Если его убрать со статьи, то объект LikeDislike не удаляется. Но если его убрать, то я не смогу обращаться к LikeDislike через статью. Допустим ваш template tag
не будет работать. Пока не понятно, как быть в такой ситуации.
Понятно тогда почему, вот что пишут в документации
Это означает, что если просто удалить объект, например, так
То лайк, дислайк останется. Аналогичное поведение будет в том случае, если удалить статью через админку.
Если же удалять через GenericRelation, что очевидно вы и делаете, то тогда лайк дислайк удаляется.
У меня сейчас нет возможности проверить, но попробуйте сделать удаление статьи через админ панель, а не как вы это делаете сейчас, полагаю, что у вас есть какой-то специальный функционал для этого.
Как раз таки я через админку и удалял. У меня был pre_delete signal, но я его закомментировал и попробовал снова. Результат такой же. При удалении статьи с полем GenericRelation, удаляется LikeDislike, а без этого поля не удаляется.
Видимо, придется писать какую-нибудь функцию для удаления.
Тогда я после работы посмотрю, как там обстоят дела. Дело в том, что в django-tagging как раз наоборот TaggedItem объекты остаются.
Можете пока тоже глянуть в исходники django-tagging, возможно это натолкнёт вас на идею. Я не раньше, чем часов через 8 смогу посмотреть туда.
Я перекопал все, что мог. В итоге пока пришел к тому, чтобы не использовать GenericRelation. Зато теперь все стало муторно.
Во вьюхе VoteView пришлось передалать:
И пришлось ещё создать дополнительные template tag. Типо:
Вместо вашего user_in.
И для вывода количества лайков на статье.
Естественно ещё и для дизлайка пришлось добавить.
Да. Знаете, я там ошибался немного по поводу GenericRelation. В Tagging это не используется, поэтому и TaggedItem остаются.
Вам придётся отказаться от GenericRelation, если вы хотите получить тот функционал, который вам нужен.
Большое спасибо вам за помощь в решении моих вопросов.
Hi, its possible to count likes and dislikes in this app something like reddit count?
Yes, it is possible.
For summary count you have here method sum_rating() in LikeDislikeManager
Just, need to rewrite javascript methods. Just write something like this in JS
It's possible to simply add vote option for non logged users?
Yes. You can use IP Address of user instead of Foreign Key of logged user.
In this article you will see, how to get IP Address from request. For field you can use GenericIPAddressField
тут view написан в class based view, если честно ничего не могу разобрать. Как это всё переписать в function view?
function view для модели Article и LikeDislike.LIKE будет выглядеть так
Для LikeDislike.DISLIKE соответственно заменить в том коде. Но это сплошная копипаста получается.
Приветствую вас Евгений , давно наблюда за развитием вашего замечательного портала, много полезно тут нашел , переодически зачитываюсь.
Теперь по сушеству, делаю портал и там идеально ложиться также по лайкам полиморфная модель (comments ,post,image,...)ну вот наткнулся на такую статью https://djbook.ru/examples/88/ Буду очень признателен , если скажете своё мнение.
Добрый день.
Да, я тоже читал ту статью в своё время и согласен с тем, что внешние ключи гораздо лучше, чем GenericForeignKey. Выборки в ряде случае работают быстрее.
Но лично мне проще для закладок, лайков и дислайков, уведомлений и прочего использовать GenericForeignKey в силу того, что в текущем состоянии проекта мне приходится совершать гораздо меньше телодвижений для внедрения новых связей к тем же самым лайкам и дислайкам, когда появляется новая модель данных. У меня просто есть соответствующий миксин с GenericRelation, который сразу разруливает все имена и устанавливает нужные связи. А с использованием ContentType очень большое количество кода удалось переписать в обобщённый код. Лично для меня это является более поддерживаемым решением, поскольку программным кодом портала я занимаюсь в одиночку. И так хватает мест, где есть риск что-то забыть или недописать.
В любом случае при росте проекта и добавлении новых моделей придётся добавлять новую колонку в полиморфную модель, как написано в той статье. Вот и посчитаем, например, для моего сайта, сколько колонок нужно было бы для одних только лайков и дислайков:
Получается по меньшей мере 16 колонок в полиморфной модели, в которой как минимум 15 всегда будут NULL. Я не уверен в том, как это будет влиять на размер баз данных, но мне кажется, что при большом проекте это может стать несколько избыточным, хранить большое количество NULL полей ради того, чтобы больше заботиться о целостности базы данных. На самом деле GenericRelation решает проблему целостности базы данных автоматически и при удалении контента также удаляет записи с GenericForeignKey. Поэтому н могу сказать, что я испытывал проблмы с этим.
Доброго времени суток.
Спасибо за хороший ответ, У меня ситуация така что в галлереи будет несколько миллионов фотографий с фильтрами и тегами , и я опасаюсь за производительност . Это основное что останавливает меня пере GenericForeignKey , пока остановился на первом способе в той статье(Альтернатива 1 - NULL поля в исходной таблице) , к сожалению не нашёл практического бенчмарка какого либо.
Кстати интересные темы нашёл тут https://emacsway.github.io/ru/django-framework/#django-models Может что полезного тоже Евгений найдёте
А как получить имя пользователя, который поставил лайк?
Думал так, но похоже что нет. {{ post.votes.likes.user.username }}
Это же QuerySet будет, а не отдельный единственный объект
Спасибо большое:)
Доброго времени суток!) Я случайно набрел на вашу статью, и она помогла мне решить некоторые мои трудности, я прошел за вами по шагам, в попытках адаптировать это под себя, и возник вопрос. У вас тут не рендениться шаблон, и как выводятся свежие данные по лайкам дизлайкам? в моем варианте, они начинают отображаться только после клика по кнопке. и дизлайк ведет себя, так же как лайк добавля или убирая лайк с того же юзера, при том не увеличивая дизлайки, я где-то ошибся?
как вы понимаете, я новичок и в питон и джанго и в js. не рубите с плеча
мой шаблон html
все, я со всем разобрался!) Извините!)
А так для общей суммы пойдёт?
У меня ещё вопросик, а не подскажите, как выводить окошко авторизоваться, если вы не авторизованы и хотите поставить лайк? Как его вообще подхватит?
Наверное да
У меня разный рендеринг для авторизованного и не авторизованного пользователя. Определяется при запросе страницы.
благодарю за ответ
Меня интересует только один вопрос в плате SQL запросов. Их почему то плодится очень много с системой лайков и дизлайков
Решили вопрос? Тоже интересует оптимизация запросов к БД
Нет я пока что не разбирался с этим. Решил заняться оптимизацией после того как закончу проэкт. Но мне бы всё же хотелось узнать мнение автора.
В случае Generic ключей имеется проблема в том, что ключ формируется по id и названию таблицы, в итоге у Django меньше возможностей для оптимизированного запроса. ЭТо один из недостатков GenericForeignKey.
Саму по себе проблему оптимизации можно попытаться решить следующими методами описанными в этой статье
Но всё равно могут остаться некоторые дополнительные запросы к базе данных. Поэтому можно кэшировать. Но стандартные средства кэширования не подойдут. Лично я разработал для этого специальный декоратор. Об этом можно почитать в этой статье EVILEG-CORE. Кэширование свойств объектов моделей с помощью model_cached_property
Я пытался решить всё с помощью вашей статьи про оптимизацию. Увы не работает. Всё так же на 5 записей 10 запросов к бд. Причём если пытатся оптимизировать с помощью Prefetch запросов становится ещё больше.
Недостаток GenericForeignKey в том, что они достаточно плохо оптимизируются, поэтому я решил это с помощью кэширования.
Собственно говоря, эти проблемы и послужили возникновению специального декоратора для кэширования свойств объектов моделей.
Можете почитать здесь Кэширование свойств объектов моделей с помощью model_cached_property
Спасибо. Я подумаю над таким решением. Если не ошибаюсь EVILEG_CORE на github выложен?
Да, можете выдрать сам декоратор оттуда, а то у меня руки не доходят переписать его для актуальной версии Django, там есть deprecated вещи
Хорошо. Большое спасибо. Посмотрю что из этого получится.
у меня ошибка почемуто в модели
models.py", line 70, in LikeDislike
vote = models.SmallIntegerField(verbose_name= ("Голос"), choices=VOTES)
NameError: name ' ' is not defined
Подскажите пожалуйсто как исправить?
Ошибка скорее всего на другой строке, но интерпретатор понял этого так. Может где-то лишний пробел добавили или кавычка не та. Во всяком случае из этого лога не ясно, в чём ошибка.
Ваша ошибка связана с gettext
Поле должно выглядеть так
Так как вы просто забыли _ и по этому вышла ошибка