Merge pull request #690 from Gluejar/edit-editions

revamp editing editions
pull/43/head
eshellman 2017-08-08 14:09:48 -04:00 committed by GitHub
commit 6bd1aec374
73 changed files with 1690 additions and 1023 deletions

View File

@ -11,7 +11,7 @@
{% if editions %}
<ul>
{% for edition in editions %}
<li> <img src="http://covers.openlibrary.org/b/isbn/{{edition.isbn_10}}-S.jpg" /> {{edition.id}} | {{edition.title}} |
<li> <img src="https://covers.openlibrary.org/b/isbn/{{edition.isbn_10}}-S.jpg" /> {{edition.id}} | {{edition.title}} |
<a href="{% url 'isbn' isbn=edition.isbn_10 %}">{{edition.isbn_10}}</a> |
<a href="{% url 'isbn' isbn=edition.isbn_13 %}">{{edition.isbn_13}}</a>
{% endfor %}

View File

@ -168,7 +168,7 @@ class ApiHelpView(TemplateView):
class OPDSNavigationView(TemplateView):
json=False
# http://stackoverflow.com/a/6867976: secret to how to change content-type
# https://stackoverflow.com/a/6867976: secret to how to change content-type
def render_to_response(self, context, **response_kwargs):
if json:

View File

@ -5,6 +5,7 @@ from django import forms
from django.contrib.admin import ModelAdmin
from django.contrib.admin import site as admin_site
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from selectable.forms import (
AutoCompleteSelectWidget,
AutoCompleteSelectField,
@ -170,12 +171,27 @@ class EbookAdmin(ModelAdmin):
exclude = ('edition','user', 'filesize')
class EbookFileAdmin(ModelAdmin):
search_fields = ('ebook__edition__title',) # search by provider using leading url
list_display = ('created', 'format', 'edition', 'asking')
search_fields = ('ebook__edition__title', 'source') # search by provider using leading url
list_display = ('created', 'format', 'ebook_link', 'asking')
date_hierarchy = 'created'
ordering = ('edition__work',)
fields = ('file', 'format','edition', 'asking', 'ebook')
readonly_fields = ('file', 'edition', 'asking', 'ebook')
fields = ('file', 'format', 'edition_link', 'ebook_link', 'source')
readonly_fields = ('file', 'edition_link', 'ebook_link', 'ebook')
def edition_link(self, obj):
if obj.edition:
link = reverse("admin:core_edition_change", args=[obj.edition.id])
return u'<a href="%s">%s</a>' % (link,obj.edition)
else:
return u''
def ebook_link(self, obj):
if obj.ebook:
link = reverse("admin:core_ebook_change", args=[obj.ebook.id])
return u'<a href="%s">%s</a>' % (link,obj.ebook)
else:
return u''
edition_link.allow_tags=True
ebook_link.allow_tags=True
class WishlistAdmin(ModelAdmin):
date_hierarchy = 'created'

View File

@ -5,36 +5,40 @@ import json
import logging
import re
import requests
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from regluit.core.validation import test_file
from datetime import timedelta
from itertools import (izip, islice)
from xml.etree import ElementTree
from urlparse import (urljoin, urlparse)
"""
django imports
"""
# django imports
from django.conf import settings
from django.core.files.base import ContentFile
from django_comments.models import Comment
from django.db import IntegrityError
from django.db.models import Q
from django.forms import ValidationError
from github3 import (login, GitHub)
from github3.repos.release import Release
from gitenberg.metadata.pandata import Pandata
from ..marc.models import inverse_marc_rels
"""
regluit imports
"""
# regluit imports
import regluit
import regluit.core.isbn
from regluit.core import models
from regluit.marc.models import inverse_marc_rels
from regluit.utils.localdatetime import now
import regluit.core.cc as cc
from . import cc
from . import models
from .parameters import WORK_IDENTIFIERS
from .validation import identifier_cleaner
from .loaders.scrape import BaseScraper
logger = logging.getLogger(__name__)
request_log = logging.getLogger("requests")
@ -75,13 +79,10 @@ def add_by_oclc_from_google(oclc):
def valid_isbn(isbn):
try:
isbn = regluit.core.isbn.ISBN(isbn)
return identifier_cleaner('isbn')(isbn)
except:
logger.exception("invalid isbn: %s", isbn)
return None
if not isbn.valid:
return None
return isbn.to_string()
def add_by_isbn(isbn, work=None, language='xx', title=''):
if not isbn:
@ -292,7 +293,7 @@ def add_by_googlebooks_id(googlebooks_id, work=None, results=None, isbn=None):
if isbn:
# check that the isbn is in db; if not, then there are two isbns for the edition
try:
isbn_edition = models.Identifier.objects.get(type='isbn', value=isbn).edition
models.Identifier.objects.get(type='isbn', value=isbn).edition
# not going to worry about isbn_edition != edition
except models.Identifier.DoesNotExist:
models.Identifier.objects.create(type='isbn', value=isbn, edition=edition, work=edition.work)
@ -479,7 +480,7 @@ def thingisbn(isbn):
Library Thing. (takes isbn_10 or isbn_13, returns isbn_10, except for 979 isbns, which come back as isbn_13')
"""
logger.info("looking up %s at ThingISBN" , isbn)
url = "http://www.librarything.com/api/thingISBN/%s" % isbn
url = "https://www.librarything.com/api/thingISBN/%s" % isbn
xml = requests.get(url, headers={"User-Agent": settings.USER_AGENT}).content
doc = ElementTree.fromstring(xml)
return [e.text for e in doc.findall('isbn')]
@ -585,12 +586,12 @@ def add_openlibrary(work, hard_refresh = False):
# find the first ISBN match in OpenLibrary
logger.info("looking up openlibrary data for work %s", work.id)
found = False
e = None # openlibrary edition json
w = None # openlibrary work json
# get the 1st openlibrary match by isbn that has an associated work
url = "http://openlibrary.org/api/books"
url = "https://openlibrary.org/api/books"
params = {"format": "json", "jscmd": "details"}
subjects = []
for edition in work.editions.all():
@ -621,7 +622,7 @@ def add_openlibrary(work, hard_refresh = False):
logger.info("got openlibrary work %s for isbn %s", work_key, isbn_key)
models.Identifier.get_or_add(type='olwk', value=work_key, work=work)
try:
w = _get_json("http://openlibrary.org" + work_key,type='ol')
w = _get_json("https://openlibrary.org" + work_key, type='ol')
if w.has_key('description'):
description = w['description']
if isinstance(description, dict):
@ -703,7 +704,7 @@ def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url
work = sister_edition.work
# attach the olwk identifier to this work if it's not none.
if ol_work_id is not None:
work_id = models.Identifier.get_or_add(type='olwk',value=ol_work_id, work=work)
models.Identifier.get_or_add(type='olwk', value=ol_work_id, work=work)
# Now pull out any existing Gutenberg editions tied to the work with the proper Gutenberg ID
try:
@ -714,7 +715,7 @@ def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url
edition.work = work
edition.save()
edition_id = models.Identifier.get_or_add(type='gtbg',value=gutenberg_etext_id, edition=edition, work=work)
models.Identifier.get_or_add(type='gtbg', value=gutenberg_etext_id, edition=edition, work=work)
# check to see whether the Edition hasn't already been loaded first
# search by url
@ -728,7 +729,7 @@ def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url
ebook = models.Ebook()
if len(ebooks) > 1:
warnings.warn("There is more than one Ebook matching url {0}".format(url))
logger.warning("There is more than one Ebook matching url {0}".format(url))
ebook.format = format
@ -743,83 +744,13 @@ def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url
return ebook
def add_missing_isbn_to_editions(max_num=None, confirm=False):
"""For each of the editions with Google Books ids, do a lookup and attach ISBNs. Set confirm to True to check db changes made correctly"""
logger.info("Number of editions with Google Books IDs but not ISBNs (before): %d",
models.Edition.objects.filter(identifiers__type='goog').exclude(identifiers__type='isbn').count())
from regluit.experimental import bookdata
gb = bookdata.GoogleBooks(key=settings.GOOGLE_BOOKS_API_KEY)
new_isbns = []
google_id_not_found = []
no_isbn_found = []
editions_to_merge = []
exceptions = []
for (i, ed) in enumerate(islice(models.Edition.objects.filter(identifiers__type='goog').exclude(identifiers__type='isbn'), max_num)):
try:
g_id = ed.identifiers.get(type='goog').value
except Exception, e:
# we might get an exception if there is, for example, more than one Google id attached to this Edition
logger.exception("add_missing_isbn_to_editions for edition.id %s: %s", ed.id, e)
exceptions.append((ed.id, e))
continue
# try to get ISBN from Google Books
try:
vol_id = gb.volumeid(g_id)
if vol_id is None:
google_id_not_found.append((ed.id, g_id))
logger.debug("g_id not found: %s", g_id)
else:
isbn = vol_id.get('isbn')
logger.info("g_id, isbn: %s %s", g_id, isbn)
if isbn is not None:
# check to see whether the isbn is actually already in the db but attached to another Edition
existing_isbn_ids = models.Identifier.objects.filter(type='isbn', value=isbn)
if len(existing_isbn_ids):
# don't try to merge editions right now, just note the need to merge
ed2 = existing_isbn_ids[0].edition
editions_to_merge.append((ed.id, g_id, isbn, ed2.id))
else:
new_id = models.Identifier(type='isbn', value=isbn, edition=ed, work=ed.work)
new_id.save()
new_isbns.append((ed.id, g_id, isbn))
else:
no_isbn_found.append((ed.id, g_id, None))
except Exception, e:
logger.exception("add_missing_isbn_to_editions for edition.id %s: %s", ed.id, e)
exceptions.append((ed.id, g_id, None, e))
logger.info("Number of editions with Google Books IDs but not ISBNs (after): %d",
models.Edition.objects.filter(identifiers__type='goog').exclude(identifiers__type='isbn').count())
ok = None
if confirm:
ok = True
for (ed_id, g_id, isbn) in new_isbns:
if models.Edition.objects.get(id=ed_id).identifiers.get(type='isbn').value != isbn:
ok = False
break
return {
'new_isbns': new_isbns,
'no_isbn_found': no_isbn_found,
'editions_to_merge': editions_to_merge,
'exceptions': exceptions,
'google_id_not_found': google_id_not_found,
'confirm': ok
}
class LookupFailure(Exception):
pass
IDTABLE = [('librarything','ltwk'),('goodreads','gdrd'),('openlibrary','olwk'),('gutenberg','gtbg'),('isbn','isbn'),('oclc','oclc'),('edition_id','edid') ]
IDTABLE = [('librarything', 'ltwk'), ('goodreads', 'gdrd'), ('openlibrary', 'olwk'),
('gutenberg', 'gtbg'), ('isbn', 'isbn'), ('oclc', 'oclc'),
('edition_id', 'edid'), ('googlebooks', 'goog'), ('doi', 'doi'),
]
def unreverse(name):
if not ',' in name:
@ -833,12 +764,70 @@ def unreverse(name):
def load_from_yaml(yaml_url, test_mode=False):
"""
This really should be called 'load_from_github_yaml'
if mock_ebook is True, don't construct list of ebooks from a release -- rather use an epub
"""
all_metadata = Pandata(yaml_url)
loader = GithubLoader(yaml_url)
for metadata in all_metadata.get_edition_list():
edition = loader.load_from_pandata(metadata)
loader.load_ebooks(metadata, edition, test_mode)
return edition.work.id if edition else None
def edition_for_ident(id_type, id_value):
print 'returning edition for {}: {}'.format(id_type, id_value)
for ident in models.Identifier.objects.filter(type=id_type, value=id_value):
return ident.edition if ident.edition else ident.work.editions[0]
def edition_for_etype(etype, metadata, default=None):
'''
assumes the metadata contains the isbn_etype attributes, and that the editions have been created.
etype is 'epub', 'pdf', etc.
'''
isbn = metadata.identifiers.get('isbn_{}'.format(etype), None)
if not isbn:
isbn = metadata.identifiers.get('isbn_electronic', None)
if isbn:
return edition_for_ident('isbn', isbn)
else:
if default:
return default
# just return some edition
for key in metadata.identifiers.keys():
return edition_for_ident(key, metadata.identifiers[key])
for key in metadata.edition_identifiers.keys():
return edition_for_ident(key, metadata.identifiers[key])
MATCH_LICENSE = re.compile(r'creativecommons.org/licenses/([^/]+)/')
def load_ebookfile(url, etype):
'''
return a ContentFile if a new ebook has been loaded
'''
ebfs = models.EbookFile.objects.filter(source=url)
if ebfs:
return None
try:
r = requests.get(url)
contentfile = ContentFile(r.content)
test_file(contentfile, etype)
return contentfile
except IOError, e:
logger.error(u'could not open {}'.format(url))
except ValidationError, e:
logger.error(u'downloaded {} was not a valid {}'.format(url, etype))
class BasePandataLoader(object):
def __init__(self, url):
self.base_url = url
def load_from_pandata(self, metadata, work=None):
''' metadata is a Pandata object'''
#find an work to associate
work = edition = None
edition = None
has_ed_id = False
if metadata.url:
new_ids = [('http', 'http', metadata.url)]
else:
@ -846,9 +835,12 @@ def load_from_yaml(yaml_url, test_mode=False):
for (identifier, id_code) in IDTABLE:
# note that the work chosen is the last associated
value = metadata.edition_identifiers.get(identifier, None)
value = identifier_cleaner(id_code)(value)
if not value:
value = metadata.identifiers.get(identifier, None)
if value:
if id_code not in WORK_IDENTIFIERS:
has_ed_id = True
value = value[0] if isinstance(value, list) else value
try:
id = models.Identifier.objects.get(type=id_code, value=value)
@ -856,6 +848,8 @@ def load_from_yaml(yaml_url, test_mode=False):
if id.edition and not edition:
edition = id.edition
except models.Identifier.DoesNotExist:
if id_code != 'edid' or not has_ed_id: #last in loop
# only need to create edid if there is no edition id for the edition
new_ids.append((identifier, id_code, value))
@ -864,12 +858,13 @@ def load_from_yaml(yaml_url, test_mode=False):
if not edition:
edition = models.Edition.objects.create(title=metadata.title, work=work)
for (identifier, id_code, value) in new_ids:
models.Identifier.set(type=id_code, value=value, edition=edition if id_code in ('isbn', 'oclc','edid') else None, work=work)
models.Identifier.set(type=id_code, value=value, edition=edition if id_code not in WORK_IDENTIFIERS else None, work=work)
if metadata.publisher: #always believe yaml
edition.set_publisher(metadata.publisher)
if metadata.publication_date: #always believe yaml
edition.publication_date = metadata.publication_date
if metadata.description and len(metadata.description)>len(work.description): #be careful about overwriting the work description
if metadata.description and len(metadata.description) > len(work.description):
#be careful about overwriting the work description
work.description = metadata.description
if metadata.creator: #always believe yaml
edition.authors.clear()
@ -894,14 +889,56 @@ def load_from_yaml(yaml_url, test_mode=False):
# the default edition uses the first cover in covers.
for cover in metadata.covers:
if cover.get('image_path', False):
edition.cover_image=urljoin(yaml_url,cover['image_path'])
edition.cover_image = urljoin(self.base_url, cover['image_path'])
break
elif cover.get('image_url', False):
edition.cover_image = cover['image_url']
break
work.save()
edition.save()
return edition
def load_ebooks(self, metadata, edition, test_mode=False, user=None):
default_edition = edition
for key in ['epub', 'pdf', 'mobi']:
url = metadata.metadata.get('download_url_{}'.format(key), None)
if url:
edition = edition_for_etype(key, metadata, default=default_edition)
if edition:
contentfile = load_ebookfile(url, key)
if contentfile:
contentfile_name = '/loaded/ebook_{}.{}'.format(edition.id, key)
path = default_storage.save(contentfile_name, contentfile)
lic = MATCH_LICENSE.search(metadata.rights_url)
license = 'CC {}'.format(lic.group(1).upper()) if lic else ''
ebf = models.EbookFile.objects.create(
format=key,
edition=edition,
source=url,
)
ebf.file.save(contentfile_name, contentfile)
ebf.file.close()
ebook = models.Ebook.objects.create(
url=ebf.file.url,
provider='Unglue.it',
rights=license,
format=key,
edition=edition,
filesize=contentfile.size,
active=False,
user=user,
)
ebf.ebook = ebook
ebf.save()
class GithubLoader(BasePandataLoader):
def load_ebooks(self, metadata, edition, test_mode=False):
# create Ebook for any ebook in the corresponding GitHub release
# assuming yaml_url of form (from GitHub, though not necessarily GITenberg)
# https://github.com/GITenberg/Adventures-of-Huckleberry-Finn_76/raw/master/metadata.yaml
url_path = urlparse(yaml_url).path.split("/")
url_path = urlparse(self.base_url).path.split("/")
(repo_owner, repo_name) = (url_path[1], url_path[2])
repo_tag = metadata._version
# allow for there not to be a token in the settings
@ -921,8 +958,12 @@ def load_from_yaml(yaml_url, test_mode=False):
for (ebook_format, ebook_name) in ebooks_in_release:
(book_name_prefix, _ ) = re.search(r'(.*)\.([^\.]*)$', ebook_name).groups()
(ebook, created) = models.Ebook.objects.get_or_create(
url=git_download_from_yaml_url(yaml_url,metadata._version,edition_name=book_name_prefix,
format_= ebook_format),
url=git_download_from_yaml_url(
self.base_url,
metadata._version,
edition_name=book_name_prefix,
format_= ebook_format
),
provider='Github',
rights=cc.match_license(metadata.rights),
format=ebook_format,
@ -930,7 +971,6 @@ def load_from_yaml(yaml_url, test_mode=False):
)
ebook.set_version(metadata._version)
return work.id
def git_download_from_yaml_url(yaml_url, version, edition_name='book', format_='epub'):
# go from https://github.com/GITenberg/Adventures-of-Huckleberry-Finn_76/raw/master/metadata.yaml
@ -954,9 +994,8 @@ def release_from_tag(repo, tag_name):
url = repo._build_url('releases', 'tags', tag_name,
base_url=repo._api)
json = repo._json(repo._get(url), 200)
return Release(json, repo) if json else None
json_obj = repo._json(repo._get(url), 200)
return Release(json_obj, repo) if json_obj else None
def ebooks_in_github_release(repo_owner, repo_name, tag, token=None):
"""
@ -980,3 +1019,18 @@ def ebooks_in_github_release(repo_owner, repo_name, tag, token=None):
return [(EBOOK_FORMATS.get(asset.content_type), asset.name)
for asset in release.iter_assets()
if EBOOK_FORMATS.get(asset.content_type) is not None]
def add_by_webpage(url, work=None, user=None):
edition = None
scraper = BaseScraper(url)
loader = BasePandataLoader(url)
pandata = Pandata()
pandata.metadata = scraper.metadata
for metadata in pandata.get_edition_list():
edition = loader.load_from_pandata(metadata, work)
work = edition.work
loader.load_ebooks(pandata, edition, user=user)
return edition if edition else None

View File

@ -5,13 +5,13 @@
## need to add versioned CC entries
INFO_CC = (
('CC BY-NC-ND', 'by-nc-nd', 'Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0)', 'http://creativecommons.org/licenses/by-nc-nd/3.0/', 'Creative Commons Attribution-NonCommercial-NoDerivs'),
('CC BY-NC-SA', 'by-nc-sa', 'Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)', 'http://creativecommons.org/licenses/by-nc-sa/3.0/', 'Creative Commons Attribution-NonCommercial-ShareAlike'),
('CC BY-NC', 'by-nc', 'Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)', 'http://creativecommons.org/licenses/by-nc/3.0/', 'Creative Commons Attribution-NonCommercial'),
('CC BY-ND', 'by-nd', 'Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)', 'http://creativecommons.org/licenses/by-nd/3.0/','Creative Commons Attribution-NoDerivs'),
('CC BY-SA', 'by-sa', 'Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)', 'http://creativecommons.org/licenses/by-sa/3.0/', 'Creative Commons Attribution-ShareAlike'),
('CC BY', 'by', 'Creative Commons Attribution 3.0 Unported (CC BY 3.0)', 'http://creativecommons.org/licenses/by/3.0/', 'Creative Commons Attribution'),
('CC0', 'cc0', 'No Rights Reserved (CC0)', 'http://creativecommons.org/about/cc0', 'No Rights Reserved (CC0)'),
('CC BY-NC-ND', 'by-nc-nd', 'Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0)', 'https://creativecommons.org/licenses/by-nc-nd/3.0/', 'Creative Commons Attribution-NonCommercial-NoDerivs'),
('CC BY-NC-SA', 'by-nc-sa', 'Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)', 'https://creativecommons.org/licenses/by-nc-sa/3.0/', 'Creative Commons Attribution-NonCommercial-ShareAlike'),
('CC BY-NC', 'by-nc', 'Creative Commons Attribution-NonCommercial 3.0 Unported (CC BY-NC 3.0)', 'https://creativecommons.org/licenses/by-nc/3.0/', 'Creative Commons Attribution-NonCommercial'),
('CC BY-ND', 'by-nd', 'Creative Commons Attribution-NoDerivs 3.0 Unported (CC BY-ND 3.0)', 'https://creativecommons.org/licenses/by-nd/3.0/','Creative Commons Attribution-NoDerivs'),
('CC BY-SA', 'by-sa', 'Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)', 'https://creativecommons.org/licenses/by-sa/3.0/', 'Creative Commons Attribution-ShareAlike'),
('CC BY', 'by', 'Creative Commons Attribution 3.0 Unported (CC BY 3.0)', 'https://creativecommons.org/licenses/by/3.0/', 'Creative Commons Attribution'),
('CC0', 'cc0', 'No Rights Reserved (CC0)', 'https://creativecommons.org/about/cc0', 'No Rights Reserved (CC0)'),
)
INFO_FREE = INFO_CC + (
('GFDL', 'gdfl', 'GNU Free Documentation License', 'http://www.gnu.org/licenses/fdl-1.3-standalone.html', 'GNU Free Documentation License'),
@ -19,7 +19,7 @@ INFO_FREE = INFO_CC + (
('OSI', 'opensource', 'OSI Approved License', 'https://opensource.org/licenses', 'OSI Approved License'),
)
INFO_PD = (
('PD-US', 'pd-us', 'Public Domain, US', 'http://creativecommons.org/about/pdm', 'Public Domain, US'),
('PD-US', 'pd-us', 'Public Domain, US', 'https://creativecommons.org/about/pdm', 'Public Domain, US'),
)
INFO_ALL = INFO_FREE + INFO_PD
# CCHOICES, CCGRANTS, and FORMATS are all used in places that expect tuples

View File

@ -58,7 +58,7 @@ def safe_strip(a_string):
class GoodreadsClient(object):
url = 'http://www.goodreads.com'
url = 'https://www.goodreads.com'
request_token_url = urljoin(url,'oauth/request_token')
authorize_url = urljoin(url, '/oauth/authorize')
access_token_url = urljoin(url,'/oauth/access_token')

View File

@ -1,6 +1,7 @@
# encoding: utf-8
## {{{ http://code.activestate.com/recipes/498104/ (r1)
## also http://stackoverflow.com/questions/4047511/checking-if-an-isbn-number-is-correct
## also https://stackoverflow.com/questions/4047511/checking-if-an-isbn-number-is-correct
import re
@ -44,12 +45,16 @@ def convert_10_to_13(isbn):
except:
return None
ISBN_REGEX = re.compile(r'^(\d{9}|\d{12})(\d|X)$')
DASH_REGEX = re.compile(r'[ \-–—]+')
def strip(s):
"""Strips away any - or spaces. If the remaining string is of length 10 or 13 with digits only in anything but the last
check digit (which may be X), then return '' -- otherwise return the remaining string"""
"""Strips away any - or spaces. If the remaining string is of length 10 or 13
with digits only in anything but the last
check digit (which may be X), then return '' -- otherwise return the remaining string
"""
try:
s = s.replace("-", "").replace(" ", "").upper()
match = re.search(r'^(\d{9}|\d{12})(\d|X)$', s)
s = DASH_REGEX.sub('', s).upper()
match = ISBN_REGEX.search(s)
except:
return None
@ -105,7 +110,8 @@ class ISBN(object):
self.__valid_13 = self.__isbn13
elif len(stripped_isbn) == 13:
# Assume ISBN 13 all have to begin with 978 or 979 and only 978 ISBNs can possibly have ISBN-10 counterpart
# Assume ISBN 13 all have to begin with 978 or 979 and only 978 ISBNs
# can possibly have ISBN-10 counterpart
self.__type = '13'
self.__isbn13 = stripped_isbn
@ -177,7 +183,9 @@ class ISBN(object):
else:
try:
other_isbn = ISBN(other)
if (self.valid and other_isbn.valid) and (self.to_string('13') == other_isbn.to_string('13')):
if ((self.valid and other_isbn.valid) and
(self.to_string('13') == other_isbn.to_string('13'))
):
return True
else:
return False

View File

@ -19,7 +19,7 @@ class LibraryThing(object):
This class retrieves and parses the CSV representation of a LibraryThing user's library.
"""
url = "https://www.librarything.com"
csv_file_url = "http://www.librarything.com/export-csv"
csv_file_url = "https://www.librarything.com/export-csv"
def __init__(self, username=None, password=None):
self.username = username
@ -160,7 +160,7 @@ class LibraryThing(object):
cookies = r.cookies
while next_page:
url = "http://www.librarything.com/catalog_bottom.php?view=%s&viewstyle=%d&collection=%d&offset=%d" % (self.username,
url = "https://www.librarything.com/catalog_bottom.php?view=%s&viewstyle=%d&collection=%d&offset=%d" % (self.username,
view_style, COLLECTION, offset)
logger.info("url: %s", url)
if cookies is None:

182
core/loaders/scrape.py Normal file
View File

@ -0,0 +1,182 @@
import re
import logging
import requests
from bs4 import BeautifulSoup
#from gitenberg.metadata.pandata import Pandata
from django.conf import settings
from urlparse import urljoin
from regluit.core import models
from regluit.core.validation import identifier_cleaner
logger = logging.getLogger(__name__)
CONTAINS_COVER = re.compile('cover')
CONTAINS_CC = re.compile('creativecommons.org')
class BaseScraper(object):
def __init__(self, url):
self.metadata = {}
self.identifiers = {'http': url}
self.doc = None
self.base = url
try:
response = requests.get(url, headers={"User-Agent": settings.USER_AGENT})
if response.status_code == 200:
self.doc = BeautifulSoup(response.content, 'lxml')
self.get_title()
self.get_language()
self.get_description()
self.get_identifiers()
self.get_keywords()
self.get_publisher()
self.get_pubdate()
self.get_authors()
self.get_cover()
self.get_downloads()
self.get_license()
if not self.metadata.get('title', None):
self.set('title', '!!! missing title !!!')
if not self.metadata.get('language', None):
self.set('language', 'en')
except requests.exceptions.RequestException as e:
logger.error(e)
self.metadata = None
self.metadata['identifiers'] = self.identifiers
def set(self, name, value):
self.metadata[name] = value
def fetch_one_el_content(self, el_name):
data_el = self.doc.find(el_name)
value = ''
if data_el:
value = data_el.text
return value
def check_metas(self, meta_list, **attrs):
value = ''
list_mode = attrs.pop('list_mode', 'longest')
for meta_name in meta_list:
attrs['name'] = meta_name
metas = self.doc.find_all('meta', attrs=attrs)
for meta in metas:
el_value = meta.get('content', '').strip()
if list_mode == 'longest':
if len(el_value) > len (value):
value = el_value
elif list_mode == 'list':
if value == '':
value = [el_value]
else:
value.append(el_value)
if value:
return value
return value
def get_title(self):
value = self.check_metas(['DC.Title','dc.title', 'citation_title', 'title'])
if not value:
value = self.fetch_one_el_content('title')
self.set('title', value)
def get_language(self):
value = self.check_metas(['DC.Language','dc.language','language'])
self.set('language', value)
def get_description(self):
value = self.check_metas(['DC.Description','dc.description','description'])
self.set('description', value)
def get_identifiers(self):
value = self.check_metas(['DC.Identifier.URI'])
value = identifier_cleaner('http')(value)
if value:
self.identifiers['http'] = value
value = self.check_metas(['DC.Identifier.DOI', 'citation_doi'])
value = identifier_cleaner('doi')(value)
if value:
self.identifiers['doi'] = value
isbns = {}
label_map = {'epub': 'EPUB', 'mobi': 'Mobi', 'paper':
'Paperback', 'pdf': 'PDF', 'hard':'Hardback'}
for key in label_map.keys():
isbn_key = 'isbn_{}'.format(key)
value = self.check_metas(['citation_isbn'], type=label_map[key])
value = identifier_cleaner('isbn')(value)
if value:
isbns[isbn_key] = value
ed_list = []
if len(isbns):
#need to create edition list
for key in isbns.keys():
isbn_type = key.split('_')[-1]
ed_list.append({
'edition': isbn_type,
'edition_identifiers': {'isbn': isbns[key]}
})
else:
value = self.check_metas(['citation_isbn'], list_mode='list')
if len(value):
for isbn in value:
isbn = identifier_cleaner('isbn')(isbn)
if isbn:
ed_list.append({
'edition': isbn,
'edition_identifiers': {'isbn': isbn}
})
if len(ed_list):
self.set('edition_list', ed_list)
def get_keywords(self):
value = self.check_metas(['keywords']).strip(',;')
if value:
self.set('subjects', re.split(' *[;,] *', value))
def get_publisher(self):
value = self.check_metas(['citation_publisher', 'DC.Source'])
if value:
self.set('publisher', value)
def get_pubdate(self):
value = self.check_metas(['citation_publication_date', 'DC.Date.issued'])
if value:
self.set('publication_date', value)
def get_authors(self):
value_list = self.check_metas(['DC.Creator.PersonalName', 'citation_author',], list_mode='list')
if not value_list:
return
if len(value_list) == 1:
creator = {'author': {'agent_name': value_list[0]}}
else:
creator_list = []
for auth in value_list:
creator_list.append({'agent_name': auth})
creator = {'authors': creator_list }
self.set('creator', creator)
def get_cover(self):
block = self.doc.find(class_=CONTAINS_COVER)
block = block if block else self.doc
img = block.find_all('img', src=CONTAINS_COVER)
if img:
cover_uri = img[0].get('src', None)
if cover_uri:
self.set('covers', [{'image_url': urljoin(self.base, cover_uri)}])
def get_downloads(self):
for dl_type in ['epub', 'mobi', 'pdf']:
dl_meta = 'citation_{}_url'.format(dl_type)
value = self.check_metas([dl_meta])
if value:
self.set('download_url_{}'.format(dl_type), value)
def get_license(self):
'''only looks for cc licenses'''
links = self.doc.find_all(href=CONTAINS_CC)
for link in links:
self.set('rights_url', link['href'])

View File

@ -23,7 +23,7 @@ def utf8_general_ci_norm(s):
Normalize a la MySQL utf8_general_ci collation
(As of 2016.05.24, we're using the utf8_general_ci collation for author names)
http://stackoverflow.com/questions/1036454/what-are-the-diffrences-between-utf8-general-ci-and-utf8-unicode-ci/1036459#1036459
https://stackoverflow.com/questions/1036454/what-are-the-diffrences-between-utf8-general-ci-and-utf8-unicode-ci/1036459#1036459
* converts to Unicode normalization form D for canonical decomposition
* removes any combining characters
@ -331,3 +331,24 @@ def loaded_book_ok(book, work, edition):
return True
ID_URLPATTERNS = {
'goog': re.compile(r'[\./]google\.com/books\?.*id=([a-zA-Z0-9\-_]{12})'),
'olwk': re.compile(r'[\./]openlibrary\.org(/works/OL\d{1,8}W)'),
'gdrd': re.compile(r'[\./]goodreads\.com/book/show/(\d{1,8})'),
'ltwk': re.compile(r'[\./]librarything\.com/work/(\d{1,8})'),
'oclc': re.compile(r'\.worldcat\.org/.*oclc/(\d{8,12})'),
'doi': re.compile(r'[\./]doi\.org/(10\.\d+/\S+)'),
'gtbg': re.compile(r'[\./]gutenberg\.org/ebooks/(\d{1,6})'),
'glue': re.compile(r'[\./]unglue\.it/work/(\d{1,7})'),
}
def ids_from_urls(url):
ids = {}
for ident in ID_URLPATTERNS.keys():
id_match = ID_URLPATTERNS[ident].search(url)
if id_match:
ids[ident] = id_match.group(1)
return ids

View File

@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand
from regluit.core.models import Subject
#http://stackoverflow.com/questions/8733233/filtering-out-certain-bytes-in-python
#https://stackoverflow.com/questions/8733233/filtering-out-certain-bytes-in-python
def valid_xml_char_ordinal(c):
codepoint = ord(c)

View File

@ -1,7 +1,7 @@
<div class="launch_top" id="degruyter_countdown" style="font-size:20px;text-align:center;width:50%"></div>
<h4>Help us unglue this book!</h4>
<p>De Gruyter has agreed to run an ungluing campaign for this book, if it can get enough support from ungluers like you. The target price will be $2100, after which the book will be free for everyone on earth to read, copy, and share, forever (under a Creative Commons <a href="http://creativecommons.org/licenses/by-nc-nd/3.0/">BY-NC-ND</a> license).</p>
<p>De Gruyter has agreed to run an ungluing campaign for this book, if it can get enough support from ungluers like you. The target price will be $2100, after which the book will be free for everyone on earth to read, copy, and share, forever (under a Creative Commons <a href="https://creativecommons.org/licenses/by-nc-nd/3.0/">BY-NC-ND</a> license).</p>
<p>They'll launch a campaign when 50 ungluers have wished for this book. Right now <span id="wisher_data"></span>. </p>

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_auto_20161109_1448'),
]
operations = [
migrations.AddField(
model_name='ebookfile',
name='source',
field=models.URLField(null=True, blank=True),
),
migrations.AlterField(
model_name='campaign',
name='status',
field=models.CharField(default=b'INITIALIZED', max_length=15, null=True, db_index=True, choices=[(b'INITIALIZED', b'INITIALIZED'), (b'ACTIVE', b'ACTIVE'), (b'SUSPENDED', b'SUSPENDED'), (b'WITHDRAWN', b'WITHDRAWN'), (b'SUCCESSFUL', b'SUCCESSFUL'), (b'UNSUCCESSFUL', b'UNSUCCESSFUL')]),
),
]

View File

@ -477,7 +477,7 @@ class Campaign(models.Model):
# copy custom premiums
new_premiums = self.premiums.filter(type='CU')
# setting pk to None will insert new copy http://stackoverflow.com/a/4736172/7782
# setting pk to None will insert new copy https://stackoverflow.com/a/4736172/7782
self.pk = None
self.status = 'INITIALIZED'

View File

@ -34,6 +34,7 @@ from regluit.core.parameters import (
AGE_LEVEL_CHOICES,
BORROWED,
BUY2UNGLUE,
ID_CHOICES_MAP,
INDIVIDUAL,
LIBRARY,
OFFER_CHOICES,
@ -41,9 +42,10 @@ from regluit.core.parameters import (
TEXT_RELATION_CHOICES,
THANKED,
THANKS,
WORK_IDENTIFIERS,
)
# fix truncated file problems per http://stackoverflow.com/questions/12984426/python-pil-ioerror-image-file-truncated-with-big-images
# fix truncated file problems per https://stackoverflow.com/questions/12984426/python-pil-ioerror-image-file-truncated-with-big-images
ImageFile.LOAD_TRUNCATED_IMAGES = True
logger = logging.getLogger(__name__)
@ -57,7 +59,6 @@ def id_for(obj, type):
except IndexError:
return ''
class Identifier(models.Model):
# olib, ltwk, goog, gdrd, thng, isbn, oclc, olwk, doab, gtbg, glue, doi
type = models.CharField(max_length=4, null=False)
@ -72,7 +73,13 @@ class Identifier(models.Model):
def set(type=None, value=None, edition=None, work=None):
# if there's already an id of this type for this work and edition, change it
# if not, create it. if the id exists and points to something else, change it.
identifier = Identifier.get_or_add(type=type, value=value, edition=edition, work=work)
try:
identifier = Identifier.objects.filter(type=type, value=value)[0]
except IndexError:
if type in WORK_IDENTIFIERS:
identifier = Identifier.objects.create(type=type, value=value, work=work)
else:
identifier = Identifier.objects.create(type=type, value=value, work=work, edition=edition)
if identifier.work.id != work.id:
identifier.work = work
identifier.save()
@ -98,6 +105,9 @@ class Identifier(models.Model):
def __unicode__(self):
return u'{0}:{1}'.format(self.type, self.value)
def label(self):
return ID_CHOICES_MAP.get(self.type, self.type)
class Work(models.Model):
created = models.DateTimeField(auto_now_add=True, db_index=True,)
title = models.CharField(max_length=1000)
@ -152,7 +162,7 @@ class Work(models.Model):
@property
def googlebooks_url(self):
if self.googlebooks_id:
return "http://books.google.com/books?id=%s" % self.googlebooks_id
return "https://books.google.com/books?id=%s" % self.googlebooks_id
else:
return ''
@ -168,7 +178,7 @@ class Work(models.Model):
@property
def goodreads_url(self):
return "http://www.goodreads.com/book/show/%s" % self.goodreads_id
return "https://www.goodreads.com/book/show/%s" % self.goodreads_id
@property
def librarything_id(self):
@ -176,7 +186,7 @@ class Work(models.Model):
@property
def librarything_url(self):
return "http://www.librarything.com/work/%s" % self.librarything_id
return "https://www.librarything.com/work/%s" % self.librarything_id
@property
def openlibrary_id(self):
@ -184,7 +194,7 @@ class Work(models.Model):
@property
def openlibrary_url(self):
return "http://openlibrary.org" + self.openlibrary_id
return "https://openlibrary.org" + self.openlibrary_id
def cover_filetype(self):
if self.uses_google_cover():
@ -202,6 +212,9 @@ class Work(models.Model):
else:
return 'image'
def work_ids(self):
return self.identifiers.filter(edition__isnull=True)
def uses_google_cover(self):
if self.preferred_edition and self.preferred_edition.cover_image:
return False
@ -1003,6 +1016,7 @@ class EbookFile(models.Model):
created = models.DateTimeField(auto_now_add=True)
asking = models.BooleanField(default=False)
ebook = models.ForeignKey('Ebook', related_name='ebook_files', null=True)
source = models.URLField(null=True, blank=True)
version = None
def check_file(self):
if self.format == 'epub':
@ -1084,7 +1098,12 @@ class Ebook(models.Model):
if self.save:
self.filesize = self.filesize if self.filesize < 2147483647 else 2147483647 # largest safe positive integer
self.save()
ebf = EbookFile.objects.create(edition=self.edition, ebook=self, format=self.format)
ebf = EbookFile.objects.create(
edition=self.edition,
ebook=self,
format=self.format,
source=self.url
)
ebf.file.save(path_for_file(ebf, None), ContentFile(r.read()))
ebf.file.close()
ebf.save()

View File

@ -15,6 +15,7 @@ AGE_LEVEL_CHOICES = (
('15-18', 'Teen - Grade 10-12, Age 15-18'),
('18-', 'Adult/Advanced Reader')
)
TEXT_RELATION_CHOICES = (
('translation', 'translation'),
('revision', 'revision'),
@ -22,6 +23,28 @@ TEXT_RELATION_CHOICES = (
('compilation', 'compilation')
)
ID_CHOICES = (
('http', 'Web Address'),
('isbn', 'ISBN'),
('doab', 'DOABooks ID'),
('gtbg', 'Project Gutenberg Number'),
('doi', 'Digital Object Identifier'),
('oclc', 'OCLC Number'),
('goog', 'Google Books ID'),
('gdrd', 'Goodreads ID'),
('thng', 'Library Thing ID'),
('olwk', 'Open Library Work ID'),
('ltwk', 'Library Thing Work ID'),
)
OTHER_ID_CHOICES = (
('glue', 'Unglue.it ID'),
('edid', 'pragmatic edition ID'),
)
WORK_IDENTIFIERS = ('doi','olwk','glue','ltwk')
ID_CHOICES_MAP = dict(ID_CHOICES)

View File

@ -39,7 +39,7 @@ logger = logging.getLogger(__name__)
# create Wishlist and UserProfile to associate with User
def create_user_objects(sender, created, instance, **kwargs):
# use get_model to avoid circular import problem with models
# don't create Wishlist or UserProfile if we are loading fixtures http://stackoverflow.com/a/3500009/7782
# don't create Wishlist or UserProfile if we are loading fixtures https://stackoverflow.com/a/3500009/7782
if not kwargs.get('raw', False):
try:
Wishlist = apps.get_model('core', 'Wishlist')

View File

@ -1,3 +1,4 @@
# encoding: utf-8
"""
external library imports
"""
@ -201,7 +202,7 @@ class BookLoaderTests(TestCase):
def test_thingisbn_mock(self):
with requests_mock.Mocker(real_http=True) as m:
with open(os.path.join(TESTDIR, '9780441569595.xml')) as lt:
m.get('http://www.librarything.com/api/thingISBN/0441007465', content=lt.read())
m.get('https://www.librarything.com/api/thingISBN/0441007465', content=lt.read())
self.test_thingisbn(mocking=True)
def test_thingisbn(self, mocking=False):
@ -222,7 +223,7 @@ class BookLoaderTests(TestCase):
# ask for related editions to be added using the work we just created
with requests_mock.Mocker(real_http=True) as m:
with open(os.path.join(TESTDIR, '9780441569595.xml')) as lt:
m.get('http://www.librarything.com/api/thingISBN/0441007465', content=lt.read())
m.get('https://www.librarything.com/api/thingISBN/0441007465', content=lt.read())
bookloader.add_related('0441007465') # should join the editions
self.assertTrue(models.Edition.objects.count() >= edbefore)
self.assertTrue(models.Work.objects.filter(language=lang).count() < langbefore)
@ -243,7 +244,7 @@ class BookLoaderTests(TestCase):
edition = bookloader.add_by_isbn('9780606301121') # A People's History Of The United States
with requests_mock.Mocker(real_http=True) as m:
with open(os.path.join(TESTDIR, '9780061989834.xml')) as lt:
m.get('http://www.librarything.com/api/thingISBN/9780606301121', content=lt.read())
m.get('https://www.librarything.com/api/thingISBN/9780606301121', content=lt.read())
edition = tasks.populate_edition.run(edition.isbn_13)
self.assertTrue(edition.work.editions.all().count() > 10)
self.assertTrue(edition.work.subjects.all().count() > 8)
@ -444,7 +445,7 @@ class BookLoaderTests(TestCase):
self.assertEqual(w.first_epub_url(), ebook_epub.url)
self.assertEqual(w.first_pdf_url(), ebook_pdf.url)
ebook_pdf.url='http://en.wikisource.org/wiki/Frankenstein'
ebook_pdf.url='https://en.wikisource.org/wiki/Frankenstein'
self.assertEqual(ebook_pdf.set_provider(), 'Wikisource')
self.user.wishlist.add_work(w, 'test')
@ -492,12 +493,12 @@ class BookLoaderTests(TestCase):
title = "Moby Dick"
ol_work_id = "/works/OL102749W"
gutenberg_etext_id = 2701
epub_url = "http://www.gutenberg.org/cache/epub/2701/pg2701.epub"
license = 'http://www.gutenberg.org/license'
epub_url = "https://www.gutenberg.org/cache/epub/2701/pg2701.epub"
license = 'https://www.gutenberg.org/license'
lang = 'en'
format = 'epub'
publication_date = datetime(2001,7,1)
seed_isbn = '9780142000083' # http://www.amazon.com/Moby-Dick-Whale-Penguin-Classics-Deluxe/dp/0142000086
seed_isbn = '9780142000083' # https://www.amazon.com/Moby-Dick-Whale-Penguin-Classics-Deluxe/dp/0142000086
ebook = bookloader.load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, epub_url, format, license, lang, publication_date)
self.assertEqual(ebook.url, epub_url)
@ -575,7 +576,7 @@ class CampaignTests(TestCase):
def test_required_fields(self):
# a campaign must have a target, deadline and a work
# see http://stackoverflow.com/questions/21458387/transactionmanagementerror-you-cant-execute-queries-until-the-end-of-the-atom
# see https://stackoverflow.com/questions/21458387/transactionmanagementerror-you-cant-execute-queries-until-the-end-of-the-atom
with transaction.atomic():
c = Campaign()
self.assertRaises(IntegrityError, c.save)
@ -591,7 +592,7 @@ class CampaignTests(TestCase):
c = Campaign(target=D('1000.00'), deadline=datetime(2013, 1, 1), work=w)
c.license = 'CC BY-NC'
c.save()
self.assertEqual(c.license_url, 'http://creativecommons.org/licenses/by-nc/3.0/')
self.assertEqual(c.license_url, 'https://creativecommons.org/licenses/by-nc/3.0/')
self.assertEqual(c.license_badge, '/static/images/ccbync.png')
def test_campaign_status(self):
@ -769,7 +770,7 @@ class GoodreadsTest(TestCase):
gc = goodreads.GoodreadsClient(key=settings.GOODREADS_API_KEY, secret=settings.GOODREADS_API_SECRET)
reviews = gc.review_list_unauth(user_id=gr_uid, shelf='read')
# test to see whether there is a book field in each of the review
# url for test is http://www.goodreads.com/review/list.xml?id=767708&shelf=read&page=1&per_page=20&order=a&v=2&key=[key]
# url for test is https://www.goodreads.com/review/list.xml?id=767708&shelf=read&page=1&per_page=20&order=a&v=2&key=[key]
self.assertTrue(all([r.has_key("book") for r in reviews]))
class LibraryThingTest(TestCase):
@ -790,6 +791,7 @@ class ISBNTest(TestCase):
milosz_10 = '006019667X'
milosz_13 = '9780060196677'
python_10 = '0-672-32978-6'
funky = '0672—329 78-6' # endash, mdash, space
python_10_wrong = '0-672-32978-7'
python_13 = '978-0-672-32978-4'
@ -798,8 +800,11 @@ class ISBNTest(TestCase):
# return None for invalid characters
self.assertEqual(None, isbn.ISBN("978-0-M72-32978-X").to_string('13'))
self.assertEqual(isbn.ISBN("978-0-M72-32978-X").valid, False)
self.assertEqual(None, bookloader.valid_isbn("978-0-M72-32978-X"))
# check that only ISBN 13 starting with 978 or 979 are accepted
self.assertEqual(None, isbn.ISBN("111-0-M72-32978-X").to_string())
self.assertEqual(None, bookloader.valid_isbn("111-0-M72-32978-X"))
self.assertEqual(isbn_python_13.to_string(), bookloader.valid_isbn(funky))
# right type?
self.assertEqual(isbn_python_10.type, '10')
@ -807,12 +812,16 @@ class ISBNTest(TestCase):
# valid?
self.assertEqual(isbn_python_10.valid, True)
self.assertEqual(isbn.ISBN(python_10_wrong).valid, False)
self.assertEqual(13, len(bookloader.valid_isbn(python_10)))
self.assertEqual(isbn_python_13.to_string(), bookloader.valid_isbn(python_10_wrong))
# do conversion -- first the outside methods
self.assertEqual(isbn.convert_10_to_13(isbn.strip(python_10)),isbn.strip(python_13))
self.assertEqual(isbn.convert_13_to_10(isbn.strip(python_13)),isbn.strip(python_10))
self.assertEqual(isbn.convert_10_to_13(isbn.strip(python_10)),isbn.strip(python_13))
self.assertEqual(isbn.convert_13_to_10('xxxxxxxxxxxxx'),None)
self.assertEqual(isbn.convert_10_to_13('xxxxxxxxxx'),None)
self.assertEqual(None, bookloader.valid_isbn('xxxxxxxxxxxxx'))
self.assertEqual(None, bookloader.valid_isbn('xxxxxxxxxx'))
# check formatting
self.assertEqual(isbn.ISBN(python_13).to_string(type='13'), '9780672329784')
@ -897,12 +906,12 @@ class DownloadPageTest(TestCase):
e2.save()
eb1 = models.Ebook()
eb1.url = "http://example.org"
eb1.url = "https://example.org"
eb1.edition = e1
eb1.format = 'epub'
eb2 = models.Ebook()
eb2.url = "http://example2.com"
eb2.url = "https://example2.com"
eb2.edition = e2
eb2.format = 'mobi'
@ -1203,7 +1212,7 @@ class OnixLoaderTests(TestCase):
'Subtitle': u'',
'TableOfContents': u'',
'Title': u'Immersion into Noise',
'URL': u'http://dx.doi.org/10.3998/ohp.9618970.0001.001',
'URL': u'https://doi.org/10.3998/ohp.9618970.0001.001',
'eISBN': u'N/A',
'eListPrice': u'N/A',
'ePublicationDate': u'',
@ -1218,7 +1227,7 @@ class OnixLoaderTests(TestCase):
'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/',
'DOI prefix': u'10.11647', 'License URL (human-readable summary)': u'https://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',
@ -1226,7 +1235,7 @@ class OnixLoaderTests(TestCase):
'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',
'License URL (our copyright tab)': u'https://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',

110
core/validation.py Normal file
View File

@ -0,0 +1,110 @@
# encoding: utf-8
'''
methods to validate and clean identifiers
'''
import re
from PyPDF2 import PdfFileReader
from django.forms import ValidationError
from regluit.pyepub import EPUB
from regluit.mobi import Mobi
from .isbn import ISBN
ID_VALIDATION = {
'http': (re.compile(r"(https?|ftp)://(-\.)?([^\s/?\.#]+\.?)+(/[^\s]*)?$",
flags=re.IGNORECASE|re.S ),
"The Web Address must be a valid http(s) URL."),
'isbn': (r'^([\dxX\-–— ]+|delete)$',
"The ISBN must be a valid ISBN-13."),
'doab': (r'^(\d{1,6}|delete)$',
"The value must be 1-6 digits."),
'gtbg': (r'^(\d{1,6}|delete)$',
"The Gutenberg number must be 1-6 digits."),
'doi': (r'^(https?://dx\.doi\.org/|https?://doi\.org/)?(10\.\d+/\S+|delete)$',
"The DOI value must be a valid DOI."),
'oclc': (r'^(\d{8,12}|delete)$',
"The OCLCnum must be 8 or more digits."),
'goog': (r'^([a-zA-Z0-9\-_]{12}|delete)$',
"The Google id must be 12 alphanumeric characters, dash or underscore."),
'gdrd': (r'^(\d{1,8}|delete)$',
"The Goodreads ID must be 1-8 digits."),
'thng': (r'(^\d{1,8}|delete)$',
"The LibraryThing ID must be 1-8 digits."),
'olwk': (r'^(/works/\)?OLd{1,8}W|delete)$',
"The Open Library Work ID looks like 'OL####W'."),
'glue': (r'^(\d{1,6}|delete)$',
"The Unglue.it ID must be 1-6 digits."),
'ltwk': (r'^(\d{1,8}|delete)$',
"The LibraryThing work ID must be 1-8 digits."),
}
def isbn_cleaner(value):
if value == 'delete':
return value
if not value:
raise forms.ValidationError('no identifier value found')
elif value == 'delete':
return value
isbn=ISBN(value)
if isbn.error:
raise forms.ValidationError(isbn.error)
isbn.validate()
return isbn.to_string()
def olwk_cleaner(value):
if not value == 'delete' and value.startswith('/works/'):
value = '/works/{}'.format(value)
return value
doi_match = re.compile( r'10\.\d+/\S+')
def doi_cleaner(value):
if not value == 'delete' and not value.startswith('10.'):
return doi_match.match(value).group(0)
return value
ID_MORE_VALIDATION = {
'isbn': isbn_cleaner,
'olwk': olwk_cleaner,
'olwk': doi_cleaner,
}
def identifier_cleaner(id_type):
if ID_VALIDATION.has_key(id_type):
(regex, err_msg) = ID_VALIDATION[id_type]
extra = ID_MORE_VALIDATION.get(id_type, None)
if isinstance(regex, (str, unicode)):
regex = re.compile(regex)
def cleaner(value):
if not value:
return None
if regex.match(value):
if extra:
value = extra(value)
return value
else:
raise ValidationError(err_msg)
return cleaner
return lambda value: value
def test_file(the_file, fformat):
if the_file and the_file.name:
if fformat == 'epub':
try:
book = EPUB(the_file.file)
except Exception as e:
raise forms.ValidationError(_('Are you sure this is an EPUB file?: %s' % e) )
elif fformat == 'mobi':
try:
book = Mobi(the_file.file)
book.parse()
except Exception as e:
raise forms.ValidationError(_('Are you sure this is a MOBI file?: %s' % e) )
elif fformat == 'pdf':
try:
doc = PdfFileReader(the_file.file)
except Exception, e:
raise forms.ValidationError(_('%s is not a valid PDF file' % the_file.name) )
return True

View File

@ -73,7 +73,7 @@ export ANSIBLE_VAULT_PASSWORD_FILE=[path]
```
To use `git diff` with these encrypted files, see the
[.gitattributes](https://github.com/Gluejar/regluit/blob/open_source/.gitattributes) has been added to allow for using `git diff` with `ansible-vault` files: [git - How to diff ansible vault changes? - Stack Overflow](http://stackoverflow.com/questions/29937195/how-to-diff-ansible-vault-changes/39511274#39511274). One highlight from the tips, run:
[.gitattributes](https://github.com/Gluejar/regluit/blob/open_source/.gitattributes) has been added to allow for using `git diff` with `ansible-vault` files: [git - How to diff ansible vault changes? - Stack Overflow](https://stackoverflow.com/questions/29937195/how-to-diff-ansible-vault-changes/39511274#39511274). One highlight from the tips, run:
```

View File

@ -11,7 +11,6 @@ from decimal import Decimal as D
from django import forms
from django.conf import settings
from django.conf.global_settings import LANGUAGES
from django.contrib.auth.models import User
from django.forms.widgets import RadioSelect
from django.forms.extras.widgets import SelectDateWidget
@ -26,8 +25,6 @@ from selectable.forms import (
AutoCompleteSelectField
)
from PyPDF2 import PdfFileReader
#regluit imports
from regluit.core.models import (
@ -55,36 +52,21 @@ from regluit.core.parameters import (
REWARDS,
BUY2UNGLUE,
THANKS,
AGE_LEVEL_CHOICES,
TEXT_RELATION_CHOICES,
)
from regluit.core.lookups import (
OwnerLookup,
WorkLookup,
PublisherNameLookup,
SubjectLookup,
EditionNoteLookup,
)
from regluit.core.validation import test_file
from regluit.utils.localdatetime import now
from regluit.utils.fields import ISBNField
from regluit.utils.text import sanitize_line, remove_badxml
from regluit.mobi import Mobi
from regluit.pyepub import EPUB
from regluit.bisac.models import BisacHeading
from .bibforms import EditionForm, IdentifierForm
from questionnaire.models import Questionnaire
logger = logging.getLogger(__name__)
nulls = [False, 'delete', '']
CREATOR_RELATIONS = (
('aut', 'Author'),
('edt', 'Editor'),
('trl', 'Translator'),
('ill', 'Illustrator'),
('dsr', 'Designer'),
('aui', 'Author of Introduction'),
)
bisac_headings = BisacHeading.objects.all()
class SurveyForm(forms.Form):
label = forms.CharField(max_length=64, required=True)
@ -110,184 +92,6 @@ class SurveyForm(forms.Form):
self.work = None
raise forms.ValidationError( 'That ISBN is not in our database')
class EditionForm(forms.ModelForm):
add_author = forms.CharField(max_length=500, required=False)
add_author_relation = forms.ChoiceField(choices=CREATOR_RELATIONS, initial=('aut', 'Author'))
add_subject = AutoCompleteSelectField(
SubjectLookup,
widget=AutoCompleteSelectWidget(SubjectLookup, allow_new=True),
label='Keyword',
required=False,
)
add_related_work = AutoCompleteSelectField(
WorkLookup,
widget=AutoCompleteSelectWidget(WorkLookup, allow_new=False, attrs={'size': 40}),
label='Related Work',
required=False,
)
add_work_relation = forms.ChoiceField(
choices=TEXT_RELATION_CHOICES,
initial=('translation', 'translation'),
required=False,
)
bisac = forms.ModelChoiceField( bisac_headings, required=False )
publisher_name = AutoCompleteSelectField(
PublisherNameLookup,
label='Publisher Name',
widget=AutoCompleteSelectWidget(PublisherNameLookup,allow_new=True),
required=False,
allow_new=True,
)
isbn = ISBNField(
label=_("ISBN"),
max_length=17,
required = False,
help_text = _("13 digits, no dash."),
error_messages = {
'invalid': _("This must be a valid ISBN-13."),
}
)
goog = forms.RegexField(
label=_("Google Books ID"),
max_length=12,
regex=r'^([a-zA-Z0-9\-_]{12}|delete)$',
required = False,
help_text = _("12 alphanumeric characters, dash or underscore, case sensitive."),
error_messages = {
'invalid': _("This value must be 12 alphanumeric characters, dash or underscore."),
}
)
gdrd = forms.RegexField(
label=_("GoodReads ID"),
max_length=8,
regex=r'^(\d+|delete)$',
required = False,
help_text = _("1-8 digits."),
error_messages = {
'invalid': _("This value must be 1-8 digits."),
}
)
thng = forms.RegexField(
label=_("LibraryThing ID"),
max_length=8,
regex=r'(^\d+|delete)$',
required = False,
help_text = _("1-8 digits."),
error_messages = {
'invalid': _("This value must be 1-8 digits."),
}
)
oclc = forms.RegexField(
label=_("OCLCnum"),
regex=r'^(\d\d\d\d\d\d\d\d\d*|delete)$',
required = False,
help_text = _("8 or more digits."),
error_messages = {
'invalid': _("This value must be 8 or more digits."),
}
)
http = forms.RegexField(
label=_("HTTP URL"),
# https://mathiasbynens.be/demo/url-regex
regex=re.compile(r"(https?|ftp)://(-\.)?([^\s/?\.#]+\.?)+(/[^\s]*)?$",
flags=re.IGNORECASE|re.S ),
required = False,
help_text = _("no spaces of funny stuff."),
error_messages = {
'invalid': _("This value must be a valid http(s) URL."),
}
)
doi = forms.RegexField(
label=_("DOI"),
regex=r'^(https?://dx\.doi\.org/|https?://doi\.org/)?(10\.\d+/\S+|delete)$',
required = False,
help_text = _("starts with '10.' or 'https://doi.org'"),
error_messages = {
'invalid': _("This value must be a valid DOI."),
}
)
language = forms.ChoiceField(choices=LANGUAGES)
age_level = forms.ChoiceField(choices=AGE_LEVEL_CHOICES, required=False)
description = forms.CharField( required=False, widget=CKEditorWidget())
coverfile = forms.ImageField(required=False)
note = AutoCompleteSelectField(
EditionNoteLookup,
widget=AutoCompleteSelectWidget(EditionNoteLookup, allow_new=True),
label='Edition Note',
required=False,
allow_new=True,
)
def __init__(self, *args, **kwargs):
super(EditionForm, self).__init__(*args, **kwargs)
self.relators = []
if self.instance:
for relator in self.instance.relators.all():
select = forms.Select(choices=CREATOR_RELATIONS).render('change_relator_%s' % relator.id , relator.relation.code )
self.relators.append({'relator':relator, 'select':select})
def clean_doi(self):
doi = self.cleaned_data["doi"]
if doi and doi.startswith("http"):
return doi.split('/',3)[3]
return doi
def clean_title(self):
return sanitize_line(self.cleaned_data["title"])
def clean_add_author(self):
return sanitize_line(self.cleaned_data["add_author"])
def clean_description(self):
return remove_badxml(self.cleaned_data["description"])
def clean(self):
has_isbn = self.cleaned_data.get("isbn", False) not in nulls
has_oclc = self.cleaned_data.get("oclc", False) not in nulls
has_goog = self.cleaned_data.get("goog", False) not in nulls
has_http = self.cleaned_data.get("http", False) not in nulls
has_doi = self.cleaned_data.get("doi", False) not in nulls
try:
has_id = self.instance.work.identifiers.all().count() > 0
except AttributeError:
has_id = False
if not has_id and not has_isbn and not has_oclc and not has_goog and not has_http and not has_doi:
raise forms.ValidationError(_("There must be either an ISBN, a DOI, a URL or an OCLC number."))
return self.cleaned_data
class Meta:
model = Edition
exclude = ('created', 'work')
widgets = {
'title': forms.TextInput(attrs={'size': 40}),
'add_author': forms.TextInput(attrs={'size': 30}),
'add_subject': forms.TextInput(attrs={'size': 30}),
'unglued': forms.CheckboxInput(),
'cover_image': forms.TextInput(attrs={'size': 60}),
}
def test_file(the_file):
if the_file and the_file.name:
if format == 'epub':
try:
book = EPUB(the_file.file)
except Exception as e:
raise forms.ValidationError(_('Are you sure this is an EPUB file?: %s' % e) )
elif format == 'mobi':
try:
book = Mobi(the_file.file)
book.parse()
except Exception as e:
raise forms.ValidationError(_('Are you sure this is a MOBI file?: %s' % e) )
elif format == 'pdf':
try:
doc = PdfFileReader(the_file.file)
except Exception, e:
raise forms.ValidationError(_('%s is not a valid PDF file' % the_file.name) )
class EbookFileForm(forms.ModelForm):
file = forms.FileField(max_length=16777216)
version_label = forms.CharField(max_length=512, required=False)
@ -315,15 +119,15 @@ class EbookFileForm(forms.ModelForm):
return self.cleaned_data.get('format','')
def clean(self):
format = self.cleaned_data['format']
fformat = self.cleaned_data['format']
the_file = self.cleaned_data.get('file', None)
test_file(the_file)
test_file(the_file, fformat)
return self.cleaned_data
class Meta:
model = EbookFile
widgets = { 'edition': forms.HiddenInput}
exclude = { 'created', 'asking', 'ebook' }
fields = ('file', 'format', 'edition')
class EbookForm(forms.ModelForm):
file = forms.FileField(max_length=16777216, required=False)

175
frontend/forms/bibforms.py Normal file
View File

@ -0,0 +1,175 @@
# encoding: utf-8
'''
forms for bib models
'''
import re
from ckeditor.widgets import CKEditorWidget
from django import forms
from django.conf.global_settings import LANGUAGES
from django.utils.translation import ugettext_lazy as _
from selectable.forms import (
AutoCompleteSelectWidget,
AutoCompleteSelectField
)
from regluit.core.lookups import (
WorkLookup,
PublisherNameLookup,
SubjectLookup,
EditionNoteLookup,
)
from regluit.bisac.models import BisacHeading
from regluit.core.models import Edition, Identifier
from regluit.core.parameters import (
AGE_LEVEL_CHOICES,
ID_CHOICES,
TEXT_RELATION_CHOICES,
)
from regluit.core.validation import identifier_cleaner
from regluit.utils.text import sanitize_line, remove_badxml
CREATOR_RELATIONS = (
('aut', 'Author'),
('edt', 'Editor'),
('trl', 'Translator'),
('ill', 'Illustrator'),
('dsr', 'Designer'),
('aui', 'Author of Introduction'),
)
NULLS = [False, 'delete', '']
bisac_headings = BisacHeading.objects.all()
class IdentifierForm(forms.ModelForm):
id_type = forms.ChoiceField(label='Identifier Type', choices=ID_CHOICES)
id_value = forms.CharField(
label='Identifier Value',
widget=forms.TextInput(attrs={'size': 60}),
required=False,
)
make_new = forms.BooleanField(
label='There\'s no existing Identifier, so please use an Unglue.it ID',
required=False,
)
identifier = None
def clean(self):
id_type = self.cleaned_data['id_type']
id_value = self.cleaned_data.get('id_value', '').strip()
make_new = self.cleaned_data.get('make_new', False)
if not make_new:
self.cleaned_data['value'] = identifier_cleaner(id_type)(id_value)
return self.cleaned_data
class Meta:
model = Identifier
fields = ('id_type', 'id_value')
widgets = {
'id_value': forms.TextInput(attrs={'size': 40}),
}
class EditionForm(forms.ModelForm):
'''
form for bibliographic data (both editions and works
'''
add_author = forms.CharField(max_length=500, required=False)
add_author_relation = forms.ChoiceField(choices=CREATOR_RELATIONS, initial=('aut', 'Author'))
add_subject = AutoCompleteSelectField(
SubjectLookup,
widget=AutoCompleteSelectWidget(SubjectLookup, allow_new=True),
label='Keyword',
required=False,
)
add_related_work = AutoCompleteSelectField(
WorkLookup,
widget=AutoCompleteSelectWidget(WorkLookup, allow_new=False, attrs={'size': 40}),
label='Related Work',
required=False,
)
add_work_relation = forms.ChoiceField(
choices=TEXT_RELATION_CHOICES,
initial=('translation', 'translation'),
required=False,
)
bisac = forms.ModelChoiceField( bisac_headings, required=False )
publisher_name = AutoCompleteSelectField(
PublisherNameLookup,
label='Publisher Name',
widget=AutoCompleteSelectWidget(PublisherNameLookup,allow_new=True),
required=False,
allow_new=True,
)
id_type = forms.ChoiceField(label='Identifier Type', choices=ID_CHOICES)
id_value = forms.CharField(
label='Identifier Value',
widget=forms.TextInput(attrs={'size': 60}),
required=False,
)
language = forms.ChoiceField(choices=LANGUAGES)
age_level = forms.ChoiceField(choices=AGE_LEVEL_CHOICES, required=False)
description = forms.CharField( required=False, widget=CKEditorWidget())
coverfile = forms.ImageField(required=False)
note = AutoCompleteSelectField(
EditionNoteLookup,
widget=AutoCompleteSelectWidget(EditionNoteLookup, allow_new=True),
label='Edition Note',
required=False,
allow_new=True,
)
def __init__(self, *args, **kwargs):
super(EditionForm, self).__init__(*args, **kwargs)
self.relators = []
if self.instance:
for relator in self.instance.relators.all():
select = forms.Select(choices=CREATOR_RELATIONS).render(
'change_relator_%s' % relator.id,
relator.relation.code
)
self.relators.append({'relator':relator, 'select':select})
def clean_doi(self):
doi = self.cleaned_data["doi"]
if doi and doi.startswith("http"):
return doi.split('/', 3)[3]
return doi
def clean_title(self):
return sanitize_line(self.cleaned_data["title"])
def clean_add_author(self):
return sanitize_line(self.cleaned_data["add_author"])
def clean_description(self):
return remove_badxml(self.cleaned_data["description"])
def clean(self):
id_type = self.cleaned_data['id_type']
id_value = self.cleaned_data.get('id_value','').strip()
if id_value:
identifier = Identifier.objects.filter(type=id_type, value=id_value)
if identifier:
err_msg = "{} is a duplicate for work #{}.".format(identifier[0], identifier[0].work.id)
self.add_error('id_value', forms.ValidationError(err_msg))
try:
self.cleaned_data['value'] = identifier_cleaner(id_type)(id_value)
except forms.ValidationError, ve:
self.add_error('id_value', forms.ValidationError('{}: {}'.format(ve.message, id_value)))
return self.cleaned_data
class Meta:
model = Edition
exclude = ('created', 'work')
widgets = {
'title': forms.TextInput(attrs={'size': 40}),
'add_author': forms.TextInput(attrs={'size': 30}),
'add_subject': forms.TextInput(attrs={'size': 30}),
'unglued': forms.CheckboxInput(),
'cover_image': forms.TextInput(attrs={'size': 60}),
}

View File

@ -11,7 +11,7 @@
<h1>Unglue.it is currently undergoing maintenance</h1>
<p>
While you wait, why not like us on <a href="http://facebook.com/unglueit">Facebook</a>, follow us on <a href="http://twitter.com/unglueit">Twitter</a>, or subscribe to our <a href="https://blog.unglue.it">blog</a>? We'll keep you up to date there with our progress fixing things.
While you wait, why not like us on <a href="https://facebook.com/unglueit">Facebook</a>, follow us on <a href="https://twitter.com/unglueit">Twitter</a>, or subscribe to our <a href="https://blog.unglue.it">blog</a>? We'll keep you up to date there with our progress fixing things.
</p>
<p>You can also help us by <a href="{% url 'feedback' %}">sending us feedback</a>.</p>

View File

@ -2,7 +2,7 @@
{% block title %} Everything You Always Wanted to Know {% endblock %}
{% block doccontent %}
<h2>About</h2>
<p><a href="https://unglue.it">Unglue.it</a> is a service provided by <a href="http://ebookfoundation.org">The Free Ebook Foundation</a> It's a place for individuals and institutions to join together to liberate specific ebooks and other types of digital content by paying authors and publishers to relicense their works under <a href="http://creativecommons.org">Creative Commons</a> licenses.</p>
<p><a href="https://unglue.it">Unglue.it</a> is a service provided by <a href="https://ebookfoundation.org">The Free Ebook Foundation</a> It's a place for individuals and institutions to join together to liberate specific ebooks and other types of digital content by paying authors and publishers to relicense their works under <a href="https://creativecommons.org">Creative Commons</a> licenses.</p>
<p>What does this mean?</p>
<ul>
@ -28,7 +28,7 @@
<div class="outer">
<div><img src="/static/images/headshots/raymond.jpg" class="mediaborder" alt="raymond" /></div>
<div class="text"><b>Raymond Yee</b> is a data architect, author, consultant, and teacher. He is author of the leading book on web mashups, <a href="http://blog.mashupguide.net/2008/02/29/the-book-is-available-now/">Pro Web 2.0 Mashups: Remixing Data and Web Services</a> (published by Apress and licensed under a Creative Commons license), and has numerous blogs at his <a href="http://raymondyee.net/">personal site</a>. At the UC Berkeley School of Information, he taught Mixing and Remixing Information, a course on using APIs to create mashups. An open data and open government afficionado, he recently co-wrote three influential reports on how the US government can improve its efforts to make data and services available through APIs. Raymond served as the Integration Advisor for the Zotero Project (a widely used open source research tool) and managed the <a href="http://www.archive.org/details/zoterocommons">Zotero Commons</a>, a collaboration between George Mason University and the Internet Archive. Raymond has been an invited speaker about web technology at the Library of Congress, Fashion Institute of Technology, the O'Reilly Emerging Technology Conference, American Library Association, the Open Education conference, Code4lib, Educause, and NISO. While earning a Ph.D. in biophysics, he taught computer science, philosophy, and personal development to middle and high school students in the Academic Talent Development Program on the Berkeley campus. Raymond is an erstwhile tubaist, admirer of J. S. Bach, and son of industrious Chinese-Canadian restaurateurs.<br /><br />
<div class="text"><b>Raymond Yee</b> is a data architect, author, consultant, and teacher. He is author of the leading book on web mashups, <a href="http://blog.mashupguide.net/2008/02/29/the-book-is-available-now/">Pro Web 2.0 Mashups: Remixing Data and Web Services</a> (published by Apress and licensed under a Creative Commons license), and has numerous blogs at his <a href="http://raymondyee.net/">personal site</a>. At the UC Berkeley School of Information, he taught Mixing and Remixing Information, a course on using APIs to create mashups. An open data and open government afficionado, he recently co-wrote three influential reports on how the US government can improve its efforts to make data and services available through APIs. Raymond served as the Integration Advisor for the Zotero Project (a widely used open source research tool) and managed the <a href="https://www.archive.org/details/zoterocommons">Zotero Commons</a>, a collaboration between George Mason University and the Internet Archive. Raymond has been an invited speaker about web technology at the Library of Congress, Fashion Institute of Technology, the O'Reilly Emerging Technology Conference, American Library Association, the Open Education conference, Code4lib, Educause, and NISO. While earning a Ph.D. in biophysics, he taught computer science, philosophy, and personal development to middle and high school students in the Academic Talent Development Program on the Berkeley campus. Raymond is an erstwhile tubaist, admirer of J. S. Bach, and son of industrious Chinese-Canadian restaurateurs.<br /><br />
Recently, Raymond has been teaching a course at UC Berkeley's School of Information science on working with Open Data; with any luck, the textbook he's working on will be a subject of a future ungluing campaign!</div>
</div>

View File

@ -8,7 +8,7 @@
<b>What if you could</b> give a book to everyone on earth? Get an ebook and read it on any device, in any format, forever? Give an ebook to your library, for them to share? Own DRM-free ebooks, legally? Read free ebooks, and know their creators had been fairly paid?
</p>
<p>
At Unglue.it, you can reward creators that have set their books free, via <a href="http://creativecommons.org">Creative Commons</a> licenses. You can buy books that WANT to be free, but need funding to get there. You can pledge toward creating free ebooks from print books.
At Unglue.it, you can reward creators that have set their books free, via <a href="https://creativecommons.org">Creative Commons</a> licenses. You can buy books that WANT to be free, but need funding to get there. You can pledge toward creating free ebooks from print books.
</p>
<p>
Traditionally published books are stuck: legal restrictions keep you from being able to enjoy and share them. Unglue.it gets them unstuck. Authors and publishers decide what amount lets them freely share their books with the world while still making a living. We raise that fee here through crowdfunding: people like you chipping in. When campaigns succeed, authors and publishers get paid, and you get a free ebook.

View File

@ -17,7 +17,7 @@
<p>Ungluing campaigns offer ways to earn income while at the same time making your books free. They're a tool for you to reach out to, discover, interact with, and increase your audience while raising the profile of your work. Your unglued book, in turn, becomes an ambassador for the rest of your work.</p>
<p>Rest assured that, when you unglue a book, you retain the copyright. You can choose whichever <a href="http://creativecommons.org/licenses/">Creative Commons license</a> serves your interests best, whether that's inviting your fans to remix your work or sharing it widely while reserving certain rights. If a book is unglued via a pledge campaign, you should set the price to zero in the Smashwords admin so as to honor the intentions of your supporters. We encourage you to develop enhanced digital editions to sell alongside the Creative Commons Licensed edition and print versions for sale. </p>
<p>Rest assured that, when you unglue a book, you retain the copyright. You can choose whichever <a href="https://creativecommons.org/licenses/">Creative Commons license</a> serves your interests best, whether that's inviting your fans to remix your work or sharing it widely while reserving certain rights. If a book is unglued via a pledge campaign, you should set the price to zero in the Smashwords admin so as to honor the intentions of your supporters. We encourage you to develop enhanced digital editions to sell alongside the Creative Commons Licensed edition and print versions for sale. </p>
<h2>The Fine Print</h2>

View File

@ -11,7 +11,7 @@
Of course, <span class="ungluer"></span> can't do that, because books are stuck. Copyright and ebook licensing often make it illegal for individuals, and even libraries, to share digital books. DRM makes sharing hard. At Unglue.it, we respect copyright and pay book creators, while enabling book lovers to share their favorite books with everyone.
</p>
<p>
In short, we get books unstuck. Authors and publishers decide what amount lets them freely share their books with the world while still making a living. We raise that fee here through crowdfunding: people like you and <span class="ungluer"></span> chipping in. When campaigns succeed, authors and publishers get paid, and they issue a free electronic edition under a <a href="http://creativecommons.org">Creative Commons</a> license. It's like public radio: once donors have covered the station's costs, its programs are free to all.
In short, we get books unstuck. Authors and publishers decide what amount lets them freely share their books with the world while still making a living. We raise that fee here through crowdfunding: people like you and <span class="ungluer"></span> chipping in. When campaigns succeed, authors and publishers get paid, and they issue a free electronic edition under a <a href="https://creativecommons.org">Creative Commons</a> license. It's like public radio: once donors have covered the station's costs, its programs are free to all.
</p>
<p>
What if your favorite book could be free to the world?

View File

@ -11,7 +11,7 @@
We all do, but we can't, because books are stuck. Copyright and ebook licensing often make it illegal for individuals, and even libraries, to share digital books. DRM makes sharing hard. At Unglue.it, we respect copyright and pay book creators, while enabling book lovers to share their favorite books with everyone.
</p>
<p>
In short, we get books unstuck. Authors and publishers decide what amount lets them freely share their books with the world while still making a living. We raise that fee here through crowdfunding: people like you and <span class="ungluer"></span> chipping in. When campaigns succeed, book creators get paid, and they issue a free electronic edition under a <a href="http://creativecommons.org">Creative Commons</a> license. It's like public radio: once donors have covered the station's costs, its programs are free to all.
In short, we get books unstuck. Authors and publishers decide what amount lets them freely share their books with the world while still making a living. We raise that fee here through crowdfunding: people like you and <span class="ungluer"></span> chipping in. When campaigns succeed, book creators get paid, and they issue a free electronic edition under a <a href="https://creativecommons.org">Creative Commons</a> license. It's like public radio: once donors have covered the station's costs, its programs are free to all.
</p>
<p>
<span class="ungluer"></span> hasn't nominated any favorite books yet, but you can. What if your favorite book could be free to the world?

View File

@ -30,7 +30,7 @@
<div id="user-block1">
<div id="block-intro-text"><br /><span class="special-user-name">Ready to Read</span></div>
</div>
<div class="user-block24"><p style="font-size: larger;"><span class="user-short-info">These {% if pub_lang %}{{ pub_lang|ez_lang_name }} language {% endif %}{% if cc.is_cc %}<a href="http://creativecommons.org/">Creative Commons</a>{% endif %} {% if license %}<a href="{{cc.url}}">{{ license }}</a>{% endif %} {%if cc.is_pd %}ebooks are ready to read - they belong to all of us!{% else %}licensed ebooks are ready to read - the people who created them want you to read and share them.{% endif %}</span></p>
<div class="user-block24"><p style="font-size: larger;"><span class="user-short-info">These {% if pub_lang %}{{ pub_lang|ez_lang_name }} language {% endif %}{% if cc.is_cc %}<a href="https://creativecommons.org/">Creative Commons</a>{% endif %} {% if license %}<a href="{{cc.url}}">{{ license }}</a>{% endif %} {%if cc.is_pd %}ebooks are ready to read - they belong to all of us!{% else %}licensed ebooks are ready to read - the people who created them want you to read and share them.{% endif %}</span></p>
{% if license %}<p style="font-size: smaller;"><a href="{{cc.url}}"><img src="{{cc.badge}}" alt="badge for {{license}}" style="float:left;padding:0.5em" /></a> {{cc.description}}
</p>{% endif %}
</div>

View File

@ -233,7 +233,7 @@ $j(document).ready(function() {
</p>
<p class="ebook_download logo"><img src="/static/images/ibooks_logo.jpg" alt="iBooks Logo" />iBooks</p>
<ul>
<li><a href="http://itunes.apple.com/us/app/ibooks/id364709193?mt=8">Download the free iBooks app</a> from the App Store.</li>
<li><a href="https://itunes.apple.com/us/app/ibooks/id364709193?mt=8">Download the free iBooks app</a> from the App Store.</li>
<li>Download the <a href="{{ formats.epub }}">epub file</a>.</li>
<li>You will be given the option of opening the file in iBooks.</li>
</ul>
@ -256,7 +256,7 @@ $j(document).ready(function() {
</p>
<p class="ebook_download logo"><img src="/static/images/ibooks_logo.jpg" alt="iBooks Logo" />iBooks</p>
<ul>
<li><a href="http://itunes.apple.com/us/app/ibooks/id364709193?mt=8">Download the free iBooks app</a> from the App Store.</li>
<li><a href="https://itunes.apple.com/us/app/ibooks/id364709193?mt=8">Download the free iBooks app</a> from the App Store.</li>
<li>Download the <a href="{{ formats.pdf }}">pdf file</a>.</li>
<li>You will be given the option of opening the file in iBooks.</li>
</ul>

View File

@ -0,0 +1,18 @@
{% if work.ebooks_all %}
<h3>Manage eBooks</h3>
<ul>
{% for ebook in work.ebooks_all %}
<li>
<a href="{{ ebook.url }}">{{ ebook.format }}</a>, created {{ ebook.created }}{% if ebook.user %},
by <a href="{% url 'supporter' ebook.user.id %}">{{ ebook.user }}</a>{% endif %}.
{% if ebook.filesize %}{{ ebook.filesize }}{% else %}??{% endif %}B
{% if ebook.version_label %}{{ ebook.version }}{% endif %}
{% if ebook.active %}<input type="submit" name="deactivate_ebook_{{ ebook.id }}" value="deactivate" class="deletebutton" title="deactivate ebook" />{% else %}<input type="submit" name="activate_ebook_{{ ebook.id }}" value="activate" class="deletebutton" title="activate ebook" />{% endif %}
</li>
{% endfor %}
</ul>
<br />
<br />
<input type="submit" name="activate_all_ebooks" value="activate all ebooks" class="deletebutton" title="activate all ebooks" />
<input type="submit" name="deactivate_all_ebooks" value="deactivate all ebooks" class="deletebutton" title="deactivate all ebooks" />
{% endif %}

View File

@ -7,7 +7,7 @@
<ul class="terms">
<li>a list of ISBNs of other editions of the work</li>
<li> <i>For Pledge Campaigns only</i><ol>
<li>the Creative Commons license selected for the campaign, formatted in accordance with best practices at <a href="http://wiki.creativecommons.org/Marking/Creators">http://wiki.creativecommons.org/Marking/Creators</a>, and a statement that for the purposes of the license, "Non-Commercial" use shall include, but not be limited to, the distribution of the Work by a commercial entity without charge.</li>
<li>the Creative Commons license selected for the campaign, formatted in accordance with best practices at <a href="https://wiki.creativecommons.org/Marking/Creators">https://wiki.creativecommons.org/Marking/Creators</a>, and a statement that for the purposes of the license, "Non-Commercial" use shall include, but not be limited to, the distribution of the Work by a commercial entity without charge.</li>
<li>an acknowledgement of Ungluers of the work, formatted in accordance with the premium descriptions on the Campaign page.</li>
<li>The Unglue.it logo</li>
<li>the text “CC edition release enabled by Unglue.it users” (including the hyperlink to the site)</li>

View File

@ -0,0 +1,249 @@
{% extends 'basedocumentation.html' %}
{% block extra_extra_head %}
{{ block.super }}
<link rel="stylesheet" href="/static/css/ui-lightness/jquery-ui-1.8.16.custom.css" type="text/css" media="screen">
<link href="/static/css/ui.fancytree.min.css" rel="stylesheet" type="text/css">
<style type="text/css">
ul.fancytree-container {
width: 100%;
height: 10em;
overflow: auto;
position: relative;
margin-top: 0.5em;
}
</style>
{{ form.media.css }}
<script type="text/javascript" src="{{ jquery_ui_home }}" ></script>
<script src="/static/js/jquery.fancytree-all.min.js" type="text/javascript"></script>
<script type="text/javascript">
$j(function(){
// Initialize Fancytree
$j("#tree").fancytree({
extensions: ["glyph"],
checkbox: true,
selectMode: 1,
glyph: {
map: {
doc: "fa fa-file-o",
docOpen: "fa fa-file-o",
checkbox: "fa fa-square-o",
checkboxSelected: "fa fa-check-square-o",
checkboxUnknown: "fa fa-square",
dragHelper: "fa arrow-right",
dropMarker: "fa long-arrow-right",
error: "fa fa-warning",
expanderClosed: "fa fa-caret-right",
expanderLazy: "fa fa-angle-right",
expanderOpen: "fa fa-caret-down",
folder: "fa fa-folder-o",
folderOpen: "fa fa-folder-open-o",
loading: "fa fa-spinner fa-pulse"
}
},
source: { url: "/bisac/tree", cache: true }
});
$j("#editform").submit(function() {
// Render hidden <input> elements for active and selected nodes
$j("#tree").fancytree("getTree").generateFormElements(selected="bisac");
//alert("POST data:\n" + $j.param($j(this).serializeArray()));
return true;
});
$j().ready(function(){
var contentblock = $j('#content-block');
contentblock.on("click", "span.deletebutton", function () {
var kw = $j(this).attr('data');
var li = $j(this).parent();
// perform action
{% if edition.work %}
jQuery.post('{% url 'kw_edit' edition.work.id %}', {'remove_kw': kw, 'csrfmiddlewaretoken': '{{ csrf_token }}' }, function(data) {
li.html('kw removed');
});
{% else %}
li.html('kw removed');
{% endif %}
});
// this is the id of the submit button
$j('#add_subject_submit').click(function(event) {
data= $j('#id_add_subject_0').attr('value')
if (data == 'xxbadform'){
alert("bad keyword");
} else {
$j('#kw_list').append('<li>' + data + '<input type="hidden" name="new_subject" value="'+data +'" /><span class="deletebutton" data="' + data +'">x</span></li>')
}; // data will be the added kw.
});
});
});
</script>
{{ form.media.js }}
{% endblock %}
{% block doccontent %}
{% if admin %}
{% if edition.pk %}
<h2>Edit Edition for <a href="{% url 'work' edition.work.id %}">{{ edition.work.title }}</a></h2>
{% else %}
<h2>Create New Edition</h2>
{% endif %}
<p>Title is required; the rest is optional, though a cover image is strongly recommended.</p>
{% if alert %}
<ul class='errorlist'>
<li>{{ alert }}</li>
</ul>
{% endif %}
<form id="editform" enctype="multipart/form-data" method="POST" action="#">
{% csrf_token %}
{{ form.work }}
{{ form.non_field_errors }}
<!--{{ form.errors }}-->
<div>
<p><b>Title</b>: {{ form.title.errors }}{{ form.title }}</p>
<p><b>Publisher Name</b> : {{ form.publisher_name.errors }}{{ form.publisher_name }}<br />(If you change this, click another form element before submitting)</p>
<p>
<b>Authors</b>:
{% if edition.pk and edition.relators or edition.new_authors %}
<ul>
{% for relator in form.relators %}
<li>{{ relator.relator.name }} {{ relator.select }} <input type="submit" name="delete_author_{{ relator.relator.author.id }}" value="x" class="deletebutton" title="delete author"></li>
{% endfor %}
{% for author in edition.new_authors %}
<li>{{ author.0 }}<input type="hidden" name="new_author" value="{{ author.0 }}" /> ({{ author.1 }})<input type="hidden" name="new_author_relation" value="{{ author.1 }}" /></li>
{% endfor %}
</ul>
{% else %}
(None listed)
{% endif %}
</p>
<p><b>Add a Creator</b> (<I>Firstname Lastname</I>): {{ form.add_author.errors }}{{ form.add_author }}{{ form.add_author_relation.errors }}{{ form.add_author_relation }}
<input type="submit" name="add_author_submit" value="Add Author" id="submit_author"></p>
<p><b>Language</b>: {{ form.language.errors }}{{ form.language }}</p>
{% if edition.pk %}
<p><b>Add a Related Work</b>: {{ form.add_work_relation.errors }}{{ form.add_work_relation }} of {{ form.add_related_work.errors }}{{ form.add_related_work }}</p>
<ul>{% for work_rel in edition.work.works_related_to.all %}
<li>
This work is a {{ work_rel.relation }} of <a href="{% url 'work' work_rel.from_work.id %}">{{ work_rel.from_work }}</a>.
<input type="submit" name="delete_work_rel_{{ work_rel.id }}" value="x" class="deletebutton" title="delete work relation">
</li>
{% endfor %}
{% for work_rel in edition.work.works_related_from.all %}
<li>
<a href="{% url 'work' work_rel.to_work.id %}">{{ work_rel.to_work }}</a> is a {{ work_rel.relation }} of this work.
<input type="submit" name="delete_work_rel_{{ work_rel.id }}" value="x" class="deletebutton" title="delete work relation">
</li>
{% endfor %}</ul>
{% endif %}
<p><b>Age Level</b>: {{ form.age_level.errors }}{{ form.age_level }}</p>
<p><b>Edition Note</b>: {{ form.note.errors }}{{ form.note }}</p>
<h4>Identifiers </h4>
{% if edition.work.work_ids %}
<p><b>For the Work:</b></p>
<ul class="bullets">
{% for ident in edition.work.work_ids %}
<li><b>{{ ident.label }}</b>: {{ ident.value }}</li>
{% endfor %}
</ul>
{% endif %}
{% if edition.identifiers.all %}
<p><b>For the Edition:</b></p>
<ul class="bullets">
{% for ident in edition.identifiers.all %}
<li><b>{{ ident.label }}</b>: {{ ident.value }}</li>
{% endfor %}
</ul>
{% endif %}
<p> Add/Change an Identifier (Enter 'delete' to remove it). </p>
{{ form.id_value.errors }}
{{ identform.as_p }}
<p><b>Description</b>: <br />
{{ form.description.errors }}{{ form.description }}<br />
(<i>{% if work.last_campaign %}
{% ifequal work.last_campaign.type 3 %}
This will appear in the Description tab on the book page.
{% else %}
The campaign pitch will override this description.
{% endifequal %}
{% else %}
This will appear in the Description tab on the book page.
{% endif %}
</i>)</p>
<p><b>Publication Date</b> (<I>four-digit year</I>): {{ form.publication_date.errors }}{{ form.publication_date }}</p>
<p><b>Subjects</b>:
<ul id="kw_list">
{% if edition.work.pk and edition.work.subjects %}
{% for subject in edition.work.subjects.all %}
<li>{{ subject.name }}
{% if subject.authority %}
({{subject.authority}})
{% endif %}
<span class="deletebutton" data="{{ subject.name }}">x</span></li>
{% endfor %}
{% endif %}
{% for new_subject in edition.new_subjects %}
<li>{{ new_subject }}<input type="hidden" name="new_subject" value="{{ new_subject }}" /></li>
{% endfor %}
</ul>
<b>Add a Subject</b>: {{ form.add_subject.errors }}{{ form.add_subject }}
<a class="fakeinput" id="add_subject_submit" style="font-size: smaller;" >Add Subject</a></p>
<p id="tree" name="is_bisac"><b>Add BISAC Subject</b>:
</p>
<p><b>Cover Image</b>: <br />
{% if edition.cover_image %}
<img src="{{edition.cover_image}}" /><br />
{% else %}
[ no cover specified for this edition ]<br />
{% endif %}
{{ form.cover_image.errors }}{{ form.cover_image }}{{ form.cover_image.help_text }}
(<i>Enter a URL for an image, at least 300 px wide. The image will be scaled to the proportions of a 6x9 cover. </i>)<br />
OR...<br />
{{ form.coverfile.errors }}{{ form.coverfile }}{{ form.coverfile.help_text }}
(<i>upload a cover image file (we'll automatically size if for you). </i>)<br />
</p>
</div>
<input type="submit" name="create_new_edition" style="font-size: larger;" value="{% if edition.pk %}Save Edits{% else %}Create Edition{% endif %}" id="submit">
{% with edition.work as work %}
{% include 'ebook_list.html' %}
{% endwith %}
</form>
{% if edition.work %}
<h2>More Edition Management</h2>
<div><a href="{% url 'merge' edition.work.id %}">Merge other works into this one</a></div>
<div><a href="{% url 'work_editions' edition.work.id %}">Remove editions from this work</a></div>
{% if edition.id %}
<div><a href="{% url 'manage_ebooks' edition.id %}">Add ebooks for this edition</a></div>
{% endif %}
{% if request.user.is_staff %}
<div><a href="{% url 'feature' edition.work.id %}">Feature this work today</a></div>
{% endif %}
<br />
{% endif %}
{% else %}
{% if edition.work %}
{% include 'edition_display.html' %}
{% else %}
Sorry, there's no work specified.
{% endif %}
{% endif %}
{% endblock %}

View File

@ -32,7 +32,7 @@
ISBN: <span itemprop="isbn">{{ edition.isbn_13 }}</span><br />
{% endif %}
{% if edition.oclc %}
OCLC: <a href="http://www.worldcat.org/oclc/{{ edition.oclc }}">{{ edition.oclc }}</a><br />
OCLC: <a href="https://www.worldcat.org/oclc/{{ edition.oclc }}">{{ edition.oclc }}</a><br />
{% endif %}
{% if edition.doi %}
DOI: <a href="https://doi.org/{{ edition.doi }}">{{ edition.doi }}</a><br />

View File

@ -22,7 +22,7 @@
<dl>
<dt>What is Unglue.it?</dt>
<dd><a href="/">Unglue.it</a> is a place for individuals and institutions to join together to make ebooks free to the world. We work together to support authors, publishers, or other rights holders who want their ebooks to be free. We support <a href="http://creativecommons.org">Creative Commons</a> licensing as an enabling tool to "unglue" the ebooks. </dd>
<dd><a href="/">Unglue.it</a> is a place for individuals and institutions to join together to make ebooks free to the world. We work together to support authors, publishers, or other rights holders who want their ebooks to be free. We support <a href="https://creativecommons.org">Creative Commons</a> licensing as an enabling tool to "unglue" the ebooks. </dd>
<dt>What are Ungluing Campaigns?</dt>
@ -65,11 +65,11 @@ You may get premiums as part of a Pledge Campaign, depending on the amount you p
<dt>Does Unglue.it own the copyright of unglued books?</dt>
<dd>No. When you unglue a book, the copyright stays with its current owner. Unglue.it does not buy copyrights. A <a href="http://creativecommons.org">Creative Commons</a> license is a licensing agreement. As with other licensing agreements, it grants certain rights to others but does not affect the status of the copyright.<br /><br />
<dd>No. When you unglue a book, the copyright stays with its current owner. Unglue.it does not buy copyrights. A <a href="https://creativecommons.org">Creative Commons</a> license is a licensing agreement. As with other licensing agreements, it grants certain rights to others but does not affect the status of the copyright.<br /><br />
Just like many traditional publishing transactions, ungluing is about licensing rights. We use <a href="http://creativecommons.org">Creative Commons</a> licenses which allow creators to choose the ways in which their works can be copied, shared and re-used.<br /><br />
Just like many traditional publishing transactions, ungluing is about licensing rights. We use <a href="https://creativecommons.org">Creative Commons</a> licenses which allow creators to choose the ways in which their works can be copied, shared and re-used.<br /><br />
If you are a copyright holder, you retain your copyright when you unglue a book. Creative Commons licenses are non-exclusive, so you also retain the right to enter into separate licensing agreements. You can read more about these licenses at the <a href="http://wiki.creativecommons.org/Frequently_Asked_Questions">Creative Commons FAQ</a>.</dd>
If you are a copyright holder, you retain your copyright when you unglue a book. Creative Commons licenses are non-exclusive, so you also retain the right to enter into separate licensing agreements. You can read more about these licenses at the <a href="https://wiki.creativecommons.org/Frequently_Asked_Questions">Creative Commons FAQ</a>.</dd>
<dt>If I'm a rights holder and my book is unglued, does that mean I can never make money from it again?</dt>
@ -271,7 +271,7 @@ Support the books you've loved and make them free to everyone.
<dl>
<dt>So what is an unglued ebook?</dt>
<dd>An unglued ebook is a book that's added a <a href="http://creativecommons.org">Creative Commons</a> license, after payments to the author, publisher, or other rights holder.<br /><br />
<dd>An unglued ebook is a book that's added a <a href="https://creativecommons.org">Creative Commons</a> license, after payments to the author, publisher, or other rights holder.<br /><br />
What does this mean for you? If you're a book lover, you can read unglued ebooks for free, on the device of your choice, in the format of your choice, and share them with all your friends. If you're a library, you can lend them to your patrons with no checkout limits or simultaneous user restrictions, and preserve them however you think best. If you're a rights holder, you get a payments in lieu of further royalties, while retaining copyright and all interests in your work.
</dd>
@ -314,7 +314,7 @@ Under all CC licenses (except CC0), you <b>cannot</b>: remove the author's name
<dt>What does non-commercial mean under Creative Commons?</dt>
<dd>Creative Commons doesn't define "commercial" thoroughly (though it's worth reading what <a href="http://wiki.creativecommons.org/FAQ#Does_my_use_violate_the_NonCommercial_clause_of_the_licenses.3F">they have to say on the topic</a>). So as part of the Unglue.it process, ungluing rights holders agree that "For purposes of interpreting the CC License, Rights Holder agrees that 'non-commercial' use shall include, without limitation, distribution by a commercial entity without charge for access to the Work."<br /><br />
<dd>Creative Commons doesn't define "commercial" thoroughly (though it's worth reading what <a href="https://wiki.creativecommons.org/FAQ#Does_my_use_violate_the_NonCommercial_clause_of_the_licenses.3F">they have to say on the topic</a>). So as part of the Unglue.it process, ungluing rights holders agree that "For purposes of interpreting the CC License, Rights Holder agrees that 'non-commercial' use shall include, without limitation, distribution by a commercial entity without charge for access to the Work."<br /><br />
Unglue.it can't provide legal advice about how to interpret Creative Commons licenses, and we encourage you to read about the licenses yourself and, if necessary, seek qualified legal advice.</dd>
@ -344,7 +344,7 @@ Unglue.it also provides links to download previously unglued books, creative com
<dl>
<dt>Why does an unglued book have to be in copyright?</dt>
<dd>Because books out of copyright are already free for you to copy, remix, and share! If a book is in the <a href="http://en.wikipedia.org/wiki/Public_domain">public domain</a>, it doesn't need to be unglued.</dd>
<dd>Because books out of copyright are already free for you to copy, remix, and share! If a book is in the <a href="https://en.wikipedia.org/wiki/Public_domain">public domain</a>, it doesn't need to be unglued.</dd>
<dt>How can I tell if a book is in copyright or not?</dt>
@ -368,7 +368,7 @@ Unglue.it signs agreements assuring the copyright status of every work we unglue
<dd>To start a campaign, you need to be a verified rights holder who has signed a Platform Services Agreement with us. </dd>
<dt>What do I need to do to become an authorized Rights Holder on Unglue.it?</dt>
<dd>Contact our Rights Holder Relations team, <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>, to discuss signing our <a href="https://www.docracy.com/1mud3ve9w8/unglue-it-non-exclusive-platform-services-agreement">Platform Services Agreement (PSA)</a>, setting a revenue goal, and running a campaign on Unglue.it to release your book under a <a href="http://creativecommons.org">Creative Commons</a> license, or to raise money for a book you've already released under such a license.</dd>
<dd>Contact our Rights Holder Relations team, <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>, to discuss signing our <a href="https://www.docracy.com/1mud3ve9w8/unglue-it-non-exclusive-platform-services-agreement">Platform Services Agreement (PSA)</a>, setting a revenue goal, and running a campaign on Unglue.it to release your book under a <a href="https://creativecommons.org">Creative Commons</a> license, or to raise money for a book you've already released under such a license.</dd>
<dt>I haven't signed an agreement, but my books are listed on Unglue.it. What's going on?</dt>
<dd>If your book is listed on Unglue.it, it's because an unglue.it user has indicated some interested in the book. Perhaps your book has been added to a user's favorites list. Users can also use the site to post links to Creative Commons licensed or public domain books. Unglue.it works with non-profit sites such as Internet Archive to improve their coverage and preservation of Creative Commons licensed books.
@ -485,7 +485,7 @@ If you want to find an interesting campaign and don't have a specific book in mi
<p>This assumes that you have a (free) Google account.</p>
<ul class="bullets">
<li>Upload your PDF to <a href="http://drive.google.com">Google Drive</a>.</li>
<li>Upload your PDF to <a href="https://drive.google.com">Google Drive</a>.</li>
<li>Click on the Share button.</li>
<li>Change the visibility to public on the web and click Save, then Done.</li>
<li>In the File menu, click the Embed this PDF option.</li>
@ -646,7 +646,7 @@ If you're concerned a campaign may not reach its goal you have several options.
<dt>If I am an author do I have to talk to my publisher or agent first?</dt>
<dd>It is your responsibility to get advice on the current status of any contracts you may have ever had for the right to publish your work, whether or not a book is in print now. <a href="http://creativecommons.org">Creative Commons</a> licenses are media neutral and worldwide (English). You may need waivers from other parties who have exclusive licenses for this book.</dd>
<dd>It is your responsibility to get advice on the current status of any contracts you may have ever had for the right to publish your work, whether or not a book is in print now. <a href="https://creativecommons.org">Creative Commons</a> licenses are media neutral and worldwide (English). You may need waivers from other parties who have exclusive licenses for this book.</dd>
<dt>If I am a publisher, but I do not have an ebook royalty in my contract, can I sign your Platform Services Agreement?</dt>
@ -666,7 +666,7 @@ If you're concerned a campaign may not reach its goal you have several options.
<dt>Can an unglued ebook be issued only in the US?</dt>
<dd>No. An unglued ebook is released to the world. It uses a <a href="http://creativecommons.org">Creative Commons</a> license which is territory-neutral. That is, the unglued ebook can be read by anyone, anywhere in the world, subject always to the limitations of the license.</dd>
<dd>No. An unglued ebook is released to the world. It uses a <a href="https://creativecommons.org">Creative Commons</a> license which is territory-neutral. That is, the unglued ebook can be read by anyone, anywhere in the world, subject always to the limitations of the license.</dd>
{% endif %}
</dl>

View File

@ -75,7 +75,7 @@
<form id="clear_goodreads_id" method="post" action="{% url 'goodreads_flush_assoc' %}">
<p><input type="submit" value="Unlink your Goodreads account from Unglue.it" /></p>
</form>
<p>You can also revoke access for Unglue.it at <a href="http://www.goodreads.com/user/edit?tab=apps">Goodreads user apps panel</a>.</p>
<p>You can also revoke access for Unglue.it at <a href="https://www.goodreads.com/user/edit?tab=apps">Goodreads user apps panel</a>.</p>
<hr />

View File

@ -44,7 +44,7 @@ The library license gives download access to one library member at a time for 14
</ol>
</dd>
<dt>Stay in touch.</dt>
<dd>You can follow us on Twitter (<a href="http://twitter.com/unglueit">@unglueit</a>), <a href="http://facebook/com/unglueit">Facebook</a>, and our <a href="https://blog.unglue.it">blog</a>, and <a href="http://eepurl.com/fKLfI">subscribe to our newsletter</a> (1-2 emails per month).</dd>
<dd>You can follow us on Twitter (<a href="https://twitter.com/unglueit">@unglueit</a>), <a href="https://facebook/com/unglueit">Facebook</a>, and our <a href="https://blog.unglue.it">blog</a>, and <a href="http://eepurl.com/fKLfI">subscribe to our newsletter</a> (1-2 emails per month).</dd>
<dt>Spread the word.</dt>
<dd>There are social media sharing links on most pages on the site. There are some right here!
<div id="widgetcode">Copy/paste this into your site:<br /><textarea rows="7" cols="22">&lt;iframe src="https://{{request.META.HTTP_HOST}}/api/widget/{{work.first_isbn_13}}/" width="152" height="325" frameborder="0"&gt;&lt;/iframe&gt;</textarea></div>
@ -56,7 +56,7 @@ The library license gives download access to one library member at a time for 14
</ul>
<div class="clearfix"></div></dd>
<dt>Educate yourself and your patrons about ebook issues and Creative Commons licenses.</dt>
<dd>Checkout limits, publishers who won't sell ebooks to libraries, DRM, companies tracking readers' behavior, library prices far in excess of consumer costs, incompatible technologies and formats, cataloging silos...you know why it's hard for you to deliver a seamless ereading experience to your patrons. Make sure they know, too. And make sure everyone knows how solutions, like <a href="http://creativecommons.org">Creative Commons</a> licenses, can help libraries and readers while respecting copyright and protecting creators' rights.</dd>
<dd>Checkout limits, publishers who won't sell ebooks to libraries, DRM, companies tracking readers' behavior, library prices far in excess of consumer costs, incompatible technologies and formats, cataloging silos...you know why it's hard for you to deliver a seamless ereading experience to your patrons. Make sure they know, too. And make sure everyone knows how solutions, like <a href="https://creativecommons.org">Creative Commons</a> licenses, can help libraries and readers while respecting copyright and protecting creators' rights.</dd>
<dt>Support <a href="{% url 'campaign_list' 'ending' %}">our active campaigns</a>.</dt>
<dd>Ultimately ebooks can't be unglued unless authors and publishers are paid for their work. Many of our staunchest supporters are librarians. There are also several libraries which have supported campaigns, including Leddy Library (University of Windsor, Ontario); the University of Alberta library ; and the Z. Smith Reynolds library (Wake Forest University).</dd>
<dt>Give feedback and ask questions.</dt>

View File

@ -77,7 +77,7 @@ function highlightTarget(targetdiv) {
</a>
{% endif %}
{% if library.user.profile.facebook_id %}
<a href="http://www.facebook.com/profile.php?id={{library.user.profile.facebook_id}}" class="nounderline">
<a href="https://www.facebook.com/profile.php?id={{library.user.profile.facebook_id}}" class="nounderline">
<img src="/static/images/supporter_icons/facebook_square.png" alt="{{ supporter }}'s Facebook" title="{{ supporter }}'s Facebook" />
</a>
{% endif %}
@ -92,7 +92,7 @@ function highlightTarget(targetdiv) {
</a>
{% endif %}
{% if library.user.profile.librarything_id %}
<a href="http://www.librarything.com/profile/{{ library.user.profile.librarything_id }}" class="nounderline">
<a href="https://www.librarything.com/profile/{{ library.user.profile.librarything_id }}" class="nounderline">
<img src="/static/images/supporter_icons/librarything_square.png" alt="{{ supporter }}'s profile on LibraryThing" title="{{ supporter }}'s page on LibraryThing" />
</a>
{% endif %}

View File

@ -13,7 +13,7 @@
{% if books %}
<ul>
{% for book in books|slice:":50" %}
<li>{% if lt_api_key %}<img src="http://covers.librarything.com/devkey/{{lt_api_key}}/small/isbn/{{book.isbn}}" />{% endif %}<img src="http://covers.openlibrary.org/b/isbn/{{book.isbn}}-S.jpg" />{{book.title.title}} | ISBN:{{book.isbn}} | Work ID:{{book.work_id}} | Book ID:{{book.book_id}}</li>
<li>{% if lt_api_key %}<img src="https://covers.librarything.com/devkey/{{lt_api_key}}/small/isbn/{{book.isbn}}" />{% endif %}<img src="https://covers.openlibrary.org/b/isbn/{{book.isbn}}-S.jpg" />{{book.title.title}} | ISBN:{{book.isbn}} | Work ID:{{book.work_id}} | Book ID:{{book.book_id}}</li>
{% endfor %}
</ul>
{% endif %}

View File

@ -181,10 +181,10 @@ Please fix the following before launching your campaign:
{% ifnotequal campaign_status 'ACTIVE' %}
{% ifnotequal campaign.type 3 %}
<h3>License being offered</h3>
<p> This is the license you are offering to use once the campaign succeeds. For more information on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>. Once your campaign is active, you'll be able to switch to a less restrictive license, but not a more restrictive one. We encourage you to pick the least restrictive license you are comfortable with, as this will increase the ways people can use your unglued ebook and motivate more people to donate.</p>
<p> This is the license you are offering to use once the campaign succeeds. For more information on the licenses you can use, see <a href="https://creativecommons.org/licenses">Creative Commons: About the Licenses</a>. Once your campaign is active, you'll be able to switch to a less restrictive license, but not a more restrictive one. We encourage you to pick the least restrictive license you are comfortable with, as this will increase the ways people can use your unglued ebook and motivate more people to donate.</p>
{% else %}
<h3>License Being Used</h3>
<p> This is the license you are using. For more information on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>.
<p> This is the license you are using. For more information on the licenses you can use, see <a href="https://creativecommons.org/licenses">Creative Commons: About the Licenses</a>.
Once your campaign is active, you'll be able to switch to a less restrictive license, but not a more restrictive one.
We encourage you to pick the least restrictive license you are comfortable with, as this will increase the ways people can use your unglued ebook and motivate more people to participate.
</p>
@ -241,7 +241,7 @@ Please fix the following before launching your campaign:
{% else %}
<p>You are offering your ebook under a <b><a href="{{campaign.license_url }}">{{ campaign.license }}</a></b> license.</p>
{% endifnotequal %}
<p>Since your campaign is active, you may only change the license to remove restrictions. For more information on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>.</p>
<p>Since your campaign is active, you may only change the license to remove restrictions. For more information on the licenses you can use, see <a href="https://creativecommons.org/licenses">Creative Commons: About the Licenses</a>.</p>
<div>
{{ form.license.errors }}<span>{{ form.license }}</span>
</div>

View File

@ -42,7 +42,7 @@ onload = function(){
ISBN: <span itemprop="isbn">{{ edition.isbn_13 }}</span><br />
{% endif %}
{% if edition.oclc %}
OCLC: <a href="http://www.worldcat.org/oclc/{{ edition.oclc }}">{{ edition.oclc }}</a><br />
OCLC: <a href="https://www.worldcat.org/oclc/{{ edition.oclc }}">{{ edition.oclc }}</a><br />
{% endif %}
{% include 'edition_upload.html' %}

View File

@ -4,227 +4,31 @@
{% block extra_extra_head %}
{{ block.super }}
<link rel="stylesheet" href="/static/css/ui-lightness/jquery-ui-1.8.16.custom.css" type="text/css" media="screen">
<link href="/static/css/ui.fancytree.min.css" rel="stylesheet" type="text/css">
<style type="text/css">
ul.fancytree-container {
width: 100%;
height: 10em;
overflow: auto;
position: relative;
margin-top: 0.5em;
}
</style>
{{ form.media.css }}
<script type="text/javascript" src="{{ jquery_ui_home }}" ></script>
<script src="/static/js/jquery.fancytree-all.min.js" type="text/javascript"></script>
<script type="text/javascript">
$j(function(){
// Initialize Fancytree
$j("#tree").fancytree({
extensions: ["glyph"],
checkbox: true,
selectMode: 1,
glyph: {
map: {
doc: "fa fa-file-o",
docOpen: "fa fa-file-o",
checkbox: "fa fa-square-o",
checkboxSelected: "fa fa-check-square-o",
checkboxUnknown: "fa fa-square",
dragHelper: "fa arrow-right",
dropMarker: "fa long-arrow-right",
error: "fa fa-warning",
expanderClosed: "fa fa-caret-right",
expanderLazy: "fa fa-angle-right",
expanderOpen: "fa fa-caret-down",
folder: "fa fa-folder-o",
folderOpen: "fa fa-folder-open-o",
loading: "fa fa-spinner fa-pulse"
}
},
source: { url: "/bisac/tree", cache: true }
});
$j("#editform").submit(function() {
// Render hidden <input> elements for active and selected nodes
$j("#tree").fancytree("getTree").generateFormElements(selected="bisac");
//alert("POST data:\n" + $j.param($j(this).serializeArray()));
return true;
});
$j().ready(function(){
var contentblock = $j('#content-block');
contentblock.on("click", "span.deletebutton", function () {
var kw = $j(this).attr('data');
var li = $j(this).parent();
// perform action
{% if edition.work %}
jQuery.post('{% url 'kw_edit' edition.work.id %}', {'remove_kw': kw, 'csrfmiddlewaretoken': '{{ csrf_token }}' }, function(data) {
li.html('kw removed');
});
{% else %}
li.html('kw removed');
{% endif %}
});
// this is the id of the submit button
$j('#add_subject_submit').click(function(event) {
data= $j('#id_add_subject_0').attr('value')
if (data == 'xxbadform'){
alert("bad keyword");
} else {
$j('#kw_list').append('<li>' + data + '<input type="hidden" name="new_subject" value="'+data +'" /><span class="deletebutton" data="' + data +'">x</span></li>')
}; // data will be the added kw.
});
});
});
</script>
{{ form.media.js }}
{% endblock %}
{% block doccontent %}
{% if admin %}
{% if edition.pk %}
<h2>Edit Edition for <a href="{% url 'work' edition.work.id %}">{{ edition.work.title }}</a></h2>
{% else %}
<h2>Create New Edition</h2>
{% endif %}
<p>Title and ISBN 13 (or DOI, OCLCNum or URL) are required; the rest is optional, though a cover image is strongly recommended.</p>
<h2>Create New Edition</h2>
<p>Step 1 - <b>Identifier</b></p>
<p>To add a work and edition to Unglue.it, we need to start with an identifier. We'll see if we can find some metadata based on the identifier you give us. If you give us an ISBN, we'll often be able to find some. If there's no ISBN, give us a URL (a web address) and we'll check the page for bibliographic info. </p>
<form id="editform" enctype="multipart/form-data" method="POST" action="#">
{% csrf_token %}
{{ form.work }}
{{ form.non_field_errors }}
<!--{{ form.errors }}-->
<div>
<p><b>Title</b>: {{ form.title.errors }}{{ form.title }}</p>
<p><b>Publisher Name</b> : {{ form.publisher_name.errors }}{{ form.publisher_name }}<br />(If you change this, click another form element before submitting)</p>
{{ form.as_p }}
<!--{{ form.errors.as_data }}-->
<p>
<b>Authors</b>:
{% if edition.pk and edition.relators or edition.new_authors %}
<ul>
{% for relator in form.relators %}
<li>{{ relator.relator.name }} {{ relator.select }} <input type="submit" name="delete_author_{{ relator.relator.author.id }}" value="x" class="deletebutton" title="delete author"></li>
{% endfor %}
{% for author in edition.new_authors %}
<li>{{ author.0 }}<input type="hidden" name="new_author" value="{{ author.0 }}" /> ({{ author.1 }})<input type="hidden" name="new_author_relation" value="{{ author.1 }}" /></li>
{% endfor %}
</ul>
{% else %}
(None listed)
{% endif %}
<input type="submit" name="new_edition_identifier" style="font-size: larger;" value="Start Edition" id="submit">
</p>
<p><b>Add a Creator</b> (<I>Firstname Lastname</I>): {{ form.add_author.errors }}{{ form.add_author }}{{ form.add_author_relation.errors }}{{ form.add_author_relation }}
<input type="submit" name="add_author_submit" value="Add Author" id="submit_author"></p>
<p><b>Language</b>: {{ form.language.errors }}{{ form.language }}</p>
{% if edition.pk %}
<p><b>Add a Related Work</b>: {{ form.add_work_relation.errors }}{{ form.add_work_relation }} of {{ form.add_related_work.errors }}{{ form.add_related_work }}</p>
<ul>{% for work_rel in edition.work.works_related_to.all %}
<li>
This work is a {{ work_rel.relation }} of <a href="{% url 'work' work_rel.from_work.id %}">{{ work_rel.from_work }}</a>.
<input type="submit" name="delete_work_rel_{{ work_rel.id }}" value="x" class="deletebutton" title="delete work relation">
</li>
{% endfor %}
{% for work_rel in edition.work.works_related_from.all %}
<li>
<a href="{% url 'work' work_rel.to_work.id %}">{{ work_rel.to_work }}</a> is a {{ work_rel.relation }} of this work.
<input type="submit" name="delete_work_rel_{{ work_rel.id }}" value="x" class="deletebutton" title="delete work relation">
</li>
{% endfor %}</ul>
{% endif %}
<p><b>Age Level</b>: {{ form.age_level.errors }}{{ form.age_level }}</p>
<p><b>Edition Note</b>: {{ form.note.errors }}{{ form.note }}</p>
<h4> Identifiers </h4>
{% if id_msg %} <span class="errorlist">{{ id_msg }} </span>{% endif %}
<p> Enter 'delete' to remove the identifier. </p>
<p><b>ISBN-13</b>: {{ form.isbn.errors }}{{ form.isbn }}</p>
<p><b>OCLC Number</b>: {{ form.oclc.errors }}{{ form.oclc }}</p>
<p><b>Google Books ID</b>: {{ form.goog.errors }}{{ form.goog }}</p>
<p><b>GoodReads ID</b>: {{ form.gdrd.errors }}{{ form.gdrd }}</p>
<p><b>LibraryThing ID</b>: {{ form.thng.errors }}{{ form.thng }}</p>
<p><b>DOI</b>: {{ form.doi.errors }}{{ form.doi }}</p>
<p><b>HTTP(S) ID</b>: {{ form.http.errors }}{{ form.http }}</p>
<p><b>Description</b>: <br />
{{ form.description.errors }}{{ form.description }}<br />
(<i>{% if work.last_campaign %}
{% ifequal work.last_campaign.type 3 %}
This will appear in the Description tab on the book page.
{% else %}
The campaign pitch will override this description.
{% endifequal %}
{% else %}
This will appear in the Description tab on the book page.
{% endif %}
</i>)</p>
<p><b>Publication Date</b> (<I>four-digit year</I>): {{ form.publication_date.errors }}{{ form.publication_date }}</p>
<p><b>Subjects</b>:
<ul id="kw_list">
{% if edition.work.pk and edition.work.subjects %}
{% for subject in edition.work.subjects.all %}
<li>{{ subject.name }}
{% if subject.authority %}
({{subject.authority}})
{% endif %}
<span class="deletebutton" data="{{ subject.name }}">x</span></li>
{% endfor %}
{% endif %}
{% for new_subject in edition.new_subjects %}
<li>{{ new_subject }}<input type="hidden" name="new_subject" value="{{ new_subject }}" /></li>
{% endfor %}
</ul>
<b>Add a Subject</b>: {{ form.add_subject.errors }}{{ form.add_subject }}
<a class="fakeinput" id="add_subject_submit" style="font-size: smaller;" >Add Subject</a></p>
<p id="tree" name="is_bisac"><b>Add BISAC Subject</b>:
</p>
<p><b>Cover Image</b>: <br />
{% if edition.cover_image %}
<img src="{{edition.cover_image}}" /><br />
{% else %}
[ no cover specified for this edition ]<br />
{% endif %}
{{ form.cover_image.errors }}{{ form.cover_image }}{{ form.cover_image.help_text }}
(<i>Enter a URL for an image, at least 300 px wide. The image will be scaled to the proportions of a 6x9 cover. </i>)<br />
OR...<br />
{{ form.coverfile.errors }}{{ form.coverfile }}{{ form.coverfile.help_text }}
(<i>upload a cover image file (we'll automatically size if for you). </i>)<br />
</p>
</div>
<input type="submit" name="create_new_edition" style="font-size: larger;" value="{% if edition.pk %}Save Edits{% else %}Create Edition{% endif %}" id="submit">
</form>
{% if edition.work %}
<h2>More Edition Management</h2>
<div><a href="{% url 'merge' edition.work.id %}">Merge other works into this one</a></div>
<div><a href="{% url 'work_editions' edition.work.id %}">Remove editions from this work</a></div>
{% if edition.id %}
<div><a href="{% url 'manage_ebooks' edition.id %}">Add ebooks for this edition</a></div>
{% endif %}
{% if request.user.is_staff %}
<div><a href="{% url 'feature' edition.work.id %}">Feature this work today</a></div>
{% endif %}
<br />
{% endif %}
{% else %}
{% if edition.work %}
{% include 'edition_display.html' %}
{% else %}
Sorry, there's no work specified.
{% endif %}
{% endif %}
{% endblock %}

View File

@ -2,7 +2,7 @@
Your donation of ${{transaction.max_amount|default:"0"}} to the Free Ebook Foundation will help us make free ebooks of all types more accessible for those that need them. The Free Ebook Foundation is a US 501(c)3 non-profit organization. Our tax ID number is 61-1767266. Your gift is tax deductible to the full extent provided by the law.
For more information about the Free Ebook Foundation, visit http://ebookfoundation.org/
For more information about the Free Ebook Foundation, visit https://ebookfoundation.org/
Thank you again for your generous support.

View File

@ -24,7 +24,7 @@
{% block comments_textual %}
<p>Your donation of ${{transaction.max_amount|default:"0"}} to the Free Ebook Foundation will help us make free ebooks of all types more accessible for those that need them. The Free Ebook Foundation is a US 501(c)3 non-profit organization. Our tax ID number is 61-1767266. Your gift is tax deductible to the full extent provided by the law.<p>
<p>For more information about the Free Ebook Foundation, visit <a href="http://ebookfoundation.org/ ">our website</a>.
<p>For more information about the Free Ebook Foundation, visit <a href="https://ebookfoundation.org/ ">our website</a>.
</p>
<p>Thank you again for your support.
</p>

View File

@ -2,7 +2,7 @@ Your Platform Services Agreement has been accepted and you're now an official Un
Here's what to do next. Find your book(s) on Unglue.it. On the More... tab of the book page, you'll now see an option to claim the book. Do this. We'll follow up. Once we've approved your claim, you'll be able to run campaigns for the book.
If your book isn't listed in Google Books (which powers our search), you won't be able to find it at Unglue.it. That's okay. You can submit your books for inclusion in Google's search results: http://books.google.com/googlebooks/publishers.html . We can also create a custom page for you; just notify us.
If your book isn't listed in Google Books (which powers our search), you won't be able to find it at Unglue.it. That's okay. You can submit your books for inclusion in Google's search results: https://books.google.com/googlebooks/publishers.html . We can also create a custom page for you; just notify us.
You can also start thinking ahead about what you'd like your campaigns to look like and how you'd like to publicize them. Some good things to brainstorm: your campaign pitch; any photos or video you can include; compelling premiums you might be able to offer; what you want your target to be and how long you think your campaign should last; and how to share your campaign with your social networks (online and off) and media contacts.

View File

@ -12,11 +12,11 @@
{% block comments_textual %}
{% ifequal campaign.type 1 %}
<div>Congratulations! You wished for a campaign, and here it is. If ungluers like you pledge {{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d, Y" }}, <I>{{ campaign.work.title }}</i> will be released under a <a href="http://creativecommons.org">Creative Commons</a> license for all to enjoy.</div>
<div>Congratulations! You wished for a campaign, and here it is. If ungluers like you pledge {{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d, Y" }}, <I>{{ campaign.work.title }}</i> will be released under a <a href="https://creativecommons.org">Creative Commons</a> license for all to enjoy.</div>
<div>You can help! <a href="{% url 'pledge' campaign.work.id %}">Pledge</a> any amount, and use the sharing options on the <a href="{% url 'work' campaign.work.id %}">campaign page</a> to tell your friends.</a></div>
{% endifequal %}
{% ifequal campaign.type 2 %}
<div>Great! You wished for a campaign, and here it is. Someday, the book will be released under a <a href="http://creativecommons.org">Creative Commons license</a> for everyone to enjoy. Every copy purchased brings that day {{ campaign.days_per_copy|floatformat }} days sooner.</div>
<div>Great! You wished for a campaign, and here it is. Someday, the book will be released under a <a href="https://creativecommons.org">Creative Commons license</a> for everyone to enjoy. Every copy purchased brings that day {{ campaign.days_per_copy|floatformat }} days sooner.</div>
<div>You can help! <a href="{% url 'purchase' campaign.work.id %}">Purchase</a> a copy, and use the sharing options on the <a href="{% url 'work' campaign.work.id %}">campaign page</a> to tell your friends.</a></div>
{% endifequal %}
{% ifequal campaign.type 3 %}

View File

@ -20,7 +20,7 @@ URL: {{ ebook.download_url }}
{% if work.ebooks.0.rights == 'PD-US' %}
A public domain ebook belongs to all of us. You can do anything you like with it.
{% else %}
The Creative Commons licensing terms for {{ work.title }} allow you to redistribute the files under the specified license terms. There's no DRM. For full details, see http://creativecommons.org/licenses/.
The Creative Commons licensing terms for {{ work.title }} allow you to redistribute the files under the specified license terms. There's no DRM. For full details, see https://creativecommons.org/licenses/.
{% endif %}
{% if work.last_campaign_status == 'SUCCESSFUL' %}

View File

@ -29,7 +29,7 @@
<h2 class="thank-you">Thank you!</h2>
{% if not campaign %}
<p class="pledge_complete">You've just donated ${{ transaction.amount|floatformat:2|intcomma }} to the <a href="http://ebookfoundation.org">Free Ebook Foundation</a></p>
<p class="pledge_complete">You've just donated ${{ transaction.amount|floatformat:2|intcomma }} to the <a href="https://ebookfoundation.org">Free Ebook Foundation</a></p>
{% endif %}
{% ifequal campaign.type 1 %}
<p class="pledge_complete">You've just {% if modified %}modified your pledge for{% else %}pledged{% endif %} ${{ transaction.amount|floatformat:2|intcomma }} to <I><a href="{% url 'work' work.id %}">{{ work.title }}</a></I>. If it reaches its goal of ${{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d Y"}}, it will be unglued for all to enjoy.</p>
@ -74,8 +74,8 @@
{% else %}
<ul class="social menu pledge">
<a href="https://www.facebook.com/sharer.php?u=http://ebookfoundation.org"><li class="facebook first"><span>Facebook</span></li></a>
<a href="https://twitter.com/intent/tweet?url=http://ebookfoundation.org&amp;text=I%20just%20supported%20The%20Free%20Ebook%20Foundation.%20Will%20you%20join%20me%3F"><li class="twitter"><span>Twitter</span></li></a>
<a href="https://www.facebook.com/sharer.php?u=https://ebookfoundation.org"><li class="facebook first"><span>Facebook</span></li></a>
<a href="https://twitter.com/intent/tweet?url=https://ebookfoundation.org&amp;text=I%20just%20supported%20The%20Free%20Ebook%20Foundation.%20Will%20you%20join%20me%3F"><li class="twitter"><span>Twitter</span></li></a>
</ul>
{% endif %}
</div>

View File

@ -60,10 +60,10 @@ When books have a clear, established legal license which promotes use, they can
<dt>Where?</dt>
<dd>The Free Ebook Foundation is a New Jersey non-for-profit corporation, but its employees and contractors live and work across North America. The best way to contact us is by email, <a href="mailto:press@gluejar.com">freeebookfoundation@gmail.com</a>.</dd>
<dt>What's your technology?</dt>
<dd>Unglue.it is built using <a href="http://python.org/">Python</a> and the <a href="https://www.djangoproject.com/">Django framework</a>. We use data from the <a href="http://code.google.com/apis/books/docs/v1/getting_started.html">Google Books</a>, <a href="http://openlibrary.org/developers/api">Open Library</a>, <a href="http://www.librarything.com/api">LibraryThing</a>, and <a href="http://www.goodreads.com/api">GoodReads</a> APIs; we appreciate that they've made these APIs available, and we're returning the favor with <a href="/api/help">our own API</a>. You're welcome to use it. We use <a href="http://lesscss.org/">Less</a> to organize our CSS. We process pledges with <a href="https://stripe.com/">Stripe</a>. We collaborate on our code at <a href="https://github.com/">GitHub</a> and deploy to the cloud with <a href="http://aws.amazon.com/ec2/">Amazon EC2</a>.</dd>
<dd>Unglue.it is built using <a href="http://python.org/">Python</a> and the <a href="https://www.djangoproject.com/">Django framework</a>. We use data from the <a href="https://code.google.com/apis/books/docs/v1/getting_started.html">Google Books</a>, <a href="https://openlibrary.org/developers/api">Open Library</a>, <a href="https://www.librarything.com/api">LibraryThing</a>, and <a href="https://www.goodreads.com/api">GoodReads</a> APIs; we appreciate that they've made these APIs available, and we're returning the favor with <a href="/api/help">our own API</a>. You're welcome to use it. We use <a href="http://lesscss.org/">Less</a> to organize our CSS. We process pledges with <a href="https://stripe.com/">Stripe</a>. We collaborate on our code at <a href="https://github.com/">GitHub</a> and deploy to the cloud with <a href="https://aws.amazon.com/ec2/">Amazon EC2</a>.</dd>
<dt>What licenses are supported? </dt>
<dd>
We support the <a href="http://creativecommons.org/licenses/">Creative Commons</a> licenses in all of our programs. Ebooks with these licenses are free and legal for everyone to read, copy, and share worldwide and requires that the author attribution.<br /><br />
We support the <a href="https://creativecommons.org/licenses/">Creative Commons</a> licenses in all of our programs. Ebooks with these licenses are free and legal for everyone to read, copy, and share worldwide and requires that the author attribution.<br /><br />
We support a additional Free Licenses in our Thanks-for-Ungluing program.
</dd>
@ -110,7 +110,7 @@ We support a additional Free Licenses in our Thanks-for-Ungluing program.
<div>
<iframe width="480" height="274" src="//www.youtube-nocookie.com/embed/YNGPCR-iM24?rel=0" frameborder="0" allowfullscreen></iframe><br />
<I>November 2011</I><br />
Eric Hellman, "The Network is Overrated"; talk at <a href="http://bib.archive.org/">Books in Browsers</a> 2011.
Eric Hellman, "The Network is Overrated"; talk at <a href="https://bib.archive.org/">Books in Browsers</a> 2011.
</div>
</div>

View File

@ -70,11 +70,11 @@ We use <a href=" http://w3c.github.io/webappsec/specs/referrer-policy">referrer
<ul class="bullets">
<li>
We use <a href="http://www.google.com/analytics/">Google Analytics</a>. If you believe their <a href="http://www.google.com/analytics/terms/us.html">terms of service</a>, they can't share this data outside Google. But Google is fundamentally an advertising company, and it's very likely that Google knows exactly who you are.
We use <a href="https://www.google.com/analytics/">Google Analytics</a>. If you believe their <a href="https://www.google.com/analytics/terms/us.html">terms of service</a>, they can't share this data outside Google. But Google is fundamentally an advertising company, and it's very likely that Google knows exactly who you are.
</li>
<li>
We use cover images from <a href="http://books.google.com/">Google Books</a>. We don't really know if Google Books does much with the data they receive as a result. Google isn't learning anything they don't already know from analytics, but the <a href="http://books.google.com/intl/en/googlebooks/privacy.html">Google Books Privacy Policy</a> is interesting reading for privacy wonks.
We use cover images from <a href="https://books.google.com/">Google Books</a>. We don't really know if Google Books does much with the data they receive as a result. Google isn't learning anything they don't already know from analytics, but the <a href="https://books.google.com/intl/en/googlebooks/privacy.html">Google Books Privacy Policy</a> is interesting reading for privacy wonks.
</li>
<li>
@ -94,7 +94,7 @@ When you download an ebook via Unglue.it, it usually comes from a third party. W
<ul class="bullets">
<li><a href="https://archive.org/about/terms.php">Internet Archive</a> (Excellent privacy!) </li>
<li><a href="http://www.gutenberg.org/wiki/Gutenberg:Privacy_policy">Project Gutenberg</a> (insecure) </li>
<li><a href="https://www.gutenberg.org/wiki/Gutenberg:Privacy_policy">Project Gutenberg</a> (insecure) </li>
<li><a href="http://www.oapen.org/about">OAPEN</a> (no privacy policy, insecure) </li>
<li><a href="http://www.hathitrust.org/privacy">Hathitrust</a> (insecure) </li>
<li><a href="https://help.github.com/articles/github-privacy-policy/">Github</a> </li>
@ -115,7 +115,7 @@ We have used a small number of third party services that do not set cookies to t
<ul class="bullets">
<li>
We've worked to pare this list down to one. <a href="http://aws.amazon.com/s3/">Amazon S3</a>. We disable logging for our S3 buckets, but we're not aware of any privacy commitment by Amazon Web Services regarding their logging practices for S3 (Simple Storage Service) separate from the Amazon privacy policies. But we can verify that S3 sets no tracking cookies.
We've worked to pare this list down to one. <a href="https://aws.amazon.com/s3/">Amazon S3</a>. We disable logging for our S3 buckets, but we're not aware of any privacy commitment by Amazon Web Services regarding their logging practices for S3 (Simple Storage Service) separate from the Amazon privacy policies. But we can verify that S3 sets no tracking cookies.
</li>
</ul>

View File

@ -150,7 +150,7 @@ function highlightTarget(targetdiv) {
</a>
{% endif %}
{% if supporter.profile.librarything_id %}
<a href="http://www.librarything.com/profile/{{ supporter.profile.librarything_id }}" class="nounderline">
<a href="https://www.librarything.com/profile/{{ supporter.profile.librarything_id }}" class="nounderline">
<img src="/static/images/supporter_icons/librarything_square.png" alt="{{ supporter }}'s profile on LibraryThing" title="{{ supporter }}'s page on LibraryThing" />
</a>
{% endif %}

View File

@ -214,7 +214,7 @@
<a id="find-google" href="{{ work.googlebooks_url }}"><img src="/static/images/supporter_icons/googlebooks_square.png" title="Find on Google Books" alt="Find on Google Books" /></a>
{% endif %}
{% if work.first_oclc %}
<a rel="nofollow" id="find-oclc" href="http://www.worldcat.org/oclc/{{ work.first_oclc }}"><img src="/static/images/supporter_icons/worldcat_square.png" title="Find on Worldcat" alt="Find on Worldcat" /></a>
<a rel="nofollow" id="find-oclc" href="https://www.worldcat.org/oclc/{{ work.first_oclc }}"><img src="/static/images/supporter_icons/worldcat_square.png" title="Find on Worldcat" alt="Find on Worldcat" /></a>
{% endif %}
<a rel="nofollow" class="find-openlibrary" href="{% url 'work_openlibrary' work_id %}"><img src="/static/images/supporter_icons/openlibrary_square.png" title="Find on OpenLibrary" alt="Find on OpenLibrary" /></a>
<a rel="nofollow" class="find-goodreads" href="{% url 'work_goodreads' work_id %}"><img src="/static/images/supporter_icons/goodreads_square.png" title="Find on GoodReads" alt="Find on GoodReads" /></a>

View File

@ -215,7 +215,7 @@ class PledgingUiTests(TestCase):
# login and heck whether user logged in
self.assertTrue(self.client.login(username=self.USERNAME, password=self.PASSWORD))
# http://stackoverflow.com/a/6013115
# https://stackoverflow.com/a/6013115
#self.assertEqual(self.client.session['_auth_user_id'], self.user.pk)
user = auth.get_user(self.client)

View File

@ -29,7 +29,7 @@ urlpatterns = [
url(r"^rightsholders/campaign/(?P<id>\d+)/results/$", views.manage_campaign, {'action': 'results'}, name="campaign_results"),
url(r"^rightsholders/campaign/(?P<id>\d+)/(?P<ebf>\d+)/makemobi/$", views.manage_campaign, {'action': 'makemobi'}, name="makemobi"),
url(r"^rightsholders/campaign/(?P<id>\d+)/mademobi/$", views.manage_campaign, {'action': 'mademobi'}, name="mademobi"),
url(r"^rightsholders/edition/(?P<work_id>\d*)/(?P<edition_id>\d*)$", views.new_edition, {'by': 'rh'}, name="rh_edition"),
url(r"^rightsholders/edition/(?P<work_id>\d*)/(?P<edition_id>\d*)$", views.edit_edition, {'by': 'rh'}, name="rh_edition"),
url(r"^rightsholders/edition/(?P<edition_id>\d*)/upload/$", views.edition_uploads, name="edition_uploads"),
url(r"^rightsholders/claim/$", views.claim, name="claim"),
url(r"^rightsholders/surveys/$", views.surveys, name="surveys"),
@ -87,8 +87,8 @@ urlpatterns = [
url(r"^work/(?P<work_id>\d+)/librarything/$", views.work_librarything, name="work_librarything"),
url(r"^work/(?P<work_id>\d+)/goodreads/$", views.work_goodreads, name="work_goodreads"),
url(r"^work/(?P<work_id>\d+)/openlibrary/$", views.work_openlibrary, name="work_openlibrary"),
url(r"^new_edition/(?P<work_id>)(?P<edition_id>)$", views.new_edition, name="new_edition"),
url(r"^new_edition/(?P<work_id>\d*)/(?P<edition_id>\d*)$", views.new_edition, name="new_edition"),
url(r"^new_edition/(?P<work_id>)(?P<edition_id>)$", views.edit_edition, name="new_edition"),
url(r"^new_edition/(?P<work_id>\d*)/(?P<edition_id>\d*)$", views.edit_edition, name="new_edition"),
url(r"^manage_ebooks/(?P<edition_id>\d*)$", views.manage_ebooks, name="manage_ebooks"),
url(r"^googlebooks/(?P<googlebooks_id>.+)/$", views.googlebooks, name="googlebooks"),
url(r"^download_ebook/(?P<ebook_id>\w+)/$", views.download_ebook, name="download_ebook"),

View File

@ -91,7 +91,6 @@ from regluit.frontend.forms import (
CustomPremiumForm,
OfferForm,
EditManagersForm,
EditionForm,
PledgeCancelForm,
getTransferCreditForm,
CCForm,
@ -137,6 +136,9 @@ from regluit.marc.views import qs_marc_records
from questionnaire.models import Landing, Questionnaire
from questionnaire.views import export_summary as answer_summary, export_csv as export_answers
from .bibedit import edit_edition, user_can_edit_work, safe_get_work, get_edition
logger = logging.getLogger(__name__)
def static_redirect_view(request, file_name, dir=""):
@ -186,16 +188,6 @@ def next(request):
else:
return HttpResponseRedirect('/')
def safe_get_work(work_id):
"""
use this rather than querying the db directly for a work by id
"""
try:
work = models.safe_get_work(work_id)
except models.Work.DoesNotExist:
raise Http404
return work
def cover_width(work):
if work.percent_of_goal() < 100:
cover_width = 100 - work.percent_of_goal()
@ -426,10 +418,7 @@ def edition_uploads(request, edition_id):
context = {}
if not request.user.is_authenticated() :
return render(request, "admins_only.html")
try:
edition = models.Edition.objects.get(id = edition_id)
except models.Edition.DoesNotExist:
raise Http404
edition = get_edition(edition_id)
campaign_type = edition.work.last_campaign().type
if not user_can_edit_work(request.user, edition.work):
return render(request, "admins_only.html")
@ -486,179 +475,6 @@ def edition_uploads(request, edition_id):
})
return render(request, 'edition_uploads.html', context)
def add_subject(subject_name, work, authority=''):
try:
subject = models.Subject.objects.get(name=subject_name)
except models.Subject.DoesNotExist:
subject = models.Subject.objects.create(name=subject_name, authority=authority)
subject.works.add(work)
def user_can_edit_work(user, work):
if user.is_staff :
return True
elif work and work.last_campaign():
return user in work.last_campaign().managers.all()
elif user.rights_holder.count() and (work == None or not work.last_campaign()): # allow rights holders to edit unless there is a campaign
return True
else:
return False
@login_required
def new_edition(request, work_id, edition_id, by=None):
if not request.user.is_authenticated():
return render(request, "admins_only.html")
# if the work and edition are set, we save the edition and set the work
language = 'en'
age_level = ''
description = ''
title = ''
if work_id:
work = safe_get_work(work_id)
language = work.language
age_level = work.age_level
description = work.description
title = work.title
else:
work = None
alert = ''
admin = user_can_edit_work(request.user, work)
if edition_id:
try:
edition = models.Edition.objects.get(id = edition_id)
except models.Edition.DoesNotExist:
raise Http404
if work:
edition.work = work
language = edition.work.language
age_level = edition.work.age_level
description = edition.work.description
else:
edition = models.Edition()
if work:
edition.work = work
edition.publication_date = work.earliest_publication_date
edition.new_authors = []
for relator in work.relators():
edition.new_authors.append((relator.author.name, relator.relation.code))
initial = {
'language': language,
'age_level': age_level,
'publisher_name': edition.publisher_name,
'isbn': edition.isbn_13,
'oclc': edition.oclc,
'description': description,
'title': title,
'goog': edition.googlebooks_id,
'gdrd': edition.goodreads_id,
'thng': edition.librarything_id,
'http': edition.http_id,
'doi': edition.id_for('doi'),
}
if request.method == 'POST':
form = None
edition.new_authors = zip(request.POST.getlist('new_author'), request.POST.getlist('new_author_relation'))
edition.new_subjects = request.POST.getlist('new_subject')
if edition.id and admin:
for author in edition.authors.all():
if request.POST.has_key('delete_author_%s' % author.id):
edition.remove_author(author)
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
break
work_rels = models.WorkRelation.objects.filter(Q(to_work=work) | Q(from_work=work))
for work_rel in work_rels:
if request.POST.has_key('delete_work_rel_%s' % work_rel.id):
work_rel.delete()
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
break
if request.POST.has_key('add_author_submit') and admin:
new_author_name = request.POST['add_author'].strip()
new_author_relation = request.POST['add_author_relation']
try:
author = models.Author.objects.get(name=new_author_name)
except models.Author.DoesNotExist:
author = models.Author.objects.create(name=new_author_name)
edition.new_authors.append((new_author_name, new_author_relation))
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
elif not form and admin:
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
if form.is_valid():
form.save()
if not work:
work = models.Work(
title=form.cleaned_data['title'],
language=form.cleaned_data['language'],
age_level=form.cleaned_data['age_level'],
description=form.cleaned_data['description'],
)
work.save()
edition.work = work
edition.save()
else:
work.description = form.cleaned_data['description']
work.title = form.cleaned_data['title']
work.publication_range = None # will reset on next access
work.language = form.cleaned_data['language']
work.age_level = form.cleaned_data['age_level']
work.save()
id_msg = ""
for id_type in ('isbn', 'oclc', 'goog', 'thng', 'gdrd', 'http', 'doi'):
id_val = form.cleaned_data[id_type]
if id_val == 'delete':
edition.identifiers.filter(type=id_type).delete()
elif id_val:
existing = models.Identifier.objects.filter(type=id_type, value=form.cleaned_data[id_type])
if existing.count() and existing[0].edition != edition:
return render(request, 'new_edition.html', {
'form': form, 'edition': edition, 'admin': admin,
'id_msg': "%s = %s already exists"%(id_type, id_val),
})
else:
models.Identifier.set(type=id_type, value=id_val, edition=edition, work=work)
for relator in edition.relators.all():
if request.POST.has_key('change_relator_%s' % relator.id):
new_relation = request.POST['change_relator_%s' % relator.id]
relator.set(new_relation)
related_work = form.cleaned_data['add_related_work']
if related_work:
models.WorkRelation.objects.get_or_create(
to_work=work,
from_work=related_work,
relation=form.cleaned_data['add_work_relation'],
)
for (author_name, author_relation) in edition.new_authors:
edition.add_author(author_name, author_relation)
if form.cleaned_data.has_key('bisac'):
bisacsh = form.cleaned_data['bisac']
while bisacsh:
add_subject(bisacsh.full_label, work, authority="bisacsh")
bisacsh = bisacsh.parent
for subject_name in edition.new_subjects:
add_subject(subject_name, work)
work_url = reverse('work', kwargs={'work_id': edition.work.id})
cover_file = form.cleaned_data.get("coverfile", None)
if cover_file:
# save it
cover_file_name = '/Users/%s/covers/%s/%s' % (request.user.username, edition.pk, cover_file.name)
file = default_storage.open(cover_file_name, 'w')
file.write(cover_file.read())
file.close()
#and put its url into cover_image
edition.cover_image = default_storage.url(cover_file_name)
edition.save()
return HttpResponseRedirect(work_url)
else:
form = EditionForm(instance=edition, initial=initial)
return render(request, 'new_edition.html', {
'form': form, 'edition': edition, 'admin':admin, 'alert':alert,
})
@login_required
def manage_ebooks(request, edition_id, by=None):
if edition_id:
@ -2761,11 +2577,11 @@ def work_librarything(request, work_id):
url = work.librarything_url
elif isbn:
# TODO: do the redirect here and capture the work id?
url = "http://www.librarything.com/isbn/%s" % isbn
url = "https://www.librarything.com/isbn/%s" % isbn
else:
term = work.title + " " + work.author()
q = urlencode({'searchtpe': 'work', 'term': term})
url = "http://www.librarything.com/search.php?" + q
url = "https://www.librarything.com/search.php?" + q
return HttpResponseRedirect(url)
def work_openlibrary(request, work_id):
@ -2777,20 +2593,20 @@ def work_openlibrary(request, work_id):
url = work.openlibrary_url
elif len(isbns) > 0:
isbns = ",".join(isbns)
u = 'http://openlibrary.org/api/books?bibkeys=%s&jscmd=data&format=json' % isbns
u = 'https://openlibrary.org/api/books?bibkeys=%s&jscmd=data&format=json' % isbns
try:
j = json.loads(requests.get(u).content)
# as long as there were some matches get the first one and route to it
if len(j.keys()) > 0:
first = j.keys()[0]
url = "http://openlibrary.org" + j[first]['key']
url = "https://openlibrary.org" + j[first]['key']
except ValueError:
# fail at openlibrary
logger.warning("failed to get OpenLibrary json at %s" % u)
# fall back to doing a search on openlibrary
if not url:
q = urlencode({'q': work.title + " " + work.author()})
url = "http://openlibrary.org/search?" + q
url = "https://openlibrary.org/search?" + q
return HttpResponseRedirect(url)
def work_goodreads(request, work_id):
@ -2799,10 +2615,10 @@ def work_goodreads(request, work_id):
if work.goodreads_id:
url = work.goodreads_url
elif isbn:
url = "http://www.goodreads.com/book/isbn/%s" % isbn
url = "https://www.goodreads.com/book/isbn/%s" % isbn
else:
q = urlencode({'query': work.title + " " + work.author()})
url = "http://www.goodreads.com/search?" + q
url = "https://www.goodreads.com/search?" + q
return HttpResponseRedirect(url)
@login_required
@ -3403,10 +3219,10 @@ def send_to_kindle(request, work_id, javascript='0'):
"""
Amazon SES has a 10 MB size limit (http://aws.amazon.com/ses/faqs/#49) in messages sent
Amazon SES has a 10 MB size limit (https://aws.amazon.com/ses/faqs/#49) in messages sent
to determine whether the file will meet this limit, we probably need to compare the
size of the mime-encoded file to 10 MB. (and it's unclear exactly what the Amazon FAQ means precisely by
MB either: http://en.wikipedia.org/wiki/Megabyte) http://www.velocityreviews.com/forums/t335208-how-to-get-size-of-email-attachment.html might help
MB either: https://en.wikipedia.org/wiki/Megabyte) http://www.velocityreviews.com/forums/t335208-how-to-get-size-of-email-attachment.html might help
for the moment, we will hardwire a 749229 limit in filesize:
* assume conservative size of megabyte, 1000000B

333
frontend/views/bibedit.py Normal file
View File

@ -0,0 +1,333 @@
'''
views to edit bibmodels
'''
from django.contrib.auth.decorators import login_required
from django.core.files.storage import default_storage
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.http import (
HttpResponseRedirect,
Http404,
)
from django.shortcuts import render
from regluit.core import models
from regluit.core.bookloader import (
add_by_googlebooks_id,
add_by_isbn,
add_by_oclc,
add_by_webpage,
)
from regluit.core.parameters import WORK_IDENTIFIERS
from regluit.core.loaders.utils import ids_from_urls
from regluit.frontend.forms import EditionForm, IdentifierForm
def user_can_edit_work(user, work):
'''
Check if a user is allowed to edit the work
'''
if user.is_staff :
return True
elif work and work.last_campaign():
return user in work.last_campaign().managers.all()
elif user.rights_holder.count() and (work == None or not work.last_campaign()):
# allow rights holders to edit unless there is a campaign
return True
else:
return False
def safe_get_work(work_id):
"""
use this rather than querying the db directly for a work by id
"""
try:
work = models.safe_get_work(work_id)
except models.Work.DoesNotExist:
raise Http404
return work
def add_subject(subject_name, work, authority=''):
'''
add a subject to a work
'''
try:
subject = models.Subject.objects.get(name=subject_name)
except models.Subject.DoesNotExist:
subject = models.Subject.objects.create(name=subject_name, authority=authority)
subject.works.add(work)
def get_edition(edition_id):
'''
get edition and 404 if not found
'''
try:
return models.Edition.objects.get(id = edition_id)
except models.Edition.DoesNotExist:
raise Http404 (duplicate-code)
def get_edition_for_id(id_type, id_value, user=None):
''' the identifier is assumed to not be in database '''
identifiers = {id_type: id_value}
if id_type == 'http':
# check for urls implying other identifiers
identifiers.update(ids_from_urls(id_value))
for new_id_type in identifiers.keys():
idents = models.Identifier.objects.filter(
type=new_id_type,
value=identifiers[new_id_type],
)
if idents:
ident = idents[0]
return ident.edition if ident.edition else ident.work.preferred_edition
#need to make a new edition
if identifiers.has_key('goog'):
edition = add_by_googlebooks_id(identifiers['goog'])
if edition:
return edition
if identifiers.has_key('isbn'):
edition = add_by_isbn(identifiers['isbn'])
if edition:
return edition
if identifiers.has_key('oclc'):
edition = add_by_oclc(identifiers['oclc'])
if edition:
return edition
if identifiers.has_key('glue'):
try:
work = models.safe_get_work(identifiers['glue'])
return work.preferred_edition
except:
pass
if identifiers.has_key('http'):
edition = add_by_webpage(identifiers['http'], user=user)
return edition
# return a dummy edition and identifier
title = '!!! missing title !!!'
work = models.Work.objects.create(title=title)
edition = models.Edition.objects.create(title='!!! missing title !!!', work=work)
for key in identifiers.keys():
if key == 'glue':
id_value = work.id
if key not in ('http', 'goog', 'oclc', 'isbn'):
if key in WORK_IDENTIFIERS:
edid = str(edition.id)
models.Identifier.objects.create(type='edid', value=edid, work=work, edition=edition)
models.Identifier.objects.create(type=key, value=id_value, work=work, edition=None)
else:
models.Identifier.objects.create(type=key, value=id_value, work=work, edition=edition)
return edition
@login_required
def new_edition(request, by=None):
'''
view for creating editions
'''
alert = ''
if request.method == 'POST':
form = IdentifierForm(data=request.POST)
if form.is_valid():
if form.cleaned_data.get('make_new', False):
edition = get_edition_for_id('glue', 'new')
else:
id_type = form.cleaned_data['id_type']
id_value = form.cleaned_data['id_value']
identifiers = models.Identifier.objects.filter(type=id_type, value=id_value)
if identifiers:
# pre-existing identifier
ident = identifiers[0]
work = ident.work
edition = ident.edition if ident.edition else work.preferred_edition
else:
edition = get_edition_for_id(id_type, id_value, user=request.user)
return HttpResponseRedirect(
reverse('new_edition', kwargs={
'work_id': edition.work.id,
'edition_id': edition.id
})
)
else:
form = IdentifierForm()
return render(request, 'new_edition.html', {'form': form, 'alert':alert})
@login_required
def edit_edition(request, work_id, edition_id, by=None):
'''
view for editing editions
'''
if not work_id and not edition_id:
return new_edition(request, by=by)
# if the work and edition are set, we save the edition and set the work
language = 'en'
age_level = ''
description = ''
title = ''
if work_id:
work = safe_get_work(work_id)
language = work.language
age_level = work.age_level
description = work.description
title = work.title
else:
work = None
alert = ''
admin = user_can_edit_work(request.user, work)
if edition_id:
edition = get_edition(edition_id)
if work:
edition.work = work
language = edition.work.language
age_level = edition.work.age_level
description = edition.work.description
else:
edition = models.Edition()
if work:
edition.work = work
edition.publication_date = work.earliest_publication_date
edition.new_authors = []
for relator in work.relators():
edition.new_authors.append((relator.author.name, relator.relation.code))
initial = {
'language': language,
'age_level': age_level,
'publisher_name': edition.publisher_name,
'description': description,
'title': title,
}
if request.method == 'POST':
form = None
edition.new_authors = zip(
request.POST.getlist('new_author'),
request.POST.getlist('new_author_relation')
)
edition.new_subjects = request.POST.getlist('new_subject')
if edition.id and admin:
for author in edition.authors.all():
if request.POST.has_key('delete_author_%s' % author.id):
edition.remove_author(author)
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
break
work_rels = models.WorkRelation.objects.filter(Q(to_work=work) | Q(from_work=work))
for work_rel in work_rels:
if request.POST.has_key('delete_work_rel_%s' % work_rel.id):
work_rel.delete()
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
break
activate_all = request.POST.has_key('activate_all_ebooks')
deactivate_all = request.POST.has_key('deactivate_all_ebooks')
ebookchange = False
for ebook in work.ebooks_all():
if request.POST.has_key('activate_ebook_%s' % ebook.id) or activate_all:
ebook.activate()
ebookchange = True
elif request.POST.has_key('deactivate_ebook_%s' % ebook.id) or deactivate_all:
ebook.deactivate()
ebookchange = True
if ebookchange:
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
if request.POST.has_key('add_author_submit') and admin:
new_author_name = request.POST['add_author'].strip()
new_author_relation = request.POST['add_author_relation']
try:
author = models.Author.objects.get(name=new_author_name)
except models.Author.DoesNotExist:
author = models.Author.objects.create(name=new_author_name)
edition.new_authors.append((new_author_name, new_author_relation))
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
elif not form and admin:
form = EditionForm(instance=edition, data=request.POST, files=request.FILES)
if form.is_valid():
form.save()
if not work:
work = models.Work(
title=form.cleaned_data['title'],
language=form.cleaned_data['language'],
age_level=form.cleaned_data['age_level'],
description=form.cleaned_data['description'],
)
work.save()
edition.work = work
edition.save()
else:
work.description = form.cleaned_data['description']
work.title = form.cleaned_data['title']
work.publication_range = None # will reset on next access
work.language = form.cleaned_data['language']
work.age_level = form.cleaned_data['age_level']
work.save()
id_type = form.cleaned_data['id_type']
id_val = form.cleaned_data['id_value']
if id_val == 'delete':
if edition.identifiers.exclude(type=id_type):
edition.identifiers.filter(type=id_type).delete()
else:
alert = ('Can\'t delete identifier - must have at least one left.')
elif id_val:
models.Identifier.set(
type=id_type,
value=id_val,
edition=edition,
work=work
)
for relator in edition.relators.all():
if request.POST.has_key('change_relator_%s' % relator.id):
new_relation = request.POST['change_relator_%s' % relator.id]
relator.set(new_relation)
related_work = form.cleaned_data['add_related_work']
if related_work:
models.WorkRelation.objects.get_or_create(
to_work=work,
from_work=related_work,
relation=form.cleaned_data['add_work_relation'],
)
for (author_name, author_relation) in edition.new_authors:
edition.add_author(author_name, author_relation)
if form.cleaned_data.has_key('bisac'):
bisacsh = form.cleaned_data['bisac']
while bisacsh:
add_subject(bisacsh.full_label, work, authority="bisacsh")
bisacsh = bisacsh.parent
for subject_name in edition.new_subjects:
add_subject(subject_name, work)
work_url = reverse('work', kwargs={'work_id': edition.work.id})
cover_file = form.cleaned_data.get("coverfile", None)
if cover_file:
# save it
cover_file_name = '/Users/%s/covers/%s/%s' % (
request.user.username,
edition.pk,
cover_file.name
)
new_file = default_storage.open(cover_file_name, 'w')
new_file.write(cover_file.read())
new_file.close()
#and put its url into cover_image
edition.cover_image = default_storage.url(cover_file_name)
edition.save()
if not alert:
return HttpResponseRedirect(work_url)
else:
form = EditionForm(instance=edition, initial=initial)
return render(request, 'edit_edition.html', {
'form': form,
'identform': IdentifierForm(),
'edition': edition,
'admin': admin,
'alert': alert,
})

View File

@ -83,7 +83,7 @@ def selective_social_user(backend, uid, user=None, *args, **kwargs):
'is_new': user is None,
'new_association': False}
# http://stackoverflow.com/a/19361220
# https://stackoverflow.com/a/19361220
# adapting https://github.com/omab/python-social-auth/blob/v0.2.10/social/apps/django_app/middleware.py#L25
class SocialAuthExceptionMiddlewareWithoutMessages(SocialAuthExceptionMiddleware):

View File

@ -235,7 +235,7 @@ class Block(models.Model):
ordering = ['lower',]
# from http://en.wikipedia.org/wiki/Luhn_algorithm#Implementation_of_standard_Mod_10
# from https://en.wikipedia.org/wiki/Luhn_algorithm#Implementation_of_standard_Mod_10
def luhn_checksum(card_number):
def digits_of(n):
return [int(d) for d in str(n)]

View File

@ -2,4 +2,4 @@
<br /><br />
Lower: <code>24.0.0.1</code> Upper: <code>24.0.0.255</code>
<br /><br />
Don't use "loopback" addresses like 127.0.0.1, or the <a href="http://en.wikipedia.org/wiki/Private_network">private network blocks</a>: 10.0.0.0 - 10.255.255.255, 172.16.0.0 - 172.31.255.255, 192.168.0.0 - 192.168.255.255. </p>
Don't use "loopback" addresses like 127.0.0.1, or the <a href="https://en.wikipedia.org/wiki/Private_network">private network blocks</a>: 10.0.0.0 - 10.255.255.255, 172.16.0.0 - 172.31.255.255, 192.168.0.0 - 192.168.255.255. </p>

View File

@ -19,7 +19,7 @@ from django.contrib.auth.models import User
# create Credit to associate with User
def create_user_objects(sender, created, instance, **kwargs):
# use get_model to avoid circular import problem with models
# don't create Credit if we are loading fixtures http://stackoverflow.com/a/3500009/7782
# don't create Credit if we are loading fixtures https://stackoverflow.com/a/3500009/7782
if not kwargs.get('raw', False):
try:
Credit = apps.get_model('payment', 'Credit')

View File

@ -57,7 +57,7 @@ STRIPE_EVENT_TYPES = ['account.updated', 'account.application.deauthorized', 'ba
logger = logging.getLogger(__name__)
# http://stackoverflow.com/questions/2348317/how-to-write-a-pager-for-python-iterators/2350904#2350904
# https://stackoverflow.com/questions/2348317/how-to-write-a-pager-for-python-iterators/2350904#2350904
def grouper(iterable, page_size):
page= []
for item in iterable:
@ -155,7 +155,7 @@ def filter_none(d):
# if you create a Customer object, then you'll be able to charge multiple times. You can create a customer with a token.
# http://en.wikipedia.org/wiki/Luhn_algorithm#Implementation_of_standard_Mod_10
# https://en.wikipedia.org/wiki/Luhn_algorithm#Implementation_of_standard_Mod_10
def luhn_checksum(card_number):
def digits_of(n):

View File

@ -46,7 +46,8 @@ feedparser==5.1.2
fef-questionnaire==4.0.0
freebase==1.0.8
#gitenberg.metadata==0.1.6
git+ssh://git@github.com/gitenberg-dev/metadata.git@0.1.11
git+https://github.com/gitenberg-dev/gitberg-build
#git+ssh://git@github.com/gitenberg-dev/metadata.git@0.1.11
github3.py==0.9.5
html5lib==1.0b3
httplib2==0.7.5
@ -101,3 +102,4 @@ pyasn1==0.1.9
pycparser==2.14
setuptools==25.0.0
urllib3==1.16
beautifulsoup4==4.6.0

View File

@ -75,7 +75,7 @@ def route53_records(domain_name, name, record_type):
def modify_rds_parameter(group_name, parameter, value, apply_immediate=False):
"""change parameter in RDS parameter group_name to value
http://stackoverflow.com/a/9085381/7782
https://stackoverflow.com/a/9085381/7782
Remember to make sure that the parameter group is actually associated with the db.
You will likely need to reboot db too.
"""

View File

@ -211,7 +211,7 @@ def test_relaunch(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=True,
# click on biggest campaign list
# I have no idea why selenium thinks a is not displayed....so that's why I'm going up one element.
# http://stackoverflow.com/a/6141678/7782
# https://stackoverflow.com/a/6141678/7782
#biggest_campaign_link = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("li > a[href*='/campaigns/ending']"))
#biggest_campaign_link.click()
#time.sleep(1)

View File

@ -10,7 +10,7 @@ contributor:
deathdate: 1933
gutenberg_agent_id: '37155'
url: http://www.gutenberg.org/2009/agents/37155
wikipedia: http://en.wikipedia.org/wiki/E._W._Kemble
wikipedia: https://en.wikipedia.org/wiki/E._W._Kemble
edition_list:
-
_edition: rtc_isbn
@ -32,8 +32,8 @@ creator:
deathdate: 1910
gutenberg_agent_id: '53'
url: http://www.gutenberg.org/2009/agents/53
wikipedia: http://en.wikipedia.org/wiki/Mark_Twain
description: 'Wikipedia: http://en.wikipedia.org/wiki/Adventures_of_Huckleberry_Finn'
wikipedia: https://en.wikipedia.org/wiki/Mark_Twain
description: 'Wikipedia: https://en.wikipedia.org/wiki/Adventures_of_Huckleberry_Finn'
gutenberg_bookshelf:
- Banned Books from Anne Haight's list
- Best Books Ever Listings
@ -45,7 +45,7 @@ language: en
publication_date: 2015-08-01
publisher: Project Gutenberg
rights: PD-US
rights_url: http://creativecommons.org/licenses/by-nc/4.0/
rights_url: https://creativecommons.org/licenses/by-nc/4.0/
subjects:
- !lcsh Male friendship -- Fiction
- !lcsh Fugitive slaves -- Fiction

View File

@ -5,7 +5,7 @@ contributor:
gutenberg_agent_id: 25396
agent_name: John Schoenherr
agent_sortname: Schoenherr, John
wikipedia: http://en.wikipedia.org/wiki/John_Schoenherr
wikipedia: https://en.wikipedia.org/wiki/John_Schoenherr
covers:
-
image_path: 20728-h/images/illus-front.jpg
@ -18,7 +18,7 @@ creator:
gutenberg_agent_id: 8301
agent_name: H. Beam Piper
agent_sortname: Piper, H. Beam
wikipedia: http://en.wikipedia.org/wiki/H._Beam_Piper
wikipedia: https://en.wikipedia.org/wiki/H._Beam_Piper
description:
funding_info:
identifiers:

View File

@ -3,7 +3,7 @@ import sys
import unicodedata
#http://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python
#https://stackoverflow.com/questions/1707890/fast-way-to-filter-illegal-xml-unicode-chars-in-python
_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F),
(0x7F, 0x84), (0x86, 0x9F),
(0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]

4
vagrant/Vagrantfile vendored
View File

@ -37,8 +37,8 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
end
# 512MB not enough for compiling lxml: http://stackoverflow.com/a/25916353/7782
# http://stackoverflow.com/a/26468913/7782 --> for how to get to this setting
# 512MB not enough for compiling lxml: https://stackoverflow.com/a/25916353/7782
# https://stackoverflow.com/a/26468913/7782 --> for how to get to this setting
node.vm.provider "virtualbox" do |v|
v.memory = 1024
v.cpus = 2

View File

@ -156,7 +156,7 @@
# installing mysql
# https://github.com/bennojoy/mysql --> probably the right way
# how do you make use of other people's playbooks in the right way?
# http://stackoverflow.com/a/7740571/7782
# https://stackoverflow.com/a/7740571/7782
- name: mysql setup
raw: debconf-set-selections <<< 'mysql-server-5.5 mysql-server/root_password password {{mysql_root_pw}}'
@ -190,8 +190,8 @@
# running stuff within a virtualenv
# http://stackoverflow.com/a/20572360
# http://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
# https://stackoverflow.com/a/20572360
# https://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
#sudo("ln -s /opt/regluit/deploy/please.conf /etc/apache2/sites-available/please")
@ -276,7 +276,7 @@
#Run syncdb on the application
# TO DO: syncdb might be deprecated
# http://stackoverflow.com/a/29683785
# https://stackoverflow.com/a/29683785
- name: django syncdb
django_manage: >

View File

@ -103,7 +103,7 @@
## installing mysql
## https://github.com/bennojoy/mysql --> probably the right way
## how do you make use of other people's playbooks in the right way?
## http://stackoverflow.com/a/7740571/7782
## https://stackoverflow.com/a/7740571/7782
#
#- name: mysql setup
# raw: debconf-set-selections <<< 'mysql-server-5.5 mysql-server/root_password password {{mysql_root_pw}}'
@ -126,8 +126,8 @@
# running stuff within a virtualenv
# http://stackoverflow.com/a/20572360
# http://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
# https://stackoverflow.com/a/20572360
# https://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
## hard coding of just

View File

@ -22,13 +22,13 @@
# installing mysql
# https://github.com/bennojoy/mysql --> probably the right way
# how do you make use of other people's playbooks in the right way?
# http://stackoverflow.com/a/7740571/7782
# https://stackoverflow.com/a/7740571/7782
post_tasks:
# running stuff within a virtualenv
# http://stackoverflow.com/a/20572360
# http://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
# https://stackoverflow.com/a/20572360
# https://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
## hard coding of {{config_name}}

View File

@ -129,7 +129,7 @@
# installing mysql
# https://github.com/bennojoy/mysql --> probably the right way
# how do you make use of other people's playbooks in the right way?
# http://stackoverflow.com/a/7740571/7782
# https://stackoverflow.com/a/7740571/7782
- name: mysql setup
raw: debconf-set-selections <<< 'mysql-server-5.5 mysql-server/root_password password {{mysql_root_pw}}'
@ -158,8 +158,8 @@
# running stuff within a virtualenv
# http://stackoverflow.com/a/20572360
# http://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
# https://stackoverflow.com/a/20572360
# https://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
## hard coding of please

View File

@ -103,7 +103,7 @@
# installing mysql
# https://github.com/bennojoy/mysql --> probably the right way
# how do you make use of other people's playbooks in the right way?
# http://stackoverflow.com/a/7740571/7782
# https://stackoverflow.com/a/7740571/7782
- name: mysql setup
raw: debconf-set-selections <<< 'mysql-server-5.5 mysql-server/root_password password {{mysql_root_pw}}'
@ -126,8 +126,8 @@
# running stuff within a virtualenv
# http://stackoverflow.com/a/20572360
# http://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
# https://stackoverflow.com/a/20572360
# https://stackoverflow.com/questions/20575084/best-way-to-always-run-ansible-inside-a-virtualenv-on-remote-machines?rq=1
## hard coding of please