mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-09-18 21:50:56 +00:00
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:
parent
ad40c429c6
commit
138f93cf43
11 changed files with 215 additions and 35 deletions
7
apps/analyzer/admin.py
Normal file
7
apps/analyzer/admin.py
Normal 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)
|
|
@ -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
7
apps/analyzer/urls.py
Normal 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),
|
||||
)
|
|
@ -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
9
apps/reader/http.py
Normal 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)
|
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 = {
|
|||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
1
urls.py
1
urls.py
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue