got search passably working and a somewhat functional ajax add to list
parent
83f553dc36
commit
409df080ce
|
@ -9,7 +9,7 @@ from tastypie import fields
|
|||
from tastypie.constants import ALL, ALL_WITH_RELATIONS
|
||||
from tastypie.resources import ModelResource, Resource, Bundle
|
||||
from tastypie.utils import trailing_slash
|
||||
from tastypie.authentication import ApiKeyAuthentication
|
||||
from tastypie.authentication import ApiKeyAuthentication, Authentication
|
||||
|
||||
from regluit.core import models
|
||||
|
||||
|
@ -17,6 +17,7 @@ from regluit.core import models
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
class UserResource(ModelResource):
|
||||
class Meta:
|
||||
authentication = ApiKeyAuthentication()
|
||||
|
@ -97,6 +98,7 @@ class EditionCoverResource(ModelResource):
|
|||
class WishlistResource(ModelResource):
|
||||
user = fields.ToOneField(UserResource, 'user')
|
||||
works = fields.ToManyField(WorkResource, 'works')
|
||||
|
||||
class Meta:
|
||||
authentication = ApiKeyAuthentication()
|
||||
queryset = models.Wishlist.objects.all()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
|
@ -15,16 +16,22 @@ class Campaign(models.Model):
|
|||
def __unicode__(self):
|
||||
return u"Campaign for %s" % self.work.title
|
||||
|
||||
def cover_image_small(self):
|
||||
first_isbn = self.work.editions.all()[0].isbn_10
|
||||
return "http://covers.openlibrary.org/b/isbn/%s-S.jpg" % first_isbn
|
||||
|
||||
|
||||
class Work(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
title = models.CharField(max_length=1000)
|
||||
openlibrary_id = models.CharField(max_length=50, null=True)
|
||||
|
||||
@classmethod
|
||||
def get_by_isbn(klass, isbn):
|
||||
for w in Work.objects.filter(Q(editions__isbn_10=isbn) | Q(editions__isbn_13=isbn)):
|
||||
return w
|
||||
return None
|
||||
|
||||
def cover_image_small(self):
|
||||
first_isbn = self.editions.all()[0].isbn_10
|
||||
return "http://covers.openlibrary.org/b/isbn/%s-S.jpg" % first_isbn
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
|
@ -62,6 +69,11 @@ class Edition(models.Model):
|
|||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
@classmethod
|
||||
def get_by_isbn(klass, isbn):
|
||||
for e in Edition.objects.filter(Q(isbn_10=isbn) | Q(isbn_13=isbn)):
|
||||
return e
|
||||
return None
|
||||
|
||||
class EditionCover(models.Model):
|
||||
openlibrary_id = models.IntegerField()
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import json
|
||||
import requests
|
||||
|
||||
def gluejar_search(q):
|
||||
"""normalizes results from the google books search suitable for gluejar
|
||||
"""
|
||||
results = []
|
||||
|
||||
for item in googlebooks_search(q)['items']:
|
||||
# TODO: better to think in terms of editions with titles
|
||||
# instead of titles with names?
|
||||
v = item['volumeInfo']
|
||||
r = {'title': v.get('title', ""),
|
||||
'description': v.get('description', ""),
|
||||
'publisher': v.get('publisher', ""),
|
||||
'google_id': item.get('selfLink')}
|
||||
|
||||
# TODO: allow multiple authors
|
||||
if v.has_key('authors') and len(v['authors']) > 0:
|
||||
r['author'] = v['authors'][0]
|
||||
else:
|
||||
r['author'] = ""
|
||||
|
||||
# pull out isbns
|
||||
for i in v.get('industryIdentifiers', []):
|
||||
if i['type'] == 'ISBN_13':
|
||||
r['isbn_13'] = i['identifier']
|
||||
if i['type'] == 'ISBN_10':
|
||||
r['isbn_10'] = i['identifier']
|
||||
|
||||
# cover image
|
||||
if v.has_key('imageLinks'):
|
||||
r['image'] = v['imageLinks'].get('smallThumbnail', "")
|
||||
else:
|
||||
r['image'] = ""
|
||||
|
||||
results.append(r)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def googlebooks_search(q):
|
||||
# XXX: need to pass IP address of user in from the frontend
|
||||
headers = {'X-Forwarded-For': '69.243.24.29'}
|
||||
r = requests.get('https://www.googleapis.com/books/v1/volumes',
|
||||
params={'q': q}, headers=headers)
|
||||
return json.loads(r.content)
|
|
@ -1,6 +1,6 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from regluit.core import bookloader, models
|
||||
from regluit.core import bookloader, models, search
|
||||
|
||||
class TestBooks(TestCase):
|
||||
|
||||
|
@ -41,3 +41,26 @@ class TestBooks(TestCase):
|
|||
self.assertEqual(models.Work.objects.all().count(), 1)
|
||||
self.assertEqual(models.Subject.objects.all().count(), 18)
|
||||
|
||||
|
||||
class SearchTests(TestCase):
|
||||
|
||||
def test_basic_search(self):
|
||||
results = search.gluejar_search('melville')
|
||||
self.assertEqual(len(results), 10)
|
||||
|
||||
r = results[0]
|
||||
self.assertTrue(r.has_key('name'))
|
||||
self.assertTrue(r.has_key('author'))
|
||||
self.assertTrue(r.has_key('description'))
|
||||
self.assertTrue(r.has_key('image'))
|
||||
self.assertTrue(r.has_key('publisher'))
|
||||
self.assertTrue(r.has_key('identifier'))
|
||||
|
||||
for r in results:
|
||||
for i in r['identifier']:
|
||||
self.assertTrue(i.has_key('name'))
|
||||
self.assertTrue(i.has_key('value'))
|
||||
|
||||
def test_googlebooks_search(self):
|
||||
response = search.googlebooks_search('melville')
|
||||
self.assertEqual(len(response['items']), 10)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<link type="text/css" rel="stylesheet" href="/static/css/book_list.css" />
|
||||
<script type="text/javascript" src="/static/js/jquery-1.6.3.min.js"></script>
|
||||
<script type="text/javascript" src="/static/js/book-panel.js"></script>
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<li>
|
||||
<div class="js-search" style="margin-top: 10px; margin-bottom: 10px;">
|
||||
<form action="{% url search %}" method="get"> <input type="text" onfocus="if (this.value=='Search for a book...') this.value='';" onblur="if (this.value=='') this.value='Search for a book...';" value="Search for a book..." size="20" class="inputbox" id="ssearchword" name="q">
|
||||
<form action="{% url search %}" method="get"><input type="text" placeholder="Search for a book..." size="20" class="inputbox" id="ssearchword" name="q" value="{{ q }}">
|
||||
</form>
|
||||
<input type="button" onclick="this.form.searchword.focus();" class="button" value="Search">
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$(".add-wishlist").each(function (index, element) {
|
||||
$(element).click(function() {
|
||||
var span = $(element).find("span");
|
||||
var isbn = span.attr('id')
|
||||
if (!isbn) return;
|
||||
$.post('/wishlist/', {'isbn': isbn}, function(data) {
|
||||
span.fadeOut();
|
||||
var newSpan = $("<span>On your wishlist!</span>").hide();
|
||||
span.replaceWith(newSpan);
|
||||
newSpan.fadeIn();
|
||||
newSpan.removeAttr("id");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Search Results{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="main-container">
|
||||
<div class="js-main">
|
||||
|
||||
{% include "explore.html" %}
|
||||
|
||||
<div id="js-maincol-fr">
|
||||
<div class="js-maincol-inner">
|
||||
<div class="content-block">
|
||||
<div class="content-block-heading">
|
||||
<h2 class="content-heading">Search Results</span></h2>
|
||||
<ul class="book-list-view">
|
||||
<li>View As:</li>
|
||||
<li class="view-list">
|
||||
<a href="#view-list">
|
||||
<img src="/static/images/booklist/view-list.png" align="view list" title="view list" height="21" width="24" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="view-list">
|
||||
<a href="#view-icon">
|
||||
<img src="/static/images/booklist/view-icon.png" align="view icon" title="view icon" height="22" width="22" />
|
||||
</a>
|
||||
</li>
|
||||
<li class="view-list">
|
||||
<a href="#view-icon-small">
|
||||
<img src="/static/images/booklist/view-small-icon.png" align="view icon small" title="view icon small" height="22" width="22" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content-block-content">
|
||||
{% for result in results %}
|
||||
<div class="book-list row1">
|
||||
<div class="book-thumb">
|
||||
<a href="#"><img src="{{ result.image }}" alt="{{ result.title }}" title="{{ result.title }}" /></a>
|
||||
</div>
|
||||
<div class="book-name">
|
||||
<span>
|
||||
{{ result.title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="add-wishlist">
|
||||
{% if result.on_wishlist %}
|
||||
<span>On Wishlist!</span>
|
||||
{% else %}
|
||||
<span id="{{ result.isbn_10 }}">Add to Wishlist</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="booklist-status">
|
||||
<span class="booklist-status-text">Status: In Progress</span>
|
||||
<span class="booklist-status-img">
|
||||
<img src="/static/images/booklist/icon1.png" title="book list status" alt="book list status" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="unglue-this none">
|
||||
<div class="unglue-this-inner1">
|
||||
<div class="unglue-this-inner2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -33,10 +33,33 @@
|
|||
</ul>
|
||||
</div>
|
||||
<div class="content-block-content">
|
||||
{% for campaign in campaigns %}
|
||||
{% include "book_list.html" %}
|
||||
{% for work in wishlist.works.all %}
|
||||
<div class="book-list row1">
|
||||
<div class="book-thumb">
|
||||
<a href="#"><img src="{{ work.cover_image_small }}" alt="Book name" title="book name" /></a>
|
||||
</div>
|
||||
<div class="book-name">
|
||||
<span>
|
||||
{{ work.title }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="add-wishlist">
|
||||
<span>Add to Wishlist</span>
|
||||
</div>
|
||||
<div class="booklist-status">
|
||||
<span class="booklist-status-text">Status: In Progress</span>
|
||||
<span class="booklist-status-img">
|
||||
<img src="/static/images/booklist/icon1.png" title="book list status" alt="book list status" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="unglue-this none">
|
||||
<div class="unglue-this-inner1">
|
||||
<div class="unglue-this-inner2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.views.generic.simple import direct_to_template
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
urlpatterns = patterns(
|
||||
"regluit.frontend.views",
|
||||
url(r"^$", "home", name="home"),
|
||||
url(r"^supporter/(?P<supporter_username>.+)/$", "supporter", name="supporter"),
|
||||
url(r"^search/$", "search", name="search"),
|
||||
url(r"^privacy/$", direct_to_template, {"template": "privacy.html"},
|
||||
url(r"^privacy/$", TemplateView.as_view(template_name="privacy.html"),
|
||||
name="privacy"),
|
||||
url(r"^wishlist/$", "wishlist", name="wishlist"),
|
||||
)
|
||||
|
|
|
@ -2,9 +2,13 @@ from django.template import RequestContext
|
|||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render, render_to_response, get_object_or_404
|
||||
|
||||
from regluit.core import models, search
|
||||
from regluit.core import models, bookloader
|
||||
from regluit.core.search import gluejar_search
|
||||
|
||||
def home(request):
|
||||
if request.user.is_authenticated():
|
||||
|
@ -14,18 +18,46 @@ def home(request):
|
|||
|
||||
def supporter(request, supporter_username):
|
||||
supporter = get_object_or_404(User, username=supporter_username)
|
||||
campaigns = models.Campaign.objects.all()
|
||||
wishlist = supporter.wishlist
|
||||
context = {
|
||||
"supporter": supporter,
|
||||
"campaigns": campaigns,
|
||||
"wishlist": wishlist,
|
||||
}
|
||||
return render(request, 'supporter.html', context)
|
||||
|
||||
def search(request):
|
||||
q = request.GET.get('q', None)
|
||||
results = search.gluejar_search(q)
|
||||
results = gluejar_search(q)
|
||||
|
||||
# flag search result as on wishlist
|
||||
# TODO: make this better and faster
|
||||
if request.user:
|
||||
for result in results:
|
||||
if not result.has_key('isbn_10'):
|
||||
continue
|
||||
work = models.Work.get_by_isbn(result['isbn_10'])
|
||||
if work and work in request.user.wishlist.works.all():
|
||||
result['on_wishlist'] = True
|
||||
else:
|
||||
result['on_wishlist'] = False
|
||||
|
||||
context = {
|
||||
"q": q,
|
||||
"results": results,
|
||||
}
|
||||
return render(request, 'search.html', context)
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
@login_required
|
||||
def wishlist(request):
|
||||
isbn = request.POST.get('isbn', None)
|
||||
edition = models.Edition.get_by_isbn(isbn)
|
||||
if not edition:
|
||||
print "loading book"
|
||||
edition = bookloader.add_book(isbn)
|
||||
if edition:
|
||||
print "adding edition"
|
||||
request.user.wishlist.works.add(edition.work)
|
||||
# TODO: redirect to work page, when it exists
|
||||
return HttpResponseRedirect('/')
|
||||
|
|
|
@ -22,7 +22,7 @@ div.book-list div.book-thumb{ margin-right:5px;}
|
|||
div.book-list div.book-name{ width:260px; margin-right:10px; background:url(../images/booklist/booklist-vline.png) right center no-repeat;}
|
||||
div.book-list div.book-name span{ display:block; line-height:normal; height:43px; line-height:43px;}
|
||||
div.book-list div.add-wishlist{ margin-right:10px; padding-right:10px;background:url(../images/booklist/booklist-vline.png) right center no-repeat;}
|
||||
div.book-list div.add-wishlist a{ font-weight:normal; color:#3d4e53; text-transform: none; background:url(../images/booklist/add-wishlist.png) left center no-repeat; padding-left:20px;}
|
||||
div.book-list div.add-wishlist span{ font-weight:normal; color:#3d4e53; text-transform: none; background:url(../images/booklist/add-wishlist.png) left center no-repeat; padding-left:20px;}
|
||||
|
||||
div.book-list div.booklist-status{ margin-right:7px;}
|
||||
span.booklist-status-text{ float:left; display:block; padding-right:5px;}
|
||||
|
@ -49,3 +49,7 @@ ul.navigation li.arrow-l a{ background:url(../images/booklist/bg.png) 0 -168px n
|
|||
ul.navigation li.arrow-r a{ background:url(../images/booklist/bg.png) -1px -185px no-repeat;width:10px; height:15px; display:block; text-indent:-10000px;}
|
||||
|
||||
.unglue-button { display: block; border: 0;}
|
||||
|
||||
.book-thumb img {
|
||||
height: 50px;
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ div.content-block-content .cols3 .column{ width:33.33%; float:left;}
|
|||
.column-left .item{ margin:0 10px 10px 0;}
|
||||
.column-center .item{ margin:0 5px 10px 5px;}
|
||||
.column-right .item{ margin:0 0 10px 10px;}
|
||||
.column .item{ border:7px solid #edf3f4; padding:10px;}
|
||||
.column .item{ border:8px solid #edf3f4; padding:10px;}
|
||||
.book-image{ padding:0 0 10px 0;}
|
||||
.book-info{ padding:0 0 10px 0; line-height:125%; position:relative;}
|
||||
.book-info span.book-new{ background:url(/static/images/icon-new.png) 0 0 no-repeat; width:38px; height:36px; display:block; position:absolute;
|
||||
|
@ -150,4 +150,4 @@ a{ font-weight:bold; font-size:13px; text-decoration:none; cursor:pointer;}
|
|||
|
||||
#footer a{
|
||||
color:#3d4e53;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue