All classifier facets and data is now saved, end to end. Now, to apply facets to incoming stories and assign scores.

This commit is contained in:
Samuel Clay 2010-01-05 16:07:11 +00:00
parent ad40c429c6
commit 138f93cf43
11 changed files with 215 additions and 35 deletions

7
apps/analyzer/admin.py Normal file
View file

@ -0,0 +1,7 @@
from apps.analyzer.models import ClassifierTitle, ClassifierAuthor, ClassifierFeed, ClassifierTag
from django.contrib import admin
admin.site.register(ClassifierTitle)
admin.site.register(ClassifierAuthor)
admin.site.register(ClassifierFeed)
admin.site.register(ClassifierTag)

View file

@ -1,7 +1,7 @@
from django.db import models
from django.contrib.auth.models import User
import datetime
from apps.rss_feeds.models import Feed, Story
from apps.rss_feeds.models import Feed, Story, StoryAuthor, Tag
from apps.reader.models import UserSubscription, UserStory
class FeatureCategory(models.Model):
@ -24,4 +24,42 @@ class Category(models.Model):
def __unicode__(self):
return '%s (%s)' % (self.category, self.count)
class ClassifierTitle(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=255)
feed = models.ForeignKey(Feed)
original_story = models.ForeignKey(Story)
creation_date = models.DateTimeField(auto_now=True)
def __unicode__(self):
return '%s: %s (%s)' % (self.user, self.title, self.feed)
class ClassifierAuthor(models.Model):
user = models.ForeignKey(User)
author = models.ForeignKey(StoryAuthor)
feed = models.ForeignKey(Feed)
original_story = models.ForeignKey(Story)
creation_date = models.DateTimeField(auto_now=True)
def __unicode__(self):
return '%s: %s (%s)' % (self.user, self.author.author_name, self.feed)
class ClassifierFeed(models.Model):
user = models.ForeignKey(User)
feed = models.ForeignKey(Feed)
original_story = models.ForeignKey(Story)
creation_date = models.DateTimeField(auto_now=True)
def __unicode__(self):
return '%s: %s' % (self.user, self.feed)
class ClassifierTag(models.Model):
user = models.ForeignKey(User)
tag = models.ForeignKey(Tag)
feed = models.ForeignKey(Feed)
original_story = models.ForeignKey(Story)
creation_date = models.DateTimeField(auto_now=True)
def __unicode__(self):
return '%s: %s (%s)' % (self.user, self.tag.name, self.feed)

7
apps/analyzer/urls.py Normal file
View file

@ -0,0 +1,7 @@
from django.conf.urls.defaults import *
from apps.analyzer import views
urlpatterns = patterns('',
(r'^$', views.index),
(r'^save/?', views.save_classifier),
)

View file

@ -1 +1,63 @@
# Create your views here.
from django.shortcuts import render_to_response, get_list_or_404, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.template import RequestContext
from django.db import IntegrityError
from django.core.cache import cache
from django.contrib.auth.models import User
from django.http import HttpResponse, HttpRequest
from django.core import serializers
from django.utils.safestring import mark_safe
from django.views.decorators.cache import cache_page
from django.views.decorators.http import require_POST
from apps.rss_feeds.models import Feed, Story, Tag
from apps.reader.models import UserSubscription, UserSubscriptionFolders, UserStory
from apps.analyzer.models import ClassifierTitle, ClassifierAuthor, ClassifierFeed, ClassifierTag
from utils import json
from utils.user_functions import get_user
from djangologging.decorators import suppress_logging_output
import logging
import datetime
import random
def index(requst):
pass
@require_POST
def save_classifier(request):
post = request.POST
facets = post.getlist('facet')
code = 0
message = 'OK'
payload = {}
feed = Feed.objects.get(pk=post['feed_id'])
story = Story.objects.get(pk=post['story_id'])
if 'title' in post and 'title' in facets:
ClassifierTitle.objects.create(user=request.user,
title=post['title'],
feed=feed,
original_story=story)
if 'author' in facets:
author = story.story_author
ClassifierAuthor.objects.create(user=request.user,
author=author,
feed=feed,
original_story=story)
if 'publisher' in facets:
ClassifierFeed.objects.create(user=request.user,
feed=feed,
original_story=story)
if 'tag' in post:
tags = post.getlist('tag')
for tag_name in tags:
tag = Tag.objects.get(name=tag_name, feed=feed)
ClassifierTag.objects.create(user=request.user,
tag=tag,
feed=feed,
original_story=story)
response = dict(code=code, message=message, payload=payload)
return HttpResponse(response)

9
apps/reader/http.py Normal file
View file

@ -0,0 +1,9 @@
from django.shortcuts import render_to_response
from django.template import RequestContext
def respond(request, template_name, context_dict, **kwargs):
"""
Use this function rather than render_to_response directly. The idea is to ensure
that we're always using RequestContext. It's too easy to forget.
"""
return render_to_response(template_name, RequestContext(request, context_dict), **kwargs)

View file

@ -1,14 +1,16 @@
from django.conf.urls.defaults import *
from apps.reader import views
urlpatterns = patterns('apps.reader.views',
(r'^$', 'index'),
(r'^load_single_feed', 'load_single_feed'),
(r'^load_feed_page', 'load_feed_page'),
(r'^load_feeds', 'load_feeds'),
(r'^refresh_feed', 'refresh_feed'),
(r'^mark_story_as_read', 'mark_story_as_read'),
(r'^mark_story_as_like', 'mark_story_as_like'),
(r'^mark_story_as_dislike', 'mark_story_as_dislike'),
(r'^mark_feed_as_read', 'mark_feed_as_read'),
(r'^get_read_feed_items', 'get_read_feed_items'),
urlpatterns = patterns('',
(r'^$', views.index),
(r'^load_single_feed', views.load_single_feed),
(r'^load_feed_page', views.load_feed_page),
(r'^load_feeds', views.load_feeds),
(r'^refresh_feed', views.refresh_feed),
(r'^mark_story_as_read', views.mark_story_as_read),
(r'^mark_story_as_like', views.mark_story_as_like),
(r'^mark_story_as_dislike', views.mark_story_as_dislike),
(r'^mark_feed_as_read', views.mark_feed_as_read),
(r'^get_read_feed_items', views.get_read_feed_items),
(r'^get_read_feed_items', views.get_read_feed_items),
)

View file

@ -761,6 +761,18 @@ form.opml_import_form input {
.NB-classifier .NB-classifier-tag label {
}
.NB-classifier .NB-classifier-tags {
margin-left: -24px;
}
.NB-classifier .NB-classifier-modal-title .NB-classifier-like {
color: #3D931B;
}
.NB-classifier .NB-classifier-modal-title .NB-classifier-dislike {
color: #932C15;
}
.simplemodal-wrap {
overflow: auto;
}

View file

@ -35,6 +35,7 @@ NEWSBLUR.AssetModel.Reader.prototype = {
},
make_request: function(url, data, callback) {
// $('body').ajaxStop();
$.ajax({
url: url,
data: data,
@ -211,6 +212,10 @@ NEWSBLUR.AssetModel.Reader.prototype = {
var self = this;
this.make_request('/opml_import/process', data, callback);
},
save_classifier: function(story_id, data, callback) {
this.make_request('/classifier/save', data, callback);
}
};
@ -229,7 +234,6 @@ NEWSBLUR.AssetModel.Preferences.prototype = {
},
make_request: function(url, data, callback) {
$.ajaxStop();
$.ajax({
url: url,
data: data,
@ -241,5 +245,7 @@ NEWSBLUR.AssetModel.Preferences.prototype = {
}
}
});
},
};
}
};

View file

@ -816,20 +816,13 @@
mark_story_as_like: function(story_id, $button) {
var feed_id = this.active_feed;
NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifier(story_id, feed_id);
NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifier(story_id, feed_id, 1);
},
mark_story_as_dislike: function(story_id, $button) {
var self = this;
var callback = function() {
return;
};
$button.addClass('disliked');
if (NEWSBLUR.Globals.is_authenticated) {
this.model.mark_story_as_dislike(story_id, callback);
}
var feed_id = this.active_feed;
NEWSBLUR.classifier = new NEWSBLUR.ReaderClassifier(story_id, feed_id, -1);
},

View file

@ -1,8 +1,9 @@
NEWSBLUR.ReaderClassifier = function(story_id, feed_id, options) {
NEWSBLUR.ReaderClassifier = function(story_id, feed_id, score, options) {
var defaults = {};
this.story_id = story_id;
this.feed_id = feed_id;
this.score = score;
this.options = $.extend({}, defaults, options);
this.model = NEWSBLUR.AssetModel.reader();
this.google_favicon_url = 'http://www.google.com/s2/favicons?domain_url=';
@ -17,6 +18,7 @@ NEWSBLUR.ReaderClassifier.prototype = {
this.handle_text_highlight();
this.handle_select_checkboxes();
this.handle_cancel();
this.handle_select_title();
this.open_modal();
},
@ -34,7 +36,7 @@ NEWSBLUR.ReaderClassifier.prototype = {
story.story_title = $('<div/>').html(story.story_title).text();
this.$classifier = $.make('div', { className: 'NB-classifier' }, [
$.make('h2', 'What do you like about this story?'),
$.make('h2', { className: 'NB-classifier-modal-title' }),
$.make('form', { method: 'post' }, [
(story.story_title && $.make('div', { className: 'NB-classifier-field' }, [
$.make('h5', 'Story Title'),
@ -43,7 +45,8 @@ NEWSBLUR.ReaderClassifier.prototype = {
$.make('label', { 'for': 'classifier_title' }, [
$.make('div', { className: 'NB-classifier-title-display' }, [
'Look for: ',
$.make('span', { className: 'NB-classifier-title NB-classifier-facet-disabled' }, 'Highlight phrases to look for in future stories')
$.make('span', { className: 'NB-classifier-title NB-classifier-facet-disabled' }, 'Highlight phrases to look for in future stories'),
$.make('input', { name: 'title', value: '', type: 'hidden', className: 'NB-classifier-title-hidden' })
])
])
])),
@ -67,6 +70,9 @@ NEWSBLUR.ReaderClassifier.prototype = {
])
]),
$.make('div', { className: 'NB-classifier-submit' }, [
$.make('input', { name: 'score', value: this.score, type: 'hidden' }),
$.make('input', { name: 'feed_id', value: this.feed_id, type: 'hidden' }),
$.make('input', { name: 'story_id', value: this.story_id, type: 'hidden' }),
$.make('input', { type: 'submit', disabled: 'true', className: 'NB-disabled', value: 'Check what you like above...' }),
' or ',
$.make('a', { href: '#', className: 'NB-classifier-cancel' }, 'cancel')
@ -88,6 +94,13 @@ NEWSBLUR.ReaderClassifier.prototype = {
]);
$('.NB-classifier-tags', this.$classifier).append($tag);
}
var $modal_title = $('.NB-classifier-modal-title', this.$classifier);
if (this.score == 1) {
$modal_title.html('What do you <b class="NB-classifier-like">like</b> about this story?');
} else if (this.score == -1) {
$modal_title.html('What do you <b class="NB-classifier-dislike">dislike</b> about this story?');
}
},
open_modal: function() {
@ -104,12 +117,12 @@ NEWSBLUR.ReaderClassifier.prototype = {
'overlayClose': true,
'onOpen': function (dialog) {
dialog.overlay.fadeIn(200, function () {
dialog.container.fadeIn(400);
dialog.data.fadeIn(400);
dialog.container.fadeIn(200);
dialog.data.fadeIn(200);
});
},
'onShow': function(dialog) {
$('#simplemodal-container').corners('4px').css({'width': 600, 'height': height});
$('#simplemodal-container').corners('6px').css({'width': 600, 'height': height});
$('.NB-classifier-tag', self.$classifier).corners('4px');
},
'onClose': function(dialog) {
@ -128,6 +141,7 @@ NEWSBLUR.ReaderClassifier.prototype = {
handle_text_highlight: function() {
var $title_highlight = $('.NB-classifier-title-highlight', this.$classifier);
var $title = $('.NB-classifier-title', this.$classifier);
var $title_hidden = $('.NB-classifier-title-hidden', this.$classifier);
var $title_checkbox = $('#classifier_title', this.$classifier);
var update = function() {
@ -136,12 +150,28 @@ NEWSBLUR.ReaderClassifier.prototype = {
if ($title.text() != text && text.length) {
$title_checkbox.attr('checked', 'checked').change();
$title.text(text).removeClass('NB-classifier-facet-disabled');
$title_hidden.val(text);
}
};
$title_highlight.keydown(update).keyup(update).mousedown(update).mouseup(update).mousemove(update);
},
handle_select_title: function() {
var $title_checkbox = $('#classifier_title', this.$classifier);
var $title = $('.NB-classifier-title', this.$classifier);
var $title_hidden = $('.NB-classifier-title-hidden', this.$classifier);
var $title_highlight = $('.NB-classifier-title-highlight', this.$classifier);
$title_checkbox.change(function() {;
if ($title.hasClass('NB-classifier-facet-disabled')) {
var text = $title_highlight.val();
$title.text(text).removeClass('NB-classifier-facet-disabled');
$title_hidden.val(text);
}
});
},
handle_select_checkboxes: function() {
var self = this;
var $submit = $('input[type=submit]', this.$classifier);
@ -166,8 +196,21 @@ NEWSBLUR.ReaderClassifier.prototype = {
});
},
save: function() {
serialize_classifier: function() {
var data = $('.NB-classifier form input').serialize();
return data;
},
save: function() {
var $save = $('.NB-classifier input[type=submit]');
var story_id = this.story_id;
var data = this.serialize_classifier();
$save.text('Saving...').addClass('NB-disabled').attr('disabled', true);
this.model.save_classifier(story_id, data, function() {
$.modal.close();
});
}
};

View file

@ -9,6 +9,7 @@ urlpatterns = patterns('',
(r'^$', include('apps.reader.urls')),
(r'^accounts/', include('apps.registration.urls')),
(r'^reader/', include('apps.reader.urls')),
(r'^classifier/', include('apps.analyzer.urls')),
(r'^opml/', include('apps.opml_import.urls')),
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/(.*)', admin.site.root)