понедельник, 12 мая 2014 г.

Опрос (ч.7)

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

1. Чтобы пользователь не мог повторно отправить форму, нужно его идентифицировать и где-то в системе записать, в каких опросах он проголосовал.
Идентификацию пользователя мы можем делать по ip. Хранить данные о пользователе можно в таблице с двумя полями: ip, question_id. Для создания этой таблицы напишем модель User в файле polls/models.py:
# ...
class User(models.Model):
    """Пользователь, участвующий в опросе"""
    ip = models.GenericIPAddressField(verbose_name='IP пользователя')
    question = models.ForeignKey(Question, verbose_name='Вопрос голосования')

    def __unicode__(self):
        return self.ip

    def get_user_ip(self, request):
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[-1].strip()
        else:
            ip = request.META.get('REMOTE_ADDR')
        return ip

    def voted_already(self):
        """Голосовал ли пользователь с данным ip в опросе"""
        user_list = User.objects.filter(ip=self.ip, question = self.question)
        return len(user_list) > 0

    class Meta:
        verbose_name = 'Пользователь'
        verbose_name_plural = 'Пользователи'

Теперь дадим команду на генерацию таблицы этой модели в БД: manage.py syncdb

2. Чтобы наша модель отобразилась в админке, зарегистрируем ее в файле polls/admin.py:
# ...
class UserAdmin(admin.ModelAdmin):
    list_display = ('ip', 'question')

admin.site.register(User, UserAdmin)

3. Добавим в представления логику отслеживания пользователя - файл polls/views.py:
# ...

class PollDetailView(DetailView):
    model = Question
    template_name = 'polls/detail.html'

    def get_context_data(self, **kwargs):
        context = super(PollDetailView, self).get_context_data(**kwargs)
        user = User()
        user.ip = user.get_user_ip(self.request)
        user.question = Question.objects.get(pk=self.kwargs['pk'])
        # Передаем в шаблон переменную
        context['voted_already'] = user.voted_already()
        return context


def vote(request, poll_id):
    """Обработка данных формы опроса"""
    question = get_object_or_404(Question, pk=poll_id)
    if not question.is_active:
        return HttpResponse('Опрос снят с публикации')

    user = User()
    user.ip = user.get_user_ip(request)
    user.question = question
    if user.voted_already():
        return HttpResponse('Вы уже голосовали в этом опросе')

    if request.POST.get('answer'):
        try:
            selected_answer = question.answer_set.get(pk=request.POST['answer'])
        except(Answer.DoesNotExist, UnicodeEncodeError, ValueError):
            return render(request, 'polls/detail.html', {
                'question': question,
                'error_message': "Указан недопустимый ответ",
            })

        selected_answer.votes += 1
        selected_answer.save()

        user.save()

        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
    else:
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "Вы не выбрали ответ.",
        })

4. Отредактируем шаблон детального представления опроса: polls/templates/polls/detail.html:
{% if question.is_active %}

    {% if voted_already %}
    <p>Вы уже голосовали в этом опросе.</p>
    {% else %}
        <p>Просим вас принять участие в опросе</p>
        
        <h1>{{ question.title }}</h1>
        {% if error_message %}
            <p><strong>{{ error_message }}</strong></p>
        {% endif %}

        <form action="{% url 'polls:vote' question.id %}" method="post">
            {% csrf_token %}
            {% for answer in question.answer_set.all %}
                <input type="radio" name="answer" 
                    id="answer{{ forloop.counter }}" value="{{ answer.id }}" />
                <label for="answer{{ forloop.counter }}">
                    {{ answer.answer }}
                </label><br />
            {% endfor %}
            <input type="submit" value="Голосовать" />
        </form>
    {% endif %}

{% else %}
    <p>Извините, опрос снят с публикации.</p>
{% endif %}

Проверьте теперь работу приложения опросов. Вам не удастся дважды проголосовать за один и тот же опрос. Чтобы проголосовать еще, вам нужно удалить в админке соответствующие записи модели User.



Комментариев нет:

Отправить комментарий