Merge branch 'master' of github.com:Gluejar/regluit into ry

pull/1/head
Raymond Yee 2012-02-05 09:42:50 -08:00
commit a35d67329d
9 changed files with 227 additions and 28 deletions

View File

@ -0,0 +1,23 @@
"""
a variety of errors can cause works to not get clustered by add_editions, or a new isbn can take time to get incorporated into clustering services.
The signature of both problems is a work with only one related edition, a singleton.
This script goes through all singleton works and attempts to add_related. 'xx' works are excluded from being source works
"""
from django.core.management.base import BaseCommand
from django.db.models import Count
from regluit.core import models, bookloader
class Command(BaseCommand):
help = "add and merge editions for singleton works"
def handle(self, **options):
print "Number of singleton Works with language!=xx: %s" % models.Work.objects.annotate(num_editions=Count('editions')).filter(num_editions=1).exclude(language='xx').count()
for work in models.Work.objects.annotate(num_editions=Count('editions')).filter(num_editions=1).exclude(language='xx'):
#check that there's still only one edition
if work.editions.count() != 1:
continue
new_editions = bookloader.add_related( work.first_isbn_13() )
print "clustered %s editions for work %s" % (len(new_editions),work )
print "Updated Number of singleton Works with language!=xx: %s" % models.Work.objects.annotate(num_editions=Count('editions')).filter(num_editions=1).exclude(language='xx').count()

View File

@ -2,11 +2,11 @@ import json
import requests
import regluit.core.isbn
def gluejar_search(q, user_ip='69.243.24.29'):
def gluejar_search(q, user_ip='69.243.24.29', page=1):
"""normalizes results from the google books search suitable for gluejar
"""
results = []
search_result=googlebooks_search(q, user_ip)
search_result=googlebooks_search(q, user_ip, page)
if 'items' in search_result.keys():
for item in search_result['items']:
v = item['volumeInfo']
@ -48,9 +48,11 @@ def gluejar_search(q, user_ip='69.243.24.29'):
return results
def googlebooks_search(q, user_ip):
def googlebooks_search(q, user_ip, page):
# XXX: need to pass IP address of user in from the frontend
headers = {'X-Forwarded-For': user_ip}
start = (page - 1) * 10
params = {'q': q, 'startIndex': start, 'maxResults': 10}
r = requests.get('https://www.googleapis.com/books/v1/volumes',
params={'q': q}, headers=headers)
params=params, headers=headers)
return json.loads(r.content)

View File

@ -81,7 +81,7 @@ class BookLoaderTests(TestCase):
bookloader.add_related('0441012035')
self.assertTrue(models.Edition.objects.count() > 15)
self.assertEqual(models.Work.objects.filter(language=lang).count(), 1)
self.assertTrue(edition.work.editions.count() > 10)
self.assertTrue(edition.work.editions.count() > 9)
def test_populate_edition(self):
@ -192,8 +192,15 @@ class SearchTests(TestCase):
self.assertTrue(r.has_key('isbn_13'))
self.assertTrue(r.has_key('googlebooks_id'))
def test_pagination(self):
r1 = search.gluejar_search('melville', page=1)
r2 = search.gluejar_search('melville', page=2)
isbns1 = set([r['isbn_13'] for r in r1])
isbns2 = set([r['isbn_13'] for r in r2])
self.assertTrue(isbns1 != isbns2)
def test_googlebooks_search(self):
response = search.googlebooks_search('melville', '69.243.24.29')
response = search.googlebooks_search('melville', '69.243.24.29', 1)
self.assertEqual(len(response['items']), 10)

View File

@ -7,6 +7,31 @@
<script type="text/javascript" src="/static/js/wishlist.js"></script>
<script type="text/javascript" src="/static/js/greenpanel.js"></script>
<script type="text/javascript" src="/static/js/toggle.js"></script>
<script type="text/javascript" src="/static/js/jquery.endless-scroll.js"></script>
<script type="text/javascript">
var $j = jQuery.noConflict();
var page = 1;
$j(document).ready(function() {
$j(document).endlessScroll({
bottomPixels: 250,
fireOnce: false,
fireDelay: false,
insertAfter: "#results-bottom",
loader: '<img src="/static/images/loading.gif">',
callback: function(p) {
page += 1;
var url = "?q={{ q }}&page=" + page;
$j.get(url, function(html) {
var view = $j(".listview").length > 0 ? "list" : "panel";
var results = $j(html).find(".book");
$j("#results").append(results);
if (view === "list") toggleList();
else togglePanel();
});
}
});
});
</script>
{% endblock %}
{% block title %}Google Books search results{% endblock %}
@ -37,9 +62,9 @@
</li>
</ul>
</div>
<div class="content-block-content">
<div id="results" class="content-block-content">
{% for work in results %}
<div class="{% cycle 'row1' 'row2' %}">
<div class="{% cycle 'row1' 'row2' %} book">
{% with work.googlebooks_id as googlebooks_id %}
{% with work.last_campaign_status as status %}
{% with work.last_campaign.deadline as deadline %}
@ -56,9 +81,9 @@
</form>
</div>
</div>
{% endfor %}
</div>
<div id="results-bottom"></div>
</div>
</div>
</div>
@ -66,4 +91,4 @@
</div>
{% endblock %}
{% block counter %}{% endblock %}
{% block counter %}{% endblock %}

View File

@ -251,7 +251,7 @@ how do I integrate the your wishlist thing with the tabs thing?
It looks like {{ supporter.username }} is just getting started, and hasn't added books just yet.<br /><br />
{% endifequal %}
{% else %}
{% paginate 20 works %}
{% lazy_paginate 20 works %}
{% for work in works %}
<div class="{% cycle 'row1' 'row2' %}">
{% with work.last_campaign_status as status %}
@ -263,7 +263,7 @@ how do I integrate the your wishlist thing with the tabs thing?
{% endfor %}
<br>
<div class="pagination content-block-heading">
{% show_pages %}
{% show_more %}
</div>
{% endifequal %}
</div>

View File

@ -820,14 +820,15 @@ def edit_user(request):
def search(request):
q = request.GET.get('q', None)
results = gluejar_search(q, request.META['REMOTE_ADDR'])
page = int(request.GET.get('page', 1))
results = gluejar_search(q, user_ip=request.META['REMOTE_ADDR'], page=page)
# flag search result as on wishlist as appropriate
if not request.user.is_anonymous():
ungluers = userlists.other_users(request.user, 5)
else:
ungluers = userlists.other_users(None, 5)
works=[]
for result in results:
try:
@ -1304,4 +1305,4 @@ def feedback(request):
def comment(request):
latest_comments = Comment.objects.all()[:20]
return render(request, "comments.html", {'latest_comments': latest_comments})
return render(request, "comments.html", {'latest_comments': latest_comments})

BIN
static/images/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,132 @@
/**
* Endless Scroll plugin for jQuery
*
* v1.4.8
*
* Copyright (c) 2008 Fred Wu
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
/**
* Usage:
*
* // using default options
* $(document).endlessScroll();
*
* // using some custom options
* $(document).endlessScroll({
* fireOnce: false,
* fireDelay: false,
* loader: "<div class=\"loading\"><div>",
* callback: function(){
* alert("test");
* }
* });
*
* Configuration options:
*
* bottomPixels integer the number of pixels from the bottom of the page that triggers the event
* fireOnce boolean only fire once until the execution of the current event is completed
* fireDelay integer delay the subsequent firing, in milliseconds, 0 or false to disable delay
* loader string the HTML to be displayed during loading
* data string|function plain HTML data, can be either a string or a function that returns a string,
* when passed as a function it accepts one argument: fire sequence (the number
* of times the event triggered during the current page session)
* insertAfter string jQuery selector syntax: where to put the loader as well as the plain HTML data
* callback function callback function, accepts one argument: fire sequence (the number of times
* the event triggered during the current page session)
* resetCounter function resets the fire sequence counter if the function returns true, this function
* could also perform hook actions since it is applied at the start of the event
* ceaseFire function stops the event (no more endless scrolling) if the function returns true
*
* Usage tips:
*
* The plugin is more useful when used with the callback function, which can then make AJAX calls to retrieve content.
* The fire sequence argument (for the callback function) is useful for 'pagination'-like features.
*/
(function($){
$.fn.endlessScroll = function(options) {
var defaults = {
bottomPixels: 50,
fireOnce: true,
fireDelay: 150,
loader: "<br />Loading...<br />",
data: "",
insertAfter: "div:last",
resetCounter: function() { return false; },
callback: function() { return true; },
ceaseFire: function() { return false; }
};
var options = $.extend({}, defaults, options);
var firing = true;
var fired = false;
var fireSequence = 0;
if (options.ceaseFire.apply(this) === true) {
firing = false;
}
if (firing === true) {
$(this).scroll(function() {
if (options.ceaseFire.apply(this) === true) {
firing = false;
return; // Scroll will still get called, but nothing will happen
}
if (this == document || this == window) {
var is_scrollable = $(document).height() - $(window).height() <= $(window).scrollTop() + options.bottomPixels;
} else {
// calculates the actual height of the scrolling container
var inner_wrap = $(".endless_scroll_inner_wrap", this);
if (inner_wrap.length == 0) {
inner_wrap = $(this).wrapInner("<div class=\"endless_scroll_inner_wrap\" />").find(".endless_scroll_inner_wrap");
}
var is_scrollable = inner_wrap.length > 0 &&
(inner_wrap.height() - $(this).height() <= $(this).scrollTop() + options.bottomPixels);
}
if (is_scrollable && (options.fireOnce == false || (options.fireOnce == true && fired != true))) {
if (options.resetCounter.apply(this) === true) fireSequence = 0;
fired = true;
fireSequence++;
$(options.insertAfter).after("<div id=\"endless_scroll_loader\">" + options.loader + "</div>");
data = typeof options.data == 'function' ? options.data.apply(this, [fireSequence]) : options.data;
if (data !== false) {
$(options.insertAfter).after("<div id=\"endless_scroll_data\">" + data + "</div>");
$("div#endless_scroll_data").hide().fadeIn();
$("div#endless_scroll_data").removeAttr("id");
options.callback.apply(this, [fireSequence]);
if (options.fireDelay !== false || options.fireDelay !== 0) {
$("body").after("<div id=\"endless_scroll_marker\"></div>");
// slight delay for preventing event firing twice
$("div#endless_scroll_marker").fadeTo(options.fireDelay, 1, function() {
$(this).remove();
fired = false;
});
}
else {
fired = false;
}
}
$("div#endless_scroll_loader").remove();
}
});
}
};
})(jQuery);

View File

@ -1,15 +1,24 @@
/* Beware of fadeIn/fadeOut jQuery animations; they add an inline "display: block"
which overrides display: none in the stylesheet. Sneaky! */
/*
* Beware of fadeIn/fadeOut jQuery animations; they add an inline
* "display: block" which overrides display: none in the stylesheet. Sneaky!
*
*/
var $j = jQuery.noConflict();
$j(document).ready(function(){
$j('#toggle-list').click(function(){
$j('.panelview').addClass("listview").removeClass("panelview");
$j(this).css({opacity: 1});
$j('#toggle-panel').css({opacity: .2});
});
$j('#toggle-panel').click(function(){
$j('.listview').addClass("panelview").removeClass("listview");
$j(this).css({opacity: 1});
$j('#toggle-list').css({opacity: .2});
});
$j(document).ready(function() {
$j('#toggle-list').click(toggleList);
$j('#toggle-panel').click(togglePanel);
});
function toggleList() {
$j('.panelview').addClass("listview").removeClass("panelview");
$j(this).css({opacity: 1});
$j('#toggle-panel').css({opacity: .2});
}
function togglePanel() {
$j('.listview').addClass("panelview").removeClass("listview");
$j(this).css({opacity: 1});
$j('#toggle-list').css({opacity: .2});
}