From 28c165a92498e726f97d9b843a67c2f5373b48a8 Mon Sep 17 00:00:00 2001 From: Stavros Korokithakis Date: Sun, 20 Nov 2016 03:14:02 +0200 Subject: [PATCH 1/2] Add preliminary classification machinery. --- classification/__init__.py | 0 classification/apps.py | 5 +++ classification/migrations/__init__.py | 0 classification/models.py | 0 classification/templates/classify.html | 35 +++++++++++++++++++ classification/urls.py | 7 ++++ classification/views.py | 13 +++++++ .../0011_conversation_classified.py | 20 +++++++++++ main/migrations/0012_auto_20161120_0050.py | 20 +++++++++++ main/models.py | 5 ++- main/templates/base.html | 4 +-- spamnesty/settings.py | 1 + spamnesty/urls.py | 1 + 13 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 classification/__init__.py create mode 100644 classification/apps.py create mode 100644 classification/migrations/__init__.py create mode 100644 classification/models.py create mode 100644 classification/templates/classify.html create mode 100644 classification/urls.py create mode 100644 classification/views.py create mode 100644 main/migrations/0011_conversation_classified.py create mode 100644 main/migrations/0012_auto_20161120_0050.py diff --git a/classification/__init__.py b/classification/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/classification/apps.py b/classification/apps.py new file mode 100644 index 0000000..f75ccfe --- /dev/null +++ b/classification/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ClassificationConfig(AppConfig): + name = 'classification' diff --git a/classification/migrations/__init__.py b/classification/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/classification/models.py b/classification/models.py new file mode 100644 index 0000000..e69de29 diff --git a/classification/templates/classify.html b/classification/templates/classify.html new file mode 100644 index 0000000..765d24a --- /dev/null +++ b/classification/templates/classify.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} + +{% block title %}Classify stuff{% endblock %} + +{% block content %} +
+
+

Classify conversations

+

+ Please classify the following conversations into their proper + categories: +

+ + + + + + + +
    + {% for category in categories %} +
  • {{ category }}
  • + {% endfor %} +
+ + {% for conversation in conversations %} + + + + + {% endfor %} +
MessageStuff
{{ conversation }}{{ conversation.category }}
+
+
+{% endblock %} diff --git a/classification/urls.py b/classification/urls.py new file mode 100644 index 0000000..1bc8b54 --- /dev/null +++ b/classification/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url + +from classification.views import classify + +urlpatterns = [ + url(r'^classify/$', classify, name="classify"), +] diff --git a/classification/views.py b/classification/views.py new file mode 100644 index 0000000..b41afe6 --- /dev/null +++ b/classification/views.py @@ -0,0 +1,13 @@ +from annoying.decorators import render_to +from django.http import Http404 + +from main.models import Conversation, SpamCategory + + +@render_to("classify.html") +def classify(request): + if not request.user.is_staff: + raise Http404 + conversations = Conversation.objects.filter(classified=False)[:10] + categories = SpamCategory.objects.all() + return {"conversations": conversations, "categories": categories} diff --git a/main/migrations/0011_conversation_classified.py b/main/migrations/0011_conversation_classified.py new file mode 100644 index 0000000..4dd24f9 --- /dev/null +++ b/main/migrations/0011_conversation_classified.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-11-20 00:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0010_auto_20161025_1320'), + ] + + operations = [ + migrations.AddField( + model_name='conversation', + name='classified', + field=models.BooleanField(default=False), + ), + ] diff --git a/main/migrations/0012_auto_20161120_0050.py b/main/migrations/0012_auto_20161120_0050.py new file mode 100644 index 0000000..68df222 --- /dev/null +++ b/main/migrations/0012_auto_20161120_0050.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.2 on 2016-11-20 00:50 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0011_conversation_classified'), + ] + + operations = [ + migrations.AlterField( + model_name='conversation', + name='classified', + field=models.BooleanField(db_index=True, default=False), + ), + ] diff --git a/main/models.py b/main/models.py index 8495045..4d4d473 100644 --- a/main/models.py +++ b/main/models.py @@ -70,7 +70,7 @@ class Domain(CharIDModel): class SpamCategory(CharIDModel): - """The categories of spam emails""" + """The categories of spam emails.""" name = models.CharField(max_length=30) default = models.BooleanField(default=False, db_index=True) @@ -164,6 +164,9 @@ class Conversation(CharIDModel): # The category of the email (sales, scam, dating, etc). category = models.ForeignKey(SpamCategory, default=get_default_category) + # Whether this has been classified by a trusted human. + classified = models.BooleanField(default=False, db_index=True) + objects = ConversationManager() def __str__(self): diff --git a/main/templates/base.html b/main/templates/base.html index 7c41931..ce3ca03 100644 --- a/main/templates/base.html +++ b/main/templates/base.html @@ -5,10 +5,10 @@ - + {{ request.site.name }} - {% block title %}{% endblock %} - + diff --git a/spamnesty/settings.py b/spamnesty/settings.py index d220883..da04680 100644 --- a/spamnesty/settings.py +++ b/spamnesty/settings.py @@ -34,6 +34,7 @@ INSTALLED_APPS = [ 'django_nose', 'bootstrap3', 'main', + 'classification', ] MIDDLEWARE = [ diff --git a/spamnesty/urls.py b/spamnesty/urls.py index 8e5d1f0..a2d747c 100644 --- a/spamnesty/urls.py +++ b/spamnesty/urls.py @@ -4,4 +4,5 @@ from django.contrib import admin urlpatterns = [ url(r'^entrary/', admin.site.urls), url(r'^', include('main.urls', namespace="main")), + url(r'^', include('classification.urls', namespace="classification")), ] -- GitLab From 86b087632d81ec578ac473498458b9139d57b824 Mon Sep 17 00:00:00 2001 From: Stavros Korokithakis Date: Sun, 20 Nov 2016 19:23:14 +0200 Subject: [PATCH 2/2] Add bulk classification page. --- classification/templates/classify.html | 43 +++++++++++++++++++++++--- classification/views.py | 21 +++++++++++-- main/models.py | 5 +++ main/templates/base.html | 2 ++ 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/classification/templates/classify.html b/classification/templates/classify.html index 765d24a..014ac0f 100644 --- a/classification/templates/classify.html +++ b/classification/templates/classify.html @@ -1,14 +1,34 @@ {% extends "base.html" %} -{% block title %}Classify stuff{% endblock %} +{% block title %}Conversation classification{% endblock %} + +{% block scripts %} + +{% endblock %} {% block content %}
+

Classify conversations

- Please classify the following conversations into their proper - categories: + Please select the appropriate category for each conversation. Each + conversation

@@ -25,8 +45,21 @@ {% for conversation in conversations %} - - + + {% endfor %}
{{ conversation }}{{ conversation.category }}{{ conversation.first_message.best_body|linebreaksbr }} +
+ + +
+
diff --git a/classification/views.py b/classification/views.py index b41afe6..3b08882 100644 --- a/classification/views.py +++ b/classification/views.py @@ -1,13 +1,28 @@ from annoying.decorators import render_to from django.http import Http404 +from django.shortcuts import get_object_or_404 +from django.views.decorators.http import require_http_methods from main.models import Conversation, SpamCategory +@require_http_methods(["GET", "POST"]) @render_to("classify.html") def classify(request): + """ + Allow staff to classify conversations into categories. + """ if not request.user.is_staff: raise Http404 - conversations = Conversation.objects.filter(classified=False)[:10] - categories = SpamCategory.objects.all() - return {"conversations": conversations, "categories": categories} + + if request.method == "POST": + conversation = get_object_or_404(Conversation, pk=request.POST.get("conversation_id")) + category = get_object_or_404(SpamCategory, pk=request.POST.get("category_id", "")) + conversation.category = category + conversation.classified = True + conversation.save() + return {"result": "success"} + else: + conversations = Conversation.objects.filter(classified=False)[:10] + categories = SpamCategory.objects.all() + return {"conversations": conversations, "spam_categories": categories} diff --git a/main/models.py b/main/models.py index 4d4d473..68fc512 100644 --- a/main/models.py +++ b/main/models.py @@ -175,6 +175,11 @@ class Conversation(CharIDModel): def get_absolute_url(self): return reverse("main:conversation-view", args=[self.id]) + @property + def first_message(self): + "Return the first message in the conversation." + return self.message_set.all().order_by("timestamp").first() + @property def calculated_sender_username(self): "Derive a username from the sender's name." diff --git a/main/templates/base.html b/main/templates/base.html index ce3ca03..5798177 100644 --- a/main/templates/base.html +++ b/main/templates/base.html @@ -19,6 +19,8 @@ + {% block scripts %} + {% endblock %} {% if not settings.DEBUG %} -- GitLab