← Back to team overview

widelands-dev team mailing list archive

[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