Merge pull request #520 from Gluejar/onix-feed

Implement ONIX 3.0 feed
pull/1/head
Raymond Yee 2015-08-28 21:57:18 -04:00
commit d4c2257511
11 changed files with 23671 additions and 6 deletions

290
api/crosswalks.py Normal file
View File

@ -0,0 +1,290 @@
relator_contrib = {
"act" : "E01",
"adp" : "B05",
"adp" : "B99",
"aft" : "A19",
"aft" : "A22",
"ant" : "A37",
"arr" : "B25",
"art" : "A07",
"aui" : "A15",
"aui" : "A16",
"aui" : "A23",
"aui" : "A24",
"aui" : "A29",
"aus" : "A03",
"aut" : "A01",
"aut" : "A38",
"clb" : "A33",
"cmm" : "A21",
"cmm" : "E04",
"cmp" : "A06",
"cnd" : "D03",
"com" : "C01",
"com" : "C02",
"com" : "C99",
"cov" : "A36",
"cre" : "A09",
"cre" : "A10",
"cre" : "A99",
"ctb" : "A02",
"ctb" : "A14",
"ctb" : "A17",
"ctb" : "A18",
"ctb" : "A20",
"ctb" : "A25",
"ctb" : "A27",
"ctb" : "A32",
"ctb" : "A34",
"ctg" : "A39",
"cwt" : "A21",
"dnc" : "E02",
"drt" : "D02",
"drt" : "D99",
"dsr" : "A11",
"edt" : "B01",
"edt" : "B02",
"edt" : "B04",
"edt" : "B09",
"edt" : "B12",
"edt" : "B13",
"edt" : "B14",
"edt" : "B15",
"edt" : "B16",
"edt" : "B19",
"edt" : "B20",
"edt" : "B21",
"edt" : "B23",
"edt" : "B24",
"ill" : "A12",
"ill" : "A35",
"ill" : "A40",
"itr" : "E06",
"ive" : "A44",
"ivr" : "A43",
"lbt" : "A04",
"lyr" : "A05",
"lyr" : "A31",
"nrt" : "B03",
"nrt" : "B07",
"nrt" : "E03",
"nrt" : "E07",
"pbd" : "B11",
"pbl" : "B18",
"pht" : "A08",
"pht" : "A13",
"pht" : "F01",
"prf" : "E08",
"prf" : "E99",
"prg" : "A30",
"pro" : "D01",
"trl" : "B06",
"trl" : "B08",
"trl" : "B10",
"voc" : "E05",
}
iso639 = {
"aa" : "aar",
"ab" : "abk",
"ae" : "ave",
"af" : "afr",
"ak" : "aka",
"am" : "amh",
"an" : "arg",
"ar" : "ara",
"as" : "asm",
"av" : "ava",
"ay" : "aym",
"az" : "aze",
"ba" : "bak",
"be" : "bel",
"bg" : "bul",
"bh" : "bih",
"bi" : "bis",
"bm" : "bam",
"bn" : "ben",
"bo" : "tib",
"br" : "bre",
"bs" : "bos",
"ca" : "cat",
"ce" : "che",
"ch" : "cha",
"co" : "cos",
"cr" : "cre",
"cs" : "cze",
"cs" : "cze",
"cu" : "chu",
"cv" : "chv",
"cy" : "wel",
"cy" : "wel",
"da" : "dan",
"de" : "ger",
"de" : "ger",
"dv" : "div",
"dz" : "dzo",
"ee" : "ewe",
"el" : "gre",
"el" : "gre",
"en" : "eng",
"eo" : "epo",
"es" : "spa",
"et" : "est",
"eu" : "baq",
"eu" : "baq",
"fa" : "per",
"fa" : "per",
"ff" : "ful",
"fi" : "fin",
"fj" : "fij",
"fo" : "fao",
"fr" : "fre",
"fr" : "fre",
"fy" : "fry",
"ga" : "gle",
"gd" : "gla",
"gl" : "glg",
"gn" : "grn",
"gu" : "guj",
"gv" : "glv",
"ha" : "hau",
"he" : "heb",
"hi" : "hin",
"ho" : "hmo",
"hr" : "hrv",
"ht" : "hat",
"hu" : "hun",
"hy" : "arm ",
"hy" : "arm",
"hz" : "her",
"ia" : "ina",
"id" : "ind",
"ie" : "ile",
"ig" : "ibo",
"ii" : "iii",
"ik" : "ipk",
"io" : "ido",
"is" : "ice",
"is" : "ice",
"it" : "ita",
"iu" : "iku",
"ja" : "jpn",
"jv" : "jav",
"ka" : "geo",
"ka" : "geo",
"kg" : "kon",
"ki" : "kik",
"kj" : "kua",
"kk" : "kaz",
"kl" : "kal",
"km" : "khm",
"kn" : "kan",
"ko" : "kor",
"kr" : "kau",
"ks" : "kas",
"ku" : "kur",
"kv" : "kom",
"kw" : "cor",
"ky" : "kir",
"la" : "lat",
"lb" : "ltz",
"lg" : "lug",
"li" : "lim",
"ln" : "lin",
"lo" : "lao",
"lt" : "lit",
"lu" : "lub",
"lv" : "lav",
"mg" : "mlg",
"mh" : "mah",
"mi" : "mao",
"mi" : "mao",
"mk" : "mac",
"mk" : "mac",
"ml" : "mal",
"mn" : "mon",
"mr" : "mar",
"ms" : "may",
"ms" : "may",
"mt" : "mlt",
"my" : "bur",
"my" : "bur",
"na" : "nau",
"nb" : "nob",
"nd" : "nde",
"ne" : "nep",
"ng" : "ndo",
"nl" : "dut",
"nl" : "dut",
"nn" : "nno",
"no" : "nor",
"nr" : "nbl",
"nv" : "nav",
"ny" : "nya",
"oc" : "oci",
"oj" : "oji",
"om" : "orm",
"or" : "ori",
"os" : "oss",
"pa" : "pan",
"pi" : "pli",
"pl" : "pol",
"ps" : "pus",
"pt" : "por",
"qu" : "que",
"rm" : "roh",
"rn" : "run",
"ro" : "rum",
"ro" : "rum",
"ru" : "rus",
"rw" : "kin",
"sa" : "san",
"sc" : "srd",
"sd" : "snd",
"se" : "sme",
"sg" : "sag",
"si" : "sin",
"sk" : "slo",
"sk" : "slo",
"sl" : "slv",
"sm" : "smo",
"sn" : "sna",
"so" : "som",
"sq" : "alb ",
"sq" : "alb",
"sr" : "srp",
"ss" : "ssw",
"st" : "sot",
"su" : "sun",
"sv" : "swe",
"sw" : "swa",
"ta" : "tam",
"te" : "tel",
"tg" : "tgk",
"th" : "tha",
"ti" : "tir",
"tk" : "tuk",
"tl" : "tgl",
"tn" : "tsn",
"to" : "ton",
"tr" : "tur",
"ts" : "tso",
"tt" : "tat",
"tw" : "twi",
"ty" : "tah",
"ug" : "uig",
"uk" : "ukr",
"ur" : "urd",
"uz" : "uzb",
"ve" : "ven",
"vi" : "vie",
"vo" : "vol",
"wa" : "wln",
"wo" : "wol",
"xh" : "xho",
"yi" : "yid",
"yo" : "yor",
"za" : "zha",
"zh" : "chi",
"zh" : "chi",
"zu" : "zul",
}

179
api/onix.py Normal file
View File

@ -0,0 +1,179 @@
import datetime
import pytz
from lxml import etree
from regluit.core import models
from regluit.core.cc import ccinfo
from regluit.bisac import Bisac
from .crosswalks import relator_contrib, iso639
feed_xml = """<?xml version="1.0" encoding="UTF-8"?>
<ONIXMessage release="3.0" xmlns="http://ns.editeur.org/onix/3.0/reference" />
"""
bisac = Bisac()
def text_node(tag, text, attrib={}):
node = etree.Element(tag, attrib=attrib)
node.text = text
return node
def onix_feed(facet, max=None):
feed = etree.fromstring(feed_xml)
feed.append(header(facet))
works = facet.works[0:max] if max else facet.works
for work in works:
editions = models.Edition.objects.filter(work=work,ebooks__isnull=False)
editions = facet.facet_object.filter_model("Edition",editions).distinct()
for edition in editions:
feed.append(product(edition, facet.facet_object))
return etree.tostring(feed, pretty_print=True)
def onix_feed_for_work(work):
feed = etree.fromstring(feed_xml)
feed.append(header(work))
for edition in models.Edition.objects.filter(work=work,ebooks__isnull=False).distinct():
feed.append(product(edition))
return etree.tostring(feed, pretty_print=True)
def header(facet=None):
header_node = etree.Element("Header")
sender_node = etree.Element("Sender")
sender_node.append(text_node("SenderName", "unglue.it"))
sender_node.append(text_node("EmailAddress", "support@gluejar.com"))
header_node.append(sender_node)
header_node.append(text_node("SentDateTime", pytz.utc.localize(datetime.datetime.utcnow()).strftime('%Y%m%dT%H%M%SZ')))
header_node.append(text_node("MessageNote", facet.title if facet else "Unglue.it Editions"))
return header_node
def product(edition, facet=None):
work=edition.work
product_node = etree.Element("Product")
product_node.append(text_node("RecordReference", "it.unglue.work.%s.%s" % (work.id, edition.id)))
product_node.append(text_node("NotificationType", "03" )) # final
ident_node = etree.SubElement(product_node, "ProductIdentifier")
ident_node.append(text_node("ProductIDType", "01" )) #proprietary
ident_node.append(text_node("IDTypeName", "unglue.it edition id" )) #proprietary
ident_node.append(text_node("IDValue", unicode(edition.id) ))
if edition.isbn_13:
ident_node = etree.SubElement(product_node, "ProductIdentifier")
ident_node.append(text_node("ProductIDType", "03" )) #proprietary
ident_node.append(text_node("IDValue", edition.isbn_13 ))
# Descriptive Detail Block
descriptive_node = etree.SubElement(product_node, "DescriptiveDetail")
descriptive_node.append(text_node("ProductComposition", "00" )) # single item
descriptive_node.append(text_node("ProductForm", "ED" )) # download
ebook = None
ebooks=facet.filter_model("Ebook",edition.ebooks.all()) if facet else edition.ebooks.all()
for ebook in ebooks:
if ebook.format=='epub':
descriptive_node.append(text_node("ProductFormDetail", "E101" ))
elif ebook.format=='pdf':
descriptive_node.append(text_node("ProductFormDetail", "E107" ))
elif ebook.format=='mobi':
descriptive_node.append(text_node("ProductFormDetail", "E116" ))
if ebook.rights:
license_node = etree.SubElement(descriptive_node, "EpubLicense")
license_node.append(text_node("EpubLicenseName", ebook.rights ))
lic_expr_node = etree.SubElement(license_node, "EpubLicenseExpression")
lic_expr_node.append(text_node("EpubLicenseExpressionType", '01' )) #human readable
lic_expr_node.append(text_node("EpubLicenseExpressionLink", ccinfo(ebook.rights).url ))
title_node = etree.SubElement(descriptive_node, "TitleDetail")
title_node.append(text_node("TitleType", '01' )) #distinctive title
title_el = etree.SubElement(title_node, "TitleElement")
title_el.append(text_node("TitleElementLevel", '01' ))
title_el.append(text_node("TitleText", edition.title ))
contrib_i = 0
for contrib in edition.relators.all():
contrib_i+=1
contrib_node = etree.SubElement(descriptive_node, "Contributor")
contrib_node.append(text_node("SequenceNumber", unicode(contrib_i )))
contrib_node.append(text_node("ContributorRole", relator_contrib.get(contrib.relation.code,"") ))
contrib_node.append(text_node("PersonName", contrib.author.name))
(lang, locale) = (edition.work.language, None)
if '_' in lang:
(lang, locale) = lang.split('_')
if len(lang)==2:
lang = iso639.get(lang, None)
if lang:
lang_node = etree.SubElement(descriptive_node, "Language")
lang_node.append(text_node("LanguageRole", "01"))
lang_node.append(text_node("LanguageCode", lang))
if locale:
lang_node.append(text_node("CountryCode", locale))
for subject in work.subjects.all():
subj_node = etree.SubElement(descriptive_node, "Subject")
if subject.authority == 'lcsh':
subj_node.append(text_node("SubjectSchemeIdentifier", "04"))
subj_node.append(text_node("SubjectHeadingText", subject.name))
elif subject.authority == 'lcc':
subj_node.append(text_node("SubjectSchemeIdentifier", "03"))
subj_node.append(text_node("SubjectCode", subject.name))
elif subject.authority == 'bisacsh':
subj_node.append(text_node("SubjectSchemeIdentifier", "10"))
subj_node.append(text_node("SubjectCode", bisac.code(subject.name)))
subj_node.append(text_node("SubjectHeadingText", subject.name))
else:
subj_node.append(text_node("SubjectSchemeIdentifier", "20"))
subj_node.append(text_node("SubjectHeadingText", subject.name))
# Collateral Detail Block
coll_node = etree.SubElement(product_node, "CollateralDetail")
desc_node = etree.SubElement(coll_node, "TextContent")
desc_node.append(text_node("TextType", '03')) # description
desc_node.append(text_node("ContentAudience", '00')) #unrestricted
desc = work.description + '<br /><br />Listed by <a href="https://unglue.it/work/%s/">Unglue.it</a>.' % work.id
try :
content = etree.XML("<div>" + desc + "</div>")
content_node = etree.SubElement(desc_node, "Text", attrib={"textformat":"05"}) #xhtml
content_node.append(content)
except etree.XMLSyntaxError:
content_node = etree.SubElement(desc_node, "Text", attrib={"textformat":"02"}) #html
content_node.text = etree.CDATA(desc)
supp_node = etree.SubElement(coll_node, "SupportingResource")
supp_node.append(text_node("ResourceContentType", '01')) #front cover
supp_node.append(text_node("ContentAudience", '00')) #unrestricted
supp_node.append(text_node("ResourceMode", '03')) #image
cover_node = etree.SubElement(supp_node, "ResourceVersion")
cover_node.append(text_node("ResourceForm", '01')) #linkable
coverfeat_node = etree.SubElement(cover_node, "ResourceVersionFeature")
coverfeat_node.append(text_node("ResourceVersionFeatureType", '01')) #image format
coverfeat_node.append(text_node("FeatureValue", 'D502')) #jpeg
cover_node.append(text_node("ResourceLink", edition.cover_image_thumbnail())) #link
# Publishing Detail Block
pubdetail_node = etree.SubElement(product_node, "PublishingDetail")
if edition.publisher_name:
pub_node = etree.SubElement(pubdetail_node, "Publisher")
pub_node.append(text_node("PublishingRole", '01')) #publisher
pub_node.append(text_node("PublisherName", edition.publisher_name.name))
pubdetail_node.append(text_node("PublishingStatus", '00')) #unspecified
if edition.publication_date:
pubdate_node = etree.SubElement(pubdetail_node, "PublishingDate")
pubdate_node.append(text_node("PublishingDateRole", '01')) #nominal pub date
pubdate_node.append(text_node("Date", edition.publication_date.replace('-','')))
# Product Supply Block
supply_node = etree.SubElement(product_node,"ProductSupply")
market_node = etree.SubElement(supply_node,"Market")
terr_node = etree.SubElement(market_node,"Territory")
terr_node.append(text_node("RegionsIncluded", 'WORLD'))
supply_detail_node = etree.SubElement(supply_node,"SupplyDetail")
supplier_node = etree.SubElement(supply_detail_node,"Supplier")
supplier_node.append(text_node("SupplierRole", '11')) #non-exclusive distributer
supplier_node.append(text_node("SupplierName", 'Unglue.it')) #non-exclusive distributer
for ebook in ebooks:
website_node = etree.SubElement(supplier_node,"Website")
website_node.append(text_node("WebsiteRole", '29')) #full content
website_node.append(text_node("WebsiteDescription", '%s file download' % ebook.format, attrib={'textformat':'06'})) #full content
website_node.append(text_node("WebsiteLink", ebook.download_url)) #full content
supply_detail_node.append(text_node("ProductAvailability", '20')) #Available
price_node = etree.SubElement(supply_detail_node,"Price")
price_node.append(text_node("PriceType", '01')) #retail excluding tax
price_node.append(text_node("PriceAmount", '0.00')) #retail excluding tax
price_node.append(text_node("CurrencyCode", 'USD')) #retail excluding tax
return product_node

View File

@ -45,8 +45,15 @@
{% endif %} {% endif %}
<h3>OPDS Catalog Feeds</h3> <h3>OPDS Catalog Feeds</h3>
<p>We have a basic implementation of <a href="http://opds-spec.org/specs/opds-catalog-1-1-20110627/">OPDS</a> feeds. You don't need a key to use them. The starting point is <a href="{% url 'opds' %}">{% url 'opds' %}</a></p> <p>We have a basic implementation of <a href="http://opds-spec.org/specs/opds-catalog-1-1-20110627/">OPDS</a> feeds. You don't need a key to use them. The starting point is <code><a href="{% url 'opds' %}">{{base_url}}{% url 'opds' %}</a></code></p>
<p>There's also an OPDS record available for every work on unglue.it. For example, requesting, <code><a href="{% url 'opds_acqusition' 'all'%}?work=13950">{{base_url}}{% url 'opds_acqusition' 'all'%}?work=13950</a></code> get you to the web page or opds record for <i>A Christmas Carol</i>.</p>
<h3>ONIX Catalog Feeds</h3>
<p>There is an <a href="http://www.editeur.org/12/about-release-3.0/">ONIX 3.0</a> feed corresponding to every facet of our <a href="{% url 'free' %}">free ebook lists</a>. You don't need a key to use them. There is a maximum of 100 books per result you can change with the max parameter. For example, here are the <a href="{% url 'onix' 'by-nc-nd/epub' %}">first hundred CC BY-ND-ND licensed books available in EPUB.</a></p>
<p>There's also an ONIX record available for every free ebook on unglue.it. For example, here is <a href="{% url 'onix_all' %}?work=140086"><i>Issues in Open Research Data</i></a>.</p>
<h3>Identifiers with Content type negotiation</h3>
<p>There's a URI to identify every work used in OPDS feeds. HTTP content negotiation is used for these ids, so requesting <code>application/atom+xml;profile=opds-catalog;kind=acquisition</code> for <code><a href="{% url 'work_identifier' '13950'%}">{{base_url}}{% url 'work_identifier' '13950' %}</a></code> get you to the web page or opds record for <i>A Christmas Carol</i>. requesting <code>text/xml</code> gets you the onix record. Otherwise, you get the normal html page.</p>
{% endblock %} {% endblock %}

View File

@ -126,7 +126,11 @@ class ApiTests(TestCase):
r = self.client.get('/api/widget/%s/'%self.work_id) r = self.client.get('/api/widget/%s/'%self.work_id)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
class OPDSTests(TestCase): class FeedTests(TestCase):
def setUp(self):
edition = bookloader.add_by_isbn_from_google(isbn='0441007465')
ebook = models.Ebook.objects.create(edition=edition, url='http://example.org/', format='epub', rights='CC BY')
self.test_work_id = edition.work.id
def test_opds(self): def test_opds(self):
r = self.client.get('/api/opds/creative_commons/') r = self.client.get('/api/opds/creative_commons/')
@ -137,6 +141,18 @@ class OPDSTests(TestCase):
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
r = self.client.get('/api/opds/active_campaigns/?order_by=title') r = self.client.get('/api/opds/active_campaigns/?order_by=title')
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
r = self.client.get('/api/opds/?work=%s' % self.test_work_id)
self.assertEqual(r.status_code, 200)
def test_nix(self):
r = self.client.get('/api/onix/by/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/api/onix/cc0/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/api/onix/epub/?max=10')
self.assertEqual(r.status_code, 200)
r = self.client.get('/api/onix/?work=%s' % self.test_work_id)
self.assertEqual(r.status_code, 200)
class AllowedRepoTests(TestCase): class AllowedRepoTests(TestCase):
def setUp(self): def setUp(self):

View File

@ -6,6 +6,7 @@ from django.views.generic.base import TemplateView
from regluit.api import resources from regluit.api import resources
from regluit.api.views import ApiHelpView from regluit.api.views import ApiHelpView
from regluit.api.views import OPDSNavigationView, OPDSAcquisitionView from regluit.api.views import OPDSNavigationView, OPDSAcquisitionView
from regluit.api.views import OnixView
v1_api = Api(api_name='v1') v1_api = Api(api_name='v1')
@ -24,6 +25,8 @@ urlpatterns = patterns('',
url(r'^widget/(?P<isbn>\w+)/$','regluit.api.views.widget', name="widget"), url(r'^widget/(?P<isbn>\w+)/$','regluit.api.views.widget', name="widget"),
url(r"^opds/$", OPDSNavigationView.as_view(template_name="opds.xml"), name="opds"), url(r"^opds/$", OPDSNavigationView.as_view(template_name="opds.xml"), name="opds"),
url(r"^opds/(?P<facet>.*)/$", OPDSAcquisitionView.as_view(), name="opds_acqusition"), url(r"^opds/(?P<facet>.*)/$", OPDSAcquisitionView.as_view(), name="opds_acqusition"),
url(r"^onix/(?P<facet>.*)/$", OnixView.as_view(), name="onix"),
url(r"^onix/$", OnixView.as_view(), name="onix_all"),
url(r'^id/work/(?P<work_id>\w+)/$', 'regluit.api.views.negotiate_content', name="work_identifier"), url(r'^id/work/(?P<work_id>\w+)/$', 'regluit.api.views.negotiate_content', name="work_identifier"),
url(r'^loader/yaml$','regluit.api.views.load_yaml', name="load_yaml"), url(r'^loader/yaml$','regluit.api.views.load_yaml', name="load_yaml"),
(r'^', include(v1_api.urls)), (r'^', include(v1_api.urls)),

View File

@ -12,11 +12,12 @@ from django.http import (
HttpResponse, HttpResponse,
HttpResponseNotFound, HttpResponseNotFound,
HttpResponseRedirect, HttpResponseRedirect,
Http404,
) )
import regluit.core.isbn import regluit.core.isbn
from regluit.core.bookloader import load_from_yaml from regluit.core.bookloader import load_from_yaml
from regluit.api import opds from regluit.api import opds, onix
from regluit.api.models import repo_allowed from regluit.api.models import repo_allowed
from regluit.core import models from regluit.core import models
@ -45,6 +46,9 @@ def negotiate_content(request,work_id):
if request.META.get('HTTP_ACCEPT', None): if request.META.get('HTTP_ACCEPT', None):
if "opds-catalog" in request.META['HTTP_ACCEPT']: if "opds-catalog" in request.META['HTTP_ACCEPT']:
return HttpResponseRedirect(reverse('opds_acqusition',args=['all'])+'?work='+work_id) return HttpResponseRedirect(reverse('opds_acqusition',args=['all'])+'?work='+work_id)
elif "text/xml" in request.META['HTTP_ACCEPT']:
return HttpResponseRedirect(reverse('onix',args=['all'])+'?work='+work_id)
return HttpResponseRedirect(reverse('work', kwargs={'work_id': work_id})) return HttpResponseRedirect(reverse('work', kwargs={'work_id': work_id}))
def widget(request,isbn): def widget(request,isbn):
@ -142,3 +146,26 @@ class OPDSAcquisitionView(View):
facet_class = opds.get_facet_class(facet)() facet_class = opds.get_facet_class(facet)()
return HttpResponse(facet_class.feed(page,order_by), return HttpResponse(facet_class.feed(page,order_by),
content_type="application/atom+xml;profile=opds-catalog;kind=acquisition") content_type="application/atom+xml;profile=opds-catalog;kind=acquisition")
class OnixView(View):
def get(self, request, *args, **kwargs):
work = request.GET.get('work', None)
if work:
try:
work=models.safe_get_work(work)
except models.Work.DoesNotExist:
raise Http404
return HttpResponse(onix.onix_feed_for_work(work),
content_type="text/xml")
facet = kwargs.get('facet', 'all')
if facet:
max = request.GET.get('max', 100)
try:
max = int(max)
except:
max = None
facet_class = opds.get_facet_class(facet)()
return HttpResponse(onix.onix_feed(facet_class, max),
content_type="text/xml")

23121
bisac/__init__.py Normal file

File diff suppressed because it is too large Load Diff

9
bisac/tests.py Normal file
View File

@ -0,0 +1,9 @@
import unittest
from . import Bisac
class TestBisac(unittest.TestCase):
def setUp(self):
self.bisac=Bisac()
def test_code(self):
self.assertEqual(self.bisac.code('Religion'),'REL000000')

View File

@ -112,7 +112,9 @@ class FormatFacetGroup(FacetGroup):
self.facet_name=facet_name self.facet_name=facet_name
def format_filter(query_set): def format_filter(query_set):
return query_set.filter(format=facet_name) return query_set.filter(format=facet_name)
model_filters = {"Ebook": format_filter} def edition_format_filter(query_set):
return query_set.filter(ebooks__format=facet_name)
model_filters = {"Ebook": format_filter, "Edition": edition_format_filter}
def get_query_set(self): def get_query_set(self):
return self._get_query_set().filter(editions__ebooks__format=self.facet_name) return self._get_query_set().filter(editions__ebooks__format=self.facet_name)
def template(self): def template(self):
@ -141,7 +143,9 @@ class LicenseFacetGroup(FacetGroup):
self.license = cc.ccinfo(facet_name) self.license = cc.ccinfo(facet_name)
def license_filter(query_set): def license_filter(query_set):
return query_set.filter(rights=cc.ccinfo(facet_name)) return query_set.filter(rights=cc.ccinfo(facet_name))
model_filters = {"Ebook": license_filter} def edition_license_filter(query_set):
return query_set.filter(ebooks__rights=cc.ccinfo(facet_name))
model_filters = {"Ebook": license_filter, "Edition": edition_license_filter}
def get_query_set(self): def get_query_set(self):
return self._get_query_set().filter(editions__ebooks__rights=self.license.license) return self._get_query_set().filter(editions__ebooks__rights=self.license.license)
def template(self): def template(self):
@ -224,7 +228,9 @@ class PublisherFacetGroup(FacetGroup):
self.publisher = None self.publisher = None
def pub_filter(query_set): def pub_filter(query_set):
return query_set.filter(edition__publisher_name__publisher__id=facet_name[4:]) return query_set.filter(edition__publisher_name__publisher__id=facet_name[4:])
model_filters = {"Ebook": pub_filter} def edition_pub_filter(query_set):
return query_set.filter(publisher_name__publisher__id=facet_name[4:])
model_filters = {"Ebook": pub_filter, "Edition": edition_pub_filter }
def get_query_set(self): def get_query_set(self):
return self._get_query_set().filter(editions__publisher_name__publisher=self.publisher) return self._get_query_set().filter(editions__publisher_name__publisher=self.publisher)
def template(self): def template(self):

View File

@ -1902,6 +1902,9 @@ def safe_get_work(work_id):
work = WasWork.objects.get(was = work_id).work work = WasWork.objects.get(was = work_id).work
except WasWork.DoesNotExist: except WasWork.DoesNotExist:
raise Work.DoesNotExist() raise Work.DoesNotExist()
except ValueError:
#work_id is not a number
raise Work.DoesNotExist()
return work return work
FORMAT_CHOICES = (('pdf','PDF'),( 'epub','EPUB'), ('html','HTML'), ('text','TEXT'), ('mobi','MOBI')) FORMAT_CHOICES = (('pdf','PDF'),( 'epub','EPUB'), ('html','HTML'), ('text','TEXT'), ('mobi','MOBI'))

View File

@ -21,6 +21,10 @@
href="/api/opds/{{ path }}/?order_by={{ order_by }}" href="/api/opds/{{ path }}/?order_by={{ order_by }}"
type="application/atom+xml;profile=opds-catalog" type="application/atom+xml;profile=opds-catalog"
title="OPDS Catalog for Unglue.it Free Books" /> title="OPDS Catalog for Unglue.it Free Books" />
<link rel="related"
href="/api/onix/{{ path }}/?max=100"
type="text/xml"
title="ONIX feed for Unglue.it Free Books" />
{% endblock %} {% endblock %}
{% block topsection %} {% block topsection %}
<div id="locationhash">#1</div> <div id="locationhash">#1</div>