Рейтинг + отзывы на Django.

Блог Полезное

Рейтинг + отзывы на Django.

Рейтинг + отзывы на Django.

Сегодня под катом сказ о том, как сделать рейтинг для любой модели на вашем сайте на Django. Это не так сложно, как кажется, запаситесь терпением и  будьте внимательны, у вас обязательно получится. Так это выглядит на нашем сайте:

Рейтинг и отзывы на Django

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

И так, у нас будет две модели:


  1. Модель школы

  2. Модель отзыва

models.py:

сlass School(models.Model):

    user = models.ForeignKey(User, default='1', verbose_name='Владелец')

    email = models.EmailField(default='test@yandex.ru')

    slug = models.SlugField(unique=True, verbose_name='ЧПУ')

    title = models.CharField(max_length=300, verbose_name='Название')

    phone_number = models.CharField(max_length=15, verbose_name='Телефон', blank=True)

    logo = models.ImageField(upload_to='logo', blank=True, verbose_name='Лого')

    body = HTMLField(verbose_name='Содержание')

    city = models.CharField(max_length=300, verbose_name ='Город')

    class Meta:

        verbose_name_plural = ' Школы'

        verbose_name = 'Школа'

    def __unicode__(self):

        return u'%s' % (self.title)

                

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

models.py:

class Review(models.Model):

    """

    Отзыв с оценкой

    """

    name = models.CharField(max_length=300, verbose_name='Имя')

    born = models.DateField(default=timezone.now, verbose_name='Дата создания')

    school = models.ForeignKey(School, verbose_name='Школа')

    RATING_CHOICES = (

    (1, '1'),

    (2, '2'),

    (3, '3'),

    (4, '4'),

    (5, '5'),

    )

    rating = models.IntegerField(choices=RATING_CHOICES, default='5', verbose_name='Рейтинг')

    body = models.TextField(verbose_name='Описание')

    email = models.EmailField()

    verificated = models.BooleanField(default=False, verbose_name='Активен')

    class Meta:

        verbose_name_plural = '    Отзывы'

        verbose_name = 'Отзыв'

    def __unicode__(self):

        return u'%s' % (self.name)

    def school_list(self):

        rew_all_sum = Review.objects.aggregate(Sum('rating')).values()[0]

        rew_all_count = Review.objects.filter(school__slug=self.kwargs['slug']).count()

И так немного поясню, есть модель школы с полями (название, пользователь который ее создал, телефон, город, лого и т.д). И есть модель отзыва (имя того кто оставил отзыв, школа, к которой привязан отзыв,  почта, дата создания, оценки, описание и поле verificated). Verificated так как изначально отзыв будет создаваться не верифицированным и не опубликованным. Чтобы отзыв был опубликован пользователю нужно будет подтвердить почту. Вы можете пропустить этот момент, но для он важен иначе приходит куча спама. Функция school_list нужна для подсчета суммы оценок и суммы общей оценки, далее более подробно опишу о ее назначении.

Теперь созданим вьюху для детального отображения школы.

views.py:

class SchoolDetail(DetailView):

    model = School

    def get_context_data(self, **kwargs):

        context = super(SchoolDetail, self).get_context_data(**kwargs)

        context['review_list'] = Review.objects.filter(school__slug=self.kwargs['slug'], verificated = True).order_by('-born')

        context['review_sum'] = School.objects.filter(slug=self.kwargs['slug']).annotate(avg_review=Avg('review__rating'))

        context['review_count'] = Review.objects.filter(school__slug=self.kwargs['slug'], verificated = True).count()

        return context

Поясню немного. Передаем дополнительный контекст в шаблон django, review_list это список всех отзывов этой конкретной школы. Review_sum - это сумма всех оценок из отзывов. Используем annonate. Она добавляет к каждому объекту QuerySet результаты указанных выражений. Выражение может содержать простое значение, указатель на другое поле модели (или связанных моделей), или функции агрегации (среднее, сумма и прочие), которые вычисляются над объектами, связанными с объектами из QuerySet. В нашем случае она содержит указатель на поле другой модели(на модель отзывы поле рейтинг) и с помощью AVG подсчитывает общую сумму всех значений. Review_count это количество отзывов.

Шаблон школы. Приведу отрывок блока ретинга, думаю сами сможете добавить его в ваш шаблон:

<p class="h3">Рейтинг автошколы</p>

   <div class="rating-stat container-fluid">

      <div class="rating-col col-sm-6">

         <div class="star-ratings" data-max-rating="5" data-avg-rating="3,000">

            <div class="star-ratings-rating-stars-container">

               <span class="stars stars{{review_sum}}"></span>

            </div>

            {%for item in review_sum%}

            {% if item.avg_review == None %}

            <div class="star-ratings-rating-average">Общая оценка: <span class="stars stars">Нет оценок</span></div>

            {%else%}

            <div class="star-ratings-rating-average">Общая оценка: <span class="stars stars{{item.avg_review|floatformat:"0"}}"></span></div>

            {%endif%}

            <div class="star-ratings-rating-count">Количество оценок: <span class="stars stars">{{review_count}}</span></div>

            <div class="star-ratings-errors"></div>

            {%endfor%}

         </div>

      </div>

   </div>

Что я тут делаю, проверяю есть ли оценки, если нет, то вывожу надпись "Нет оценок", если есть, то вывожу количество звездочек от 1 до 5 в зависимости от оценок, floatformat использую, чтобы цифры были целыми иначе стили не будут применены, как вы поняли тут будет вывод звезд с помощью css, код ниже. На сайте это выглядит так

Стили прилагаются:

.school-detail .rating-stat .h3 {

    margin-top: 0;

    font-weight: 400;

    font-family: "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif

}

.school-detail .rating-stat .stars {

    font-size: 25px;

    vertical-align: middle

}

.stars {

    font: normal normal normal 16px/1.8 FontAwesome;

    color: #f5c711;

    letter-spacing: .2em

}

.stars:after {

    color: #ccc

}

.stars.stars0_5:before {

    content: "\f123"

}

.stars.stars0_5:after {

    content: "\f006\f006\f006\f006"

}

.stars.stars1_5:before {

    content: "\f005\f123"

}

.stars.stars1_5:after {

    content: "\f006\f006\f006"

}

.stars.stars2_5:before {

    content: "\f005\f005\f123"

}

.stars.stars2_5:after {

    content: "\f006\f006"

}

.stars.stars3_5:before {

    content: "\f005\f005\f005\f123"

}

.stars.stars3_5:after {

    content: "\f006"

}

.stars.stars4_5:before {

    content: "\f005\f005\f005\f005\f123"

}

.stars.stars0:after {

    content: "\f006 \f006 \f006 \f006 \f006"

}

.stars.stars1:before {

    content: "\f005"

}

.stars.stars1:after {

    content: "\f006\f006\f006\f006"

}

.stars.stars2:before {

    content: "\f005\f005"

}

.stars.stars2:after {

    content: "\f006\f006\f006"

}

.stars.stars3:before {

    content: "\f005\f005\f005"

}

.stars.stars3:after {

    content: "\f006\f006"

}

.stars.stars4:before {

    content: "\f005\f005\f005\f005"

}

.stars.stars4:after {

    content: "\f006"

}

.stars.stars5:before {

    content: "\f005\f005\f005\f005\f005"

}

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

forms.py:

class ReviewAddForm(forms.ModelForm):

    class Meta:

        model = Review

        fields = '__all__'

        exclude = ['school','born']

        

views.py:

class ReviewAdd(CreateView):

    model = Review

    form_class = ReviewAddForm

    template_name = 'school/review_add.html'

    success_url = 'reviewsend'

    

    def form_valid(self,form):

        obj = form.save(commit=False)

        school = get_object_or_404(School, slug=self.kwargs['slug'])

        obj.school = school

        obj.save()

        slug = self.kwargs['slug']

        subject = 'Отзыв'

        message = u'Перейдите по ссылке для активации отзыва ' + 'http://ваш_сайт/school/' + '%s' % slug + '/verification/' + '%s' % obj.id

        email = form.cleaned_data['email']

        from_email = 'info@vodibezopasno.com'

        send_mail(subject, message, from_email, [email,])

        return super(ReviewAdd, self).form_valid(form)

Тут приведен пример проверки почты, при создании отзыва отправляется письмо на почту создателю с ссылкой на страницу подтверждения почты. Если переход осуществляется, поле verificated переходит в значение True и отзыв публикуется. 

Ссылка на создание отзыва 

<p class="text-center"><a href="{%url 'school:reviewadd' slug=school.slug%}">Напишите отзыв об автошколе с подтверждением по email</a></p>

urls.py:

url(r'^(?P<slug>[\w-]+)/review/add$', ReviewAdd.as_view(), name='reviewadd'),

review_add.html:

{% extends 'base.html'%}

{% block content %}

<form action="" class="address-form form-horizontal" method="post" enctype="multipart/form-data">

   {% csrf_token %}

   <fieldset>

      <div class="form-group required">

         <div class="col-sm-4">

            <label for="id_name">Имя:</label> 

         </div>

         <div class="col-sm-8">

            <input id="id_name" maxlength="300" name="name" type="text" required="">

         </div>

         </div>

         <div class="form-group required">

         <div class="col-sm-4">

            <label for="id_email">Email:</label> 

         </div>

         <div class="col-sm-8">

            <input id="id_email" maxlength="300" name="email" type="text" required="">

         </div>

      </div>

      <div class="form-group required">

         <div class="col-sm-4">

            <label for="id_rating">Рейтинг:</label> 

         </div>

         <div class="col-sm-8">

            <div class="stars">

               <input class="star star-5" id="star-5" type="radio" name="rating" value="5"/>

               <label class="star star-5" for="star-5"></label>

               <input class="star star-4" id="star-4" type="radio" name="rating" value="4"/>

               <label class="star star-4" for="star-4"></label>

               <input class="star star-3" id="star-3" type="radio" name="rating" value="3"/>

               <label class="star star-3" for="star-3"></label>

               <input class="star star-2" id="star-2" type="radio" name="rating" value="2"/>

               <label class="star star-2" for="star-2"></label>

               <input class="star star-1" id="star-1" type="radio" name="rating" value="1"/>

               <label class="star star-1" for="star-1"></label>

            </div>

         </div>

      </div>

      <div class="form-group required">

         <div class="col-sm-4">

            <label for="id_body">Описание:</label> 

         </div>

         <div class="col-sm-8">

            <textarea cols="40" id="id_body" name="body" rows="10" required=""></textarea>

         </div>

      </div>

      <input type="submit" class="btn btn-primary" value="Добавить">

   </fieldset>

</form>

{%endblock%}

Как видите поле школы и дату создания мы исключили. Они добавляются автоматически, в функции мы добавили эту возможность в form_valid. 

Рейтинг на Django

Теперь можете вывести все свои отзывы на страницы школы, добавьте в шаблон:

{% for item in review_list|slice:":10" %}

   <div class="panel review-item">

      <div class="review-heading row">

         <div class="col-sm-6 meta text-sm-center">

            <div class="img-circle avatar sprite avatar48"></div>

            <p class="title h5"><b>{{item.name}}</b></p>

            <p class="h6 text-muted time">{{item.born}}</p>

         </div>

         <div class="col-sm-6 rating text-sm-center text-right">

            <span class="stars stars{{item.rating}}"></span>

         </div>

      </div>

      <div class="review-description">

         <p>{{item.body}}</p>

      </div>

   </div>

   {%endfor%}

Рейтинг и отзывы на Django будут теперь отображены на вашей странице. Стили здесь приводить не буду, сами добавите какие угодно. Теперь самое интересное, вывод списка школ по рейтингу.

Рейтинг на Django, вывод школ.

Напишем функцию вывода

views.py:

class SchoolList(ListView):

    model = School

    def get_context_data(self, **kwargs):

        context = super(SchoolList, self).get_context_data(**kwargs)

        context['school_list'] = School.objects.all().annotate(avg_review=Avg('review__rating')).order_by('-avg_review')

        return context

        

Опять на помощь приходит anotate и AVG. Шаблон в пример приводить не буду, тут сами добьете, это уже не проблема. Спасибо тем, кто дочитал до конца, надеюсь вам пригодится это материал и вы им воспользуетесь.