widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #17779
[Merge] lp:~widelands-dev/widelands-website/quiz_captcha into lp:widelands-website
kaputtnik has proposed merging lp:~widelands-dev/widelands-website/quiz_captcha into lp:widelands-website.
Commit message:
Add a quiz captcha to https://www.widelands.org/legal_notice/ to prevent spam mails
Requested reviews:
Widelands Developers (widelands-dev)
Related bugs:
Bug #1799375 in Widelands Website: "Stop spam coming over legal_notice"
https://bugs.launchpad.net/widelands-website/+bug/1799375
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands-website/quiz_captcha/+merge/369213
Added a new app called quiz_captcha and implemented it in https://www.widelands.org/legal_notice/
A quiz is a question/answer pair, set through the admin interface. If the answer don't match the question, a form error is triggered and a new question will be shown.
Testing can be done on alpha, i have set 4 questions: https://alpha.widelands.org/legal_notice/
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands-website/quiz_captcha into lp:widelands-website.
=== modified file 'mainpage/forms.py'
--- mainpage/forms.py 2019-03-03 15:17:58 +0000
+++ mainpage/forms.py 2019-06-23 18:35:44 +0000
@@ -4,7 +4,8 @@
from django import forms
from django_registration.forms import RegistrationForm
from nocaptcha_recaptcha.fields import NoReCaptchaField
-from wlprofile.models import Profile as wlprofile
+#from wlprofile.models import Profile as wlprofile
+from quiz_captcha.fields import QuizField
# Overwritten form to include a captcha
@@ -18,3 +19,4 @@
forename = forms.CharField(max_length=80, required=False)
email = forms.EmailField()
inquiry = forms.CharField(widget=forms.Textarea)
+ quiz = QuizField()
\ No newline at end of file
=== modified file 'mainpage/settings.py'
--- mainpage/settings.py 2019-04-11 16:11:21 +0000
+++ mainpage/settings.py 2019-06-23 18:35:44 +0000
@@ -106,6 +106,7 @@
'documentation',
'privacy_policy.apps.PrivacyPolicyConfig',
'haystack', # search engine; see option HAYSTACK_CONNECTIONS
+ 'quiz_captcha',
# Modified 3rd party apps
'wiki.apps.WikiConfig', # This is based on wikiapp, but has some local modifications
=== modified file 'mainpage/templates/mainpage/legal_notice.html'
--- mainpage/templates/mainpage/legal_notice.html 2019-01-24 18:03:54 +0000
+++ mainpage/templates/mainpage/legal_notice.html 2019-06-23 18:35:44 +0000
@@ -23,44 +23,45 @@
<ul>
<li>E-Mail Holger Rapp: sirver(at)gmx.de</li>
<li>Contact form. Using this form sends E-Mails to following person(s):
- <ul>
- {% for name, recipient in inquiry_recipients %}
- <li>{{ name }}: {{ recipient }} </li>
- {% endfor %}
- </ul>
+ <ul>
+ {% for name, recipient in inquiry_recipients %}
+ <li>{{ name }}: {{ recipient }} </li>
+ {% endfor %}
+ </ul>
- <form action="/legal_notice/" method="post">{% csrf_token %}
- <table>
- <tr>
- <td><label for="id_forename">First name (optional): </label></td>
- <td><input id="id_forename" type="text" name="forename" maxlength="80"></td>
- </tr>
- <tr>
- <td><label for="id_surname">Last name (optional): </label></td>
- <td><input id="id_surname" type="text" name="surname" maxlength="80"></td>
- </tr>
- <tr>
- <td><label for="id_email">Email:</label></td>
- <td><input type="email" name="email" id="id_email" required>
- {% for error in form.email.errors %}
- <span class="errormessage">{{ error }}</span>
- {% endfor %}
- </td>
- </tr>
- <tr>
- <td><label for="id_inquiry">Inquiry: </label>{{ form.inquiry.errors }}</td>
- <td><textarea id="id_inquiry" rows="10" cols="40" name="inquiry" required></textarea>
- {% for error in form.inquiry.errors %}
- <span class="errormessage">{{ error }}</span>
- {% endfor %}
- </td>
- </tr>
- <tr>
- <td></td>
- <td><input type="submit" value="Submit"></td>
- </tr>
- </table>
- </form>
+ <form action="/legal_notice/" method="post">
+ {% csrf_token %}
+ <table>
+ <tr>
+ <td><label for="id_forename">First name (optional): </label></td>
+ <td>{{ form.forename }}</td>
+ </tr>
+ <tr>
+ <td><label for="id_surname">Last name (optional): </label></td>
+ <td>{{ form.surname }}</td>
+ </tr>
+ <tr>
+ <td><label for="id_email">Email:</label></td>
+ <td>{{ form.email }}
+ {% for error in form.email.errors %}
+ <span class="errormessage">{{ error }}</span>
+ {% endfor %}
+ </td>
+ </tr>
+ <tr>
+ <td><label for="id_inquiry">Inquiry: </label></td>
+ <td>{{ form.inquiry }}
+ {% for error in form.inquiry.errors %}
+ <span class="errormessage">{{ error }}</span>
+ {% endfor %}
+ </td>
+ </tr>
+ </table>
+ {% include "quiz_captcha/quest.html" %}
+ <p>
+ <input type="submit" value="Submit">
+ </p>
+ </form>
</li>
</ul>
</div>
=== modified file 'mainpage/views.py'
--- mainpage/views.py 2019-06-07 20:25:05 +0000
+++ mainpage/views.py 2019-06-23 18:35:44 +0000
@@ -19,6 +19,7 @@
def legal_notice(request):
"""The legal notice page to fullfill law."""
+
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
@@ -41,8 +42,13 @@
# Redirect after POST
return HttpResponseRedirect('/legal_notice_thanks/')
- else:
+ else: # form is not valid
+ # assign new values to the quiz field
+ form.fields['quiz'].init_values()
+
+ else: # request.GET
form = ContactForm() # An unbound form
+ form.fields['quiz'].init_values()
return render(request, 'mainpage/legal_notice.html', {
'form': form,
=== added directory 'quiz_captcha'
=== added file 'quiz_captcha/__init__.py'
=== added file 'quiz_captcha/admin.py'
--- quiz_captcha/admin.py 1970-01-01 00:00:00 +0000
+++ quiz_captcha/admin.py 2019-06-23 18:35:44 +0000
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib import admin
+from quiz_captcha.models import Quiz
+
+class QuizAdmin(admin.ModelAdmin):
+ list_display=['question', 'answer',]
+
+admin.site.register(Quiz, QuizAdmin)
=== added file 'quiz_captcha/apps.py'
--- quiz_captcha/apps.py 1970-01-01 00:00:00 +0000
+++ quiz_captcha/apps.py 2019-06-23 18:35:44 +0000
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class QuizCaptchaConfig(AppConfig):
+ name = 'quiz_captcha'
=== added file 'quiz_captcha/fields.py'
--- quiz_captcha/fields.py 1970-01-01 00:00:00 +0000
+++ quiz_captcha/fields.py 2019-06-23 18:35:44 +0000
@@ -0,0 +1,42 @@
+from django import forms
+from django.shortcuts import get_object_or_404
+from django.core.exceptions import ValidationError
+from quiz_captcha.models import Quiz
+
+
+class QuizField(forms.CharField):
+ """Extendet CharField with a Quiz object."""
+
+ quest = Quiz()
+
+ def __init__(self, *args, **kwargs):
+ super(QuizField, self).__init__(
+ label=QuizField.quest.question, **kwargs)
+
+ def init_values(self, *args, **kwargs):
+ """Set a new label and quiz for the field."""
+
+ try:
+ if not QuizField.quest:
+ # There was no quest set yet
+ QuizField.quest = Quiz.objects.get_random()
+ else:
+ # A quest was set, exclude it
+ QuizField.quest = Quiz.objects.get_random(
+ exclude_pk=QuizField.quest.pk)
+
+ self.label = QuizField.quest.question
+ except:
+ # No Quizz available or other errors
+ # Maybe this could be better implemented by raising ValidationError
+ # but i couldn't figure it out
+ self.label = ''
+
+ def validate(self, value):
+ # Run default validation, e.g. 'This field is required'
+ super(QuizField, self).validate(value)
+
+ # Validate the answer against the quest object
+ if value != QuizField.quest.answer:
+ raise ValidationError(
+ 'Your answer was wrong. Please try to answer again.')
=== added directory 'quiz_captcha/migrations'
=== added file 'quiz_captcha/migrations/0001_initial.py'
--- quiz_captcha/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
+++ quiz_captcha/migrations/0001_initial.py 2019-06-23 18:35:44 +0000
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-06-20 11:03
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Quiz',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('question', models.CharField(help_text="A question mark isn't applied automatically.", max_length=200)),
+ ('answer', models.CharField(help_text='Depending on the database collation setting, this might be case insensitive.', max_length=200)),
+ ],
+ options={
+ 'verbose_name_plural': 'Quizzes',
+ },
+ ),
+ ]
=== added file 'quiz_captcha/migrations/__init__.py'
=== added file 'quiz_captcha/models.py'
--- quiz_captcha/models.py 1970-01-01 00:00:00 +0000
+++ quiz_captcha/models.py 2019-06-23 18:35:44 +0000
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+from django.db import models
+import random
+
+
+class QuizRandomManager(models.Manager):
+
+ def get_random(self, exclude_pk=None):
+
+ qs = self.model.objects.all()
+
+ if qs:
+ # There is at least one quizz set
+ if len(qs) == 1:
+ return qs.first()
+
+ if exclude_pk != None:
+ # We don't want to show the same question again
+ qs = qs.exclude(pk=exclude_pk)
+
+ ids = [quiz.pk for quiz in qs ]
+ i = ids[random.randint(0, len(ids)-1)]
+ return qs.get(pk=i)
+
+ return None
+
+
+class Quiz(models.Model):
+ question = models.CharField(
+ max_length = 200,
+ help_text = "A question mark isn't applied automatically.",
+ )
+ answer = models.CharField(
+ max_length=200,
+ help_text = "Depending on the database collation setting, this might be case insensitive.",
+ )
+
+ class Meta:
+ verbose_name_plural = 'Quizzes'
+
+ objects = QuizRandomManager()
+
+ def __str__(self):
+ return self.question
=== added directory 'quiz_captcha/templates'
=== added directory 'quiz_captcha/templates/quiz_captcha'
=== added file 'quiz_captcha/templates/quiz_captcha/quest.html'
--- quiz_captcha/templates/quiz_captcha/quest.html 1970-01-01 00:00:00 +0000
+++ quiz_captcha/templates/quiz_captcha/quest.html 2019-06-23 18:35:44 +0000
@@ -0,0 +1,14 @@
+<div class="quiz_captcha">
+ {% if form.quiz.label != '' %}
+ <p>Please answer the question:</p>
+ <p>{{ form.quiz.label }}
+ <!--We render the input manually to prevent showing the value given prior-->
+ <input type="text" name="quiz" required="" id="id_quiz">
+ {% for error in form.quiz.errors %}
+ <span class="errormessage">{{ error }}</span>
+ {% endfor %}
+ </p>
+ {% else %}
+ <p class="errormessage">Error! You may have to add a quizz in the admin interface...</p>
+ {% endif %}
+</div>
=== added file 'quiz_captcha/tests.py'
--- quiz_captcha/tests.py 1970-01-01 00:00:00 +0000
+++ quiz_captcha/tests.py 2019-06-23 18:35:44 +0000
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.test import TestCase
+
+# Create your tests here.
Follow ups