Merge branch 'master' into sysadmin

pull/1/head
Raymond Yee 2016-06-21 09:05:06 -07:00
commit 723c8e3ec8
33 changed files with 402 additions and 480 deletions

View File

@ -7,7 +7,7 @@ import unicodedata
from regluit.core.models import Work, Edition, Author, PublisherName, Identifier, Subject
from regluit.core.isbn import ISBN
from regluit.core.bookloader import add_by_isbn_from_google
from regluit.core.bookloader import add_by_isbn_from_google, merge_works
from regluit.api.crosswalks import inv_relator_contrib
from regluit.bisac.models import BisacHeading
@ -36,33 +36,49 @@ def utf8_general_ci_norm(s):
def get_authors(book):
authors=[]
for i in range(1,3):
fname=u'Author{}First'.format(i)
lname=u'Author{}Last'.format(i)
role=u'Author{}Role'.format(i)
authname = u'{} {}'.format(book[fname],book[lname])
if authname != u' ':
role = book[role] if book[role].strip() else 'A01'
authors.append((authname,role))
else:
break
authlist = book["AuthorsList"].replace(' and ', ', ').split(', ')
if len(authlist)>3:
for authname in authlist[3:]:
authors.append((authname, 'A01'))
if book.get('AuthorsList',''):
#UMich
for i in range(1,3):
fname=u'Author{}First'.format(i)
lname=u'Author{}Last'.format(i)
role=u'Author{}Role'.format(i)
authname = u'{} {}'.format(book[fname],book[lname])
if authname != u' ':
role = book[role] if book[role].strip() else 'A01'
authors.append((authname,role))
else:
break
authlist = book["AuthorsList"].replace(' and ', ', ').split(', ')
if len(authlist)>3:
for authname in authlist[3:]:
authors.append((authname, 'A01'))
else:
#OBP
for i in range(1,6):
fname= book.get(u'Contributor {} first name'.format(i), '')
lname= book.get(u'Contributor {} surname'.format(i), '')
role= book.get(u'ONIX Role Code (List 17){}'.format(i), '')
authname = u'{} {}'.format(fname,lname)
if authname != u' ':
role = role if role.strip() else 'A01'
authors.append((authname,role))
else:
break
return authors
def get_subjects(book):
subjects=[]
for i in range(1,3):
key=u'BISACCode{}'.format(i)
if book[key] != '':
for i in range(1,5):
key = u'BISACCode{}'.format(i) #UMich dialect
key2 = u'BISAC subject code {}'.format(i) #OBP dialect
code = book.get(key,'')
code = code if code else book.get(key2,'')
if code != '':
try:
bisac=BisacHeading.objects.get(notation=book[key])
bisac=BisacHeading.objects.get(notation=code)
subjects.append(bisac)
except BisacHeading.DoesNotExist:
logger.warning( "Please add BISAC {}".format(book[key]))
logger.warning( "Please add BISAC {}".format(code))
return subjects
def add_subject(subject_name, work, authority=''):
@ -72,7 +88,21 @@ def add_subject(subject_name, work, authority=''):
subject=Subject.objects.create(name=subject_name, authority=authority)
subject.works.add(work)
def get_title(book):
title = book.get('FullTitle','') #UMICH
if title:
return title
title = book.get('Title','') #OBP
sub = book.get('Subtitle','')
if sub:
return u'{}: {}'.format(title,sub)
else:
return title
def get_cover(book):
cover_url = book.get('Cover URL','') #OBP
if cover_url:
return cover_url
url = book['URL']
if "10.3998" in url:
# code for umich books; can generalize, of course!
@ -94,8 +124,9 @@ def get_cover(book):
def get_isbns(book):
isbns = []
edition = None
for code in ['eISBN','PaperISBN','ClothISBN']:
if book[code] not in ('','N/A'):
#'ISBN 1' is OBP, others are UMICH
for code in ['eISBN', 'ISBN 3','PaperISBN', 'ISBN 2', 'ClothISBN', 'ISBN 1', 'ISBN 4', 'ISBN 5']:
if book.get(code, '') not in ('','N/A'):
values = book[code].split(',')
for value in values:
isbn = ISBN(value).to_string()
@ -106,15 +137,53 @@ def get_isbns(book):
edition = Edition.get_by_isbn(isbn)
return (isbns, edition )
def get_pubdate(book):
value = book.get('CopyrightYear','') #UMICH
if value:
return value
value = book.get('publication year','') #OBP
sub = book.get('publication month','')
sub2 = book.get('publication day','')
if sub2:
return u'{}-{}-{}'.format(value,sub,sub2)
elif sub:
return u'{}-{}'.format(value,sub,sub2)
else:
return value
def get_publisher(book):
value = book.get('Publisher','')
if value:
return value
if book.get('DOI prefix','')=='10.11647':
return "Open Book Publishers"
def get_url(book):
url = book.get('URL','')
url = url if url else u'https://dx.doi.org/{}/{}'.format( book.get('DOI prefix',''),book.get('DOI suffix',''))
return url
def get_description(book):
value = book.get('DescriptionBrief','')
value = value if value else book.get('Plain Text Blurb','')
return value
def get_language(book):
value = book.get('ISO Language Code','')
return value
def load_from_books(books):
''' books is an iterator of book dicts.
each book must have attributes
(umich dialect)
eISBN, ClothISBN, PaperISBN, Publisher, FullTitle, Title, Subtitle, AuthorsList,
Author1Last, Author1First, Author1Role, Author2Last, Author2First, Author2Role, Author3Last,
Author3First, Author3Role, AuthorBio, TableOfContents, Excerpt, DescriptionLong,
DescriptionBrief, BISACCode1, BISACCode2, BISACCode3, CopyrightYear, ePublicationDate,
eListPrice, ListPriceCurrencyType, List Price in USD (paper ISBN), eTerritoryRights,
SubjectListMARC, , Book-level DOI, URL, License
'''
# Goal: get or create an Edition and Work for each given book
@ -125,12 +194,14 @@ def load_from_books(books):
# try first to get an Edition already in DB with by one of the ISBNs in book
(isbns, edition) = get_isbns(book)
title=book['FullTitle']
if len(isbns)==0:
continue
title=get_title(book)
authors = get_authors(book)
# if matching by ISBN doesn't work, then create a Work and Edition
# with a title and the first ISBN
if not edition and len(isbns):
if not edition:
work = Work(title=title)
work.save()
edition= Edition(title=title, work=work)
@ -140,14 +211,18 @@ def load_from_books(books):
work=edition.work
# at this point, work and edition exist
if book.get('URL'):
Identifier.set(type='http', value=book['URL'], edition=edition, work=work)
url = get_url(book)
if url:
Identifier.set(type='http', value=url, edition=edition, work=work)
# make sure each isbn is represented by an Edition
# also associate authors, publication date, cover, publisher
for isbn in isbns:
edition = add_by_isbn_from_google(isbn)
edition = add_by_isbn_from_google(isbn, work=work)
if edition and edition.work != work:
merge_works(work, edition.work)
work = work if work.pk is not None else edition.work
edition.work=work # because integrity errors if not
if not edition:
edition= Edition(title=title, work=work)
edition.save()
@ -156,16 +231,23 @@ def load_from_books(books):
edition.authors.clear()
for (author, role) in authors:
edition.add_author(author, inv_relator_contrib.get(role, 'aut'))
edition.publication_date = book['CopyrightYear']
edition.publication_date = get_pubdate(book)
edition.cover_image = get_cover(book)
edition.set_publisher(book['Publisher'])
edition.save()
edition.set_publisher(get_publisher(book))
# possibly replace work.description
description = book['DescriptionBrief']
description = get_description(book)
if len(description)>len (work.description):
work.description = description
work.save()
# set language
lang= get_language(book)
if lang:
work.language = lang
work.save()
# add a bisac subject (and ancestors) to work
for bisacsh in get_subjects(book):
while bisacsh:
@ -178,9 +260,9 @@ def load_from_books(books):
results.append((book, work, edition))
try:
logger.info ("{} {} {}\n".format(i, title, loading_ok))
logger.info (u"{} {} {}\n".format(i, title, loading_ok))
except Exception as e:
logger.info ("{} {}\n".format(i, title, str(e) ))
logger.info (u"{} {}\n".format(i, title, str(e) ))
return results
@ -195,9 +277,9 @@ def loaded_book_ok(book, work, edition):
return False
try:
url_id = Identifier.objects.get(type='http', value=book['URL'])
url_id = Identifier.objects.get(type='http', value=get_url(book))
if url_id is None:
logger.info ("url_id problem: work.id {}, url: {}".format(work.id, book['URL']))
logger.info ("url_id problem: work.id {}, url: {}".format(work.id, get_url(book)))
return False
except Exception as e:
logger.info (str(e))
@ -224,14 +306,14 @@ def loaded_book_ok(book, work, edition):
return False
try:
edition_for_isbn.publication_date = book['CopyrightYear']
edition_for_isbn.publication_date = get_pubdate(book)
edition_for_isbn.cover_image = get_cover(book)
edition_for_isbn.set_publisher(book['Publisher'])
edition_for_isbn.set_publisher(get_publisher(book))
except:
return False
# work description
description = book['DescriptionBrief']
description = get_description(book)
if not ((work.description == description) or (len(description) <len (work.description))):
return False

View File

@ -0,0 +1,37 @@
"""
print user emails
"""
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from regluit.core import models
class Command(BaseCommand):
help = "claim books for rights_holder based on a text file of ISBNs"
args = "<rights_holder_id> <filename>"
def handle(self, rights_holder_id, filename, **options):
try:
rh = models.RightsHolder.objects.get(id=int(rights_holder_id))
except models.Identifier.DoesNotExist:
print '{} not a rights_holder'.format(rights_holder_id)
return
with open(filename) as f:
for isbn in f:
isbn = isbn.strip()
try:
work = models.Identifier.objects.get(type='isbn',value=isbn).work
try:
c = models.Claim.objects.get(work=work)
print '{} already claimed by {}'.format(work, c.rights_holder)
except models.Claim.DoesNotExist:
c = models.Claim.objects.create(
work=work,
rights_holder=rh,
user=rh.owner,
status='active')
print '{} claimed for {}'.format(work, rh)
except models.Identifier.DoesNotExist:
print '{} not loaded'.format(isbn)
continue

View File

@ -8,6 +8,6 @@ class Command(BaseCommand):
args = "<filename>"
def handle(self, filename, **options):
sheetreader= UnicodeDictReader(open(filename,'rU'), dialect=csv.excel_tab)
sheetreader= UnicodeDictReader(open(filename,'rU'), dialect=csv.excel)
load_from_books(sheetreader)
print "finished loading"

View File

@ -0,0 +1,13 @@
import csv
from django.core.management.base import BaseCommand
from regluit.core.loaders.utils import UnicodeDictReader, load_from_books
class Command(BaseCommand):
help = "load books based on a csv spreadsheet of onix data"
args = "<filename>"
def handle(self, filename, **options):
sheetreader= UnicodeDictReader(open(filename,'rU'), dialect=csv.excel_tab)
load_from_books(sheetreader)
print "finished loading"

View File

@ -1123,8 +1123,12 @@ class Work(models.Model):
@property
def googlebooks_id(self):
preferred_id=self.preferred_edition.googlebooks_id
# note that there's always a preferred edition
try:
preferred_id=self.preferred_edition.googlebooks_id
# note that there should always be a preferred edition
except AttributeError:
# this work has no edition.
return ''
if preferred_id:
return preferred_id
try:
@ -1307,6 +1311,7 @@ class Work(models.Model):
return WasWork.objects.get(was=self.id).work.preferred_edition
except WasWork.DoesNotExist:
#should not happen
logger.warning('work {} has no edition'.format(self.id))
return None
def last_campaign_status(self):
@ -2064,7 +2069,7 @@ class Ebook(models.Model):
return ebf.file
except IndexError:
# response has no Content-Length header probably a bad link
logging.error( 'Bad link error: {}'.format(ebook.url) )
logging.error( 'Bad link error: {}'.format(self.url) )
except IOError:
logger.error(u'could not open {}'.format(self.url) )
else:

View File

@ -1133,7 +1133,64 @@ class OnixLoaderTests(TestCase):
'eISBN': u'N/A',
'eListPrice': u'N/A',
'ePublicationDate': u'',
'eTerritoryRights': u''}
'eTerritoryRights': u''},
{'': u'',
'CAD price eub': u'9.95',
'Title': u'That Greece Might Still Be Free',
'USD price epub': u'9.95',
'ISBN 2 with dashes': u'978-1-906924-01-0',
'Plain Text Blurb': u'When in 1821, the Greeks rose in violent revolution against the rule of the Ottoman Turks, waves of sympathy spread across Western Europe and the United States. More than a thousand volunteers set out to fight for the cause. The Philhellenes, whether they set out to recreate the Athens of Pericles, start a new crusade, or make money out of a war, all felt that Greece had unique claim on the sympathy of the world. As Lord Byron wrote, "I dreamed that Greece might still be Free"; and he died at Missolonghi trying to translate that dream into reality. William St Clair\'s meticulously researched and highly readable account of their aspirations and experiences was hailed as definitive when it was first published. Long out of print, it remains the standard account of the Philhellenic movement and essential reading for any students of the Greek War of Independence, Byron, and European Romanticism. Its relevance to more modern ethnic and religious conflicts is becoming increasingly appreciated by scholars worldwide. This revised edition includes a new introduction by Roderick Beaton, an updated bibliography and many new illustrations.',
'Cover URL': u'http://www.openbookpublishers.com/shopimages/products/cover/3',
'keywords': u'Greece; Greek History; Lord Byron; War of Independence; Philhellenes; war; history; Romanticism',
'Publication type': u'Monograph', 'GBP price epub': u'5.95', 'publication month': u'11', 'no of tables': u'',
'GBP price paperback': u'15.95', 'AUD price epub': u'9.95', 'ISBN 4 with dashes': u'978-1-906924-02-7-epub',
'DOI prefix': u'10.11647', 'License URL (human-readable summary)': u'http://creativecommons.org/licenses/by-nc-nd/2.0/',
'Contributor 5 surname': u'', 'Contributor 1 first name': u'William', 'Contributor 6 first name': u'',
'ONIX Role Code (List 17)6': u'', 'ONIX Role Code (List 17)5': u'', 'ONIX Role Code (List 17)4': u'',
'ONIX Role Code (List 17)3': u'', 'ONIX Role Code (List 17)2': u'A24', 'ONIX Role Code (List 17)1': u'A01',
'GBP price hardback': u'29.95', 'Subtitle': u'The Philhellenes in the War of Independence', 'ONIX tag6': u'',
'ISBN 3 with dashes': u'978-1-906924-02-7', 'Countries excluded': u'None', 'first edition publication date': u'39753',
'Original Language': u'English', 'ISBN 1 with dashes': u'978-1-906924-00-3', 'Contributor 4 first name': u'',
'ISBN 5 with dashes': u'978-1-906924-02-7-mobi', 'Contributor 2 surname': u'Beaton',
'License URL (our copyright tab)': u'http://www.openbookpublishers.com/isbn/9781906924003#copyright',
'BISAC subject code 1': u'HIS042000', 'BISAC subject code 3': u'HIS037060',
'BISAC subject code 2': u'HIS054000', 'BISAC subject code 5': u'',
'BISAC subject code 4': u'', 'Status': u'Active', 'Geographic rights': u'Worldwide',
'Series Name': u'', 'Contributor 5 first name': u'', 'ISSN Print with dashes': u'',
'ISBN 5': u'9781906924027mobi', 'Contributor 1 surname': u'St Clair',
'Contributor 2 first name': u'Roderick',
'Book-page permanent URL': u'http://www.openbookpublishers.com/isbn/9781906924003',
'EUR price hardback': u'36.95', 'EUR price epub': u'7.95', 'Contributor 6 surname': u'',
'current edition number (integers only)': u'1',
'Table of Content': u"Introduction by Roderick Beaton\n1. The Outbreak\n2. The Return of the Ancient Hellenes\n3. The Regiment\n4. Two Kinds of War\n5. The Cause of Greece, the Cause of Europe\n6. The Road to Marseilles\n7. Chios\n8. The Battalion of Philhellenes\n9. The Battle of Peta\n10. The Triumph of the Captains\n11. The Return Home\n12. The German Legion\n13. Knights and Crusaders\n14. Secrets of State\n15. Enter the British\n16. Lord Byron joins the Cause\n17. 'To bring Freedom and Knowledge to Greece'\n18. Arrivals at Missolonghi\n19. The Byron Brigade\n20. Essays in Regeneration\n21. The New Apostles\n22. The English Gold\n23. The Coming of the Arabs\n24. The Shade of Napoleon\n25. 'No freedom to fight for at home'\n26. French Idealism and French Cynicism\n27. Regulars Again\n28. A New Fleet\n29. Athens and Navarino\n30. America to the Rescue\n31. Later\nAppendix I: Remarks on Numbers\nAppendix II: The Principal Philhellenic Expeditions\nNotes on the Select Bibliography\nSelect Bibliography\nBibliography of Primary and Secondary Material Since 1972\nNotes\nIndex",
'no of illustrations': u'41', 'OBP Role Name6': u'', 'GBP price PDF': u'5.95',
'OBP Role Name4': u'', 'OBP Role Name5': u'', 'OBP Role Name2': u'Introduction',
'OBP Role Name3': u'', 'OBP Role Name1': u'Author', 'ONIX Status code': u'04',
'LLC (Library of Congress Codes)': u'', 'publication day': u'1',
'Copyright holder 2': u'', 'Language': u'English', 'Contributor 3 first name': u'',
'CAD price hardback': u'54.95', 'USD price paperback': u'26.95', 'ONIX tag1': u'By (author)',
'ONIX tag3': u'', 'ONIX tag2': u'Introduction by', 'ONIX tag5': u'',
'ONIX tag4': u'', 'no of audio/video': u'0', 'EUR price mobi': u'7.95',
'Version of the license': u'2.0', 'publication year': u'2008',
'CAD price paperback': u'29.95', 'Full-text URL - PDF': u'http://www.openbookpublishers.com/reader/3',
'Copyright holder 1': u'William St Clair', 'Copyright holder 3': u'',
'Short Blurb (less than 100 words)': u'When in 1821, the Greeks rose in violent revolution against Ottoman rule, waves of sympathy spread across western Europe and the USA. Inspired by a belief that Greece had a unique claim on the sympathy of the world, more than a thousand Philhellenes set out to fight for the cause. This meticulously researched and highly readable account of their aspirations and experiences has long been the standard account of the Philhellenic movement and essential reading for students of the Greek War of Independence, Byron and European Romanticism. Its relevance to more modern conflicts is also becoming increasingly appreciated.',
'BIC subject code 3': u'3JH', 'BIC subject code 2': u'1DVG', 'BIC subject code 1': u'HBJD',
'ISSN Digital with dashes': u'', 'USD price mobi': u'9.95', 'BIC subject code 5': u'',
'BIC subject code 4': u'', 'ONIX Language Code': u'eng', 'AUD price paperback': u'29.95',
'AUD price mobi': u'9.95', 'No. in the Series': u'', 'CAD price PDF': u'9.95',
'CAD price mobi': u'9.95', 'DOI suffix': u'OBP.0001', 'USD price PDF': u'9.95',
'Book-page URL': u'http://www.openbookpublishers.com/product/3',
'Academic discipline (OBP)': u'History and Biography', 'EUR price paperback': u'19.95',
'License': u'CC BY-NC-ND', 'AUD price PDF': u'9.95', 'Contributor 3 surname': u'',
'AUD price hardback': u'54.95', 'ISBN 4': u'9781906924027epub', 'no of pages': u'440',
'ISBN 2': u'9781906924010', 'ISBN 3': u'9781906924027', 'ISBN 1': u'9781906924003',
'pages': u'xxi + 419', 'Contributor 4 surname': u'', 'USD price hardback': u'48.95',
'Full-text URL - HTML': u'http://www.openbookpublishers.com/htmlreader/978-1-906924-00-3/main.html',
'GBP price mobi': u'5.95', 'Format 1': u'Paperback ', 'EUR price PDF': u'7.95', 'Format 3': u'pdf',
'Format 2': u'Hardback', 'Format 5': u'mobi', 'Format 4': u'epub', 'MARC Code1': u'aut',
'MARC Code2': u'aui', 'MARC Code3': u'', 'MARC Code4': u'', 'MARC Code5': u'',
'MARC Code6': u'', 'ISO Language Code': u'en'}
]
results = load_from_books(TEST_BOOKS)

View File

@ -3,13 +3,13 @@
{% block title %}{{ block.super }}Questionnaire{% endblock title %}
{% block search_box %}
{% render_with_landing '' %}
<a href="{{landing_object.publishers.0.url}}"><img style="float:left;margin:10px" src="{{landing_object.publishers.0.logo_url}}" /></a>
<a href="{{landing_object.publishers.0.url}}"><img style="float:left;margin:10px" src="{{landing_object.publishers.0.logo_url}}" alt="{{landing_object.publishers.0.name}}" /></a>
{% endblock %}
{% block signin %}
{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="/static/bootstrap/bootstrap.min.css" type="text/css"/>
<link rel="stylesheet" href="/static/questionnaire.css"></script>
<link rel="stylesheet" href="/static/bootstrap/bootstrap.min.css" type="text/css" />
<link rel="stylesheet" href="/static/questionnaire.css" />
<style type="text/css">
{% block styleextra %}
{% endblock %}

View File

@ -67,7 +67,7 @@
<div class="js-topmenu" id="authenticated">
<ul class="menu">
<li>
<a class="notbutton" href="#"><span id="welcome">Hi, {{ user.username }} <i class="fa fa-chevron-down"> </span></i></a>
<a class="notbutton" href="#"><span id="welcome">Hi, {{ user.username }} <i class="fa fa-chevron-down"> </i></span></a>
</li>
<li>{% block avatar %}
<img class="user-avatar" src="{{ user.profile.avatar_url }}" height=36 width="36" alt="Avatar for {{ user.username }}" title="{{ user.username }}" />{% endblock %}
@ -98,7 +98,7 @@
{% else %}
<div class="js-topmenu">
<ul class="menu">
<li><a class="notbutton" href="{% url 'superlogin' %}?next={% if request.GET.next %}{{ request.GET.next|urlencode }}{% else %}{{ request.get_full_path|urlencode}}{% endif %}" class="hijax"><span>Sign In</span></a></li>
<li><a class="notbutton hijax" href="{% url 'superlogin' %}?next={% if request.GET.next %}{{ request.GET.next|urlencode }}{% else %}{{ request.get_full_path|urlencode}}{% endif %}"><span>Sign In</span></a></li>
{% if not suppress_search_box %}
{% ifnotequal request.get_full_path "/accounts/register/" %}
<li class="last"><a class="btn btn-signup" href="{% url 'registration_register' %}?next={% if request.GET.next %}{{ request.GET.next|urlencode }}{% else %}{{ request.get_full_path|urlencode}}{% endif %}">Sign Up <i class="fa fa-chevron-right"></i></a></li>

View File

@ -391,7 +391,16 @@ Unglue.it signs agreements assuring the copyright status of every work we unglue
<dt>How can I claim a work for which I am the rights holder?</dt>
<dd>On every book page there is a More... tab. If you have a signed Platform Services Agreement on file, one of the options on the More... tab will be "Claim This Work". If you represent more than one rights holder, choose the correct one for this work and click "Claim".<br /><br />If you expect to see a "Claim" button and do not, either we do not have a PSA from you yet, or we have not yet verified and filed it. Please contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</dd>
<dd>On every book page there is a More... tab. If you have a signed Platform Services Agreement on file, one of the options on the More... tab will be "Claim This Work". If you represent more than one rights holder, choose the correct one for this work and click "Claim".
<br /><br />If the book is not already in Unglue.it or in Google Books, you'll need to enter the metatadata for the book by hand or work with us to load the data from a spreadsheet or Onix file.
<ul class="bullets">
<li>Use <a href="{% url 'new_edition' '' '' %}">this form</a> to enter the metadata.</li>
<li>Your ebooks must have ISBNs or OCLC numbers assigned.</li>
<li>Your metadata should have title, authors, language, description.</li>
<li>If you have a lot of books to enter, <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">contact us</a> to load ONIX or CSV files for you.</li>
</ul>
<br /><br />If you expect to see a "Claim" button and do not, either we do not have a PSA from you yet, or we have not yet verified and filed it. Please contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</dd>
<dt>Why should I claim my works?</dt>

View File

@ -4,6 +4,6 @@
{% if work.num_wishes %}
<a href="{% if workid %}{% url 'work' workid %}{% else %}{% url 'googlebooks' googlebooks_id %}{% endif %}?tab=3" class="nobold"><span class="rounded"><span class="grey"><span class="">Faved by&nbsp;</span>{{ work.num_wishes }}</span></span></a>
{% else %}
<a href="{% if workid %}{% url 'work' workid %}{% else %}{% url 'googlebooks' googlebooks_id %}{% endif %}?tab=3" class="nobold"><span class="rounded"><span class="grey"><span class="">No Faves</span></span></a>
<a href="{% if workid %}{% url 'work' workid %}{% else %}{% url 'googlebooks' googlebooks_id %}{% endif %}?tab=3" class="nobold"><span class="rounded"><span class="grey">No Faves</span></span></a>
{% endif %}
{% endif %}

View File

@ -106,7 +106,7 @@ $j(document).ready(function() {
loader: '<img src="/static/images/loading.gif" alt="loading..." />',
callback: function(p) {
page += 1;
var url = "?q={{ q }}&gbo={{ gbo }}&page=" + page;
var url = "?q={{ q|urlencode }}&gbo={{ gbo }}&ty={{ ty }}&page=" + page;
$j.get(url, function(html) {
var view = $j(".listview").length > 0 ? "list" : "panel";
var results = $j(html).find(".book");
@ -133,17 +133,17 @@ $j(document).ready(function() {
<div id="content-block">
{% if campaign_works %}
<div class="content-block-heading">
<h2 class="content-heading">These books are Free, or want to be!</span></h2>
<h2 class="content-heading">These books are Free, or want to be!</h2>
<ul class="book-list-view">
<li>View As:</li>
<li class="view-list">
<a href="#" id="toggle-list">
<img src="/static/images/booklist/view-list.png" align="view list" title="view list" height="21" width="24" alt="use list view" />
<img src="/static/images/booklist/view-list.png" title="view list" height="21" width="24" alt="use list view" />
</a>
</li>
<li class="view-list">
<a href="#" id="toggle-panel">
<img src="/static/images/booklist/view-icon.png" align="view icon" title="view icon" height="22" width="22" alt="use panel view" />
<img src="/static/images/booklist/view-icon.png" title="view icon" height="22" width="22" alt="use panel view" />
</a>
</li>
</ul>
@ -167,7 +167,7 @@ $j(document).ready(function() {
{% else %}
<div class="content-block-heading">
<h2 class="content-heading"><a href="https://www.google.com/search?q={{q }}&amp;tbm=bks">Google Books</a> search results</span></h2>
<h2 class="content-heading"><a href="https://www.google.com/search?q={{q|urlencode }}&amp;tbm=bks">Google Books</a> search results</h2>
{% if not campaign_works %}
<ul class="book-list-view">

View File

@ -21,7 +21,8 @@
<dl>
{% for landing in work.landings.all %}
<dt>Configured survey: {{ landing }} </dt>
<dd>Link: {{ landing.url }}</dd>
<dd>Link: {{ landing.url }}<br />
Completed {{ landing.runinfohistory_set.all.count }} times</dd>
{% endfor %}
</dl>
<a href="{% url 'new_survey' work.id %}">Set up a new survey</a> for this work.
@ -32,5 +33,10 @@
<dd>To run a survey for a book, you need to be an unglue.it rights holder and claim the book</dd>
{% endfor %}
</dl>
<p>
<a href="{% url 'new_survey' '' %}">Set up a survey</a> using isbn.
</p>
<p>
Add "?next=https://example.com/any_url" to the end of a survey url to add a redirect on completion of the survey.
</p>
{% endblock %}

View File

@ -96,9 +96,9 @@
<div>
<div class="pubinfo">
<h3 class="book-author">
<span itemprop="author">{{ work.relators.0.name }}</span>{% ifequal work.authors.count 2 %}
and <span itemprop="author">{{ work.relators.1.name }}</span>
{% endifequal %}{% if work.relators.count > 2 %}{% for author in work.relators %}{% if not forloop.first %}, <span itemprop="author">{{ author.name }}</span>{% endif %}{% endfor %}
<span itemprop="author"><a href="{% url 'search' %}?q={{ work.relators.0.author.name|urlencode }}&amp;ty=au" />{{ work.relators.0.name }}</a></span>{% ifequal work.authors.count 2 %}
and <span itemprop="author"><a href="{% url 'search' %}?q={{ work.relators.1.author.name|urlencode }}&amp;ty=au" />{{ work.relators.1.name }}</a></span>
{% endifequal %}{% if work.relators.count > 2 %}{% for author in work.relators %}{% if not forloop.first %}, <span itemprop="author"><a href="{% url 'search' %}?q={{ author.author.name|urlencode }}&amp;ty=au" />{{ author.name }}</a></span>{% endif %}{% endfor %}
{% endif %}
</h3>
<h3 class="book-year">

View File

@ -2218,14 +2218,21 @@ class ManageAccount(FormView):
def search(request):
q = request.GET.get('q', '')
ty = request.GET.get('ty', 'g') # ge= 'general, au= 'author'
request.session['q']=q
page = int(request.GET.get('page', 1))
try:
page = int(request.GET.get('page', 1))
except ValueError:
# garbage in page
page = 1
gbo = request.GET.get('gbo', 'n') # gbo is flag for google books only
our_stuff = Q(is_free=True) | Q(campaigns__isnull=False )
if q != '' and page==1 and not gbo=='y':
isbnq = ISBN(q)
if isbnq.valid:
work_query = Q(identifiers__value=str(isbnq), identifiers__type="isbn")
elif ty=='au':
work_query = Q(editions__authors__name=q)
else:
work_query = Q(title__icontains=q) | Q(editions__authors__name__icontains=q) | Q(subjects__name__iexact=q)
campaign_works = models.Work.objects.filter(our_stuff).filter(work_query).distinct()
@ -2252,6 +2259,7 @@ def search(request):
context = {
"q": q,
"gbo": gbo,
"ty": ty,
"results": works,
"campaign_works": campaign_works
}

View File

@ -6,7 +6,7 @@ from .. import add_type, question_proc, answer_proc, AnswerException
from ..utils import get_runid_from_request
@question_proc('choice', 'choice-freeform', 'dropdown')
@question_proc('choice', 'choice-freeform', 'dropdown', 'choice-optional', 'choice-freeform-optional')
def question_choice(request, question):
choices = []
jstriggers = []
@ -27,22 +27,25 @@ def question_choice(request, question):
for choice in question.choices():
choices.append( ( choice.value == val, choice, ) )
if question.type == 'choice-freeform':
if question.type in ( 'choice-freeform','choice-freeform-optional'):
jstriggers.append('%s_comment' % question.number)
template = question.type[:-9] if question.type.endswith('-optional') else question.type
return {
'choices' : choices,
'sel_entry' : val == '_entry_',
'qvalue' : val or '',
'required' : True,
"template" : "questionnaire/{}.html".format(template),
'required' : not question.type in ( 'choice-optional', 'choice-freeform-optional'),
'comment' : request.POST.get(key2, ""),
'jstriggers': jstriggers,
}
@answer_proc('choice', 'choice-freeform', 'dropdown')
@answer_proc('choice', 'choice-freeform', 'dropdown', 'choice-optional', 'choice-freeform-optional')
def process_choice(question, answer):
opt = answer['ANSWER'] or ''
if not opt:
if not opt and not question.type.endswith( '-optional'):
raise AnswerException(_(u'You must select an option'))
if opt == '_entry_' and question.type == 'choice-freeform':
opt = answer.get('comment','')
@ -51,11 +54,13 @@ def process_choice(question, answer):
return dumps([[opt]])
else:
valid = [c.value for c in question.choices()]
if opt not in valid:
if opt and opt not in valid:
raise AnswerException(_(u'Invalid option!'))
return dumps([opt])
add_type('choice', 'Choice [radio]')
add_type('choice-freeform', 'Choice with a freeform option [radio]')
add_type('choice-optional', 'Optional choice [radio]')
add_type('choice-freeform-optional', 'Optional choice with a freeform option [radio]')
add_type('dropdown', 'Dropdown choice [select]')
@question_proc('choice-multiple', 'choice-multiple-freeform', 'choice-multiple-values')

View File

@ -8,7 +8,7 @@ from ..utils import get_runid_from_request
#true if either 'required' or if 'requiredif' is satisfied
#def is_required
@question_proc('choice-yesno', 'choice-yesnocomment', 'choice-yesnodontknow')
@question_proc('choice-yesno', 'choice-yesnocomment', 'choice-yesnodontknow','choice-yesno-optional', 'choice-yesnocomment-optional', 'choice-yesnodontknow-optional')
def question_yesno(request, question):
key = "question_%s" % question.number
key2 = "question_%s_comment" % question.number
@ -18,11 +18,11 @@ def question_yesno(request, question):
cd = question.getcheckdict()
jstriggers = []
if qtype == 'choice-yesnocomment':
if qtype.startswith('choice-yesnocomment'):
hascomment = True
else:
hascomment = False
if qtype == 'choice-yesnodontknow' or 'dontknow' in cd:
if qtype.startswith( 'choice-yesnodontknow') or 'dontknow' in cd:
hasdontknow = True
else:
hasdontknow = False
@ -50,7 +50,7 @@ def question_yesno(request, question):
checks = ' checks="dep_check(\'%s,dontknow\')"' % question.number
return {
'required': True,
'required': not qtype.endswith("-optional"),
'checks': checks,
'value': val,
'qvalue': val,
@ -82,14 +82,14 @@ def question_open(request, question):
}
@answer_proc('open', 'open-textfield', 'choice-yesno', 'choice-yesnocomment', 'choice-yesnodontknow')
@answer_proc('open', 'open-textfield', 'choice-yesno', 'choice-yesnocomment', 'choice-yesnodontknow','choice-yesno-optional', 'choice-yesnocomment-optional', 'choice-yesnodontknow-optional')
def process_simple(question, ansdict):
# print 'process_simple has question, ansdict ', question, ',', ansdict
checkdict = question.getcheckdict()
ans = ansdict['ANSWER'] or ''
qtype = question.get_type()
if qtype.startswith('choice-yesno'):
if ans not in ('yes', 'no', 'dontknow'):
if ans not in ('yes', 'no', 'dontknow') and not qtype.endswith('-optional'):
raise AnswerException(_(u'You must select an option'))
if qtype == 'choice-yesnocomment' \
and len(ansdict.get('comment', '').strip()) == 0:
@ -121,6 +121,9 @@ add_type('open-textfield', 'Open Answer, multi-line [textarea]')
add_type('choice-yesno', 'Yes/No Choice [radio]')
add_type('choice-yesnocomment', 'Yes/No Choice with optional comment [radio, input]')
add_type('choice-yesnodontknow', 'Yes/No/Don\'t know Choice [radio]')
add_type('choice-yesno-optional', 'Optional Yes/No Choice [radio]')
add_type('choice-yesnocomment-optional', 'Optional Yes/No Choice with optional comment [radio, input]')
add_type('choice-yesnodontknow-optional', 'Optional Yes/No/Don\'t know Choice [radio]')
@answer_proc('comment')

File diff suppressed because one or more lines are too long

View File

@ -22,14 +22,14 @@
}
.questionset-text p{
font-size: 1.3em;
line-height: 1.3em;
font-size: 1.2em;
line-height: 1.2em;
}
.question-text {
font-size: 1.3em;
line-height: 1.3em;
font-size: 1.2em;
line-height: 1.2em;
}
div.input {

View File

@ -6,8 +6,8 @@
<title>{% block title %}Questionnaire{% endblock title %}</title>
<link rel="stylesheet" href="/static/bootstrap/bootstrap.min.css" type="text/css"/>
<link rel="stylesheet" href="/static/questionnaire.css"></script>
<link rel="stylesheet" href="/static/bootstrap/bootstrap.min.css" type="text/css" />
<link rel="stylesheet" href="/static/questionnaire.css" />
<style type="text/css">
{% block styleextra %}

View File

@ -1,7 +1,7 @@
{% load i18n %}
<div class="clearfix">
<div class="input">
<ul class="inputs-list">
<ul class="inputs-list list-unstyled">
{% for sel, choice in qdict.choices %}
<li>
<label>
@ -15,9 +15,11 @@
<div class="input">
<input onClick="valchanged('{{ question.number }}', '_entry_');" type="radio" id="{{ question.number }}_entry" name="question_{{ question.number }}" value="_entry_" {% if qdict.sel_entry %} checked {% endif %}>
{% if question.extra %}
<span class="extra-block">{{ question.extra }}</span>
<label for="{{ question.number }}_entry"><span class="extra-block">{{ question.extra }}</span></label>
{% else %}
<label for="{{ question.number }}_entry">{% trans "Other..." %}</label>
{% endif %}
<input id="{{ question.number }}_comment" checks="dep_check('{{ question.number }},_entry_')" type="input" name="question_{{ question.number }}_comment" value="{{ qdict.comment }}">
<input id="{{ question.number }}_comment" checks="dep_check('{{ question.number }},_entry_')" type="text" name="question_{{ question.number }}_comment" id="{{ question.number }}_comment" value="{{ qdict.comment }}">
</div>
</div>

View File

@ -2,10 +2,10 @@
<div class="clearfix">
<div class="input">
<ul class="inputs-list">
<ul class="inputs-list list-unstyled">
{% for choice, key, checked, prev_value in qdict.choices %}
<li>
<!-- <label> -->
<label>
<span class="{{ qdict.type }}-text">
<input onClick="valchanged('{{ question.number }}_{{ choice.value }}', this.checked);" type="checkbox" id="{{ key }}" name="{{ key }}" value="{{ choice.value }}" {{ checked }}>
{{ choice.text }}
@ -18,7 +18,7 @@
&#37; <!-- percentage sign: all choice-multiple-values currently represent percentages and must add up to 100% -->
</span>
{% endif %}
<!-- </label> -->
</label>
</li>
{% endfor %}
{% if qdict.type == 'choice-multiple-values' %}
@ -29,7 +29,11 @@
{% if question.extra %}
<li>
<span class="extra-block">{{ question.extra }}</span>
<label for="{{ question.number }}extra"><span class="extra-block">{{ question.extra }}</span></label>
</li>
{% else %}
<li>
<label for="{{ question.number }}extra">{% trans "Other..." %}</label>
</li>
{% endif %}
{% if qdict.extras %}
@ -38,7 +42,7 @@
{% if not forloop.last or not forloop.first %}
<b>{{ forloop.counter }}.</b>
{% endif %}
<input type="text" name="{{ key }}" size="50" value="{{ value }}">
<input type="text" id="{{ question.number }}extra{% if not forloop.first %}{{ forloop.counter }}{% endif %}" name="{{ key }}" size="50" value="{{ value }}">
</li>
{% endfor %}
{% endif %}

View File

@ -1,7 +1,7 @@
{% load i18n %}
<div class="clearfix">
<div class="input">
<ul class="inputs-list">
<ul class="inputs-list list-unstyled">
<!-- yes -->
<li>
@ -31,12 +31,12 @@
<!-- comment -->
{% if qdict.hascomment %}
<li>
<li><label>
{% if question.extra %}
<span class="extra-block">{{ question.extra }}</span><br />
{% endif %}
<input type="text" id="{{ question.number }}_comment" name="question_{{ question.number }}_comment" value="{{ qdict.comment }}" size="50" {{ qdict.checks|safe }} placeholder="{% trans 'comment' %}">
</li>
</label></li>
{% endif %}
</ul>
</div>

View File

@ -1,7 +1,7 @@
{% load i18n %}
<div class="clearfix">
<div class="input">
<ul class="inputs-list">
<ul class="inputs-list list-unstyled">
{% for sel, choice in qdict.choices %}
<li>
<label>

View File

@ -1,7 +1,18 @@
{% extends "base-questionnaire.html" %}
{% block questionnaire %}
<h2>
Thanks for completing the survey!
</h2>
<p>{{ landing_object.claim.all.0.rights_holder }}</p>
<div class="question-text">
{{ landing_object.claim.all.0.rights_holder }}
{% if request.COOKIES.next %}
<p>redirecting in 5 seconds...</p>
<script type="text/JavaScript">
setTimeout(function(){location.replace('/next/');}, 5000);
</script>
{% endif %}
</div>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% load i18n %}
<div class="clearfix">
<div class="input">
<textarea class="span8" name="question_{{ question.number }}" cols="60" rows="10">{{ qdict.value }}</textarea>
<textarea class="span8" name="question_{{ question.number }}" cols="60" rows="10" id={{ question.number }}>{{ qdict.value }}</textarea>
{% if question.extra %}
<span class="help-block">{{ question.extra }}</span>
{% endif %}

View File

@ -3,16 +3,31 @@
{% load static %}
{% load dynamicStyleTags %}
{% load landings %}
{% block title %}
Survey: {{ questionset.heading }}
{% endblock %}
{% block headextra %}
<script type="text/javascript" src="{% static 'jquery-1.7.1.min.js' %}"></script>
<script type="text/javascript" src="{% static 'questionset.js' %}"></script>
<link rel="stylesheet" href="{% static 'progressbar.css' %}"/>
<link rel="stylesheet" href="{% static 'progressbar.css' %}" />
{% if questionsetstylesheet|getAssociatedStylesheets %}
<style type="text/css">
{{ questionsetstylesheet|getAssociatedStylesheets|safe }}
</style>
{% endif %}
{% if progress %}
{% if questionset.questionnaire.name|add:"Progress"|getAssociatedStylesheets %}
<style type="text/css">
{{ questionset.questionnaire.name|add:"Progress"|getAssociatedStylesheets|safe }}
</style>
{% else %}
<style type="text/css">
{{ "CommonProgressStyles"|getAssociatedStylesheets|safe }}
</style>
{% endif %}
{% endif %}
{% for x in jsinclude %}
<script type="text/javascript" src="{{ x }}"></script>
{% endfor %}
@ -38,15 +53,6 @@
{% block questionnaire %}
{% if progress %}
{% if questionset.questionnaire.name|add:"Progress"|getAssociatedStylesheets %}
<style type="text/css">
{{ questionset.questionnaire.name|add:"Progress"|getAssociatedStylesheets|safe }}
</style>
{% else %}
<style type="text/css">
{{ "CommonProgressStyles"|getAssociatedStylesheets|safe }}
</style>
{% endif %}
<div id="progress_bar" class="ui-progress-bar ui-container">
<div class="ui-progress" style="width: {{progress}}%;">
<span class="ui-label"><b class="value">{{progress}}%</b></span>
@ -108,12 +114,14 @@
{% include qdict.template %}
{% else %}
<div class="question-text {% if qdict.required %}required{% endif %}">
<label for="{{ question.number }}">
<span class="qnumber">{{ question.display_number|safe }}.</span>
{% if question.parse_html %}
{{ question.text|safe }}
{% else %}
{{ question.text }}
{% endif %}
</label>
</div>
<div class="answer">
{% if error %}
@ -139,7 +147,7 @@
<div style="text-align: center;" class="well questionset-submit">
<input class="btn large primary" name="submit" type="submit" value="{% trans "Continue" %}">
<input class="btn large primary" name="submit" type="submit" value="{% if questionset.next %}{% trans 'Continue' %}{% else %}{% trans 'Finish' %}{% endif %}" />
</div>

View File

@ -812,9 +812,9 @@ def _table_headers(questions):
ql.sort(lambda x, y: numal_sort(x.number, y.number))
columns = []
for q in ql:
if q.type == 'choice-yesnocomment':
if q.type.startswith('choice-yesnocomment'):
columns.extend([q.number, q.number + "-freeform"])
elif q.type == 'choice-freeform':
elif q.type.startswith('choice-freeform'):
columns.extend([q.number, q.number + "-freeform"])
elif q.type.startswith('choice-multiple'):
cl = [c.value for c in q.choice_set.all()]
@ -1063,7 +1063,9 @@ def generate_run(request, questionnaire_id, subject_id=None, context={}):
request.session['runcode'] = key
questionnaire_start.send(sender=None, runinfo=run, questionnaire=qu)
return HttpResponseRedirect(reverse('questionnaire', kwargs=kwargs))
response = HttpResponseRedirect(reverse('questionnaire', kwargs=kwargs))
response.set_cookie('next', context.get('next',''))
return response
def generate_error(request):
return 400/0
@ -1077,6 +1079,8 @@ class SurveyView(TemplateView):
nonce = self.kwargs['nonce']
landing = get_object_or_404(Landing, nonce=nonce)
context["landing"] = landing
context["next"] = self.request.GET.get('next', '')
return context

View File

@ -37,7 +37,7 @@ django-nose-selenium==0.7.3
#django-notification==0.2
git+git://github.com/eshellman/django-notification.git@8bb7afbbb07e8cad74bc1cf17e0ac6fc117c0497
django-registration==1.0
django-selectable==0.7.0
django-selectable==0.9.0
django-smtp-ssl==1.0
django-storages==1.1.6
django-tastypie==0.12.2

View File

@ -486,3 +486,7 @@ MOBIGEN_PASSWORD = "CXq5FSEQFgXtP_s"
QUESTIONNAIRE_USE_SESSION = False
QUESTIONNAIRE_DEBUG = True
# Selenium related -- set if Se tests run
FIREFOX_PATH = ''
CHROMEDRIVER_PATH = ''

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -41,6 +41,12 @@
font-weight:normal;
color:@text-blue;
}
h3.book-author span a, h3.book-year span a{
font-size: @font-size-default;
font-weight:normal;
color:@link-color;
}
> div {
width:100%;

View File

@ -11,6 +11,7 @@ from urlparse import (urlparse, urlunparse)
from selenium import selenium, webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
"""
django imports
@ -42,6 +43,21 @@ def set_test_logging():
sel.setLevel(logging.INFO)
def selenium_driver(browser='firefox'):
if browser == 'firefox':
firefox_capabilities = DesiredCapabilities.FIREFOX
firefox_capabilities['marionette'] = True
firefox_capabilities['binary'] = settings.FIREFOX_PATH
driver = webdriver.Firefox(capabilities=firefox_capabilities)
elif browser == 'htmlunit':
# HTMLUNIT with JS -- not successful
driver = webdriver.Remote("http://localhost:4444/wd/hub", webdriver.DesiredCapabilities.HTMLUNITWITHJS)
else:
driver = webdriver.Chrome(executable_path=settings.CHROMEDRIVER_PATH)
return driver
class GoogleWebDriverTest(unittest.TestCase):
@ -51,7 +67,7 @@ class GoogleWebDriverTest(unittest.TestCase):
# This is an empty array where we will store any verification errors
# we find in our tests
self.selenium = webdriver.Firefox()
self.selenium = selenium_driver(browser='firefox')
set_test_logging()
def test_google_rc(self):
@ -174,16 +190,8 @@ def test_relaunch(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=True,
# this assumes that we don't have donation functionality on
assert settings.NONPROFIT.is_on == False
if browser == 'firefox':
sel = webdriver.Firefox()
elif browser == 'chrome':
sel = webdriver.Chrome(executable_path='/Users/raymondyee/C/src/Gluejar/regluit/test/chromedriver')
elif browser == 'htmlunit':
# HTMLUNIT with JS -- not successful
sel = webdriver.Remote("http://localhost:4444/wd/hub", webdriver.DesiredCapabilities.HTMLUNITWITHJS)
else:
sel = webdriver.Firefox()
sel = selenium_driver(browser=browser)
time.sleep(5)
@ -356,7 +364,7 @@ def successful_campaign_signal():
def berkeley_search():
sel = webdriver.Firefox()
sel = selenium_driver(browser='firefox')
sel.get("http://berkeley.edu")
search = WebDriverWait(sel,5).until(lambda d: d.find_element_by_css_selector('input[id="search_text"]'))
search.send_keys("quantum computing")