Merge newfoundation

foundation/fix-header-menu
Nicholas Antonov 2018-02-14 23:56:53 -05:00
commit fc1d12c5d5
284 changed files with 14689 additions and 6786 deletions

1
.gitignore vendored
View File

@ -13,5 +13,4 @@ logs/*
celerybeat.pid celerybeat.pid
celerybeat-schedule celerybeat-schedule
.gitignore~ .gitignore~
static/scss/*.css
static/scss/*.css.map static/scss/*.css.map

View File

@ -41,11 +41,13 @@ to install python-setuptools in step 1:
1. `django-admin.py celeryd --loglevel=INFO` start the celery daemon to perform asynchronous tasks like adding related editions, and display logging information in the foreground. 1. `django-admin.py celeryd --loglevel=INFO` start the celery daemon to perform asynchronous tasks like adding related editions, and display logging information in the foreground.
1. `django-admin.py celerybeat -l INFO` to start the celerybeat daemon to handle scheduled tasks. 1. `django-admin.py celerybeat -l INFO` to start the celerybeat daemon to handle scheduled tasks.
1. `django-admin.py runserver 0.0.0.0:8000` (you can change the port number from the default value of 8000) 1. `django-admin.py runserver 0.0.0.0:8000` (you can change the port number from the default value of 8000)
1. make sure a [redis server](https://redis.io/topics/quickstart) is running
1. Point your browser to http://localhost:8000/ 1. Point your browser to http://localhost:8000/
CSS development CSS development
1. We are using Less version 2.8 for CSS. http://incident57.com/less/. We use minified CSS. 1. We used Less version 2.8 for CSS. http://incident57.com/less/. We use minified CSS.
1. New CSS development is using SCSS. Install libsass and django-compressor.
Production Deployment Production Deployment
--------------------- ---------------------

View File

@ -42,7 +42,7 @@ def header(facet=None):
header_node = etree.Element("Header") header_node = etree.Element("Header")
sender_node = etree.Element("Sender") sender_node = etree.Element("Sender")
sender_node.append(text_node("SenderName", "unglue.it")) sender_node.append(text_node("SenderName", "unglue.it"))
sender_node.append(text_node("EmailAddress", "support@gluejar.com")) sender_node.append(text_node("EmailAddress", "unglueit@ebookfoundation.org"))
header_node.append(sender_node) header_node.append(sender_node)
header_node.append(text_node("SentDateTime", pytz.utc.localize(datetime.datetime.utcnow()).strftime('%Y%m%dT%H%M%SZ'))) header_node.append(text_node("SentDateTime", pytz.utc.localize(datetime.datetime.utcnow()).strftime('%Y%m%dT%H%M%SZ')))
header_node.append(text_node("MessageNote", facet.title if facet else "Unglue.it Editions")) header_node.append(text_node("MessageNote", facet.title if facet else "Unglue.it Editions"))

View File

@ -76,6 +76,20 @@ XML: <a href="/api/v1/identifier/?format=xml&amp;api_key={{api_key}}&amp;usernam
<h3>OPDS Catalog Feeds</h3> <h3>OPDS Catalog Feeds</h3>
<p>We have a basic implementation of <a href="http://opds-spec.org/specs/opds-catalog-1-1-20110627/">OPDS</a> feeds. You don't need a key to use them. The starting point is <code><a href="{% url 'opds' %}">{{base_url}}{% url 'opds' %}</a></code></p> <p>We have a basic implementation of <a href="http://opds-spec.org/specs/opds-catalog-1-1-20110627/">OPDS</a> feeds. You don't need a key to use them. The starting point is <code><a href="{% url 'opds' %}">{{base_url}}{% url 'opds' %}</a></code></p>
<p>
Examples:
<dl>
<dt>filtered by format</dt>
<dd><code><a href="{% url 'opds_acqusition' 'epub' %}">{{base_url}}{% url 'opds_acqusition' 'epub' %}</a></code></dd>
<dt>filtered by license</dt>
<dd><code><a href="{% url 'opds_acqusition' 'by-sa' %}">{{base_url}}{% url 'opds_acqusition' 'by-sa' %}</a></code></dd>
<dt>filtered by title search</dt>
<dd><code><a href="{% url 'opds_acqusition' 's.open' %}">{{base_url}}{% url 'opds_acqusition' 's.open' %}</a></code></dd>
<dt>filtered by keyword</dt>
<dd><code><a href="{% url 'opds_acqusition' 'kw.fiction' %}">{{base_url}}{% url 'opds_acqusition' 'kw.fiction' %}</a></code></dd>
<dt>filtered by ungluer</dt>
<dd><code><a href="{% url 'opds_acqusition' '@eric' %}">{{base_url}}{% url 'opds_acqusition' '@eric' %}</a></code></dd>
</p>
<p>There's also an OPDS record available for every work on unglue.it. For example, requesting, <code><a href="{% url 'opds_acqusition' 'all'%}?work=13950">{{base_url}}{% url 'opds_acqusition' 'all'%}?work=13950</a></code> get you to the web page or opds record for <i>A Christmas Carol</i>.</p> <p>There's also an OPDS record available for every work on unglue.it. For example, requesting, <code><a href="{% url 'opds_acqusition' 'all'%}?work=13950">{{base_url}}{% url 'opds_acqusition' 'all'%}?work=13950</a></code> get you to the web page or opds record for <i>A Christmas Carol</i>.</p>
<h3>ONIX Catalog Feeds</h3> <h3>ONIX Catalog Feeds</h3>

View File

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>{% load sass_tags %}
<html> <html>
<head> <head>
<title>unglue.it: {{work.title}}</title> <title>unglue.it: {{work.title}}</title>
<link type="text/css" rel="stylesheet" href="/static/css/sitewide4.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/sitewide4.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_panel2.scss' %}" />
<style type="text/css"> <style type="text/css">
body { body {

View File

@ -7,7 +7,7 @@ from django.contrib import auth
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.shortcuts import render, render_to_response, get_object_or_404 from django.shortcuts import render, render_to_response
from django.template import RequestContext from django.template import RequestContext
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import View, TemplateView from django.views.generic.base import View, TemplateView

View File

@ -1,136 +1,151 @@
""" #
django imports #django imports
""" #
from django import forms from django import forms
from django.contrib.admin import ModelAdmin from django.contrib.admin import ModelAdmin, register
from django.contrib.admin import site as admin_site
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from selectable.forms import (
from selectable.forms import (
AutoCompleteSelectWidget, AutoCompleteSelectWidget,
AutoCompleteSelectField, AutoCompleteSelectField,
AutoCompleteSelectMultipleWidget, AutoCompleteSelectMultipleWidget,
AutoCompleteSelectMultipleField, AutoCompleteSelectMultipleField,
) )
""" #
regluit imports # regluit imports
""" #"""
from . import models from .lookups import (
from regluit.core.lookups import (
PublisherNameLookup, PublisherNameLookup,
WorkLookup, WorkLookup,
OwnerLookup, OwnerLookup,
EditionLookup EditionLookup,
EbookLookup,
) )
from . import models
class ClaimAdminForm(forms.ModelForm): class ClaimAdminForm(forms.ModelForm):
work = AutoCompleteSelectField( work = AutoCompleteSelectField(
lookup_class=WorkLookup, lookup_class=WorkLookup,
widget=AutoCompleteSelectWidget(lookup_class=WorkLookup), widget=AutoCompleteSelectWidget(lookup_class=WorkLookup),
required=True, required=True,
) )
user = AutoCompleteSelectField( user = AutoCompleteSelectField(
lookup_class=OwnerLookup, lookup_class=OwnerLookup,
widget=AutoCompleteSelectWidget(lookup_class=OwnerLookup), widget=AutoCompleteSelectWidget(lookup_class=OwnerLookup),
required=True, required=True,
) )
class Meta(object): class Meta(object):
model = models.Claim model = models.Claim
exclude = () exclude = ()
@register(models.Claim)
class ClaimAdmin(ModelAdmin): class ClaimAdmin(ModelAdmin):
list_display = ('work', 'rights_holder', 'status') list_display = ('work', 'rights_holder', 'status')
date_hierarchy = 'created' date_hierarchy = 'created'
form = ClaimAdminForm form = ClaimAdminForm
class RightsHolderAdminForm(forms.ModelForm): class RightsHolderAdminForm(forms.ModelForm):
owner = AutoCompleteSelectField( owner = AutoCompleteSelectField(
lookup_class=OwnerLookup, lookup_class=OwnerLookup,
widget=AutoCompleteSelectWidget(lookup_class=OwnerLookup), widget=AutoCompleteSelectWidget(lookup_class=OwnerLookup),
required=True, required=True,
) )
class Meta(object): class Meta(object):
model = models.RightsHolder model = models.RightsHolder
exclude = () exclude = ()
@register(models.RightsHolder)
class RightsHolderAdmin(ModelAdmin): class RightsHolderAdmin(ModelAdmin):
date_hierarchy = 'created' date_hierarchy = 'created'
form = RightsHolderAdminForm form = RightsHolderAdminForm
@register(models.Acq)
class AcqAdmin(ModelAdmin): class AcqAdmin(ModelAdmin):
readonly_fields = ('work', 'user', 'lib_acq', 'watermarked') readonly_fields = ('work', 'user', 'lib_acq', 'watermarked')
search_fields = ['user__username'] search_fields = ['user__username']
date_hierarchy = 'created' date_hierarchy = 'created'
@register(models.Premium)
class PremiumAdmin(ModelAdmin): class PremiumAdmin(ModelAdmin):
list_display = ('campaign', 'amount', 'description') list_display = ('campaign', 'amount', 'description')
date_hierarchy = 'created' date_hierarchy = 'created'
fields = ('type', 'amount', 'description', 'limit')
class CampaignAdminForm(forms.ModelForm): class CampaignAdminForm(forms.ModelForm):
managers = AutoCompleteSelectMultipleField( managers = AutoCompleteSelectMultipleField(
lookup_class=OwnerLookup, lookup_class=OwnerLookup,
widget= AutoCompleteSelectMultipleWidget(lookup_class=OwnerLookup), widget=AutoCompleteSelectMultipleWidget(lookup_class=OwnerLookup),
required=True, required=True,
) )
class Meta(object): class Meta(object):
model = models.Campaign model = models.Campaign
fields = ('managers', 'name', 'description', 'details', 'license', 'activated', 'paypal_receiver', fields = (
'status', 'type', 'email', 'do_watermark', 'use_add_ask', ) 'managers', 'name', 'description', 'details', 'license', 'paypal_receiver',
'status', 'type', 'email', 'do_watermark', 'use_add_ask', 'charitable',
)
@register(models.Campaign)
class CampaignAdmin(ModelAdmin): class CampaignAdmin(ModelAdmin):
list_display = ('work', 'created', 'status') list_display = ('work', 'created', 'status')
date_hierarchy = 'created' date_hierarchy = 'created'
search_fields = ['work'] search_fields = ['work']
form = CampaignAdminForm form = CampaignAdminForm
@register(models.Work)
class WorkAdmin(ModelAdmin): class WorkAdmin(ModelAdmin):
search_fields = ['title'] search_fields = ['title']
ordering = ('title',) ordering = ('title',)
list_display = ('title', 'created') list_display = ('title', 'created')
date_hierarchy = 'created' date_hierarchy = 'created'
fields = ('title', 'description', 'language', 'featured', 'publication_range', fields = ('title', 'description', 'language', 'featured', 'publication_range',
'age_level', 'openlibrary_lookup') 'age_level', 'openlibrary_lookup')
@register(models.Author)
class AuthorAdmin(ModelAdmin): class AuthorAdmin(ModelAdmin):
search_fields = ('name',) search_fields = ('name',)
date_hierarchy = 'created' date_hierarchy = 'created'
ordering = ('name',) ordering = ('name',)
exclude = ('editions',) exclude = ('editions',)
subject_authorities = (('','keywords'),('lcsh', 'LC subjects'), ('lcc', 'LC classifications'), ('bisacsh', 'BISAC heading'), ) subject_authorities = (
('', 'keywords'),
('lcsh', 'LC subjects'),
('lcc', 'LC classifications'),
('bisacsh', 'BISAC heading'),
)
class SubjectAdminForm(forms.ModelForm): class SubjectAdminForm(forms.ModelForm):
authority = forms.ChoiceField(choices=subject_authorities, required=False ) authority = forms.ChoiceField(choices=subject_authorities, required=False)
class Meta(object): class Meta(object):
model = models.Subject model = models.Subject
fields = 'name', 'authority', 'is_visible' fields = 'name', 'authority', 'is_visible'
@register(models.Subject)
class SubjectAdmin(ModelAdmin): class SubjectAdmin(ModelAdmin):
search_fields = ('name',) search_fields = ('name',)
date_hierarchy = 'created' date_hierarchy = 'created'
ordering = ('name',) ordering = ('name',)
form = SubjectAdminForm form = SubjectAdminForm
class EditionAdminForm(forms.ModelForm): class EditionAdminForm(forms.ModelForm):
work = AutoCompleteSelectField( work = AutoCompleteSelectField(
lookup_class=WorkLookup, lookup_class=WorkLookup,
label='Work', label='Work',
widget=AutoCompleteSelectWidget(lookup_class=WorkLookup), widget=AutoCompleteSelectWidget(lookup_class=WorkLookup),
required=True, required=True,
) )
publisher_name = AutoCompleteSelectField( publisher_name = AutoCompleteSelectField(
lookup_class=PublisherNameLookup, lookup_class=PublisherNameLookup,
label='Publisher Name', label='Publisher Name',
widget=AutoCompleteSelectWidget(lookup_class=PublisherNameLookup), widget=AutoCompleteSelectWidget(lookup_class=PublisherNameLookup),
required=False, required=False,
) )
class Meta(object): class Meta(object):
model = models.Edition model = models.Edition
exclude = () exclude = ()
@register(models.Edition)
class EditionAdmin(ModelAdmin): class EditionAdmin(ModelAdmin):
list_display = ('title', 'publisher_name', 'created') list_display = ('title', 'publisher_name', 'created')
date_hierarchy = 'created' date_hierarchy = 'created'
@ -139,153 +154,168 @@ class EditionAdmin(ModelAdmin):
class PublisherAdminForm(forms.ModelForm): class PublisherAdminForm(forms.ModelForm):
name = AutoCompleteSelectField( name = AutoCompleteSelectField(
lookup_class=PublisherNameLookup, lookup_class=PublisherNameLookup,
label='Name', label='Name',
widget=AutoCompleteSelectWidget(lookup_class=PublisherNameLookup), widget=AutoCompleteSelectWidget(lookup_class=PublisherNameLookup),
required=True, required=True,
) )
class Meta(object): class Meta(object):
model = models.Publisher model = models.Publisher
exclude = () exclude = ()
@register(models.Publisher)
class PublisherAdmin(ModelAdmin): class PublisherAdmin(ModelAdmin):
list_display = ('name', 'url', 'logo_url', 'description') list_display = ('name', 'url', 'logo_url', 'description')
ordering = ('name',) ordering = ('name',)
form = PublisherAdminForm form = PublisherAdminForm
@register(models.PublisherName)
class PublisherNameAdmin(ModelAdmin): class PublisherNameAdmin(ModelAdmin):
list_display = ('name', 'publisher') list_display = ('name', 'publisher')
ordering = ('name',) ordering = ('name',)
search_fields = ['name'] search_fields = ['name']
@register(models.Relation)
class RelationAdmin(ModelAdmin): class RelationAdmin(ModelAdmin):
list_display = ('code', 'name') list_display = ('code', 'name')
search_fields = ['name'] search_fields = ['name']
class EbookAdminForm(forms.ModelForm):
edition = AutoCompleteSelectField(
lookup_class=EditionLookup,
label='Edition',
widget=AutoCompleteSelectWidget(lookup_class=EditionLookup, attrs={'size':60}),
required=True,
)
class Meta(object):
model = models.Ebook
exclude = ('user', 'filesize', 'download_count')
@register(models.Ebook)
class EbookAdmin(ModelAdmin): class EbookAdmin(ModelAdmin):
search_fields = ('edition__title','^url') # search by provider using leading url form = EbookAdminForm
list_display = ('__unicode__','created', 'user','edition') search_fields = ('edition__title', '^url') # search by provider using leading url
list_display = ('__unicode__', 'created', 'user', 'edition')
date_hierarchy = 'created' date_hierarchy = 'created'
ordering = ('edition__title',) ordering = ('edition__title',)
exclude = ('edition','user', 'filesize') readonly_fields = ('user', 'filesize', 'download_count')
class EbookFileAdminForm(forms.ModelForm):
edition = AutoCompleteSelectField(
lookup_class=EditionLookup,
label='Edition',
widget=AutoCompleteSelectWidget(lookup_class=EditionLookup, attrs={'size':60}),
required=True,
)
ebook = AutoCompleteSelectField(
lookup_class=EbookLookup,
label='Ebook',
widget=AutoCompleteSelectWidget(lookup_class=EbookLookup, attrs={'size':60}),
required=False,
)
class Meta(object):
model = models.EbookFile
fields = ('file', 'format', 'edition', 'ebook', 'source')
@register(models.EbookFile)
class EbookFileAdmin(ModelAdmin): class EbookFileAdmin(ModelAdmin):
form = EbookFileAdminForm
search_fields = ('ebook__edition__title', 'source') # search by provider using leading url search_fields = ('ebook__edition__title', 'source') # search by provider using leading url
list_display = ('created', 'format', 'ebook_link', 'asking') list_display = ('created', 'format', 'ebook_link', 'asking')
date_hierarchy = 'created' date_hierarchy = 'created'
ordering = ('edition__work',) ordering = ('edition__work',)
fields = ('file', 'format', 'edition_link', 'ebook_link', 'source') fields = ('file', 'format', 'edition', 'edition_link', 'ebook', 'ebook_link', 'source')
readonly_fields = ('file', 'edition_link', 'ebook_link', 'ebook') readonly_fields = ('file', 'edition_link', 'ebook_link',)
def edition_link(self, obj): def edition_link(self, obj):
if obj.edition: if obj.edition:
link = reverse("admin:core_edition_change", args=[obj.edition_id]) link = reverse("admin:core_edition_change", args=[obj.edition_id])
return u'<a href="%s">%s</a>' % (link,obj.edition) return u'<a href="%s">%s</a>' % (link, obj.edition)
else: return u''
return u''
def ebook_link(self, obj): def ebook_link(self, obj):
if obj.ebook: if obj.ebook:
link = reverse("admin:core_ebook_change", args=[obj.ebook_id]) link = reverse("admin:core_ebook_change", args=[obj.ebook_id])
return u'<a href="%s">%s</a>' % (link,obj.ebook) return u'<a href="%s">%s</a>' % (link, obj.ebook)
else: return u''
return u'' edition_link.allow_tags = True
edition_link.allow_tags=True ebook_link.allow_tags = True
ebook_link.allow_tags=True
@register(models.Wishlist)
class WishlistAdmin(ModelAdmin): class WishlistAdmin(ModelAdmin):
date_hierarchy = 'created' date_hierarchy = 'created'
@register(models.UserProfile)
class UserProfileAdmin(ModelAdmin): class UserProfileAdmin(ModelAdmin):
search_fields = ('user__username',) search_fields = ('user__username',)
date_hierarchy = 'created' date_hierarchy = 'created'
exclude = ('user',) exclude = ('user',)
@register(models.Gift)
class GiftAdmin(ModelAdmin): class GiftAdmin(ModelAdmin):
list_display = ('to', 'acq_admin_link', 'giver', ) list_display = ('to', 'acq_admin_link', 'giver',)
search_fields = ('giver__username', 'to') search_fields = ('giver__username', 'to')
readonly_fields = ('giver', 'acq',) readonly_fields = ('giver', 'acq',)
def acq_admin_link(self, gift): def acq_admin_link(self, gift):
return "<a href='/admin/core/acq/%s/'>%s</a>" % (gift.acq_id, gift.acq) return "<a href='/admin/core/acq/%s/'>%s</a>" % (gift.acq_id, gift.acq)
acq_admin_link.allow_tags = True acq_admin_link.allow_tags = True
@register(models.CeleryTask)
class CeleryTaskAdmin(ModelAdmin): class CeleryTaskAdmin(ModelAdmin):
pass pass
@register(models.Press)
class PressAdmin(ModelAdmin): class PressAdmin(ModelAdmin):
list_display = ('title', 'source', 'date') list_display = ('title', 'source', 'date')
ordering = ('-date',) ordering = ('-date',)
class WorkRelationAdminForm(forms.ModelForm): class WorkRelationAdminForm(forms.ModelForm):
to_work = AutoCompleteSelectField( to_work = AutoCompleteSelectField(
lookup_class=WorkLookup, lookup_class=WorkLookup,
label='To Work', label='To Work',
widget=AutoCompleteSelectWidget(lookup_class=WorkLookup), widget=AutoCompleteSelectWidget(lookup_class=WorkLookup),
required=True, required=True,
) )
from_work = AutoCompleteSelectField( from_work = AutoCompleteSelectField(
lookup_class=WorkLookup, lookup_class=WorkLookup,
label='From Work', label='From Work',
widget=AutoCompleteSelectWidget(lookup_class=WorkLookup), widget=AutoCompleteSelectWidget(lookup_class=WorkLookup),
required=True, required=True,
) )
class Meta(object): class Meta(object):
model = models.WorkRelation model = models.WorkRelation
exclude = () exclude = ()
@register(models.WorkRelation)
class WorkRelationAdmin(ModelAdmin): class WorkRelationAdmin(ModelAdmin):
form = WorkRelationAdminForm form = WorkRelationAdminForm
list_display = ('to_work', 'relation', 'from_work') list_display = ('to_work', 'relation', 'from_work')
class IdentifierAdminForm(forms.ModelForm): class IdentifierAdminForm(forms.ModelForm):
work = AutoCompleteSelectField( work = AutoCompleteSelectField(
lookup_class=WorkLookup, lookup_class=WorkLookup,
label='Work', label='Work',
widget=AutoCompleteSelectWidget(lookup_class=WorkLookup, attrs={'size':60}), widget=AutoCompleteSelectWidget(lookup_class=WorkLookup, attrs={'size':60}),
required=False, required=False,
) )
edition = AutoCompleteSelectField( edition = AutoCompleteSelectField(
lookup_class=EditionLookup, lookup_class=EditionLookup,
label='Edition', label='Edition',
widget=AutoCompleteSelectWidget(lookup_class=EditionLookup, attrs={'size':60}), widget=AutoCompleteSelectWidget(lookup_class=EditionLookup, attrs={'size':60}),
required=True, required=False,
) )
class Meta(object): class Meta(object):
model = models.Identifier model = models.Identifier
exclude = () exclude = ()
@register(models.Identifier)
class IdentifierAdmin(ModelAdmin): class IdentifierAdmin(ModelAdmin):
form = IdentifierAdminForm form = IdentifierAdminForm
list_display = ('type', 'value') list_display = ('type', 'value')
search_fields = ('type', 'value') search_fields = ('type', 'value')
@register(models.Offer)
class OfferAdmin(ModelAdmin): class OfferAdmin(ModelAdmin):
list_display = ('work', 'license', 'price', 'active') list_display = ('work', 'license', 'price', 'active')
search_fields = ('work__title',) search_fields = ('work__title',)
readonly_fields = ('work',) readonly_fields = ('work',)
admin_site.register(models.Acq, AcqAdmin)
admin_site.register(models.Author, AuthorAdmin)
admin_site.register(models.Badge, ModelAdmin)
admin_site.register(models.Campaign, CampaignAdmin)
admin_site.register(models.CeleryTask, CeleryTaskAdmin)
admin_site.register(models.Claim, ClaimAdmin)
admin_site.register(models.Ebook, EbookAdmin)
admin_site.register(models.EbookFile, EbookFileAdmin)
admin_site.register(models.Edition, EditionAdmin)
admin_site.register(models.Gift, GiftAdmin)
admin_site.register(models.Identifier, IdentifierAdmin)
admin_site.register(models.Offer, OfferAdmin)
admin_site.register(models.Premium, PremiumAdmin)
admin_site.register(models.Press, PressAdmin)
admin_site.register(models.Publisher, PublisherAdmin)
admin_site.register(models.PublisherName, PublisherNameAdmin)
admin_site.register(models.Relation, RelationAdmin)
admin_site.register(models.RightsHolder, RightsHolderAdmin)
admin_site.register(models.Subject, SubjectAdmin)
admin_site.register(models.UserProfile, UserProfileAdmin)
admin_site.register(models.Wishlist, WishlistAdmin)
admin_site.register(models.Work, WorkAdmin)
admin_site.register(models.WorkRelation, WorkRelationAdmin)

View File

@ -4,24 +4,22 @@ external library imports
import json import json
import logging import logging
import re 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 datetime import timedelta
from xml.etree import ElementTree from xml.etree import ElementTree
from urlparse import (urljoin, urlparse) from urlparse import (urljoin, urlparse)
import requests
# django imports # django imports
from django.conf import settings from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django_comments.models import Comment from django.core.files.storage import default_storage
from django.db import IntegrityError from django.db import IntegrityError
from django.forms import ValidationError from django.forms import ValidationError
from django_comments.models import Comment
from github3 import (login, GitHub) from github3 import (login, GitHub)
from github3.repos.release import Release from github3.repos.release import Release
@ -31,6 +29,7 @@ from gitenberg.metadata.pandata import Pandata
import regluit import regluit
import regluit.core.isbn import regluit.core.isbn
from regluit.core.validation import test_file
from regluit.marc.models import inverse_marc_rels from regluit.marc.models import inverse_marc_rels
from regluit.utils.localdatetime import now from regluit.utils.localdatetime import now
@ -38,7 +37,6 @@ from . import cc
from . import models from . import models
from .parameters import WORK_IDENTIFIERS from .parameters import WORK_IDENTIFIERS
from .validation import identifier_cleaner, unreverse_name from .validation import identifier_cleaner, unreverse_name
from .loaders.scrape import get_scraper, scrape_sitemap
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
request_log = logging.getLogger("requests") request_log = logging.getLogger("requests")
@ -51,7 +49,7 @@ def add_by_oclc(isbn, work=None):
def add_by_oclc_from_google(oclc): def add_by_oclc_from_google(oclc):
if oclc: if oclc:
logger.info("adding book by oclc %s" , oclc) logger.info("adding book by oclc %s", oclc)
else: else:
return None return None
try: try:
@ -63,8 +61,8 @@ def add_by_oclc_from_google(oclc):
except LookupFailure, e: except LookupFailure, e:
logger.exception("lookup failure for %s", oclc) logger.exception("lookup failure for %s", oclc)
return None return None
if not results.has_key('items') or len(results['items']) == 0: if not results.has_key('items') or not results['items']:
logger.warn("no google hits for %s" , oclc) logger.warn("no google hits for %s", oclc)
return None return None
try: try:
@ -133,11 +131,10 @@ def get_google_isbn_results(isbn):
except LookupFailure: except LookupFailure:
logger.exception("lookup failure for %s", isbn) logger.exception("lookup failure for %s", isbn)
return None return None
if not results.has_key('items') or len(results['items']) == 0: if not results.has_key('items') or not results['items']:
logger.warn("no google hits for %s" , isbn) logger.warn("no google hits for %s", isbn)
return None return None
else: return results
return results
def add_ebooks(item, edition): def add_ebooks(item, edition):
access_info = item.get('accessInfo') access_info = item.get('accessInfo')
@ -175,7 +172,8 @@ def update_edition(edition):
except (models.Identifier.DoesNotExist, IndexError): except (models.Identifier.DoesNotExist, IndexError):
return edition return edition
# do a Google Books lookup on the isbn associated with the edition (there should be either 0 or 1 isbns associated # do a Google Books lookup on the isbn associated with the edition
# (there should be either 0 or 1 isbns associated
# with an edition because of integrity constraint in Identifier) # with an edition because of integrity constraint in Identifier)
# if we get some data about this isbn back from Google, update the edition data accordingly # if we get some data about this isbn back from Google, update the edition data accordingly
@ -189,8 +187,9 @@ def update_edition(edition):
title = d['title'] title = d['title']
else: else:
title = '' title = ''
if len(title) == 0: if not title:
# need a title to make an edition record; some crap records in GB. use title from parent if available # need a title to make an edition record; some crap records in GB.
# use title from parent if available
title = edition.work.title title = edition.work.title
# check for language change # check for language change
@ -199,9 +198,11 @@ def update_edition(edition):
if len(language) > 5: if len(language) > 5:
language = language[0:5] language = language[0:5]
# if the language of the edition no longer matches that of the parent work, attach edition to the # if the language of the edition no longer matches that of the parent work,
# attach edition to the
if edition.work.language != language: if edition.work.language != language:
logger.info("reconnecting %s since it is %s instead of %s" %(googlebooks_id, language, edition.work.language)) logger.info("reconnecting %s since it is %s instead of %s",
googlebooks_id, language, edition.work.language)
old_work = edition.work old_work = edition.work
new_work = models.Work(title=title, language=language) new_work = models.Work(title=title, language=language)
@ -209,10 +210,10 @@ def update_edition(edition):
edition.work = new_work edition.work = new_work
edition.save() edition.save()
for identifier in edition.identifiers.all(): for identifier in edition.identifiers.all():
logger.info("moving identifier %s" % identifier.value) logger.info("moving identifier %s", identifier.value)
identifier.work = new_work identifier.work = new_work
identifier.save() identifier.save()
if old_work and old_work.editions.count()==0: if old_work and old_work.editions.count() == 0:
#a dangling work; make sure nothing else is attached! #a dangling work; make sure nothing else is attached!
merge_works(new_work, old_work) merge_works(new_work, old_work)
@ -223,7 +224,12 @@ def update_edition(edition):
edition.save() edition.save()
# create identifier if needed # create identifier if needed
models.Identifier.get_or_add(type='goog', value=googlebooks_id, edition=edition, work=edition.work) models.Identifier.get_or_add(
type='goog',
value=googlebooks_id,
edition=edition,
work=edition.work
)
for a in d.get('authors', []): for a in d.get('authors', []):
edition.add_author(a) edition.add_author(a)
@ -240,7 +246,7 @@ def add_by_isbn_from_google(isbn, work=None):
""" """
if not isbn: if not isbn:
return None return None
if len(isbn)==10: if len(isbn) == 10:
isbn = regluit.core.isbn.convert_10_to_13(isbn) isbn = regluit.core.isbn.convert_10_to_13(isbn)
@ -254,14 +260,18 @@ def add_by_isbn_from_google(isbn, work=None):
results = get_google_isbn_results(isbn) results = get_google_isbn_results(isbn)
if results: if results:
try: try:
return add_by_googlebooks_id(results['items'][0]['id'], work=work, results=results['items'][0], isbn=isbn) return add_by_googlebooks_id(
results['items'][0]['id'],
work=work,
results=results['items'][0],
isbn=isbn
)
except LookupFailure, e: except LookupFailure, e:
logger.exception("failed to add edition for %s", isbn) logger.exception("failed to add edition for %s", isbn)
except IntegrityError, e: except IntegrityError, e:
logger.exception("google books data for %s didn't fit our db", isbn) logger.exception("google books data for %s didn't fit our db", isbn)
return None return None
else: return None
return None
def get_work_by_id(type, value): def get_work_by_id(type, value):
if value: if value:
@ -296,7 +306,12 @@ def add_by_googlebooks_id(googlebooks_id, work=None, results=None, isbn=None):
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 # not going to worry about isbn_edition != edition
except models.Identifier.DoesNotExist: except models.Identifier.DoesNotExist:
models.Identifier.objects.create(type='isbn', value=isbn, edition=edition, work=edition.work) models.Identifier.objects.create(
type='isbn',
value=isbn,
edition=edition,
work=edition.work
)
return edition return edition
except models.Identifier.DoesNotExist: except models.Identifier.DoesNotExist:
pass pass
@ -314,8 +329,9 @@ def add_by_googlebooks_id(googlebooks_id, work=None, results=None, isbn=None):
title = d['title'] title = d['title']
else: else:
title = '' title = ''
if len(title)==0: if not title:
# need a title to make an edition record; some crap records in GB. use title from parent if available # need a title to make an edition record; some crap records in GB.
# use title from parent if available
if work: if work:
title = work.title title = work.title
else: else:
@ -327,8 +343,8 @@ def add_by_googlebooks_id(googlebooks_id, work=None, results=None, isbn=None):
if len(language) > 5: if len(language) > 5:
language = language[0:5] language = language[0:5]
if work and work.language != language: if work and work.language != language:
logger.info("not connecting %s since it is %s instead of %s" % logger.info("not connecting %s since it is %s instead of %s",
(googlebooks_id, language, work.language)) googlebooks_id, language, work.language)
work = None work = None
# isbn = None # isbn = None
if not isbn: if not isbn:
@ -355,7 +371,7 @@ def add_by_googlebooks_id(googlebooks_id, work=None, results=None, isbn=None):
try: try:
e = models.Identifier.objects.get(type='goog', value=googlebooks_id).edition e = models.Identifier.objects.get(type='goog', value=googlebooks_id).edition
e.new = False e.new = False
logger.warning( " whoa nellie, somebody else created an edition while we were working.") logger.warning(" whoa nellie, somebody else created an edition while we were working.")
if work.new: if work.new:
work.delete() work.delete()
return e return e
@ -385,8 +401,8 @@ def add_by_googlebooks_id(googlebooks_id, work=None, results=None, isbn=None):
def relate_isbn(isbn, cluster_size=1): def relate_isbn(isbn, cluster_size=1):
"""add a book by isbn and then see if there's an existing work to add it to so as to make a cluster """add a book by isbn and then see if there's an existing work to add it to so as to make a
bigger than cluster_size. cluster bigger than cluster_size.
""" """
logger.info("finding a related work for %s", isbn) logger.info("finding a related work for %s", isbn)
@ -396,12 +412,12 @@ def relate_isbn(isbn, cluster_size=1):
if edition.work is None: if edition.work is None:
logger.info("didn't add related to null work") logger.info("didn't add related to null work")
return None return None
if edition.work.editions.count()>cluster_size: if edition.work.editions.count() > cluster_size:
return edition.work return edition.work
for other_isbn in thingisbn(isbn): for other_isbn in thingisbn(isbn):
# 979's come back as 13 # 979's come back as 13
logger.debug("other_isbn: %s", other_isbn) logger.debug("other_isbn: %s", other_isbn)
if len(other_isbn)==10: if len(other_isbn) == 10:
other_isbn = regluit.core.isbn.convert_10_to_13(other_isbn) other_isbn = regluit.core.isbn.convert_10_to_13(other_isbn)
related_edition = add_by_isbn(other_isbn, work=edition.work) related_edition = add_by_isbn(other_isbn, work=edition.work)
if related_edition: if related_edition:
@ -411,9 +427,9 @@ def relate_isbn(isbn, cluster_size=1):
related_edition.work = edition.work related_edition.work = edition.work
related_edition.save() related_edition.save()
elif related_edition.work_id != edition.work_id: elif related_edition.work_id != edition.work_id:
logger.debug("merge_works path 1 %s %s", edition.work_id, related_edition.work_id ) logger.debug("merge_works path 1 %s %s", edition.work_id, related_edition.work_id)
merge_works(related_edition.work, edition.work) merge_works(related_edition.work, edition.work)
if related_edition.work.editions.count()>cluster_size: if related_edition.work.editions.count() > cluster_size:
return related_edition.work return related_edition.work
return edition.work return edition.work
@ -438,7 +454,7 @@ def add_related(isbn):
for other_isbn in thingisbn(isbn): for other_isbn in thingisbn(isbn):
# 979's come back as 13 # 979's come back as 13
logger.debug("other_isbn: %s", other_isbn) logger.debug("other_isbn: %s", other_isbn)
if len(other_isbn)==10: if len(other_isbn) == 10:
other_isbn = regluit.core.isbn.convert_10_to_13(other_isbn) other_isbn = regluit.core.isbn.convert_10_to_13(other_isbn)
related_edition = add_by_isbn(other_isbn, work=work) related_edition = add_by_isbn(other_isbn, work=work)
@ -450,7 +466,7 @@ def add_related(isbn):
related_edition.work = work related_edition.work = work
related_edition.save() related_edition.save()
elif related_edition.work_id != work.id: elif related_edition.work_id != work.id:
logger.debug("merge_works path 1 %s %s", work.id, related_edition.work_id ) logger.debug("merge_works path 1 %s %s", work.id, related_edition.work_id)
work = merge_works(work, related_edition.work) work = merge_works(work, related_edition.work)
else: else:
if other_editions.has_key(related_language): if other_editions.has_key(related_language):
@ -461,13 +477,13 @@ def add_related(isbn):
# group the other language editions together # group the other language editions together
for lang_group in other_editions.itervalues(): for lang_group in other_editions.itervalues():
logger.debug("lang_group (ed, work): %s", [(ed.id, ed.work_id) for ed in lang_group]) logger.debug("lang_group (ed, work): %s", [(ed.id, ed.work_id) for ed in lang_group])
if len(lang_group)>1: if len(lang_group) > 1:
lang_edition = lang_group[0] lang_edition = lang_group[0]
logger.debug("lang_edition.id: %s", lang_edition.id) logger.debug("lang_edition.id: %s", lang_edition.id)
# compute the distinct set of works to merge into lang_edition.work # compute the distinct set of works to merge into lang_edition.work
works_to_merge = set([ed.work for ed in lang_group[1:]]) - set([lang_edition.work]) works_to_merge = set([ed.work for ed in lang_group[1:]]) - set([lang_edition.work])
for w in works_to_merge: for w in works_to_merge:
logger.debug("merge_works path 2 %s %s", lang_edition.work_id, w.id ) logger.debug("merge_works path 2 %s %s", lang_edition.work_id, w.id)
merged_work = merge_works(lang_edition.work, w) merged_work = merge_works(lang_edition.work, w)
models.WorkRelation.objects.get_or_create( models.WorkRelation.objects.get_or_create(
to_work=lang_group[0].work, to_work=lang_group[0].work,
@ -479,9 +495,10 @@ def add_related(isbn):
def thingisbn(isbn): def thingisbn(isbn):
"""given an ISBN return a list of related edition ISBNs, according to """given an ISBN return a list of related edition ISBNs, according to
Library Thing. (takes isbn_10 or isbn_13, returns isbn_10, except for 979 isbns, which come back as isbn_13') 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) logger.info("looking up %s at ThingISBN", isbn)
url = "https://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 xml = requests.get(url, headers={"User-Agent": settings.USER_AGENT}).content
doc = ElementTree.fromstring(xml) doc = ElementTree.fromstring(xml)
@ -492,16 +509,17 @@ def merge_works(w1, w2, user=None):
"""will merge the second work (w2) into the first (w1) """will merge the second work (w2) into the first (w1)
""" """
logger.info("merging work %s into %s", w2.id, w1.id) logger.info("merging work %s into %s", w2.id, w1.id)
# don't merge if the works are the same or at least one of the works has no id (for example, when w2 has already been deleted) # don't merge if the works are the same or at least one of the works has no id
#(for example, when w2 has already been deleted)
if w1 is None or w2 is None or w1.id == w2.id or w1.id is None or w2.id is None: if w1 is None or w2 is None or w1.id == w2.id or w1.id is None or w2.id is None:
return w1 return w1
if w2.selected_edition != None and w1.selected_edition == None: if w2.selected_edition is not None and w1.selected_edition is None:
#the merge should be reversed #the merge should be reversed
temp = w1 temp = w1
w1 = w2 w1 = w2
w2 = temp w2 = temp
models.WasWork(was=w2.pk, work=w1, user=user).save() models.WasWork(was=w2.pk, work=w1, user=user).save()
for ww in models.WasWork.objects.filter(work = w2): for ww in models.WasWork.objects.filter(work=w2):
ww.work = w1 ww.work = w1
ww.save() ww.save()
if w2.description and not w1.description: if w2.description and not w1.description:
@ -561,10 +579,12 @@ def merge_works(w1, w2, user=None):
return w1 return w1
def detach_edition(e): def detach_edition(e):
"""will detach edition from its work, creating a new stub work. if remerge=true, will see if there's another work to attach to """
will detach edition from its work, creating a new stub work. if remerge=true, will see if
there's another work to attach to
""" """
logger.info("splitting edition %s from %s", e, e.work) logger.info("splitting edition %s from %s", e, e.work)
w = models.Work(title=e.title, language = e.work.language) w = models.Work(title=e.title, language=e.work.language)
w.save() w.save()
for identifier in e.identifiers.all(): for identifier in e.identifiers.all():
@ -574,10 +594,13 @@ def detach_edition(e):
e.work = w e.work = w
e.save() e.save()
SPAM_STRINGS = ["GeneralBooksClub.com", "AkashaPublishing.Com"]
def despam_description(description): def despam_description(description):
""" a lot of descriptions from openlibrary have free-book promotion text; this removes some of it.""" """ a lot of descriptions from openlibrary have free-book promotion text;
if description.find("GeneralBooksClub.com")>-1 or description.find("AkashaPublishing.Com")>-1: this removes some of it."""
return "" for spam in SPAM_STRINGS:
if description.find(spam) > -1:
return ""
pieces = description.split("1stWorldLibrary.ORG -") pieces = description.split("1stWorldLibrary.ORG -")
if len(pieces) > 1: if len(pieces) > 1:
return pieces[1] return pieces[1]
@ -586,7 +609,7 @@ def despam_description(description):
return pieces[1] return pieces[1]
return description return description
def add_openlibrary(work, hard_refresh = False): def add_openlibrary(work, hard_refresh=False):
if (not hard_refresh) and work.openlibrary_lookup is not None: if (not hard_refresh) and work.openlibrary_lookup is not None:
# don't hit OL if we've visited in the past month or so # don't hit OL if we've visited in the past month or so
if now()- work.openlibrary_lookup < timedelta(days=30): if now()- work.openlibrary_lookup < timedelta(days=30):
@ -616,17 +639,38 @@ def add_openlibrary(work, hard_refresh = False):
if e[isbn_key].has_key('details'): if e[isbn_key].has_key('details'):
if e[isbn_key]['details'].has_key('oclc_numbers'): if e[isbn_key]['details'].has_key('oclc_numbers'):
for oclcnum in e[isbn_key]['details']['oclc_numbers']: for oclcnum in e[isbn_key]['details']['oclc_numbers']:
models.Identifier.get_or_add(type='oclc', value=oclcnum, work=work, edition=edition) models.Identifier.get_or_add(
type='oclc',
value=oclcnum,
work=work,
edition=edition
)
if e[isbn_key]['details'].has_key('identifiers'): if e[isbn_key]['details'].has_key('identifiers'):
ids = e[isbn_key]['details']['identifiers'] ids = e[isbn_key]['details']['identifiers']
if ids.has_key('goodreads'): if ids.has_key('goodreads'):
models.Identifier.get_or_add(type='gdrd', value=ids['goodreads'][0], work=work, edition=edition) models.Identifier.get_or_add(
type='gdrd',
value=ids['goodreads'][0],
work=work, edition=edition
)
if ids.has_key('librarything'): if ids.has_key('librarything'):
models.Identifier.get_or_add(type='ltwk', value=ids['librarything'][0], work=work) models.Identifier.get_or_add(
type='ltwk',
value=ids['librarything'][0],
work=work
)
if ids.has_key('google'): if ids.has_key('google'):
models.Identifier.get_or_add(type='goog', value=ids['google'][0], work=work) models.Identifier.get_or_add(
type='goog',
value=ids['google'][0],
work=work
)
if ids.has_key('project_gutenberg'): if ids.has_key('project_gutenberg'):
models.Identifier.get_or_add(type='gute', value=ids['project_gutenberg'][0], work=work) models.Identifier.get_or_add(
type='gute',
value=ids['project_gutenberg'][0],
work=work
)
if e[isbn_key]['details'].has_key('works'): if e[isbn_key]['details'].has_key('works'):
work_key = e[isbn_key]['details']['works'].pop(0)['key'] work_key = e[isbn_key]['details']['works'].pop(0)['key']
logger.info("got openlibrary work %s for isbn %s", work_key, isbn_key) logger.info("got openlibrary work %s for isbn %s", work_key, isbn_key)
@ -639,7 +683,9 @@ def add_openlibrary(work, hard_refresh = False):
if description.has_key('value'): if description.has_key('value'):
description = description['value'] description = description['value']
description = despam_description(description) description = despam_description(description)
if not work.description or work.description.startswith('{') or len(description) > len(work.description): if not work.description or \
work.description.startswith('{') or \
len(description) > len(work.description):
work.description = description work.description = description
work.save() work.save()
if w.has_key('subjects') and len(w['subjects']) > len(subjects): if w.has_key('subjects') and len(w['subjects']) > len(subjects):
@ -670,19 +716,20 @@ def _get_json(url, params={}, type='gb'):
if response.status_code == 200: if response.status_code == 200:
return json.loads(response.content) return json.loads(response.content)
else: else:
logger.error("unexpected HTTP response: %s" % response) logger.error("unexpected HTTP response: %s", response)
if response.content: if response.content:
logger.error("response content: %s" % response.content) logger.error("response content: %s", response.content)
raise LookupFailure("GET failed: url=%s and params=%s" % (url, params)) raise LookupFailure("GET failed: url=%s and params=%s" % (url, params))
def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url, format, license, lang, publication_date): def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url,
format, license, lang, publication_date):
# let's start with instantiating the relevant Work and Edition if they don't already exist ''' let's start with instantiating the relevant Work and Edition if they don't already exist'''
try: try:
work = models.Identifier.objects.get(type='olwk', value=ol_work_id).work work = models.Identifier.objects.get(type='olwk', value=ol_work_id).work
except models.Identifier.DoesNotExist: # try to find an Edition with the seed_isbn and use that work to hang off of except models.Identifier.DoesNotExist:
# try to find an Edition with the seed_isbn and use that work to hang off of
sister_edition = add_by_isbn(seed_isbn) sister_edition = add_by_isbn(seed_isbn)
if sister_edition.new: if sister_edition.new:
# add related editions asynchronously # add related editions asynchronously
@ -694,14 +741,18 @@ def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url
# Now pull out any existing Gutenberg editions tied to the work with the proper Gutenberg ID # Now pull out any existing Gutenberg editions tied to the work with the proper Gutenberg ID
try: try:
edition = models.Identifier.objects.get(type='gtbg', value=gutenberg_etext_id ).edition edition = models.Identifier.objects.get(type='gtbg', value=gutenberg_etext_id).edition
except models.Identifier.DoesNotExist: except models.Identifier.DoesNotExist:
edition = models.Edition() edition = models.Edition()
edition.title = title edition.title = title
edition.work = work edition.work = work
edition.save() edition.save()
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 # check to see whether the Edition hasn't already been loaded first
# search by url # search by url
@ -709,9 +760,9 @@ def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url
# format: what's the controlled vocab? -- from Google -- alternative would be mimetype # format: what's the controlled vocab? -- from Google -- alternative would be mimetype
if len(ebooks): if ebooks:
ebook = ebooks[0] ebook = ebooks[0]
elif len(ebooks) == 0: # need to create new ebook else: # need to create new ebook
ebook = models.Ebook() ebook = models.Ebook()
if len(ebooks) > 1: if len(ebooks) > 1:
@ -720,7 +771,7 @@ def load_gutenberg_edition(title, gutenberg_etext_id, ol_work_id, seed_isbn, url
ebook.format = format ebook.format = format
ebook.provider = 'Project Gutenberg' ebook.provider = 'Project Gutenberg'
ebook.url = url ebook.url = url
ebook.rights = license ebook.rights = license
# is an Ebook instantiable without a corresponding Edition? (No, I think) # is an Ebook instantiable without a corresponding Edition? (No, I think)
@ -734,9 +785,9 @@ class LookupFailure(Exception):
pass pass
IDTABLE = [('librarything', 'ltwk'), ('goodreads', 'gdrd'), ('openlibrary', 'olwk'), IDTABLE = [('librarything', 'ltwk'), ('goodreads', 'gdrd'), ('openlibrary', 'olwk'),
('gutenberg', 'gtbg'), ('isbn', 'isbn'), ('oclc', 'oclc'), ('gutenberg', 'gtbg'), ('isbn', 'isbn'), ('oclc', 'oclc'),
('edition_id', 'edid'), ('googlebooks', 'goog'), ('doi', 'doi'), ('http','http'), ('googlebooks', 'goog'), ('doi', 'doi'), ('http', 'http'), ('edition_id', 'edid'),
] ]
def load_from_yaml(yaml_url, test_mode=False): def load_from_yaml(yaml_url, test_mode=False):
""" """
@ -755,7 +806,7 @@ def edition_for_ident(id_type, id_value):
#print 'returning edition for {}: {}'.format(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): for ident in models.Identifier.objects.filter(type=id_type, value=id_value):
return ident.edition if ident.edition else ident.work.editions[0] return ident.edition if ident.edition else ident.work.editions[0]
def edition_for_etype(etype, metadata, default=None): def edition_for_etype(etype, metadata, default=None):
''' '''
assumes the metadata contains the isbn_etype attributes, and that the editions have been created. assumes the metadata contains the isbn_etype attributes, and that the editions have been created.
@ -774,7 +825,7 @@ def edition_for_etype(etype, metadata, default=None):
return edition_for_ident(key, metadata.identifiers[key]) return edition_for_ident(key, metadata.identifiers[key])
for key in metadata.edition_identifiers.keys(): for key in metadata.edition_identifiers.keys():
return edition_for_ident(key, metadata.identifiers[key]) return edition_for_ident(key, metadata.identifiers[key])
MATCH_LICENSE = re.compile(r'creativecommons.org/licenses/([^/]+)/') MATCH_LICENSE = re.compile(r'creativecommons.org/licenses/([^/]+)/')
def load_ebookfile(url, etype): def load_ebookfile(url, etype):
@ -793,14 +844,14 @@ def load_ebookfile(url, etype):
logger.error(u'could not open {}'.format(url)) logger.error(u'could not open {}'.format(url))
except ValidationError, e: except ValidationError, e:
logger.error(u'downloaded {} was not a valid {}'.format(url, etype)) logger.error(u'downloaded {} was not a valid {}'.format(url, etype))
class BasePandataLoader(object): class BasePandataLoader(object):
def __init__(self, url): def __init__(self, url):
self.base_url = url self.base_url = url
def load_from_pandata(self, metadata, work=None): def load_from_pandata(self, metadata, work=None):
''' metadata is a Pandata object''' ''' metadata is a Pandata object'''
#find an work to associate #find an work to associate
edition = None edition = None
has_ed_id = False has_ed_id = False
@ -817,7 +868,7 @@ class BasePandataLoader(object):
if value: if value:
if id_code not in WORK_IDENTIFIERS: if id_code not in WORK_IDENTIFIERS:
has_ed_id = True has_ed_id = True
value = value[0] if isinstance(value, list) else value value = value[0] if isinstance(value, list) else value
try: try:
id = models.Identifier.objects.get(type=id_code, value=value) id = models.Identifier.objects.get(type=id_code, value=value)
if work and id.work and id.work_id is not work.id: if work and id.work and id.work_id is not work.id:
@ -836,7 +887,6 @@ class BasePandataLoader(object):
# only need to create edid if there is no edition id for the edition # only need to create edid if there is no edition id for the edition
new_ids.append((identifier, id_code, value)) new_ids.append((identifier, id_code, value))
if not work: if not work:
work = models.Work.objects.create(title=metadata.title, language=metadata.language) work = models.Work.objects.create(title=metadata.title, language=metadata.language)
if not edition: if not edition:
@ -844,7 +894,7 @@ class BasePandataLoader(object):
(note, created) = models.EditionNote.objects.get_or_create(note=metadata.edition_note) (note, created) = models.EditionNote.objects.get_or_create(note=metadata.edition_note)
else: else:
note = None note = None
edition = models.Edition.objects.create( edition = models.Edition.objects.create(
title=metadata.title, title=metadata.title,
work=work, work=work,
note=note, note=note,
@ -858,24 +908,31 @@ class BasePandataLoader(object):
) )
if metadata.publisher: #always believe yaml if metadata.publisher: #always believe yaml
edition.set_publisher(metadata.publisher) edition.set_publisher(metadata.publisher)
if metadata.publication_date: #always believe yaml if metadata.publication_date: #always believe yaml
edition.publication_date = metadata.publication_date edition.publication_date = metadata.publication_date
#be careful about overwriting the work description
if metadata.description and len(metadata.description) > len(work.description): if metadata.description and len(metadata.description) > len(work.description):
#be careful about overwriting the work description # don't over-write reasonably long descriptions
work.description = metadata.description if len(work.description) < 500:
if metadata.creator and not edition.authors.count(): work.description = metadata.description
if metadata.creator and not edition.authors.count():
edition.authors.clear() edition.authors.clear()
for key in metadata.creator.keys(): for key in metadata.creator.keys():
creators = metadata.creator[key] creators = metadata.creator[key]
rel_code = inverse_marc_rels.get(key, 'aut') rel_code = inverse_marc_rels.get(key, None)
if not rel_code:
rel_code = inverse_marc_rels.get(key.rstrip('s'), 'auth')
creators = creators if isinstance(creators, list) else [creators] creators = creators if isinstance(creators, list) else [creators]
for creator in creators: for creator in creators:
edition.add_author(unreverse_name(creator.get('agent_name', '')), relation=rel_code) edition.add_author(unreverse_name(creator.get('agent_name', '')), relation=rel_code)
for yaml_subject in metadata.subjects: #always add yaml subjects (don't clear) for yaml_subject in metadata.subjects: #always add yaml subjects (don't clear)
if isinstance(yaml_subject, tuple): if isinstance(yaml_subject, tuple):
(authority, heading) = yaml_subject (authority, heading) = yaml_subject
elif isinstance(yaml_subject, str): elif isinstance(yaml_subject, str) or isinstance(yaml_subject, unicode) :
(authority, heading) = ( '', yaml_subject) (authority, heading) = ('', yaml_subject)
else: else:
continue continue
subject = models.Subject.set_by_name(heading, work=work, authority=authority) subject = models.Subject.set_by_name(heading, work=work, authority=authority)
@ -902,7 +959,7 @@ class BasePandataLoader(object):
contentfile = load_ebookfile(url, key) contentfile = load_ebookfile(url, key)
if contentfile: if contentfile:
contentfile_name = '/loaded/ebook_{}.{}'.format(edition.id, key) contentfile_name = '/loaded/ebook_{}.{}'.format(edition.id, key)
path = default_storage.save(contentfile_name, contentfile) path = default_storage.save(contentfile_name, contentfile)
lic = MATCH_LICENSE.search(metadata.rights_url) lic = MATCH_LICENSE.search(metadata.rights_url)
license = 'CC {}'.format(lic.group(1).upper()) if lic else '' license = 'CC {}'.format(lic.group(1).upper()) if lic else ''
ebf = models.EbookFile.objects.create( ebf = models.EbookFile.objects.create(
@ -922,10 +979,10 @@ class BasePandataLoader(object):
active=False, active=False,
user=user, user=user,
) )
ebf.ebook = ebook ebf.ebook = ebook
ebf.save() ebf.save()
class GithubLoader(BasePandataLoader): class GithubLoader(BasePandataLoader):
def load_ebooks(self, metadata, edition, test_mode=False): def load_ebooks(self, metadata, edition, test_mode=False):
# create Ebook for any ebook in the corresponding GitHub release # create Ebook for any ebook in the corresponding GitHub release
@ -947,16 +1004,16 @@ class GithubLoader(BasePandataLoader):
# not using ebook_name in this code # not using ebook_name in this code
ebooks_in_release = [('epub', 'book.epub')] ebooks_in_release = [('epub', 'book.epub')]
else: else:
ebooks_in_release = ebooks_in_github_release(repo_owner, repo_name, repo_tag, token=token) ebooks_in_release = ebooks_in_github_release(repo_owner, repo_name, repo_tag, token=token)
for (ebook_format, ebook_name) in ebooks_in_release: for (ebook_format, ebook_name) in ebooks_in_release:
(book_name_prefix, _ ) = re.search(r'(.*)\.([^\.]*)$', ebook_name).groups() (book_name_prefix, _) = re.search(r'(.*)\.([^\.]*)$', ebook_name).groups()
(ebook, created) = models.Ebook.objects.get_or_create( (ebook, created) = models.Ebook.objects.get_or_create(
url=git_download_from_yaml_url( url=git_download_from_yaml_url(
self.base_url, self.base_url,
metadata._version, metadata._version,
edition_name=book_name_prefix, edition_name=book_name_prefix,
format_= ebook_format format_=ebook_format
), ),
provider='Github', provider='Github',
rights=cc.match_license(metadata.rights), rights=cc.match_license(metadata.rights),
@ -967,8 +1024,10 @@ class GithubLoader(BasePandataLoader):
def git_download_from_yaml_url(yaml_url, version, edition_name='book', format_='epub'): 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 '''
# to https://github.com/GITenberg/Adventures-of-Huckleberry-Finn_76/releases/download/v0.0.3/Adventures-of-Huckleberry-Finn.epub go from https://github.com/GITenberg/Adventures-of-Huckleberry-Finn_76/raw/master/metadata.yaml
to https://github.com/GITenberg/Adventures-of-Huckleberry-Finn_76/releases/download/v0.0.3/Adventures-of-Huckleberry-Finn.epub
'''
if yaml_url.endswith('raw/master/metadata.yaml'): if yaml_url.endswith('raw/master/metadata.yaml'):
repo_url = yaml_url[0:-24] repo_url = yaml_url[0:-24]
#print (repo_url,version,edition_name) #print (repo_url,version,edition_name)
@ -1014,21 +1073,10 @@ def ebooks_in_github_release(repo_owner, repo_name, tag, token=None):
for asset in release.iter_assets() for asset in release.iter_assets()
if EBOOK_FORMATS.get(asset.content_type) is not None] if EBOOK_FORMATS.get(asset.content_type) is not None]
def add_by_webpage(url, work=None, user=None): def add_from_bookdatas(bookdatas):
edition = None ''' bookdatas are iterators of scrapers '''
scraper = get_scraper(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
def add_by_sitemap(url, maxnum=None):
editions = [] editions = []
for bookdata in scrape_sitemap(url, maxnum=maxnum): for bookdata in bookdatas:
edition = work = None edition = work = None
loader = BasePandataLoader(bookdata.base) loader = BasePandataLoader(bookdata.base)
pandata = Pandata() pandata = Pandata()
@ -1040,6 +1088,3 @@ def add_by_sitemap(url, maxnum=None):
if edition: if edition:
editions.append(edition) editions.append(edition)
return editions return editions

View File

@ -1,7 +1,8 @@
from django.apps import apps from django.apps import apps
from django.contrib.auth.models import User
from django.db.models import Q from django.db.models import Q
from regluit.core import cc from regluit.core import cc
class BaseFacet(object): class BaseFacet(object):
facet_name = 'all' facet_name = 'all'
model_filters ={} model_filters ={}
@ -129,8 +130,10 @@ class FormatFacetGroup(FacetGroup):
return "These eBooks available in %s format." % self.facet_name return "These eBooks available in %s format." % self.facet_name
return FormatFacet return FormatFacet
idtitles = {'doab': 'indexed in DOAB', 'gtbg':'available in Project Gutenberg'} idtitles = {'doab': 'indexed in DOAB', 'gtbg':'available in Project Gutenberg',
idlabels = {'doab': 'DOAB', 'gtbg':'Project Gutenberg'} '-doab': 'not in DOAB', '-gtbg':'not from Project Gutenberg', }
idlabels = {'doab': 'DOAB', 'gtbg':'Project Gutenberg',
'-doab': 'not DOAB', '-gtbg':'not Project Gutenberg'}
class IdFacetGroup(FacetGroup): class IdFacetGroup(FacetGroup):
def __init__(self): def __init__(self):
super(FacetGroup,self).__init__() super(FacetGroup,self).__init__()
@ -143,10 +146,16 @@ class IdFacetGroup(FacetGroup):
def set_name(self): def set_name(self):
self.facet_name=facet_name self.facet_name=facet_name
def id_filter(query_set): def id_filter(query_set):
return query_set.filter(identifiers__type=facet_name) if facet_name[0] == '-':
return query_set.exclude(identifiers__type=facet_name[1:])
else:
return query_set.filter(identifiers__type=facet_name)
model_filters = {} model_filters = {}
def get_query_set(self): def get_query_set(self):
return self._get_query_set().filter(identifiers__type=self.facet_name) if facet_name[0] == '-':
return self._get_query_set().exclude(identifiers__type=self.facet_name[1:])
else:
return self._get_query_set().filter(identifiers__type=self.facet_name)
def template(self): def template(self):
return 'facets/id.html' return 'facets/id.html'
@property @property
@ -276,6 +285,42 @@ class SearchFacetGroup(FacetGroup):
def description(self): def description(self):
return "eBooks for {}".format(self.term) return "eBooks for {}".format(self.term)
return KeywordFacet return KeywordFacet
class SupporterFacetGroup(FacetGroup):
def __init__(self):
super(FacetGroup,self).__init__()
self.title = 'Supporter Faves'
# make facets in TOPKW available for display
self.facets = []
self.label = '{} are ...'.format(self.title)
def has_facet(self, facet_name):
# recognize any facet_name that starts with "@" as a valid facet name
return facet_name.startswith('@')
def get_facet_class(self, facet_name):
class SupporterFacet(NamedFacet):
def set_name(self):
self.facet_name = facet_name
self.username = self.facet_name[1:]
try:
user = User.objects.get(username=self.username)
self.fave_set = user.wishlist.works.all()
except User.DoesNotExist:
self.fave_set = self.model.objects.none()
def get_query_set(self):
return self._get_query_set().filter(pk__in=self.fave_set)
def template(self):
return 'facets/supporter.html'
@property
def description(self):
return "eBooks faved by @{}".format(self.username)
return SupporterFacet
class PublisherFacetGroup(FacetGroup): class PublisherFacetGroup(FacetGroup):
@ -325,7 +370,7 @@ class PublisherFacetGroup(FacetGroup):
return PublisherFacet return PublisherFacet
# order of groups in facet_groups determines order of display on /free/ # order of groups in facet_groups determines order of display on /free/
facet_groups = [KeywordFacetGroup(), FormatFacetGroup(), LicenseFacetGroup(), PublisherFacetGroup(), IdFacetGroup(), SearchFacetGroup()] facet_groups = [KeywordFacetGroup(), FormatFacetGroup(), LicenseFacetGroup(), PublisherFacetGroup(), IdFacetGroup(), SearchFacetGroup(), SupporterFacetGroup()]
def get_facet(facet_name): def get_facet(facet_name):
for facet_group in facet_groups: for facet_group in facet_groups:

View File

@ -465,7 +465,7 @@
"pk": 150, "pk": 150,
"model": "core.premium", "model": "core.premium",
"fields": { "fields": {
"description": "No premium, thanks! I just want to help unglue.", "description": "Nothing extra, thanks! I just want to support this campaign.",
"campaign": null, "campaign": null,
"created": "2011-11-17T22:03:37", "created": "2011-11-17T22:03:37",
"amount": "0", "amount": "0",

View File

@ -98,7 +98,7 @@
"campaign": null, "campaign": null,
"amount": 0, "amount": 0,
"type": "00", "type": "00",
"description": "No premium, thanks! I just want to help unglue.", "description": "Nothing extra, thanks! I just want to support this campaign",
"created": "2011-11-17 22:03:37" "created": "2011-11-17 22:03:37"
} }
}, },

View File

@ -0,0 +1,54 @@
import requests
from bs4 import BeautifulSoup
from django.conf import settings
from gitenberg.metadata.pandata import Pandata
from regluit.core.bookloader import add_from_bookdatas, BasePandataLoader
from .scrape import BaseScraper
from .hathitrust import HathitrustScraper
from .pressbooks import PressbooksScraper
from .springer import SpringerScraper
from .ubiquity import UbiquityScraper
from .smashwords import SmashwordsScraper
def get_scraper(url):
scrapers = [
PressbooksScraper,
HathitrustScraper,
SpringerScraper,
UbiquityScraper,
SmashwordsScraper,
BaseScraper,
]
for scraper in scrapers:
if scraper.can_scrape(url):
return scraper(url)
def scrape_sitemap(url, maxnum=None):
try:
response = requests.get(url, headers={"User-Agent": settings.USER_AGENT})
doc = BeautifulSoup(response.content, 'lxml')
for page in doc.find_all('loc')[0:maxnum]:
scraper = get_scraper(page.text)
if scraper.metadata.get('genre', None) == 'book':
yield scraper
except requests.exceptions.RequestException as e:
logger.error(e)
def add_by_webpage(url, work=None, user=None):
edition = None
scraper = get_scraper(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
def add_by_sitemap(url, maxnum=None):
return add_from_bookdatas(scrape_sitemap(url, maxnum=maxnum))

View File

@ -0,0 +1,63 @@
import re
import requests
from RISparser import read as readris
from django.conf import settings
from regluit.core.validation import identifier_cleaner
from .scrape import BaseScraper
class HathitrustScraper(BaseScraper):
can_scrape_hosts = ['hathitrust.org']
can_scrape_strings = ['hdl.handle.net/2027/']
CATALOG = re.compile(r'catalog.hathitrust.org/Record/(\d+)')
def setup(self):
catalog_a = self.doc.find('a', href=self.CATALOG)
if catalog_a:
catalog_num = self.CATALOG.search(catalog_a['href']).group(1)
ris_url = 'https://catalog.hathitrust.org/Search/SearchExport?handpicked={}&method=ris'.format(catalog_num)
response = requests.get(ris_url, headers={"User-Agent": settings.USER_AGENT})
records = readris(response.text.splitlines()) if response.status_code == 200 else []
for record in records:
self.record = record
return
self.record = {}
def get_downloads(self):
dl_a = self.doc.select_one('#fullPdfLink')
value = dl_a['href'] if dl_a else None
if value:
self.set(
'download_url_{}'.format('pdf'),
'https://babel.hathitrust.org{}'.format(value)
)
def get_isbns(self):
isbn = self.record.get('issn', [])
value = identifier_cleaner('isbn', quiet=True)(isbn)
return {'print': value} if value else {}
def get_title(self):
self.set('title', self.record.get('title', ''))
def get_keywords(self):
self.set('subjects', self.record.get('keywords', []))
def get_publisher(self):
self.set('publisher', self.record.get('publisher', ''))
def get_pubdate(self):
self.set('publication_date', self.record.get('year', ''))
def get_description(self):
notes = self.record.get('notes', [])
self.set('description', '\r'.join(notes))
def get_genre(self):
self.set('genre', self.record.get('type_of_reference', '').lower())

View File

@ -0,0 +1,43 @@
from regluit.core.validation import identifier_cleaner
from . import BaseScraper
class PressbooksScraper(BaseScraper):
can_scrape_hosts = ['bookkernel.com', 'milnepublishing.geneseo.edu',
'press.rebus.community', 'pb.unizin.org']
can_scrape_strings = ['pressbooks']
def get_downloads(self):
for dl_type in ['epub', 'mobi', 'pdf']:
download_el = self.doc.select_one('.{}'.format(dl_type))
if download_el and download_el.find_parent():
value = download_el.find_parent().get('href')
if value:
self.set('download_url_{}'.format(dl_type), value)
def get_publisher(self):
value = self.get_dt_dd('Publisher')
if not value:
value = self.doc.select_one('.cie-name')
value = value.text if value else None
if value:
self.set('publisher', value)
else:
super(PressbooksScraper, self).get_publisher()
def get_title(self):
value = self.doc.select_one('.entry-title a[title]')
value = value['title'] if value else None
if value:
self.set('title', value)
else:
super(PressbooksScraper, self).get_title()
def get_isbns(self):
'''add isbn identifiers and return a dict of edition keys and ISBNs'''
isbns = {}
for (key, label) in [('electronic', 'Ebook ISBN'), ('paper', 'Print ISBN')]:
isbn = identifier_cleaner('isbn', quiet=True)(self.get_dt_dd(label))
if isbn:
self.identifiers['isbn_{}'.format(key)] = isbn
isbns[key] = isbn
return isbns

View File

@ -1,14 +1,14 @@
import re import re
import logging import logging
from urlparse import urlparse
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
#from gitenberg.metadata.pandata import Pandata #from gitenberg.metadata.pandata import Pandata
from django.conf import settings from django.conf import settings
from urlparse import urljoin from urlparse import urljoin
from RISparser import read as readris
from regluit.core import models from regluit.core import models
from regluit.core.validation import identifier_cleaner, authlist_cleaner from regluit.core.validation import authlist_cleaner, identifier_cleaner, validate_date
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -16,10 +16,29 @@ CONTAINS_COVER = re.compile('cover')
CONTAINS_CC = re.compile('creativecommons.org') CONTAINS_CC = re.compile('creativecommons.org')
CONTAINS_OCLCNUM = re.compile('worldcat.org/oclc/(\d+)') CONTAINS_OCLCNUM = re.compile('worldcat.org/oclc/(\d+)')
class BaseScraper(object): class BaseScraper(object):
''' '''
designed to make at least a decent gues for webpages that embed metadata designed to make at least a decent guess for webpages that embed metadata
''' '''
can_scrape_hosts = False
can_scrape_strings = False
@classmethod
def can_scrape(cls, url):
''' return True if the class can scrape the URL '''
if not (cls.can_scrape_hosts or cls.can_scrape_strings):
return True
if cls.can_scrape_hosts:
urlhost = urlparse(url).hostname
if urlhost:
for host in cls.can_scrape_hosts:
if urlhost.endswith(host):
return True
if cls.can_scrape_strings:
for pass_str in cls.can_scrape_strings:
if url.find(pass_str) >= 0:
return True
return False
def __init__(self, url): def __init__(self, url):
self.metadata = {} self.metadata = {}
self.identifiers = {'http': url} self.identifiers = {'http': url}
@ -30,6 +49,8 @@ class BaseScraper(object):
if response.status_code == 200: if response.status_code == 200:
self.base = response.url self.base = response.url
self.doc = BeautifulSoup(response.content, 'lxml') self.doc = BeautifulSoup(response.content, 'lxml')
for review in self.doc.find_all(itemtype="http://schema.org/Review"):
review.clear()
self.setup() self.setup()
self.get_genre() self.get_genre()
self.get_title() self.get_title()
@ -55,23 +76,22 @@ class BaseScraper(object):
# #
# utilities # utilities
# #
def set(self, name, value): def set(self, name, value):
self.metadata[name] = value self.metadata[name] = value
def fetch_one_el_content(self, el_name): def fetch_one_el_content(self, el_name):
data_el = self.doc.find(el_name) data_el = self.doc.find(el_name)
value = '' value = ''
if data_el: if data_el:
value = data_el.text value = data_el.text
return value return value
def check_metas(self, meta_list, **attrs): def check_metas(self, meta_list, **attrs):
value = '' value = ''
list_mode = attrs.pop('list_mode', 'longest') list_mode = attrs.pop('list_mode', 'longest')
for meta_name in meta_list: for meta_name in meta_list:
attrs['name'] = meta_name attrs['name'] = re.compile('^{}$'.format(meta_name), flags=re.I)
metas = self.doc.find_all('meta', attrs=attrs) metas = self.doc.find_all('meta', attrs=attrs)
if len(metas) == 0: if len(metas) == 0:
# some sites put schema.org metadata in metas # some sites put schema.org metadata in metas
@ -79,6 +99,11 @@ class BaseScraper(object):
attrs['itemprop'] = meta_name attrs['itemprop'] = meta_name
metas = self.doc.find_all('meta', attrs=attrs) metas = self.doc.find_all('meta', attrs=attrs)
del(attrs['itemprop']) del(attrs['itemprop'])
if len(metas) == 0:
# og metadata in often in 'property' not name
attrs['property'] = meta_name
metas = self.doc.find_all('meta', attrs=attrs)
del(attrs['property'])
for meta in metas: for meta in metas:
el_value = meta.get('content', '').strip() el_value = meta.get('content', '').strip()
if list_mode == 'longest': if list_mode == 'longest':
@ -86,25 +111,34 @@ class BaseScraper(object):
value = el_value value = el_value
elif list_mode == 'list': elif list_mode == 'list':
if value == '': if value == '':
value = [el_value] value = [el_value]
else: else:
value.append(el_value) value.append(el_value)
if value: if value:
return value return value
return value return value
def get_dt_dd(self, name): def get_dt_dd(self, name):
''' get the content of <dd> after a <dt> containing name''' ''' get the content of <dd> after a <dt> containing name'''
dt = self.doc.find('dt', string=re.compile(name)) dt = self.doc.find('dt', string=re.compile(name))
dd = dt.find_next_sibling('dd') if dt else None dd = dt.find_next_sibling('dd') if dt else None
return dd.text if dd else None return dd.text if dd else None
def get_itemprop(self, name): def get_itemprop(self, name, **attrs):
value_list = [] value_list = []
list_mode = attrs.pop('list_mode', 'list')
attrs = {'itemprop': name} attrs = {'itemprop': name}
props = self.doc.find_all(attrs=attrs) props = self.doc.find_all(attrs=attrs)
attrs = {'property': name}
props = props if props else self.doc.find_all(attrs=attrs)
for el in props: for el in props:
value_list.append(el.text) if list_mode == 'one_item':
return el.text if el.text else el.get('content')
else:
if el.text:
value_list.append(el.text)
elif el.has_key('content'):
value_list.append(el['content'])
return value_list return value_list
def setup(self): def setup(self):
@ -115,24 +149,23 @@ class BaseScraper(object):
# #
def get_genre(self): def get_genre(self):
value = self.check_metas(['DC.Type', 'dc.type', 'og:type']) value = self.check_metas([r'dc\.type', 'og:type'])
if value and value in ('Text.Book', 'book'): if value and value in ('Text.Book', 'book'):
self.set('genre', 'book') self.set('genre', 'book')
def get_title(self): def get_title(self):
value = self.check_metas(['DC.Title', 'dc.title', 'citation_title', 'og:title', 'title']) value = self.check_metas([r'dc\.title', 'citation_title', 'og:title', 'title'])
if not value: if not value:
value = self.fetch_one_el_content('title') value = self.fetch_one_el_content('title')
self.set('title', value) self.set('title', value)
def get_language(self): def get_language(self):
value = self.check_metas(['DC.Language', 'dc.language', 'language', 'inLanguage']) value = self.check_metas([r'dc\.language', 'language', 'inLanguage'])
self.set('language', value) self.set('language', value)
def get_description(self): def get_description(self):
value = self.check_metas([ value = self.check_metas([
'DC.Description', r'dc\.description',
'dc.description',
'og:description', 'og:description',
'description' 'description'
]) ])
@ -141,36 +174,43 @@ class BaseScraper(object):
def get_isbns(self): def get_isbns(self):
'''return a dict of edition keys and ISBNs''' '''return a dict of edition keys and ISBNs'''
isbns = {} isbns = {}
label_map = {'epub': 'EPUB', 'mobi': 'Mobi', isbn_cleaner = identifier_cleaner('isbn', quiet=True)
label_map = {'epub': 'EPUB', 'mobi': 'Mobi',
'paper': 'Paperback', 'pdf':'PDF', 'hard':'Hardback'} 'paper': 'Paperback', 'pdf':'PDF', 'hard':'Hardback'}
for key in label_map.keys(): for key in label_map.keys():
isbn_key = 'isbn_{}'.format(key) isbn_key = 'isbn_{}'.format(key)
value = self.check_metas(['citation_isbn'], type=label_map[key]) value = self.check_metas(['citation_isbn'], type=label_map[key])
value = identifier_cleaner('isbn')(value) value = isbn_cleaner(value)
if value: if value:
isbns[isbn_key] = value isbns[isbn_key] = value
self.identifiers[isbn_key] = value self.identifiers[isbn_key] = value
if not isbns:
values = self.check_metas(['book:isbn', 'books:isbn'], list_mode='list')
values = values if values else self.get_itemprop('isbn')
if values:
value = isbn_cleaner(values[0])
isbns = {'':value} if value else {}
return isbns return isbns
def get_identifiers(self): def get_identifiers(self):
value = self.check_metas(['DC.Identifier.URI']) value = self.check_metas([r'DC\.Identifier\.URI'])
if not value: if not value:
value = self.doc.select_one('link[rel=canonical]') value = self.doc.select_one('link[rel=canonical]')
value = value['href'] if value else None value = value['href'] if value else None
value = identifier_cleaner('http')(value) value = identifier_cleaner('http', quiet=True)(value)
if value: if value:
self.identifiers['http'] = value self.identifiers['http'] = value
value = self.check_metas(['DC.Identifier.DOI', 'citation_doi']) value = self.check_metas([r'DC\.Identifier\.DOI', 'citation_doi'])
value = identifier_cleaner('doi')(value) value = identifier_cleaner('doi', quiet=True)(value)
if value: if value:
self.identifiers['doi'] = value self.identifiers['doi'] = value
#look for oclc numbers #look for oclc numbers
links = self.doc.find_all(href=CONTAINS_OCLCNUM) links = self.doc.find_all(href=CONTAINS_OCLCNUM)
for link in links: for link in links:
oclcmatch = CONTAINS_OCLCNUM.search(link['href']) oclcmatch = CONTAINS_OCLCNUM.search(link['href'])
if oclcmatch: if oclcmatch:
value = identifier_cleaner('oclc')(oclcmatch.group(1)) value = identifier_cleaner('oclc', quiet=True)(oclcmatch.group(1))
if value: if value:
self.identifiers['oclc'] = value self.identifiers['oclc'] = value
break break
@ -189,7 +229,7 @@ class BaseScraper(object):
value = self.check_metas(['citation_isbn'], list_mode='list') value = self.check_metas(['citation_isbn'], list_mode='list')
if len(value): if len(value):
for isbn in value: for isbn in value:
isbn = identifier_cleaner('isbn')(isbn) isbn = identifier_cleaner('isbn', quiet=True)(isbn)
if isbn: if isbn:
ed_list.append({ ed_list.append({
'_edition': isbn, '_edition': isbn,
@ -197,185 +237,86 @@ class BaseScraper(object):
}) })
if len(ed_list): if len(ed_list):
self.set('edition_list', ed_list) self.set('edition_list', ed_list)
def get_keywords(self): def get_keywords(self):
value = self.check_metas(['keywords']).strip(',;') value = self.check_metas(['keywords']).strip(',;')
if value: if value:
self.set('subjects', re.split(' *[;,] *', value)) self.set('subjects', re.split(' *[;,] *', value))
def get_publisher(self): def get_publisher(self):
value = self.check_metas(['citation_publisher', 'DC.Source']) value = self.check_metas(['citation_publisher', r'DC\.Source'])
if value: if value:
self.set('publisher', value) self.set('publisher', value)
def get_pubdate(self): def get_pubdate(self):
value = self.check_metas(['citation_publication_date', 'DC.Date.issued', 'datePublished']) value = self.get_itemprop('datePublished', list_mode='one_item')
if not value:
value = self.check_metas([
'citation_publication_date', r'DC\.Date\.issued', 'datePublished',
'books:release_date', 'book:release_date'
])
if value: if value:
self.set('publication_date', value) value = validate_date(value)
if value:
self.set('publication_date', value)
def get_author_list(self):
value_list = self.get_itemprop('author')
if not value_list:
value_list = self.check_metas([
r'DC\.Creator\.PersonalName',
'citation_author',
'author',
], list_mode='list')
if not value_list:
return []
return value_list
def get_role(self):
return 'author'
def get_authors(self): def get_authors(self):
value_list = self.check_metas([ role = self.get_role()
'DC.Creator.PersonalName', value_list = self.get_author_list()
'citation_author',
'author',
], list_mode='list')
if not value_list:
value_list = self.get_itemprop('author')
if not value_list:
return
creator_list = [] creator_list = []
value_list = authlist_cleaner(value_list) value_list = authlist_cleaner(value_list)
if len(value_list) == 1: if len(value_list) == 0:
self.set('creator', {'author': {'agent_name': value_list[0]}})
return return
for auth in value_list: if len(value_list) == 1:
self.set('creator', {role: {'agent_name': value_list[0]}})
return
for auth in value_list:
creator_list.append({'agent_name': auth}) creator_list.append({'agent_name': auth})
self.set('creator', {'authors': creator_list }) self.set('creator', {'{}s'.format(role): creator_list })
def get_cover(self): def get_cover(self):
image_url = self.check_metas(['og.image', 'image', 'twitter:image']) image_url = self.check_metas(['og:image', 'image', 'twitter:image'])
if not image_url: if not image_url:
block = self.doc.find(class_=CONTAINS_COVER) block = self.doc.find(class_=CONTAINS_COVER)
block = block if block else self.doc block = block if block else self.doc
img = block.find_all('img', src=CONTAINS_COVER) img = block.find_all('img', src=CONTAINS_COVER)
if img: if img:
cover_uri = img[0].get('src', None) image_url = img[0].get('src', None)
if image_url: if image_url:
if not image_url.startswith('http'): if not image_url.startswith('http'):
image_url = urljoin(self.base, image_url) image_url = urljoin(self.base, image_url)
self.set('covers', [{'image_url': image_url}]) self.set('covers', [{'image_url': image_url}])
def get_downloads(self): def get_downloads(self):
for dl_type in ['epub', 'mobi', 'pdf']: for dl_type in ['epub', 'mobi', 'pdf']:
dl_meta = 'citation_{}_url'.format(dl_type) dl_meta = 'citation_{}_url'.format(dl_type)
value = self.check_metas([dl_meta]) value = self.check_metas([dl_meta])
if value: if value:
self.set('download_url_{}'.format(dl_type), value) self.set('download_url_{}'.format(dl_type), value)
def get_license(self): def get_license(self):
'''only looks for cc licenses''' '''only looks for cc licenses'''
links = self.doc.find_all(href=CONTAINS_CC) links = self.doc.find_all(href=CONTAINS_CC)
for link in links: for link in links:
self.set('rights_url', link['href']) self.set('rights_url', link['href'])
@classmethod
def can_scrape(cls, url):
''' return True if the class can scrape the URL '''
return True
class PressbooksScraper(BaseScraper):
def get_downloads(self):
for dl_type in ['epub', 'mobi', 'pdf']:
download_el = self.doc.select_one('.{}'.format(dl_type))
if download_el and download_el.find_parent():
value = download_el.find_parent().get('href')
if value:
self.set('download_url_{}'.format(dl_type), value)
def get_publisher(self):
value = self.get_dt_dd('Publisher')
if not value:
value = self.doc.select_one('.cie-name')
value = value.text if value else None
if value:
self.set('publisher', value)
else:
super(PressbooksScraper, self).get_publisher()
def get_title(self):
value = self.doc.select_one('.entry-title a[title]')
value = value['title'] if value else None
if value:
self.set('title', value)
else:
super(PressbooksScraper, self).get_title()
def get_isbns(self):
'''add isbn identifiers and return a dict of edition keys and ISBNs'''
isbns = {}
for (key, label) in [('electronic', 'Ebook ISBN'), ('paper', 'Print ISBN')]:
isbn = identifier_cleaner('isbn')(self.get_dt_dd(label))
if isbn:
self.identifiers['isbn_{}'.format(key)] = isbn
isbns[key] = isbn
return isbns
@classmethod
def can_scrape(cls, url):
''' return True if the class can scrape the URL '''
return url.find('press.rebus.community') > 0 or url.find('pressbooks.com') > 0
class HathitrustScraper(BaseScraper):
CATALOG = re.compile(r'catalog.hathitrust.org/Record/(\d+)')
def setup(self):
catalog_a = self.doc.find('a', href=self.CATALOG)
if catalog_a:
catalog_num = self.CATALOG.search(catalog_a['href']).group(1)
ris_url = 'https://catalog.hathitrust.org/Search/SearchExport?handpicked={}&method=ris'.format(catalog_num)
response = requests.get(ris_url, headers={"User-Agent": settings.USER_AGENT})
records = readris(response.text.splitlines()) if response.status_code == 200 else []
for record in records:
self.record = record
return
self.record = {}
def get_downloads(self):
dl_a = self.doc.select_one('#fullPdfLink')
value = dl_a['href'] if dl_a else None
if value:
self.set(
'download_url_{}'.format('pdf'),
'https://babel.hathitrust.org{}'.format(value)
)
def get_isbns(self):
isbn = self.record.get('issn', [])
value = identifier_cleaner('isbn')(isbn)
return {'print': value} if value else {}
def get_title(self):
self.set('title', self.record.get('title', ''))
def get_keywords(self):
self.set('subjects', self.record.get('keywords', []))
def get_publisher(self):
self.set('publisher', self.record.get('publisher', ''))
def get_pubdate(self):
self.set('publication_date', self.record.get('year', ''))
def get_description(self):
notes = self.record.get('notes', [])
self.set('description', '\r'.join(notes))
def get_genre(self):
self.set('genre', self.record.get('type_of_reference', '').lower())
@classmethod
def can_scrape(cls, url):
''' return True if the class can scrape the URL '''
return url.find('hathitrust.org') > 0 or url.find('hdl.handle.net/2027/') > 0
def get_scraper(url):
scrapers = [PressbooksScraper, HathitrustScraper, BaseScraper]
for scraper in scrapers:
if scraper.can_scrape(url):
return scraper(url)
def scrape_sitemap(url, maxnum=None):
try:
response = requests.get(url, headers={"User-Agent": settings.USER_AGENT})
doc = BeautifulSoup(response.content, 'lxml')
for page in doc.find_all('loc')[0:maxnum]:
scraper = get_scraper(page.text)
if scraper.metadata.get('genre', None) == 'book':
yield scraper
except requests.exceptions.RequestException as e:
logger.error(e)

View File

@ -0,0 +1,32 @@
import re
from urlparse import urljoin
from regluit.core.loaders.scrape import BaseScraper
SWCAT = re.compile(r'^https://www\.smashwords\.com/books/category.*')
class SmashwordsScraper(BaseScraper):
can_scrape_strings =['smashwords.com']
def get_keywords(self):
kws = self.doc.find_all('a', href=SWCAT)
value = list(set(kw.string.strip() for kw in kws))
if value:
self.set('subjects', value)
def get_description(self):
desc = self.doc.select_one('#longDescription')
if desc:
value = desc.get_text() if hasattr(desc, 'get_text') else desc.string
if value.strip():
self.set('description', value.strip())
def get_downloads(self):
dldiv = self.doc.select_one('#download')
if dldiv:
for dl_type in ['epub', 'mobi', 'pdf']:
dl_link = dldiv.find('a', href=re.compile(r'.*\.{}'.format(dl_type)))
if dl_link:
url = urljoin(self.base,dl_link['href'])
self.set('download_url_{}'.format(dl_type), url)
def get_publisher(self):
self.set('publisher', 'Smashwords')

124
core/loaders/springer.py Normal file
View File

@ -0,0 +1,124 @@
import re
from urlparse import urljoin
import requests
from bs4 import BeautifulSoup
from django.conf import settings
from regluit.core.validation import identifier_cleaner
from regluit.core.bookloader import add_from_bookdatas
from .scrape import BaseScraper, CONTAINS_CC
MENTIONS_CC = re.compile(r'CC BY(-NC)?(-ND|-SA)?', flags=re.I)
HAS_YEAR = re.compile(r'(19|20)\d\d')
class SpringerScraper(BaseScraper):
can_scrape_strings =['10.1007', '10.1057']
def get_downloads(self):
for dl_type in ['epub', 'mobi', 'pdf']:
download_el = self.doc.find('a', title=re.compile(dl_type.upper()))
if download_el:
value = download_el.get('href')
if value:
value = urljoin(self.base, value)
self.set('download_url_{}'.format(dl_type), value)
def get_description(self):
desc = self.doc.select_one('#book-description')
if desc:
value = ''
for div in desc.contents:
text = div.get_text() if hasattr(div, 'get_text') else div.string
if text:
text = text.replace(u'\xa0', u' ')
value = u'{}<p>{}</p>'.format(value, text)
self.set('description', value)
def get_keywords(self):
value = []
for kw in self.doc.select('.Keyword'):
value.append(kw.text.strip())
if value:
if 'Open Access' in value:
value.remove('Open Access')
self.set('subjects', value)
def get_identifiers(self):
super(SpringerScraper, self).get_identifiers()
el = self.doc.select_one('#doi-url')
if el:
value = identifier_cleaner('doi', quiet=True)(el.text)
if value:
self.identifiers['doi'] = value
def get_isbns(self):
isbns = {}
el = self.doc.select_one('#print-isbn')
if el:
value = identifier_cleaner('isbn', quiet=True)(el.text)
if value:
isbns['paper'] = value
el = self.doc.select_one('#electronic-isbn')
if el:
value = identifier_cleaner('isbn', quiet=True)(el.text)
if value:
isbns['electronic'] = value
return isbns
def get_title(self):
el = self.doc.select_one('#book-title')
value = ''
if el:
value = el.text.strip()
if value:
value = value.replace('\n', ': ', 1)
self.set('title', value)
if not value:
super(SpringerScraper, self).get_title()
def get_role(self):
if self.doc.select_one('#editors'):
return 'editor'
return 'author'
def get_author_list(self):
for el in self.doc.select('.authors__name'):
yield el.text.strip().replace(u'\xa0', u' ')
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'])
return
mention = self.doc.find(string=MENTIONS_CC)
if mention:
lic = MENTIONS_CC.search(mention).group(0)
lic_url = 'https://creativecommons.org/licenses/{}/'.format(lic[3:].lower())
self.set('rights_url', lic_url)
def get_pubdate(self):
pubinfo = self.doc.select_one('#copyright-info')
if pubinfo:
yearmatch = HAS_YEAR.search(pubinfo.string)
if yearmatch:
self.set('publication_date', yearmatch.group(0))
def get_publisher(self):
self.set('publisher', 'Springer')
search_url = 'https://link.springer.com/search/page/{}?facet-content-type=%22Book%22&package=openaccess'
def load_springer(num_pages):
def springer_open_books(num_pages):
for page in range(1, num_pages+1):
url = search_url.format(page)
response = requests.get(url, headers={"User-Agent": settings.USER_AGENT})
if response.status_code == 200:
base = response.url
doc = BeautifulSoup(response.content, 'lxml')
for link in doc.select('a.title'):
book_url = urljoin(base, link['href'])
yield SpringerScraper(book_url)
return add_from_bookdatas(springer_open_books(num_pages))

31
core/loaders/ubiquity.py Normal file
View File

@ -0,0 +1,31 @@
import re
from urlparse import urlparse
from regluit.utils.lang import get_language_code
from . import BaseScraper
HAS_EDS = re.compile(r'\(eds?\.\)')
UBIQUITY_HOSTS = ["ubiquitypress.com", "kriterium.se", "oa.finlit.fi", "humanities-map.net",
"oa.psupress.org", "larcommons.net", "uwestminsterpress.co.uk", "stockholmuniversitypress.se",
"luminosoa.org",
]
class UbiquityScraper(BaseScraper):
can_scrape_hosts = UBIQUITY_HOSTS
def get_role(self):
descs = self.doc.select('section.book-description')
for desc in descs:
if desc.find(string=HAS_EDS):
return 'editor'
return super(self, UbiquityScraper).get_role()
def get_language(self):
langlabel = self.doc.find(string='Language')
lang = langlabel.parent.parent.find_next_sibling() if langlabel else ''
lang = lang.get_text() if lang else ''
lang = get_language_code(lang) if lang else ''
if lang:
self.set('language', lang)
else:
super(self, UbiquityScraper).get_language()

View File

@ -3,7 +3,7 @@ from selectable.registry import registry
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Count from django.db.models import Count
from regluit.core.models import Work, PublisherName, Edition, Subject, EditionNote from regluit.core.models import Work, PublisherName, Edition, Subject, EditionNote, Ebook
from regluit.utils.text import sanitize_line from regluit.utils.text import sanitize_line
class OwnerLookup(ModelLookup): class OwnerLookup(ModelLookup):
@ -13,12 +13,12 @@ class OwnerLookup(ModelLookup):
class WorkLookup(ModelLookup): class WorkLookup(ModelLookup):
model = Work model = Work
search_fields = ('title__istartswith',) search_fields = ('title__istartswith',)
def get_item_label(self,item): def get_item_label(self, item):
return "%s (%s, %s)"%(item.title,item.id,item.language) return "%s (%s, %s)"%(item.title, item.id, item.language)
def get_item_value(self,item): def get_item_value(self, item):
return "%s (%s, %s)"%(item.title,item.id,item.language) return "%s (%s, %s)"%(item.title, item.id, item.language)
def get_query(self, request, term): def get_query(self, request, term):
results = super(WorkLookup, self).get_query(request, term) results = super(WorkLookup, self).get_query(request, term)
return results return results
@ -31,7 +31,21 @@ class PublisherNameLookup(ModelLookup):
publisher_name, created = PublisherName.objects.get_or_create(name=value) publisher_name, created = PublisherName.objects.get_or_create(name=value)
publisher_name.save() publisher_name.save()
return publisher_name return publisher_name
class EbookLookup(ModelLookup):
model = Ebook
search_fields = ('edition__title__icontains',)
filters = {'edition__isnull': False, }
def get_item(self, value):
item = None
if value:
try:
item = Ebook.objects.get(pk=value)
except (ValueError, Ebook.DoesNotExist):
item = None
return item
class EditionLookup(ModelLookup): class EditionLookup(ModelLookup):
model = Edition model = Edition
search_fields = ('title__icontains',) search_fields = ('title__icontains',)
@ -52,9 +66,11 @@ class EditionLookup(ModelLookup):
class SubjectLookup(ModelLookup): class SubjectLookup(ModelLookup):
model = Subject model = Subject
search_fields = ('name__icontains',) search_fields = ('name__icontains',)
def get_query(self, request, term): def get_query(self, request, term):
return super(SubjectLookup, self).get_query( request, term).annotate(Count('works')).order_by('-works__count') return super(SubjectLookup, self).get_query(
request, term
).annotate(Count('works')).order_by('-works__count')
class EditionNoteLookup(ModelLookup): class EditionNoteLookup(ModelLookup):
model = EditionNote model = EditionNote
@ -69,4 +85,5 @@ registry.register(WorkLookup)
registry.register(PublisherNameLookup) registry.register(PublisherNameLookup)
registry.register(EditionLookup) registry.register(EditionLookup)
registry.register(SubjectLookup) registry.register(SubjectLookup)
registry.register(EditionNoteLookup) registry.register(EditionNoteLookup)
registry.register(EbookLookup)

View File

@ -0,0 +1,22 @@
from __future__ import print_function
from django.core.management.base import BaseCommand
from django.db import IntegrityError
from regluit.core import models
class Command(BaseCommand):
help = "clean work and edition titles, work descriptions, and author and publisher names"
def handle(self, **options):
for ident in models.Identifier.objects.filter(type='http', edition__isnull=False):
ident.edition = None
ident.save()
for edition in models.Edition.objects.filter(work__isnull=True):
for ident in edition.identifiers.all():
if ident.work:
edition.work = work
edition.save()
break
if not edition.work:
edition.delete()

View File

@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from regluit.core.bookloader import add_by_sitemap from regluit.core.loaders import add_by_sitemap
class Command(BaseCommand): class Command(BaseCommand):
help = "load books based on a website sitemap" help = "load books based on a website sitemap"

View File

@ -0,0 +1,12 @@
from django.core.management.base import BaseCommand
from regluit.core.loaders.springer import load_springer
class Command(BaseCommand):
help = "load books from springer open"
args = "<pages>"
def handle(self, pages, **options):
books = load_springer(int(pages))
print "loaded {} books".format(len(books))

View File

@ -0,0 +1,36 @@
from django.core.management.base import BaseCommand
from regluit.core.models import Work
class Command(BaseCommand):
help = "generate mobi ebooks where needed and possible."
args = "<max>"
def handle(self, max=None, **options):
if max:
try:
max = int(max)
except ValueError:
max = 1
else:
max = 1
epubs = Work.objects.filter(editions__ebooks__format='epub').distinct().order_by('-id')
i = 0
for work in epubs:
if not work.ebooks().filter(format="mobi"):
for ebook in work.ebooks().filter(format="epub"):
ebf = ebook.get_archive_ebf()
if ebf:
try:
print u'making mobi for {}'.format(work.title)
if ebf.make_mobi():
print 'made mobi'
i = i + 1
break
else:
print 'failed to make mobi'
except:
print 'failed to make mobi'
if i >= max:
break

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0010_userprofile_works'),
]
operations = [
migrations.RenameField(
model_name='rightsholder',
old_name='can_sell',
new_name='approved',
),
migrations.AddField(
model_name='rightsholder',
name='address',
field=models.CharField(default=b'', max_length=400),
),
migrations.AddField(
model_name='rightsholder',
name='mailing',
field=models.CharField(default=b'', max_length=400),
),
migrations.AddField(
model_name='rightsholder',
name='signature',
field=models.CharField(default=b'', max_length=100),
),
migrations.AddField(
model_name='rightsholder',
name='signer',
field=models.CharField(default=b'', max_length=100),
),
migrations.AddField(
model_name='rightsholder',
name='signer_ip',
field=models.CharField(max_length=40, null=True),
),
migrations.AddField(
model_name='rightsholder',
name='signer_title',
field=models.CharField(default=b'', max_length=30),
),
migrations.AddField(
model_name='rightsholder',
name='telephone',
field=models.CharField(max_length=30, blank=True),
),
migrations.AlterField(
model_name='rightsholder',
name='email',
field=models.CharField(default=b'', max_length=100),
),
]

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0011_auto_20171110_1253'),
]
operations = [
migrations.AddField(
model_name='campaign',
name='charitable',
field=models.BooleanField(default=False),
),
]

View File

@ -93,6 +93,7 @@ from .bibmodels import (
WorkRelation, WorkRelation,
) )
from .rh_models import Claim, RightsHolder
pm = PostMonkey(settings.MAILCHIMP_API_KEY) pm = PostMonkey(settings.MAILCHIMP_API_KEY)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -148,65 +149,6 @@ class CeleryTask(models.Model):
f = getattr(regluit.core.tasks, self.function_name) f = getattr(regluit.core.tasks, self.function_name)
return f.AsyncResult(self.task_id).info return f.AsyncResult(self.task_id).info
class Claim(models.Model):
STATUSES = ((u'active', u'Claim has been accepted.'),
(u'pending', u'Claim is pending acceptance.'),
(u'release', u'Claim has not been accepted.'),
)
created = models.DateTimeField(auto_now_add=True)
rights_holder = models.ForeignKey("RightsHolder", related_name="claim", null=False)
work = models.ForeignKey("Work", related_name="claim", null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="claim", null=False)
status = models.CharField(max_length=7, choices=STATUSES, default='active')
@property
def can_open_new(self):
# whether a campaign can be opened for this claim
#must be an active claim
if self.status != 'active':
return False
#can't already be a campaign
for campaign in self.campaigns:
if campaign.status in ['ACTIVE', 'INITIALIZED']:
return 0 # cannot open a new campaign
if campaign.status in ['SUCCESSFUL']:
return 2 # can open a THANKS campaign
return 1 # can open any type of campaign
def __unicode__(self):
return self.work.title
@property
def campaign(self):
return self.work.last_campaign()
@property
def campaigns(self):
return self.work.campaigns.all()
def notify_claim(sender, created, instance, **kwargs):
if 'example.org' in instance.user.email or hasattr(instance, 'dont_notify'):
return
try:
(rights, new_rights) = User.objects.get_or_create(email='rights@gluejar.com', defaults={'username':'RightsatUnglueit'})
except:
rights = None
if instance.user == instance.rights_holder.owner:
ul = (instance.user, rights)
else:
ul = (instance.user, instance.rights_holder.owner, rights)
notification.send(ul, "rights_holder_claim", {'claim': instance,})
post_save.connect(notify_claim, sender=Claim)
class RightsHolder(models.Model):
created = models.DateTimeField(auto_now_add=True)
email = models.CharField(max_length=100, blank=True)
rights_holder_name = models.CharField(max_length=100, blank=False)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="rights_holder", null=False)
can_sell = models.BooleanField(default=False)
def __unicode__(self):
return self.rights_holder_name
class Premium(models.Model): class Premium(models.Model):
PREMIUM_TYPES = ((u'00', u'Default'), (u'CU', u'Custom'), (u'XX', u'Inactive')) PREMIUM_TYPES = ((u'00', u'Default'), (u'CU', u'Custom'), (u'XX', u'Inactive'))
@ -458,6 +400,7 @@ class Campaign(models.Model):
publisher = models.ForeignKey("Publisher", related_name="campaigns", null=True) publisher = models.ForeignKey("Publisher", related_name="campaigns", null=True)
do_watermark = models.BooleanField(default=True) do_watermark = models.BooleanField(default=True)
use_add_ask = models.BooleanField(default=True) use_add_ask = models.BooleanField(default=True)
charitable = models.BooleanField(default=False)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.problems = [] self.problems = []

View File

@ -106,10 +106,10 @@ class Identifier(models.Model):
def __unicode__(self): def __unicode__(self):
return u'{0}:{1}'.format(self.type, self.value) return u'{0}:{1}'.format(self.type, self.value)
def label(self): def label(self):
return ID_CHOICES_MAP.get(self.type, self.type) return ID_CHOICES_MAP.get(self.type, self.type)
def url(self): def url(self):
return id_url(self.type, self.value) return id_url(self.type, self.value)
@ -127,7 +127,7 @@ class Work(models.Model):
is_free = models.BooleanField(default=False) is_free = models.BooleanField(default=False)
landings = GenericRelation(Landing, related_query_name='works') landings = GenericRelation(Landing, related_query_name='works')
related = models.ManyToManyField('self', symmetrical=False, blank=True, through='WorkRelation', related_name='reverse_related') related = models.ManyToManyField('self', symmetrical=False, blank=True, through='WorkRelation', related_name='reverse_related')
age_level = models.CharField(max_length=5, choices=AGE_LEVEL_CHOICES, default='', blank=True) age_level = models.CharField(max_length=5, choices=AGE_LEVEL_CHOICES, default='', blank=True)
class Meta: class Meta:
ordering = ['title'] ordering = ['title']
@ -137,7 +137,7 @@ class Work(models.Model):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._last_campaign = None self._last_campaign = None
super(Work, self).__init__(*args, **kwargs) super(Work, self).__init__(*args, **kwargs)
def id_for(self, type): def id_for(self, type):
return id_for(self, type) return id_for(self, type)
@ -149,6 +149,14 @@ class Work(models.Model):
def doab(self): def doab(self):
return id_for(self, 'doab') return id_for(self, 'doab')
@property
def doi(self):
return self.id_for('doi')
@property
def http_id(self):
return self.id_for('http')
@property @property
def googlebooks_id(self): def googlebooks_id(self):
try: try:
@ -197,7 +205,7 @@ class Work(models.Model):
@property @property
def openlibrary_url(self): def openlibrary_url(self):
return id_url('olwk', self.openlibrary_id) return id_url('olwk', self.openlibrary_id)
def cover_filetype(self): def cover_filetype(self):
if self.uses_google_cover(): if self.uses_google_cover():
return 'jpeg' return 'jpeg'
@ -395,14 +403,14 @@ class Work(models.Model):
def pdffiles(self): def pdffiles(self):
return EbookFile.objects.filter(edition__work=self, format='pdf').exclude(file='').order_by('-created') return EbookFile.objects.filter(edition__work=self, format='pdf').exclude(file='').order_by('-created')
def versions(self): def versions(self):
version_labels = [] version_labels = []
for ebook in self.ebooks_all(): for ebook in self.ebooks_all():
if ebook.version_label and not ebook.version_label in version_labels: if ebook.version_label and not ebook.version_label in version_labels:
version_labels.append(ebook.version_label) version_labels.append(ebook.version_label)
return version_labels return version_labels
def formats(self): def formats(self):
fmts = [] fmts = []
for fmt in ['pdf', 'epub', 'mobi', 'html']: for fmt in ['pdf', 'epub', 'mobi', 'html']:
@ -414,7 +422,7 @@ class Work(models.Model):
def remove_old_ebooks(self): def remove_old_ebooks(self):
# this method is triggered after an file upload or new ebook saved # this method is triggered after an file upload or new ebook saved
old = Ebook.objects.filter(edition__work=self, active=True).order_by('-version_iter', '-created') old = Ebook.objects.filter(edition__work=self, active=True).order_by('-version_iter', '-created')
# keep highest version ebook for each format and version label # keep highest version ebook for each format and version label
done_format_versions = [] done_format_versions = []
for eb in old: for eb in old:
@ -423,7 +431,7 @@ class Work(models.Model):
eb.deactivate() eb.deactivate()
else: else:
done_format_versions.append(format_version) done_format_versions.append(format_version)
# check for failed uploads. # check for failed uploads.
null_files = EbookFile.objects.filter(edition__work=self, file='') null_files = EbookFile.objects.filter(edition__work=self, file='')
for ebf in null_files: for ebf in null_files:
@ -760,12 +768,12 @@ class Subject(models.Model):
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
@classmethod @classmethod
def set_by_name(cls, subject, work=None, authority=None): def set_by_name(cls, subject, work=None, authority=None):
''' use this method whenever you would be creating a new subject!''' ''' use this method whenever you would be creating a new subject!'''
subject = subject.strip() subject = subject.strip()
# make sure it's not a ; delineated list # make sure it's not a ; delineated list
subjects = subject.split(';') subjects = subject.split(';')
for additional_subject in subjects[1:]: for additional_subject in subjects[1:]:
@ -790,12 +798,12 @@ class Subject(models.Model):
if not subject_obj.authority and authority: if not subject_obj.authority and authority:
subject_obj.authority = authority subject_obj.authority = authority
subject_obj.save() subject_obj.save()
subject_obj.works.add(work) subject_obj.works.add(work)
return subject_obj return subject_obj
else: else:
return None return None
def __unicode__(self): def __unicode__(self):
return self.name return self.name
@ -887,6 +895,8 @@ class Edition(models.Model):
return regluit.core.isbn.convert_13_to_10(self.isbn_13) return regluit.core.isbn.convert_13_to_10(self.isbn_13)
def id_for(self, type): def id_for(self, type):
if type in WORK_IDENTIFIERS:
return self.work.id_for(type)
return id_for(self, type) return id_for(self, type)
@property @property
@ -905,18 +915,10 @@ class Edition(models.Model):
def oclc(self): def oclc(self):
return self.id_for('oclc') return self.id_for('oclc')
@property
def doi(self):
return self.id_for('doi')
@property @property
def goodreads_id(self): def goodreads_id(self):
return self.id_for('gdrd') return self.id_for('gdrd')
@property
def http_id(self):
return self.id_for('http')
@staticmethod @staticmethod
def get_by_isbn(isbn): def get_by_isbn(isbn):
if len(isbn) == 10: if len(isbn) == 10:
@ -1070,13 +1072,24 @@ class EbookFile(models.Model):
def make_mobi(self): def make_mobi(self):
if not self.format == 'epub' or not settings.MOBIGEN_URL: if not self.format == 'epub' or not settings.MOBIGEN_URL:
return False return False
new_mobi_ebf = EbookFile.objects.create(edition=self.edition, format='mobi', asking=self.asking) try:
new_mobi_ebf.file.save(path_for_file('ebf', None), ContentFile(mobi.convert_to_mobi(self.file.url))) mobi_cf = ContentFile(mobi.convert_to_mobi(self.file.url))
except:
return False
new_mobi_ebf = EbookFile.objects.create(
edition=self.edition,
format='mobi',
asking=self.asking,
source=self.file.url
)
new_mobi_ebf.file.save(path_for_file('ebf', None), mobi_cf)
new_mobi_ebf.save() new_mobi_ebf.save()
if self.ebook: if self.ebook:
new_ebook = Ebook.objects.create( new_ebook = Ebook.objects.create(
edition=self.edition, edition=self.edition,
format='mobi', format='mobi',
provider='Unglue.it',
url=new_mobi_ebf.file.url, url=new_mobi_ebf.file.url,
rights=self.ebook.rights, rights=self.ebook.rights,
version_label=self.ebook.version_label, version_label=self.ebook.version_label,
@ -1167,7 +1180,7 @@ class Ebook(models.Model):
return '.{}'.format(self.version_iter) return '.{}'.format(self.version_iter)
else: else:
return '().{}'.format(self.version_label, self.version_iter) return '().{}'.format(self.version_label, self.version_iter)
def set_version(self, version): def set_version(self, version):
#set both version_label and version_iter with one string with format "version.iter" #set both version_label and version_iter with one string with format "version.iter"
version_pattern = r'(.*)\.(\d+)$' version_pattern = r'(.*)\.(\d+)$'
@ -1177,11 +1190,11 @@ class Ebook(models.Model):
else: else:
self.version_label = version self.version_label = version
self.save() self.save()
def set_next_iter(self): def set_next_iter(self):
# set the version iter to the next unused iter for that version # set the version iter to the next unused iter for that version
for ebook in Ebook.objects.filter( for ebook in Ebook.objects.filter(
edition=self.edition, edition=self.edition,
version_label=self.version_label, version_label=self.version_label,
format=self.format, format=self.format,
provider=self.provider provider=self.provider
@ -1190,7 +1203,7 @@ class Ebook(models.Model):
break break
self.version_iter = iter + 1 self.version_iter = iter + 1
self.save() self.save()
@property @property
def rights_badge(self): def rights_badge(self):
if self.rights is None: if self.rights is None:

113
core/models/rh_models.py Normal file
View File

@ -0,0 +1,113 @@
from notification import models as notification
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.template.loader import render_to_string
from django.utils.html import strip_tags
class Claim(models.Model):
STATUSES = ((u'active', u'Claim has been accepted.'),
(u'pending', u'Claim is pending acceptance.'),
(u'release', u'Claim has not been accepted.'),
)
created = models.DateTimeField(auto_now_add=True)
rights_holder = models.ForeignKey("RightsHolder", related_name="claim", null=False)
work = models.ForeignKey("Work", related_name="claim", null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="claim", null=False)
status = models.CharField(max_length=7, choices=STATUSES, default='active')
@property
def can_open_new(self):
# whether a campaign can be opened for this claim
#must be an active claim
if self.status != 'active':
return False
#can't already be a campaign
for campaign in self.campaigns:
if campaign.status in ['ACTIVE', 'INITIALIZED']:
return 0 # cannot open a new campaign
if campaign.status in ['SUCCESSFUL']:
return 2 # can open a THANKS campaign
return 1 # can open any type of campaign
def __unicode__(self):
return self.work.title
@property
def campaign(self):
return self.work.last_campaign()
@property
def campaigns(self):
return self.work.campaigns.all()
def notify_claim(sender, created, instance, **kwargs):
if 'example.org' in instance.user.email or hasattr(instance, 'dont_notify'):
return
try:
(rights, new_rights) = User.objects.get_or_create(
email='rights@ebookfoundation.org',
defaults={'username':'RightsatFEF'}
)
except:
rights = None
if instance.user == instance.rights_holder.owner:
user_list = (instance.user, rights)
else:
user_list = (instance.user, instance.rights_holder.owner, rights)
notification.send(user_list, "rights_holder_claim", {'claim': instance,})
post_save.connect(notify_claim, sender=Claim)
class RightsHolder(models.Model):
created = models.DateTimeField(auto_now_add=True)
email = models.CharField(max_length=100, blank=False, default='')
rights_holder_name = models.CharField(max_length=100, blank=False)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="rights_holder", null=False)
approved = models.BooleanField(default=False)
address = models.CharField(max_length=400, blank=False, default='')
mailing = models.CharField(max_length=400, blank=False, default='')
telephone = models.CharField(max_length=30, blank=True)
signer = models.CharField(max_length=100, blank=False, default='')
signer_ip = models.CharField(max_length=40, null=True)
signer_title = models.CharField(max_length=30, blank=False, default='')
signature = models.CharField(max_length=100, blank=False, default='' )
def __unicode__(self):
return self.rights_holder_name
def notify_rh(sender, created, instance, **kwargs):
# don't notify for tests or existing rights holders
if 'example.org' in instance.email or instance.id < 47:
return
try:
(rights, new_rights) = User.objects.get_or_create(
email='rights@ebookfoundation.org',
defaults={'username':'RightsatFEF'}
)
except:
rights = None
user_list = (instance.owner, rights)
if created:
notification.send(user_list, "rights_holder_created", {'rights_holder': instance,})
elif instance.approved:
agreement = strip_tags(
render_to_string(
'accepted_agreement.html',
{'rights_holder': instance,}
)
)
signature = ''
notification.send(
user_list,
"rights_holder_accepted",
{'rights_holder': instance, 'agreement':agreement, 'signature':signature, }
)
for claim in instance.claim.filter(status='pending'):
claim.status = 'active'
claim.save()
post_save.connect(notify_rh, sender=RightsHolder)

View File

@ -42,7 +42,7 @@ OTHER_ID_CHOICES = (
('edid', 'pragmatic edition ID'), ('edid', 'pragmatic edition ID'),
) )
WORK_IDENTIFIERS = ('doi','olwk','glue','ltwk') WORK_IDENTIFIERS = ('doi','olwk','glue','ltwk', 'http')
ID_CHOICES_MAP = dict(ID_CHOICES) ID_CHOICES_MAP = dict(ID_CHOICES)

View File

@ -60,6 +60,8 @@ def gluejar_search(q, user_ip='69.243.24.29', page=1):
def googlebooks_search(q, user_ip, page): def googlebooks_search(q, user_ip, page):
if len(q) < 2 or len(q) > 2000:
return {}
# XXX: need to pass IP address of user in from the frontend # XXX: need to pass IP address of user in from the frontend
headers = {'X-Forwarded-For': user_ip} headers = {'X-Forwarded-For': user_ip}
start = (page - 1) * 10 start = (page - 1) * 10

View File

@ -81,7 +81,8 @@ def create_notice_types( **kwargs):
notification.create_notice_type("pledge_status_change", _("Your Pledge Has Been Modified"), _("Your ungluing pledge has been modified.")) notification.create_notice_type("pledge_status_change", _("Your Pledge Has Been Modified"), _("Your ungluing pledge has been modified."))
notification.create_notice_type("pledge_charged", _("Your Pledge has been Executed"), _("You have contributed to a successful ungluing campaign.")) notification.create_notice_type("pledge_charged", _("Your Pledge has been Executed"), _("You have contributed to a successful ungluing campaign."))
notification.create_notice_type("pledge_failed", _("Unable to charge your credit card"), _("A charge to your credit card did not go through.")) notification.create_notice_type("pledge_failed", _("Unable to charge your credit card"), _("A charge to your credit card did not go through."))
notification.create_notice_type("rights_holder_created", _("Agreement Accepted"), _("You have become a verified Unglue.it rights holder.")) notification.create_notice_type("rights_holder_created", _("Agreement Accepted"), _("You have applied to become an Unglue.it rights holder."))
notification.create_notice_type("rights_holder_accepted", _("Agreement Accepted"), _("You have become a verified Unglue.it rights holder."))
notification.create_notice_type("rights_holder_claim", _("Claim Entered"), _("A claim has been entered.")) notification.create_notice_type("rights_holder_claim", _("Claim Entered"), _("A claim has been entered."))
notification.create_notice_type("wishlist_unsuccessful_amazon", _("Campaign shut down"), _("An ungluing campaign that you supported had to be shut down due to an Amazon Payments policy change.")) notification.create_notice_type("wishlist_unsuccessful_amazon", _("Campaign shut down"), _("An ungluing campaign that you supported had to be shut down due to an Amazon Payments policy change."))
notification.create_notice_type("pledge_gift_credit", _("Gift Credit Balance"), _("You have a gift credit balance")) notification.create_notice_type("pledge_gift_credit", _("Gift Credit Balance"), _("You have a gift credit balance"))

View File

@ -200,9 +200,11 @@ def notify_unclaimed_gifts():
unclaimed = Gift.objects.filter(used=None) unclaimed = Gift.objects.filter(used=None)
for gift in unclaimed: for gift in unclaimed:
""" """
send notice every 7 days send notice every 7 days, but stop at 10x
""" """
unclaimed_duration = (now() - gift.acq.created ).days unclaimed_duration = (now() - gift.acq.created ).days
if unclaimed_duration > 70:
return
if unclaimed_duration > 0 and unclaimed_duration % 7 == 0 : # first notice in 7 days if unclaimed_duration > 0 and unclaimed_duration % 7 == 0 : # first notice in 7 days
notification.send_now([gift.acq.user], "purchase_gift_waiting", {'gift':gift}, True) notification.send_now([gift.acq.user], "purchase_gift_waiting", {'gift':gift}, True)
notification.send_now([gift.giver], "purchase_notgot_gift", {'gift':gift}, True) notification.send_now([gift.giver], "purchase_notgot_gift", {'gift':gift}, True)

View File

@ -890,10 +890,10 @@ class WorkTests(TestCase):
self.assertEqual(e2, self.w2.preferred_edition) self.assertEqual(e2, self.w2.preferred_edition)
def test_valid_subject(self): def test_valid_subject(self):
self.assertTrue(valid_subject('A, valid, suj\xc3t')) self.assertTrue(valid_subject(u'A, valid, suj\xc3t'))
self.assertFalse(valid_subject('A, valid, suj\xc3t, ')) self.assertFalse(valid_subject(u'A, valid, suj\xc3t, '))
self.assertFalse(valid_subject('A valid suj\xc3t \x01')) self.assertFalse(valid_subject(u'A valid suj\xc3t \x01'))
Subject.set_by_name('A, valid, suj\xc3t; A, valid, suj\xc3t, ', work=self.w1) Subject.set_by_name(u'A, valid, suj\xc3t; A, valid, suj\xc3t, ', work=self.w1)
self.assertEqual(1, self.w1.subjects.count()) self.assertEqual(1, self.w1.subjects.count())
sub = Subject.set_by_name('nyt:hardcover_advice=2011-06-18', work=self.w1) sub = Subject.set_by_name('nyt:hardcover_advice=2011-06-18', work=self.w1)
self.assertEqual(sub.name, 'NYT Bestseller - Hardcover advice') self.assertEqual(sub.name, 'NYT Bestseller - Hardcover advice')

View File

@ -3,40 +3,44 @@
methods to validate and clean identifiers methods to validate and clean identifiers
''' '''
import re import re
import datetime
from dateutil.parser import parse
from PyPDF2 import PdfFileReader from PyPDF2 import PdfFileReader
from django.forms import ValidationError from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from regluit.pyepub import EPUB from regluit.pyepub import EPUB
from regluit.mobi import Mobi from regluit.mobi import Mobi
from .isbn import ISBN from .isbn import ISBN
ID_VALIDATION = { ID_VALIDATION = {
'http': (re.compile(r"(https?|ftp)://(-\.)?([^\s/?\.#]+\.?)+(/[^\s]*)?$", 'http': (re.compile(r"(https?|ftp)://(-\.)?([^\s/?\.#]+\.?)+(/[^\s]*)?$",
flags=re.IGNORECASE|re.S ), flags=re.IGNORECASE|re.S),
"The Web Address must be a valid http(s) URL."), "The Web Address must be a valid http(s) URL."),
'isbn': (r'^([\dxX\-–— ]+|delete)$', 'isbn': (r'^([\dxX\-–— ]+|delete)$',
"The ISBN must be a valid ISBN-13."), "The ISBN must be a valid ISBN-13."),
'doab': (r'^(\d{1,6}|delete)$', 'doab': (r'^(\d{1,6}|delete)$',
"The value must be 1-6 digits."), "The value must be 1-6 digits."),
'gtbg': (r'^(\d{1,6}|delete)$', 'gtbg': (r'^(\d{1,6}|delete)$',
"The Gutenberg number must be 1-6 digits."), "The Gutenberg number must be 1-6 digits."),
'doi': (r'^(https?://dx\.doi\.org/|https?://doi\.org/)?(10\.\d+/\S+|delete)$', 'doi': (r'^(https?://dx\.doi\.org/|https?://doi\.org/)?(10\.\d+/\S+|delete)$',
"The DOI value must be a valid DOI."), "The DOI value must be a valid DOI."),
'oclc': (r'^(\d{8,12}|delete)$', 'oclc': (r'^(\d{8,12}|delete)$',
"The OCLCnum must be 8 or more digits."), "The OCLCnum must be 8 or more digits."),
'goog': (r'^([a-zA-Z0-9\-_]{12}|delete)$', 'goog': (r'^([a-zA-Z0-9\-_]{12}|delete)$',
"The Google id must be 12 alphanumeric characters, dash or underscore."), "The Google id must be 12 alphanumeric characters, dash or underscore."),
'gdrd': (r'^(\d{1,8}|delete)$', 'gdrd': (r'^(\d{1,8}|delete)$',
"The Goodreads ID must be 1-8 digits."), "The Goodreads ID must be 1-8 digits."),
'thng': (r'(^\d{1,8}|delete)$', 'thng': (r'(^\d{1,8}|delete)$',
"The LibraryThing ID must be 1-8 digits."), "The LibraryThing ID must be 1-8 digits."),
'olwk': (r'^(/works/\)?OLd{1,8}W|delete)$', 'olwk': (r'^(/works/\)?OLd{1,8}W|delete)$',
"The Open Library Work ID looks like 'OL####W'."), "The Open Library Work ID looks like 'OL####W'."),
'glue': (r'^(\d{1,6}|delete)$', 'glue': (r'^(\d{1,6}|delete)$',
"The Unglue.it ID must be 1-6 digits."), "The Unglue.it ID must be 1-6 digits."),
'ltwk': (r'^(\d{1,8}|delete)$', 'ltwk': (r'^(\d{1,8}|delete)$',
"The LibraryThing work ID must be 1-8 digits."), "The LibraryThing work ID must be 1-8 digits."),
} }
def isbn_cleaner(value): def isbn_cleaner(value):
@ -46,7 +50,7 @@ def isbn_cleaner(value):
raise ValidationError('no identifier value found') raise ValidationError('no identifier value found')
elif value == 'delete': elif value == 'delete':
return value return value
isbn=ISBN(value) isbn = ISBN(value)
if isbn.error: if isbn.error:
raise ValidationError(isbn.error) raise ValidationError(isbn.error)
isbn.validate() isbn.validate()
@ -57,20 +61,23 @@ def olwk_cleaner(value):
value = '/works/{}'.format(value) value = '/works/{}'.format(value)
return value return value
doi_match = re.compile( r'10\.\d+/\S+') doi_match = re.compile(r'10\.\d+/\S+')
def doi_cleaner(value): def doi_cleaner(value):
if not value == 'delete' and not value.startswith('10.'): if not value == 'delete' and not value.startswith('10.'):
return doi_match.match(value).group(0) try:
return doi_match.search(value).group(0)
except AttributeError:
return ''
return value return value
ID_MORE_VALIDATION = { ID_MORE_VALIDATION = {
'isbn': isbn_cleaner, 'isbn': isbn_cleaner,
'olwk': olwk_cleaner, 'olwk': olwk_cleaner,
'olwk': doi_cleaner, 'doi': doi_cleaner,
} }
def identifier_cleaner(id_type): def identifier_cleaner(id_type, quiet=False):
if ID_VALIDATION.has_key(id_type): if ID_VALIDATION.has_key(id_type):
(regex, err_msg) = ID_VALIDATION[id_type] (regex, err_msg) = ID_VALIDATION[id_type]
extra = ID_MORE_VALIDATION.get(id_type, None) extra = ID_MORE_VALIDATION.get(id_type, None)
@ -79,12 +86,18 @@ def identifier_cleaner(id_type):
def cleaner(value): def cleaner(value):
if not value: if not value:
return None return None
if regex.match(value): try:
if extra: if regex.match(value):
value = extra(value) if extra:
return value value = extra(value)
else: return value
raise ValidationError(err_msg) else:
raise ValidationError(err_msg)
except ValidationError as ve:
if quiet:
return None
else:
raise ve
return cleaner return cleaner
return lambda value: value return lambda value: value
@ -94,18 +107,18 @@ def test_file(the_file, fformat):
try: try:
book = EPUB(the_file.file) book = EPUB(the_file.file)
except Exception as e: except Exception as e:
raise ValidationError(_('Are you sure this is an EPUB file?: %s' % e) ) raise ValidationError(_('Are you sure this is an EPUB file?: %s' % e))
elif fformat == 'mobi': elif fformat == 'mobi':
try: try:
book = Mobi(the_file.file) book = Mobi(the_file.file)
book.parse() book.parse()
except Exception as e: except Exception as e:
raise ValidationError(_('Are you sure this is a MOBI file?: %s' % e) ) raise ValidationError(_('Are you sure this is a MOBI file?: %s' % e))
elif fformat == 'pdf': elif fformat == 'pdf':
try: try:
doc = PdfFileReader(the_file.file) PdfFileReader(the_file.file)
except Exception, e: except Exception, e:
raise ValidationError(_('%s is not a valid PDF file' % the_file.name) ) raise ValidationError(_('%s is not a valid PDF file' % the_file.name))
return True return True
def valid_xml_char_ordinal(c): def valid_xml_char_ordinal(c):
@ -118,7 +131,7 @@ def valid_xml_char_ordinal(c):
0x10000 <= codepoint <= 0x10FFFF 0x10000 <= codepoint <= 0x10FFFF
) )
def valid_subject( subject_name ): def valid_subject(subject_name):
num_commas = 0 num_commas = 0
for c in subject_name: for c in subject_name:
if not valid_xml_char_ordinal(c): if not valid_xml_char_ordinal(c):
@ -165,7 +178,7 @@ def auth_cleaner(auth):
is not a list of author names''' is not a list of author names'''
cleaned = [] cleaned = []
if ';' in auth or reversed_name.match(auth): if ';' in auth or reversed_name.match(auth):
authlist = semicolon_list_delim.split(auth) authlist = semicolon_list_delim.split(auth)
authlist = [unreverse_name(name) for name in authlist] authlist = [unreverse_name(name) for name in authlist]
else: else:
auth = _and_.sub(',', auth) auth = _and_.sub(',', auth)
@ -173,3 +186,20 @@ def auth_cleaner(auth):
for auth in authlist: for auth in authlist:
cleaned.append(spaces.sub(' ', auth.strip())) cleaned.append(spaces.sub(' ', auth.strip()))
return cleaned return cleaned
MATCHYEAR = re.compile(r'(1|2)\d\d\d')
MATCHYMD = re.compile(r'(1|2)\d\d\d-\d\d-\d\d')
def validate_date(date_string):
ymd = MATCHYMD.search(date_string)
if ymd:
return ymd.group(0)
try:
date = parse(date_string.strip(), default=datetime.date(999, 1, 1))
if date.year != 999:
return date.strftime('%Y')
except ValueError:
year = MATCHYEAR.search(date_string)
if year:
return year.group(0)
return ''

View File

@ -4,7 +4,7 @@ WSGISocketPrefix /opt/regluit
<VirtualHost *:80> <VirtualHost *:80>
ServerName localvm ServerName localvm
ServerAdmin info@gluejar.com ServerAdmin info@ebookfoundation.org
Redirect permanent / https://192.168.33.10.xip.io:443/ Redirect permanent / https://192.168.33.10.xip.io:443/

View File

@ -4,7 +4,7 @@ import logging
import re import re
import unicodedata import unicodedata
from datetime import timedelta, date from datetime import date
from decimal import Decimal as D from decimal import Decimal as D
#django imports #django imports
@ -16,11 +16,7 @@ from django.forms.widgets import RadioSelect
from django.forms.extras.widgets import SelectDateWidget from django.forms.extras.widgets import SelectDateWidget
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from ckeditor.widgets import CKEditorWidget
from selectable.forms import ( from selectable.forms import (
AutoCompleteSelectMultipleWidget,
AutoCompleteSelectMultipleField,
AutoCompleteSelectWidget, AutoCompleteSelectWidget,
AutoCompleteSelectField AutoCompleteSelectField
) )
@ -59,11 +55,21 @@ from regluit.core.lookups import (
SubjectLookup, SubjectLookup,
) )
from regluit.core.validation import test_file from regluit.core.validation import test_file
from regluit.utils.localdatetime import now
from regluit.utils.fields import ISBNField from regluit.utils.fields import ISBNField
from .bibforms import EditionForm, IdentifierForm from .bibforms import EditionForm, IdentifierForm
from .rh_forms import (
CCDateForm,
CloneCampaignForm,
date_selector,
DateCalculatorForm,
EditManagersForm,
ManageCampaignForm,
OpenCampaignForm,
RightsHolderForm,
UserClaimForm
)
from questionnaire.models import Questionnaire from questionnaire.models import Questionnaire
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -148,12 +154,12 @@ class EbookForm(forms.ModelForm):
new_label = self.data.get('new_version_label','') new_label = self.data.get('new_version_label','')
return new_label if new_label else self.cleaned_data['version_label'] return new_label if new_label else self.cleaned_data['version_label']
def clean_provider(self): def set_provider(self):
url = self.cleaned_data['url'] url = self.cleaned_data['url']
new_provider = Ebook.infer_provider(url) new_provider = Ebook.infer_provider(url)
if url and not new_provider: if url and not new_provider:
raise forms.ValidationError(_("At this time, ebook URLs must point at Internet Archive, Wikisources, Wikibooks, Hathitrust, Project Gutenberg, raw files at Github, Google Books, or OApen.")) raise forms.ValidationError(_("At this time, ebook URLs must point at Internet Archive, Wikisources, Wikibooks, Hathitrust, Project Gutenberg, raw files at Github, Google Books, or OApen."))
return new_provider if new_provider else "Unglue.it" self.cleaned_data['provider'] = new_provider if new_provider else "Unglue.it"
def clean_url(self): def clean_url(self):
url = self.cleaned_data['url'] url = self.cleaned_data['url']
@ -164,6 +170,7 @@ class EbookForm(forms.ModelForm):
raise forms.ValidationError(_("There's already an ebook with that url.")) raise forms.ValidationError(_("There's already an ebook with that url."))
def clean(self): def clean(self):
self.set_provider()
format = self.cleaned_data.get('format', '') format = self.cleaned_data.get('format', '')
the_file = self.cleaned_data.get('file', None) the_file = self.cleaned_data.get('file', None)
url = self.cleaned_data.get('url', None) url = self.cleaned_data.get('url', None)
@ -174,50 +181,6 @@ class EbookForm(forms.ModelForm):
self.cleaned_data['url'] = '' self.cleaned_data['url'] = ''
return self.cleaned_data return self.cleaned_data
def UserClaimForm ( user_instance, *args, **kwargs ):
class ClaimForm(forms.ModelForm):
i_agree = forms.BooleanField(error_messages={'required': 'You must agree to the Terms in order to claim a work.'})
rights_holder = forms.ModelChoiceField(queryset=user_instance.rights_holder.all(), empty_label=None)
class Meta:
model = Claim
exclude = ('status',)
widgets = {
'user': forms.HiddenInput,
'work': forms.HiddenInput,
}
def __init__(self):
super(ClaimForm, self).__init__(*args, **kwargs)
return ClaimForm()
class RightsHolderForm(forms.ModelForm):
owner = AutoCompleteSelectField(
OwnerLookup,
label='Owner',
widget=AutoCompleteSelectWidget(OwnerLookup),
required=True,
error_messages={'required': 'Please ensure the owner is a valid Unglue.it account.'},
)
email = forms.EmailField(
label=_("notification email address for rights holder"),
max_length=100,
error_messages={'required': 'Please enter an email address for the rights holder.'},
)
class Meta:
model = RightsHolder
exclude = ()
def clean_rights_holder_name(self):
rights_holder_name = self.data["rights_holder_name"]
try:
RightsHolder.objects.get(rights_holder_name__iexact=rights_holder_name)
except RightsHolder.DoesNotExist:
return rights_holder_name
raise forms.ValidationError(_("Another rights holder with that name already exists."))
class ProfileForm(forms.ModelForm): class ProfileForm(forms.ModelForm):
clear_facebook = forms.BooleanField(required=False) clear_facebook = forms.BooleanField(required=False)
clear_twitter = forms.BooleanField(required=False) clear_twitter = forms.BooleanField(required=False)
@ -251,23 +214,6 @@ class ProfileForm(forms.ModelForm):
self.cleaned_data["avatar_source"] == UNGLUEITAR self.cleaned_data["avatar_source"] == UNGLUEITAR
return self.cleaned_data return self.cleaned_data
class CloneCampaignForm(forms.Form):
campaign_id = forms.IntegerField(required = True, widget = forms.HiddenInput)
class OpenCampaignForm(forms.ModelForm):
managers = AutoCompleteSelectMultipleField(
OwnerLookup,
label='Campaign Managers',
widget=AutoCompleteSelectMultipleWidget(OwnerLookup),
required=True,
error_messages = {'required': "You must have at least one manager for a campaign."},
)
userid = forms.IntegerField( required = True, widget = forms.HiddenInput )
class Meta:
model = Campaign
fields = 'name', 'work', 'managers', 'type'
widgets = { 'work': forms.HiddenInput, "name": forms.HiddenInput, }
def getTransferCreditForm(maximum, data=None, *args, **kwargs ): def getTransferCreditForm(maximum, data=None, *args, **kwargs ):
class TransferCreditForm(forms.Form): class TransferCreditForm(forms.Form):
recipient = AutoCompleteSelectField( recipient = AutoCompleteSelectField(
@ -320,19 +266,6 @@ class OtherWorkForm(WorkForm):
super(OtherWorkForm, self).__init__(*args, **kwargs) super(OtherWorkForm, self).__init__(*args, **kwargs)
self.fields['other_work'].widget.update_query_parameters({'language':self.work.language}) self.fields['other_work'].widget.update_query_parameters({'language':self.work.language})
class EditManagersForm(forms.ModelForm):
managers = AutoCompleteSelectMultipleField(
OwnerLookup,
label='Campaign Managers',
widget=AutoCompleteSelectMultipleWidget(OwnerLookup),
required=True,
error_messages = {'required': "You must have at least one manager for a campaign."},
)
class Meta:
model = Campaign
fields = ('id', 'managers')
widgets = { 'id': forms.HiddenInput }
class CustomPremiumForm(forms.ModelForm): class CustomPremiumForm(forms.ModelForm):
class Meta: class Meta:
@ -357,140 +290,6 @@ class OfferForm(forms.ModelForm):
'license': forms.HiddenInput, 'license': forms.HiddenInput,
} }
date_selector = range(date.today().year, settings.MAX_CC_DATE.year+1)
class CCDateForm(object):
target = forms.DecimalField(
min_value= D(settings.UNGLUEIT_MINIMUM_TARGET),
error_messages={'required': 'Please specify a Revenue Target.'}
)
minimum_target = settings.UNGLUEIT_MINIMUM_TARGET
maximum_target = settings.UNGLUEIT_MAXIMUM_TARGET
max_cc_date = settings.MAX_CC_DATE
def clean_target(self):
new_target = self.cleaned_data['target']
if new_target < D(settings.UNGLUEIT_MINIMUM_TARGET):
raise forms.ValidationError(_('A campaign may not be launched with a target less than $%s' % settings.UNGLUEIT_MINIMUM_TARGET))
if new_target > D(settings.UNGLUEIT_MAXIMUM_TARGET):
raise forms.ValidationError(_('A campaign may not be launched with a target more than $%s' % settings.UNGLUEIT_MAXIMUM_TARGET))
return new_target
def clean_cc_date_initial(self):
new_cc_date_initial = self.cleaned_data['cc_date_initial']
if new_cc_date_initial.date() > settings.MAX_CC_DATE:
raise forms.ValidationError('The initial Ungluing Date cannot be after %s'%settings.MAX_CC_DATE)
elif new_cc_date_initial - now() < timedelta(days=0):
raise forms.ValidationError('The initial Ungluing date must be in the future!')
return new_cc_date_initial
class DateCalculatorForm(CCDateForm, forms.ModelForm):
revenue = forms.DecimalField()
cc_date_initial = forms.DateTimeField(
widget = SelectDateWidget(years=date_selector)
)
class Meta:
model = Campaign
fields = 'target', 'cc_date_initial', 'revenue',
def getManageCampaignForm ( instance, data=None, initial=None, *args, **kwargs ):
def get_queryset():
work = instance.work
return Edition.objects.filter(work = work)
class ManageCampaignForm(CCDateForm, forms.ModelForm):
target = forms.DecimalField( required= (instance.type in {REWARDS, BUY2UNGLUE}))
deadline = forms.DateTimeField(
required = (instance.type==REWARDS),
widget = SelectDateWidget(years=date_selector) if instance.status=='INITIALIZED' else forms.HiddenInput
)
cc_date_initial = forms.DateTimeField(
required = (instance.type==BUY2UNGLUE) and instance.status=='INITIALIZED',
widget = SelectDateWidget(years=date_selector) if instance.status=='INITIALIZED' else forms.HiddenInput
)
paypal_receiver = forms.EmailField(
label=_("contact email address for this campaign"),
max_length=100,
error_messages={'required': 'You must enter the email we should contact you at for this campaign.'},
)
edition = forms.ModelChoiceField(
get_queryset(),
widget=RadioSelect(),
empty_label='no edition selected',
required=False,
)
publisher = forms.ModelChoiceField(
instance.work.publishers(),
empty_label='no publisher selected',
required=False,
)
work_description = forms.CharField( required=False , widget=CKEditorWidget())
class Meta:
model = Campaign
fields = ('description', 'details', 'license', 'target', 'deadline', 'paypal_receiver',
'edition', 'email', 'publisher', 'cc_date_initial', "do_watermark", "use_add_ask",
)
widgets = { 'deadline': SelectDateWidget }
def clean_target(self):
if self.instance.type == THANKS:
return None
new_target = super(ManageCampaignForm, self).clean_target()
if self.instance:
if self.instance.status == 'ACTIVE' and self.instance.target < new_target:
raise forms.ValidationError(_('The fundraising target for an ACTIVE campaign cannot be increased.'))
return new_target
def clean_cc_date_initial(self):
if self.instance.type in {REWARDS, THANKS} :
return None
if self.instance:
if self.instance.status != 'INITIALIZED':
# can't change this once launched
return self.instance.cc_date_initial
return super(ManageCampaignForm, self).clean_cc_date_initial()
def clean_deadline(self):
if self.instance.type in {BUY2UNGLUE, THANKS} :
return None
new_deadline_date = self.cleaned_data['deadline']
new_deadline = new_deadline_date + timedelta(hours=23, minutes=59)
if self.instance:
if self.instance.status == 'ACTIVE':
return self.instance.deadline
if new_deadline_date - now() > timedelta(days=int(settings.UNGLUEIT_LONGEST_DEADLINE)):
raise forms.ValidationError(_('The chosen closing date is more than %s days from now' % settings.UNGLUEIT_LONGEST_DEADLINE))
elif new_deadline - now() < timedelta(days=0):
raise forms.ValidationError(_('The chosen closing date is in the past'))
return new_deadline
def clean_license(self):
new_license = self.cleaned_data['license']
if self.instance:
if self.instance.status == 'ACTIVE' and self.instance.license != new_license:
# should only allow change to a less restrictive license
if self.instance.license == 'CC BY-ND' and new_license in ['CC BY-NC-ND', 'CC BY-NC-SA', 'CC BY-NC']:
raise forms.ValidationError(_('The proposed license for an ACTIVE campaign may not add restrictions.'))
elif self.instance.license == 'CC BY' and new_license != 'CC0':
raise forms.ValidationError(_('The proposed license for an ACTIVE campaign may not add restrictions.'))
elif self.instance.license == 'CC BY-NC' and new_license in ['CC BY-NC-ND', 'CC BY-NC-SA', 'CC BY-SA', 'CC BY-ND']:
raise forms.ValidationError(_('The proposed license for an ACTIVE campaign may not add restrictions.'))
elif self.instance.license == 'CC BY-ND' and new_license in ['CC BY-NC-ND', 'CC BY-NC-SA', 'CC BY-SA', 'CC BY-NC']:
raise forms.ValidationError(_('The proposed license for an ACTIVE campaign may not add restrictions.'))
elif self.instance.license == 'CC BY-SA' and new_license in ['CC BY-NC-ND', 'CC BY-NC-SA', 'CC BY-ND', 'CC BY-NC']:
raise forms.ValidationError(_('The proposed license for an ACTIVE campaign may not add restrictions.'))
elif self.instance.license == 'CC BY-NC-SA' and new_license in ['CC BY-NC-ND', 'CC BY-ND']:
raise forms.ValidationError(_('The proposed license for an ACTIVE campaign may not add restrictions.'))
elif self.instance.license == 'CC0' :
raise forms.ValidationError(_('The proposed license for an ACTIVE campaign may not add restrictions.'))
elif self.instance.license in ['GDFL', 'LAL']:
raise forms.ValidationError(_('Once you start a campaign with GDFL or LAL, you can\'t use any other license.'))
return new_license
if initial and not initial.get('edition', None) and not instance.edition:
initial['edition'] = instance.work.editions.all()[0]
return ManageCampaignForm(instance=instance, data=data, initial=initial)
class CampaignPurchaseForm(forms.Form): class CampaignPurchaseForm(forms.Form):
anonymous = forms.BooleanField(required=False, label=_("Make this purchase anonymous, please")) anonymous = forms.BooleanField(required=False, label=_("Make this purchase anonymous, please"))
@ -587,12 +386,12 @@ class CampaignPledgeForm(forms.Form):
min_value=D('1.00'), min_value=D('1.00'),
max_value=D('2000.00'), max_value=D('2000.00'),
decimal_places=2, decimal_places=2,
label="Pledge Amount", label="Support Amount",
) )
def amount(self): def amount(self):
return self.cleaned_data["preapproval_amount"] if self.cleaned_data else None return self.cleaned_data["preapproval_amount"] if self.cleaned_data else None
anonymous = forms.BooleanField(required=False, label=_("Make this pledge anonymous, please")) anonymous = forms.BooleanField(required=False, label=_("Make this support anonymous, please"))
ack_name = forms.CharField( ack_name = forms.CharField(
required=False, required=False,
max_length=64, max_length=64,
@ -601,6 +400,7 @@ class CampaignPledgeForm(forms.Form):
ack_dedication = forms.CharField(required=False, max_length=140, label=_("Your dedication:")) ack_dedication = forms.CharField(required=False, max_length=140, label=_("Your dedication:"))
premium_id = forms.IntegerField(required=False) premium_id = forms.IntegerField(required=False)
donation = forms.BooleanField(required=False, label=_("Make this a donation, not a pledge."))
premium = None premium = None
@property @property
@ -637,6 +437,10 @@ class CampaignPledgeForm(forms.Form):
elif preapproval_amount < self.premium.amount: elif preapproval_amount < self.premium.amount:
logger.info("raising form validating error") logger.info("raising form validating error")
raise forms.ValidationError(_("Sorry, you must pledge at least $%s to select that premium." % (self.premium.amount))) raise forms.ValidationError(_("Sorry, you must pledge at least $%s to select that premium." % (self.premium.amount)))
donation = self.cleaned_data.get('donation', False)
if donation and self.premium.amount > 0:
raise forms.ValidationError(_("Sorry, donations are not eligible for premiums."))
return self.cleaned_data return self.cleaned_data
class TokenCCMixin(forms.Form): class TokenCCMixin(forms.Form):

View File

@ -21,6 +21,7 @@ from regluit.core.lookups import (
EditionNoteLookup, EditionNoteLookup,
) )
from regluit.bisac.models import BisacHeading from regluit.bisac.models import BisacHeading
from regluit.core.cc import CHOICES as RIGHTS_CHOICES
from regluit.core.models import Edition, Identifier from regluit.core.models import Edition, Identifier
from regluit.core.parameters import ( from regluit.core.parameters import (
AGE_LEVEL_CHOICES, AGE_LEVEL_CHOICES,
@ -122,6 +123,8 @@ class EditionForm(forms.ModelForm):
required=False, required=False,
allow_new=True, allow_new=True,
) )
set_rights = forms.CharField(widget=forms.Select(choices=RIGHTS_CHOICES), required=False)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(EditionForm, self).__init__(*args, **kwargs) super(EditionForm, self).__init__(*args, **kwargs)
self.relators = [] self.relators = []
@ -133,12 +136,6 @@ class EditionForm(forms.ModelForm):
) )
self.relators.append({'relator':relator, 'select':select}) 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): def clean_title(self):
return sanitize_line(self.cleaned_data["title"]) return sanitize_line(self.cleaned_data["title"])
@ -157,7 +154,7 @@ class EditionForm(forms.ModelForm):
err_msg = "{} is a duplicate for work #{}.".format(identifier[0], identifier[0].work_id) err_msg = "{} is a duplicate for work #{}.".format(identifier[0], identifier[0].work_id)
self.add_error('id_value', forms.ValidationError(err_msg)) self.add_error('id_value', forms.ValidationError(err_msg))
try: try:
self.cleaned_data['value'] = identifier_cleaner(id_type)(id_value) self.cleaned_data['id_value'] = identifier_cleaner(id_type)(id_value)
except forms.ValidationError, ve: except forms.ValidationError, ve:
self.add_error('id_value', forms.ValidationError('{}: {}'.format(ve.message, id_value))) self.add_error('id_value', forms.ValidationError('{}: {}'.format(ve.message, id_value)))
return self.cleaned_data return self.cleaned_data

315
frontend/forms/rh_forms.py Normal file
View File

@ -0,0 +1,315 @@
from datetime import date, timedelta
from decimal import Decimal as D
from ckeditor.widgets import CKEditorWidget
from selectable.forms import (
AutoCompleteSelectMultipleWidget,
AutoCompleteSelectMultipleField,
)
from django import forms
from django.conf import settings
from django.forms.extras.widgets import SelectDateWidget
from django.forms.widgets import RadioSelect
from django.utils.translation import ugettext_lazy as _
from regluit.core.lookups import OwnerLookup
from regluit.core.models import Campaign, Edition, Claim, RightsHolder, WasWork
from regluit.core.parameters import *
from regluit.utils.localdatetime import now
class RightsHolderForm(forms.ModelForm):
email = forms.EmailField(
help_text='notification email address for rights holder',
max_length=100,
error_messages={
'required': 'Please enter a contact email address for the rights holder.'
},
)
use4both = forms.BooleanField(label='use business address as mailing address', required=False)
def clean_rights_holder_name(self):
rights_holder_name = self.data["rights_holder_name"]
try:
RightsHolder.objects.get(rights_holder_name__iexact=rights_holder_name)
except RightsHolder.DoesNotExist:
return rights_holder_name
raise forms.ValidationError('Another rights holder with that name already exists.')
class Meta:
model = RightsHolder
exclude = ('approved', 'signer_ip')
widgets = {
'address': forms.Textarea(attrs={'rows': 4}),
'mailing': forms.Textarea(attrs={'rows': 4}),
'owner': forms.HiddenInput()
}
help_texts = {
'signature': 'Type your name to enter your electronic signature.',
'rights_holder_name': 'Enter the rights holder\'s legal name.',
}
error_messages = {
'address': {
'required': 'A business address for the rights holder is required.'
},
'mailing': {
'required': 'Enter a mailing address for the rights holder, if different from the \
business address.'
},
'rights_holder_name': {
'required': 'Enter the rights holder\'s legal name.'
},
'signature': {
'required': 'Type your name to enter your electronic signature.'
},
'signer': {
'required': 'Please enter the name of the person signing on behalf of the rights \
holder.'
},
'signer_title': {
'required': 'Please enter the signer\'s title. (Use \'self\' if you are \
personally the rights holder.)'
},
}
class UserClaimForm (forms.ModelForm):
i_agree = forms.BooleanField(
error_messages={'required': 'You must agree to the Terms in order to claim a work.'}
)
def __init__(self, user_instance, *args, **kwargs):
super(UserClaimForm, self).__init__(*args, **kwargs)
self.fields['rights_holder'] = forms.ModelChoiceField(
queryset=user_instance.rights_holder.all(),
empty_label=None,
)
def clean_work(self):
work = self.cleaned_data.get('work', None)
if not work:
try:
workids = self.data['claim-work']
if workids:
work = WasWork.objects.get(was = workids[0]).work
else:
raise forms.ValidationError('That work does not exist.')
except WasWork.DoesNotExist:
raise forms.ValidationError('That work does not exist.')
return work
class Meta:
model = Claim
exclude = ('status',)
widgets = {
'user': forms.HiddenInput,
'work': forms.HiddenInput,
}
class EditManagersForm(forms.ModelForm):
managers = AutoCompleteSelectMultipleField(
OwnerLookup,
label='Campaign Managers',
widget=AutoCompleteSelectMultipleWidget(OwnerLookup),
required=True,
error_messages = {'required': "You must have at least one manager for a campaign."},
)
class Meta:
model = Campaign
fields = ('id', 'managers')
widgets = { 'id': forms.HiddenInput }
class OpenCampaignForm(forms.ModelForm):
managers = AutoCompleteSelectMultipleField(
OwnerLookup,
label='Campaign Managers',
widget=AutoCompleteSelectMultipleWidget(OwnerLookup),
required=True,
error_messages = {'required': "You must have at least one manager for a campaign."},
)
userid = forms.IntegerField( required = True, widget = forms.HiddenInput )
class Meta:
model = Campaign
fields = 'name', 'work', 'managers', 'type'
widgets = { 'work': forms.HiddenInput, "name": forms.HiddenInput, }
class CloneCampaignForm(forms.Form):
campaign_id = forms.IntegerField(required = True, widget = forms.HiddenInput)
date_selector = range(date.today().year, settings.MAX_CC_DATE.year+1)
class CCDateForm(object):
target = forms.DecimalField(
min_value= D(settings.UNGLUEIT_MINIMUM_TARGET),
error_messages={'required': 'Please specify a Revenue Target.'}
)
minimum_target = settings.UNGLUEIT_MINIMUM_TARGET
maximum_target = settings.UNGLUEIT_MAXIMUM_TARGET
max_cc_date = settings.MAX_CC_DATE
def clean_target(self):
new_target = self.cleaned_data['target']
if new_target < D(settings.UNGLUEIT_MINIMUM_TARGET):
raise forms.ValidationError(_(
'A campaign may not be launched with a target less than $%s' % settings.UNGLUEIT_MINIMUM_TARGET
))
if new_target > D(settings.UNGLUEIT_MAXIMUM_TARGET):
raise forms.ValidationError(_(
'A campaign may not be launched with a target more than $%s' % settings.UNGLUEIT_MAXIMUM_TARGET
))
return new_target
def clean_cc_date_initial(self):
new_cc_date_initial = self.cleaned_data['cc_date_initial']
if new_cc_date_initial.date() > settings.MAX_CC_DATE:
raise forms.ValidationError('The initial Ungluing Date cannot be after %s'%settings.MAX_CC_DATE)
elif new_cc_date_initial - now() < timedelta(days=0):
raise forms.ValidationError('The initial Ungluing date must be in the future!')
return new_cc_date_initial
class DateCalculatorForm(CCDateForm, forms.ModelForm):
revenue = forms.DecimalField()
cc_date_initial = forms.DateTimeField(
widget = SelectDateWidget(years=date_selector)
)
class Meta:
model = Campaign
fields = 'target', 'cc_date_initial', 'revenue',
class ManageCampaignForm(CCDateForm, forms.ModelForm):
def __init__(self, instance=None , **kwargs):
super(ManageCampaignForm, self).__init__(instance=instance, **kwargs)
work = instance.work
edition_qs = Edition.objects.filter(work=work)
self.fields['edition'] = forms.ModelChoiceField(
edition_qs,
widget=RadioSelect(),
empty_label='no edition selected',
required=False,
)
self.fields['target'] = forms.DecimalField(
required=(instance.type in {REWARDS, BUY2UNGLUE})
)
self.fields['deadline'] = forms.DateTimeField(
required = (instance.type==REWARDS),
widget = SelectDateWidget(years=date_selector) if instance.status=='INITIALIZED' \
else forms.HiddenInput
)
self.fields['cc_date_initial'] = forms.DateTimeField(
required = (instance.type==BUY2UNGLUE) and instance.status=='INITIALIZED',
widget = SelectDateWidget(years=date_selector) if instance.status=='INITIALIZED' \
else forms.HiddenInput
)
self.fields['publisher'] = forms.ModelChoiceField(
instance.work.publishers(),
empty_label='no publisher selected',
required=False,
)
if self.initial and not self.initial.get('edition', None) and not instance.edition:
self.initial['edition'] = instance.work.editions.all()[0]
paypal_receiver = forms.EmailField(
label=_("contact email address for this campaign"),
max_length=100,
error_messages={
'required': 'You must enter the email we should contact you at for this campaign.'
},
)
work_description = forms.CharField(required=False , widget=CKEditorWidget())
class Meta:
model = Campaign
fields = ('description', 'details', 'license', 'target', 'deadline', 'paypal_receiver',
'edition', 'email', 'publisher', 'cc_date_initial', "do_watermark", "use_add_ask",
)
widgets = { 'deadline': SelectDateWidget }
def clean_target(self):
if self.instance.type == THANKS:
return None
new_target = super(ManageCampaignForm, self).clean_target()
if self.instance:
if self.instance.status == 'ACTIVE' and self.instance.target < new_target:
raise forms.ValidationError(
_('The fundraising target for an ACTIVE campaign cannot be increased.')
)
return new_target
def clean_cc_date_initial(self):
if self.instance.type in {REWARDS, THANKS} :
return None
if self.instance:
if self.instance.status != 'INITIALIZED':
# can't change this once launched
return self.instance.cc_date_initial
return super(ManageCampaignForm, self).clean_cc_date_initial()
def clean_deadline(self):
if self.instance.type in {BUY2UNGLUE, THANKS} :
return None
new_deadline_date = self.cleaned_data['deadline']
new_deadline = new_deadline_date + timedelta(hours=23, minutes=59)
if self.instance:
if self.instance.status == 'ACTIVE':
return self.instance.deadline
if new_deadline_date - now() > timedelta(days=int(settings.UNGLUEIT_LONGEST_DEADLINE)):
raise forms.ValidationError(_(
'The chosen closing date is more than {} days from now'.format(
settings.UNGLUEIT_LONGEST_DEADLINE
)
))
elif new_deadline - now() < timedelta(days=0):
raise forms.ValidationError(_('The chosen closing date is in the past'))
return new_deadline
def clean_license(self):
NO_ADDED_RESTRICTIONS = _(
'The proposed license for an ACTIVE campaign may not add restrictions.'
)
new_license = self.cleaned_data['license']
if self.instance:
if self.instance.status == 'ACTIVE' and self.instance.license != new_license:
# should only allow change to a less restrictive license
if self.instance.license == 'CC BY-ND' and new_license in [
'CC BY-NC-ND',
'CC BY-NC-SA',
'CC BY-NC'
]:
raise forms.ValidationError(NO_ADDED_RESTRICTIONS)
elif self.instance.license == 'CC BY' and new_license != 'CC0':
raise forms.ValidationError(NO_ADDED_RESTRICTIONS)
elif self.instance.license == 'CC BY-NC' and new_license in [
'CC BY-NC-ND',
'CC BY-NC-SA',
'CC BY-SA',
'CC BY-ND'
]:
raise forms.ValidationError(NO_ADDED_RESTRICTIONS)
elif self.instance.license == 'CC BY-ND' and new_license in [
'CC BY-NC-ND',
'CC BY-NC-SA',
'CC BY-SA',
'CC BY-NC'
]:
raise forms.ValidationError(NO_ADDED_RESTRICTIONS)
elif self.instance.license == 'CC BY-SA' and new_license in [
'CC BY-NC-ND',
'CC BY-NC-SA',
'CC BY-ND',
'CC BY-NC'
]:
raise forms.ValidationError(NO_ADDED_RESTRICTIONS)
elif self.instance.license == 'CC BY-NC-SA' and new_license in [
'CC BY-NC-ND',
'CC BY-ND'
]:
raise forms.ValidationError(NO_ADDED_RESTRICTIONS)
elif self.instance.license == 'CC0' :
raise forms.ValidationError(NO_ADDED_RESTRICTIONS)
elif self.instance.license in ['GDFL', 'LAL']:
raise forms.ValidationError(_(
'Once you start a campaign with GDFL or LAL, you can\'t use any other license.'
))
return new_license

View File

@ -36,6 +36,7 @@ base.html extra_css(empty) extra_js(empty) extra_head(empty)
about.html about.html
about_smashwords.html about_smashwords.html
admins_only.html admins_only.html
agreed.html
api_help.html api_help.html
ask_rh.html ask_rh.html
campaign_admin.html extra_extra_head campaign_admin.html extra_extra_head
@ -69,7 +70,13 @@ base.html extra_css(empty) extra_js(empty) extra_head(empty)
press_new.html press_new.html
press_submitterator.html press_submitterator.html
privacy.html privacy.html
programs.html
rh_agree.html
rh_tools.html extra_extra_head rh_tools.html extra_extra_head
rh_campaigns.html
rh_intro.html
rh_works.html
rh_yours.html
rights_holders.html extra_extra_head rights_holders.html extra_extra_head
surveys.html surveys.html
terms.html extra_css terms.html extra_css
@ -131,7 +138,9 @@ base.html extra_css(empty) extra_js(empty) extra_head(empty)
recommended.html recommended.html
COMPONENT TEMPLATES COMPONENT TEMPLATES
about_lightbox_footer.html about_lightbox_footer.html
accepted_agreement.html
add_your_books.html
book_plain.html book_plain.html
book_panel_addbutton.html book_panel_addbutton.html
cardform.html cardform.html
@ -158,6 +167,7 @@ registration/login_form.html
registration/password_reset_email.html registration/password_reset_email.html
registration/registration_closed.html registration/registration_closed.html
registration/test_template_name.html registration/test_template_name.html
rights_holder_agreement.html
sidebar_pledge_complete.html sidebar_pledge_complete.html
slideshow.html slideshow.html
split.html split.html

View File

@ -1,51 +0,0 @@
{% extends "basedocumentation.html" %}
{% 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="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>
<li>Book-lovers and libraries everywhere can join together to set books free.</li>
<li>Authors and publishers get the compensation they deserve.</li>
<li>Books that are out of print, not available as ebooks, or otherwise hard to enjoy will be available for everyone to read, share, learn from, and love -- freely and legally.</li>
</ul>
<p>You can learn more about us in our <a href="{% url 'faq' %}">FAQs</a> and our <a href="{% url 'press' %}">press page</a>.
<h3>Team</h3>
<div class="pressimages">
<div class="outer">
<div><img src="/static/images/headshots/eric.jpg" class="mediaborder" alt="eric" /></div>
<div class="text"><b>Eric Hellman</b>, President of the Free Ebook Foundation, is a technologist, entrepreneur, and writer. After 10 years at Bell Labs in physics research, Eric became interested in technologies surrounding e-journals and libraries. His first business, Openly Informatics, developed OpenURL linking software and knowledgebases, and was acquired by OCLC in 1996. At OCLC, he led the effort to productize and expand the xISBN service, and began the development of OCLC's Electronic Resource Management offerings. After leaving OCLC, Eric began blogging at <a href="https://go-to-hellman.blogspot.com">Go To Hellman</a>. He covers the intersection of technology, libraries and ebooks, and has written extensively on the Semantic Web and Linked Data. Eric has a B.S.E. from Princeton University, and a Ph. D. in Electrical Engineering from Stanford University</div>
</div>
<div class="outer">
<div><img src="/static/images/headshots/amanda2.jpg" class="mediaborder" alt="amanda" /></div>
<div class="text"><b>Amanda Mecke</b> is an expert in literary rights management. Before founding her own <a href="http://www.ameckeco.com/">literary agency</a>, Amanda was VP, Director of Subsidiary Rights for Bantam Dell, a division of Random House Inc. from 1989-2003, where she led a department that sold international and domestic book rights and pioneered early electronic licenses for subscription databases, CD-ROMs, audiobooks, and ebooks. She was also a co-leader of the Random House/SAP Contracts and Royalties software development team. Prior to joining Bantam Dell, Amanda ran the New York marketing office of the University of California Press. While there she served the board of the American Association of University Presses and was President of Women in Scholarly Publishing. Amanda has been a speaker at the Frankfurt Book Messe Rights Workshop, NYU Summer Publishing Program, American Independent Writers conference, and the International Womens Writers Guild. She has a B.A. from Pitzer College, Claremont, California and a Ph.D. in English from UCLA. Amanda continues to represent original work by her literary agency clients.<br /><br />
Amanda will be spending much of her time reaching out to authors, publishers, and other rights holders and identifying works that will attract financial support from book lovers who want to see the ebooks available for free to anyone, anywhere. </div>
</div>
<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="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>
<h3>Past Team</h3>
<div class="outer">
<div class="text"><b>Andromeda Yelton</b> was one of Unglue.it's founding staff members; she's gone on to pursue another passion, <a href="https://blog.unglue.it/2013/07/24/teaching-libraries-to-code/">teaching libraries to code</a>. She blogs at <a href="http://andromedayelton.com/">Across Divided Networks</a>. Check out her web site if you think she can help you!</div>
</div>
<div class="outer">
<div class="text"><b><a href="http://www.designanthem.com/">Design Anthem</a></b> helped us make the site pretty. </div>
</div>
<div class="outer">
<div class="text"><b>Jason Kace</b> wrote code. <b>Ed Summers</b> wrote some more code. Both of them helped us write even more code.
</div>
</div>
</div>
{% endblock %}

View File

@ -8,7 +8,7 @@
<ul> <ul>
<li>Make sure your book meets Smashwords' <a href="http://www.smashwords.com/distribution">standards for inclusion</a> in their Premium Catalog. Consult the <a href="http://www.smashwords.com/books/view/52">Smashwords Style Guide</a> for details.</li> <li>Make sure your book meets Smashwords' <a href="http://www.smashwords.com/distribution">standards for inclusion</a> in their Premium Catalog. Consult the <a href="http://www.smashwords.com/books/view/52">Smashwords Style Guide</a> for details.</li>
<li>Contact <a href="mailto:rights@gluejar.com">rights@gluejar.com</a> about signing a Platform Services Agreement with Unglue.it, and follow the instructions on the <a href="/rightsholders/">rights holder tools page</a> to claim your book, develop a publicity strategy, and start a campaign.</li> <li>Contact <a href="mailto:rights@ebookfoundation.org">rights@ebookfoundation.org</a> about signing a Platform Services Agreement with Unglue.it, and follow the instructions on the <a href="/rightsholders/">rights holder tools page</a> to claim your book, develop a publicity strategy, and start a campaign.</li>
</ul> </ul>
<h2>Why Unglue Your Book?</h2> <h2>Why Unglue Your Book?</h2>

View File

@ -0,0 +1,3 @@
{% with form=rights_holder rights_holder_name=rights_holder.rights_holder_name signer_title=rights_holder.signer_title owner=rights_holder.owner.username created=rights_holder.created fef_sig='ERIC HELLMAN' %}
{% include 'rights_holder_agreement.html' %}
{% endwith %}

View File

@ -0,0 +1,20 @@
<p id="add_your_books"><b>Claiming a work</b></p>
<p>If your book is indexed in Google books, we can add it to our database automagically. Click on the result list to add your book to our database.</p>
<form action="{% url 'search' %}" method="get">
<div class="inputalign">
<input type="text" id="watermark" size="25" onfocus="imgfocus()" onblur="imgblur(15)" class="inputbox" name="q" value="{{ q }}"><input type="hidden" name="page" value="2">
<input type="submit" class="button">
</div>
</form>
<ul class="bullets">
<li>Use the Claim option on the More... tab of each book's page.</li>
<li>Agree to our <a href="{% url 'terms' %}">Terms</a> on the following page. This includes agreeing that you are making the claim in good faith.</li>
<li>If you have any questions or you claim a work by mistake, <a href="mailto:rights@ebookfoundation.org?subject=claiming%20works">email us</a>.</li>
</ul>
<p><b>Entering a work</b></p>
<p>If your book is not in Google books, you'll need to enter the metadata yourself.</p>
<ul class="bullets">
<li>Use <a href="{% url 'new_edition' '' '' %}">this form</a> to enter the metadata.</li>
<li>Your metadata should have title, authors, language, description.</li>
<li>If you have a lot of books to enter, <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">contact us</a> to load ONIX or CSV files for you.</li>
</ul>

View File

@ -0,0 +1,19 @@
{% extends 'basedocumentation.html' %}
{% block title %} Agreement Submitted {% endblock %}
{% 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">
<script type="text/javascript" src="{{ jquery_ui_home }}"></script>
{% endblock %}
{% block topsection %}
{% endblock %}
{% block doccontent %}
<h2>You're a pending Unglue.it Rights Holder!</h2>
Your Unglue.it Rights Holder Agreement has been submitted. Our staff will verify your existence and email you a countersigned agreement. At that point, you'll be able to use all of the Unglue.it rights holder tools.
{% endblock %}

View File

@ -1,14 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
{% load truncatechars %} {% load truncatechars %}{% load sass_tags %}
{% load sass_tags %}
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="referrer" content="origin" /> <meta name="referrer" content="origin" />
<title>unglue.it {% block title %}{% endblock %}</title> <title>unglue.it {% block title %}{% endblock %}</title>
<link type="text/css" rel="stylesheet" href="/static/css/sitewide4.css" />
<link REL="SHORTCUT ICON" HREF="/static/images/favicon.ico"> <link REL="SHORTCUT ICON" HREF="/static/images/favicon.ico">
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@unglueit" />
{% block extra_meta %}{% endblock %}
<link type="text/css" rel="stylesheet" href="{% sass_src 'scss/sitewide4.scss' %}" />
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
<link href="{% sass_src 'scss/global.scss' %}" rel="stylesheet" type="text/css" /> <link href="{% sass_src 'scss/global.scss' %}" rel="stylesheet" type="text/css" />
@ -133,25 +135,32 @@
{% block content %}{% endblock %} {% block content %}{% endblock %}
{% block footer %} {% block footer %}
<div id="footer"> <div class="footer hide utilityheaders">
<div class="js-main"> <ul>
<li><a href="{% url 'about' %}">Team</a></li>
<li><a href="https://blog.unglue.it">Blog</a></li>
<li><a href="{% url 'terms' %}">Terms of Use</a></li>
<li><a href="{% url 'faq' %}">General FAQ</a></li>
</ul>
</div>
<div class="footer utilityheaders">
<div class="column"> <div class="column">
<span>About Unglue.it</span> <span>About Unglue.it</span>
<ul> <ul>
<li><a href="{% url 'about_specific' 'main' %}" class="hijax">Concept</a></li> <li><a href="{% url 'about_specific' 'main' %}" class="hijax">Concept</a></li>
<li><a href="{% url 'about' %}">Team</a></li>
<li><a href="https://blog.unglue.it">Blog</a></li> <li><a href="https://blog.unglue.it">Blog</a></li>
<li><a href="{% url 'press' %}">Press</a></li> <li><a href="{% url 'press' %}">Press</a></li>
<li><a href="http://eepurl.com/fKLfI">Newsletter</a></li> <li><a href="http://eepurl.com/fKLfI">Newsletter</a></li>
</ul> </ul>
</div> </div>
<div class="column"> <div class="column show-for-medium">
<span>Your account</span> <span>Your account</span>
<ul> <ul>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li><a href="{% url 'manage_account' %}">Account Settings</a></li> <li><a href="{% url 'manage_account' %}">Account Settings</a></li>
<li><a href="{% url 'new_edition' '' '' %}">Add Books to Unglue.it</a></li>
{% endif %} {% endif %}
<li><a href="{% url 'rightsholders' %}">Rights Holder Tools</a></li> <li><a href="{% url 'rightsholders' %}">For Rights Holders</a></li>
<li><a href="{% url 'privacy' %}">Privacy</a></li> <li><a href="{% url 'privacy' %}">Privacy</a></li>
<li><a href="{% url 'terms' %}">Terms of Use</a></li> <li><a href="{% url 'terms' %}">Terms of Use</a></li>
{% for library in user.libraries.all %} {% for library in user.libraries.all %}
@ -159,7 +168,6 @@
{% endfor %} {% endfor %}
{% if user.is_staff %} {% if user.is_staff %}
<li><a href="{% url 'rh_admin' %}">Unglue.it Administration</a></li> <li><a href="{% url 'rh_admin' %}">Unglue.it Administration</a></li>
<li><a href="{% url 'new_edition' '' '' %}">Create New Editions</a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
@ -170,19 +178,15 @@
<li><a href="{% url 'faq_location' 'rightsholders' %}">Author/Publisher FAQ</a></li> <li><a href="{% url 'faq_location' 'rightsholders' %}">Author/Publisher FAQ</a></li>
<li><a href="{% url 'api_help' %}">API</a></li> <li><a href="{% url 'api_help' %}">API</a></li>
<li><a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">Support</a> <li><a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">Support</a>
<li><a href="{% url 'libraries' %}">Unglue.it &hearts; Libraries</a> <li><a href="{% url 'libraries' %}">Unglue.it for Libraries</a>
</ul> </ul>
</div> </div>
<div class="column"> <div class="column show-for-medium">
<span>Contact</span> <span>Contact</span>
<ul> <ul>
<li>General inquiries</li> <li> <a href="mailto:info@ebookfoundation.org"><i class="fa fa-envelope fa-2x"></i></a> <a href="https://twitter.com/unglueit"><i class="fa fa-twitter fa-2x"></i></a> <a href="https://facebook/com/unglueit"><i class="fa fa-facebook fa-2x"></i></a></li>
<li><a href="mailto:faq@gluejar.com">faq@gluejar.com</a></li>
<li>Rights Holders</li>
<li><a href="mailto:rights@gluejar.com">rights@gluejar.com</a></li>
</ul> </ul>
</div> </div>
</div>
</div> </div>
{% endblock %} {% endblock %}
</div> </div>

View File

@ -1,11 +1,13 @@
{% extends "registration/registration_base.html" %} {% extends "registration/registration_base.html" %}
{% load sass_tags %}
{% block extra_js %} {% block extra_js %}
<script type="text/javascript" src="/static/js/definitions.js"></script> <script type="text/javascript" src="/static/js/definitions.js"></script>
{% endblock %} {% endblock %}
{% block extra_extra_head %} {% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/landingpage4.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/landingpage4.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_panel2.scss' %}" />
<script type="text/javascript" src="/static/js/greenpanel.js"></script> <script type="text/javascript" src="/static/js/greenpanel.js"></script>
{% endblock %} {% endblock %}

View File

@ -1,8 +1,9 @@
{% extends "registration/registration_base.html" %} {% extends "registration/registration_base.html" %}
{% load sass_tags %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/campaign2.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/pledge.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}

View File

@ -3,7 +3,7 @@
{% load purchased %} {% load purchased %}
{% load lib_acqs %} {% load lib_acqs %}
{% load bookpanel %} {% load bookpanel %}
{% with first_ebook=work.first_ebook supporters=work.last_campaign.supporters thumbnail=work.cover_image_thumbnail author=work.authors_short title=work.title last_campaign=work.last_campaign status=work.last_campaign.status deadline=work.last_campaign.deadline workid=work.id wishlist=request.user.wishlist.works.all %} {% with first_ebook=work.first_ebook thumbnail=work.cover_image_thumbnail author=work.authors_short title=work.title last_campaign=work.last_campaign status=work.last_campaign.status deadline=work.last_campaign.deadline workid=work.id wishlist=request.user.wishlist.works.all %}
{% purchased %}{% lib_acqs %}{% bookpanel %} {% purchased %}{% lib_acqs %}{% bookpanel %}
<div class="thewholebook listview tabs {% if tab_override %}{{tab_override}}{% elif first_ebook or status == 'SUCCESSFUL' %}tabs-1{% elif status == 'ACTIVE' %}tabs-2{% else %}tabs-3{% endif %}"> <div class="thewholebook listview tabs {% if tab_override %}{{tab_override}}{% elif first_ebook or status == 'SUCCESSFUL' %}tabs-1{% elif status == 'ACTIVE' %}tabs-2{% else %}tabs-3{% endif %}">
<div class="listview book-list"> <div class="listview book-list">
@ -109,7 +109,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% if request.user.id in supporters %} {% if not supported %}
<div class="white_text bottom_button"> <div class="white_text bottom_button">
{% include "book_panel_addbutton.html" %} {% include "book_panel_addbutton.html" %}
</div> </div>
@ -119,7 +119,7 @@
</div> </div>
<div class="white_text bottom_button" > <div class="white_text bottom_button" >
{% if work.last_campaign.type == 1 %} {% if work.last_campaign.type == 1 %}
<a href="{% url 'pledge' work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Pledge</span></span></a> <a href="{% url 'pledge' work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Support</span></span></a>
{% elif work.last_campaign.type == 2 %} {% elif work.last_campaign.type == 2 %}
{% if in_library %} {% if in_library %}
<a href="{% url 'purchase' work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Reserve It</span></span></a> <a href="{% url 'purchase' work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Reserve It</span></span></a>
@ -219,7 +219,7 @@
{% endif %} {% endif %}
{% elif show_pledge %} {% elif show_pledge %}
<div class="listview panelfront side1 add-wishlist"> <div class="listview panelfront side1 add-wishlist">
<span class="booklist_pledge"><a href="{% url 'pledge' work_id=workid %}" class="fakeinput">Pledge</a></span> <span class="booklist_pledge"><a href="{% url 'pledge' work_id=workid %}" class="fakeinput">Support</a></span>
</div> </div>
{% elif show_purchase %} {% elif show_purchase %}
<div class="listview panelfront side1 add-wishlist"> <div class="listview panelfront side1 add-wishlist">
@ -241,8 +241,8 @@
<span>{% if purchased.gifts.all.count %}A gift to you!{% else %}Purchased!{% endif %}</span> <span>{% if purchased.gifts.all.count %}A gift to you!{% else %}Purchased!{% endif %}</span>
{% elif borrowed %} {% elif borrowed %}
<span>Borrowed! ...until</span> <span>Borrowed! ...until</span>
{% elif request.user.id in supporters %} {% elif supported %}
<span>Pledged!</span> <span>Supported!</span>
{% else %} {% else %}
<span>Faved!</span> <span>Faved!</span>
{% endif %} {% endif %}

View File

@ -12,9 +12,9 @@
<div class="moreinfo create-account"> <div class="moreinfo create-account">
<span title="{% if workid %}{% url 'work' workid %}{% else %}{% url 'googlebooks' googlebooks_id %}{% endif %}">Login to Fave</span> <span title="{% if workid %}{% url 'work' workid %}{% else %}{% url 'googlebooks' googlebooks_id %}{% endif %}">Login to Fave</span>
</div> </div>
{% elif request.user.id in supporters %} {% elif supported %}
<div class="moreinfo on-wishlist"> <div class="moreinfo on-wishlist">
<a href="{% url 'work' workid %}">Pledged!</a> <a href="{% url 'work' workid %}">Supported!</a>
</div> </div>
{% elif supporter == request.user %} {% elif supporter == request.user %}
{% if wishlist %} {% if wishlist %}

View File

@ -2,13 +2,14 @@
{% load endless %} {% load endless %}
{% load lang_utils %} {% load lang_utils %}
{% load sass_tags %}
{% block title %} {{ facet_label }} Campaigns {% endblock %} {% block title %} {{ facet_label }} Campaigns {% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/supporter_layout.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_list.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_list.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_panel2.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/lists.css"> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/lists.scss' %}">
{% endblock %} {% endblock %}
{% block extra_head %} {% block extra_head %}
<script> <script>

View File

@ -2,13 +2,14 @@
{% load endless %} {% load endless %}
{% load lang_utils %} {% load lang_utils %}
{% load sass_tags %}
{% block title %} Creative Commons {{license}} Books {% if pub_lang %} in {{pub_lang|ez_lang_name}}{% endif %}{% endblock %} {% block title %} Creative Commons {{license}} Books {% if pub_lang %} in {{pub_lang|ez_lang_name}}{% endif %}{% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/supporter_layout.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_list.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_list.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_panel2.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/lists.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/lists.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_head %} {% block extra_head %}
<script type="text/javascript" src="/static/js/wishlist.js"></script> <script type="text/javascript" src="/static/js/wishlist.js"></script>

View File

@ -29,6 +29,10 @@
{% endifequal %} {% endifequal %}
{% endfor %} {% endfor %}
<h3> Interfering claims </h3>
<p>
When a rights holder claims a work in unglue.it, they warrant and represent that they have sufficient rights to administer the work. Mistakes and umisunderstandings can occur, especially for older works with unclear contracts and hard-to-find rights holders. For that reason, rights holders also agree to respond promptly to rights inquiries, and unglue.it reserves the right to suspend campaigns if disputes over rights arise. If you have a question about claims for this work, please contact rights@ebookfoundation.org
</p>
{% else %} {% else %}
<form method="POST" action="#"> <form method="POST" action="#">
@ -38,8 +42,4 @@
<input type="submit" name="submit" value="Confirm Claim"> <input type="submit" name="submit" value="Confirm Claim">
</form> </form>
{% endif %} {% endif %}
<h3> Interfering claims </h3>
<p>
When a rights holder claims a work in unglue.it, they warrant and represent that they have sufficient rights to release a Creative Commons edition of that work. Unfortunately, mistakes can occur, especially for older works with unclear contracts and hard-to-find rights holders. For that reason, rights holders also agree to respond promptly to rights inquiries, and unglue.it reserves the right to suspend campaigns when disputes over rights arise. If you have a question about claims for this work, please contact rights@gluejar.com
</p>
{% endblock %} {% endblock %}

View File

@ -1,3 +1,3 @@
<h3> Terms and Conditions for Claiming Works </h3> <h3> Terms and Conditions for Claiming Works </h3>
<p>By claiming this work, you agree that your claim is governed by a Platform Services Agreement in effect between you (or an entity that you act as agent for) and the Free Ebook Foundation, Inc., the operator of the Unglue.it website, and by the <a href="{% url 'terms' %}">unglue.it Website Terms </a>.</p> <p>By claiming this work, you agree that your claim is governed by a Rights Holder Agreement in effect between you (or an entity that you act as agent for) and the Free Ebook Foundation, Inc., the operator of the Unglue.it website, and by the <a href="{% url 'terms' %}">unglue.it Website Terms </a>.</p>

View File

@ -1,9 +1,11 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load sass_tags %}
{% block title %} Comments {% endblock %} {% block title %} Comments {% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/supporter_layout.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/comments.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/comments.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_head %} {% block extra_head %}
<script type="text/javascript" src="/static/js/wishlist.js"></script> <script type="text/javascript" src="/static/js/wishlist.js"></script>

View File

@ -1,10 +1,11 @@
{% extends 'comments/base.html' %} {% extends 'comments/base.html' %}
{% load i18n %} {% load i18n %}
{% load sass_tags %}
{% block title %}{% trans "Preview your comment" %}{% endblock %} {% block title %}{% trans "Preview your comment" %}{% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/campaign2.scss' %}" />
{% endblock %} {% endblock %}
{% block doccontent %} {% block doccontent %}

View File

@ -1,6 +1,8 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load humanize %} {% load humanize %}
{% load sass_tags %}
{% with work.title as title %} {% with work.title as title %}
{% block title %} {% block title %}
&mdash; Downloads for {{ work.title }} &mdash; Downloads for {{ work.title }}
@ -8,6 +10,7 @@
{% block extra_js %} {% block extra_js %}
<script type="text/javascript" src="/static/js/embed.js"></script> <script type="text/javascript" src="/static/js/embed.js"></script>
<!-- select {% sass_src 'scss/enhanced_download.scss' %} or {% sass_src 'scss/enhanced_download_ie.scss' %} -->
<script type="text/javascript" src="/static/js/download_page.js"></script> <script type="text/javascript" src="/static/js/download_page.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var $j = jQuery.noConflict(); var $j = jQuery.noConflict();

View File

@ -3,16 +3,18 @@
<ul> <ul>
{% for ebook in work.ebooks_all %} {% for ebook in work.ebooks_all %}
<li> <li>
<a href="{{ ebook.url }}">{{ ebook.format }}, {{ ebook.rights }}</a>, created {{ ebook.created }}{% if ebook.user %}, <a href="{{ ebook.url }}">{{ ebook.format }}{%if ebook.rights %}, {{ ebook.rights }}{% endif %}</a>, created {{ ebook.created }}{% if ebook.user %},
by <a href="{% url 'supporter' ebook.user_id %}">{{ ebook.user }}</a>{% endif %}. by <a href="{% url 'supporter' ebook.user_id %}">{{ ebook.user }}</a>{% endif %}.
{% if ebook.filesize %}{{ ebook.filesize }}{% else %}??{% endif %}B {% if ebook.filesize %}{{ ebook.filesize }}{% else %}??{% endif %}B
{% if ebook.version_label %}{{ ebook.version }}{% endif %} {% 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 %} {% 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 %}{% if request.user.is_staff %} <a href="{% url 'admin:core_ebook_change' ebook.id %}">admin</a>{% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<br /> <br />
<br />
<input type="submit" name="activate_all_ebooks" value="activate all ebooks" class="deletebutton" title="activate all ebooks" /> <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" /> <input type="submit" name="deactivate_all_ebooks" value="deactivate all ebooks" class="deletebutton" title="deactivate all ebooks" />
<br />
<br />
{{ form.set_rights}}<input type="submit" name="set_ebook_rights" value="set ebook licenses" class="deletebutton" title="set ebook licenses" />
{% endif %} {% endif %}

View File

@ -1,7 +1,6 @@
<ul class="terms"> <ul class="terms">
<li><i>For Thanks-For-Ungluing Campaigns</i>, the rightsholder is responsible for providing valid, high-quality EPUB, MOBI, and PDF files.</li> <li><i>For Thanks-For-Ungluing Campaigns</i>, the rightsholder is responsible for providing valid, high-quality EPUB, MOBI, and PDF files.</li>
<li><i>For Buy-To-Unglue Campaigns</i> and <i>Pledge Campaigns</i> ebook files should use the EPUB standard format format according to best practice at time of submission. At minimum, the files should pass the <a href=https://code.google.com/p/epubcheck/">epubcheck</a> tool. Exceptions will be made for content requiring more page layout than available in prevailing EPUB implementations; in those cases, the files will usually be PDF.</li> <li><i>For Buy-To-Unglue Campaigns</i> and <i>Pledge Campaigns</i> ebook files should use the EPUB standard format format according to best practice at time of submission. At minimum, the files should pass the <a href=https://code.google.com/p/epubcheck/">epubcheck</a> tool. Exceptions will be made for content requiring more page layout than available in prevailing EPUB implementations; in those cases, the files will usually be PDF.</li>
<li>Files shall be compliant where ever reasonable with the QED inspection checklist specified at <a href="http://qed.digitalbookworld.com/why-qed/criteria">http://qed.digitalbookworld.com/why-qed/criteria</a> </li>
<li>The file should have no more than 0.2 typographical or scanning errors per page of English text.</li> <li>The file should have no more than 0.2 typographical or scanning errors per page of English text.</li>
<li>The file shall contain front matter which includes the following: <li>The file shall contain front matter which includes the following:
<ul class="terms"> <ul class="terms">

View File

@ -32,12 +32,6 @@
{% if edition.oclc %} {% if edition.oclc %}
OCLC: <a href="https://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 %} {% endif %}
{% if edition.doi %}
DOI: <a href="https://doi.org/{{ edition.doi }}">{{ edition.doi }}</a><br />
{% endif %}
{% if edition.http_id %}
web: <a href="{{ edition.http_id }}">{{ edition.http_id }}</a><br />
{% endif %}
{% if not managing %} {% if not managing %}
{% if user_can_edit_work %} {% if user_can_edit_work %}
<a href="{% url 'new_edition' work_id edition.id %}">Edit this edition</a><br /> <a href="{% url 'new_edition' work_id edition.id %}">Edit this edition</a><br />

View File

@ -9,10 +9,6 @@
<style type="text/css"> <style type="text/css">
.header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em} .header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em}
.panelborders{border-width:1px 0;border-style:solid none;border-color:#fff} .panelborders{border-width:1px 0;border-style:solid none;border-color:#fff}
.roundedspan{border:1px solid #d4d4d4;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;padding:1px;color:#fff;margin:0 8px 0 0;display:inline-block}
.roundedspan>span{padding:7px 7px;min-width:15px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;text-align:center;display:inline-block}
.roundedspan>span .hovertext{display:none}
.roundedspan>span:hover .hovertext{display:inline}
.mediaborder{padding:5px;border:solid 5px #edf3f4} .mediaborder{padding:5px;border:solid 5px #edf3f4}
.actionbuttons{width:auto;height:36px;line-height:36px;background:#8dc63f;-moz-border-radius:32px;-webkit-border-radius:32px;border-radius:32px;color:white;cursor:pointer;font-size:13px;font-weight:bold;padding:0 15px;border:0;margin:5px 0} .actionbuttons{width:auto;height:36px;line-height:36px;background:#8dc63f;-moz-border-radius:32px;-webkit-border-radius:32px;border-radius:32px;color:white;cursor:pointer;font-size:13px;font-weight:bold;padding:0 15px;border:0;margin:5px 0}
.header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em} .header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em}
@ -44,7 +40,6 @@
.launch_top.alert{border-color:#e35351;font-size:13px} .launch_top.alert{border-color:#e35351;font-size:13px}
.preview_content{border:solid 3px #e35351;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;clear:both;padding:5px 10px;font-size:13px;width:90%;width:80%;margin:10px auto} .preview_content{border:solid 3px #e35351;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;clear:both;padding:5px 10px;font-size:13px;width:90%;width:80%;margin:10px auto}
.preview_content a{color:#8dc63f} .preview_content a{color:#8dc63f}
.utilityheaders{text-transform:uppercase;color:#3d4e53;font-size:15px;display:block}
html,body{height:100%} html,body{height:100%}
body{padding:0 0 20px 0;margin:0;font-size:13px;line-height:16.900000000000002px;font-family:"Lucida Grande","Lucida Sans Unicode","Lucida Sans",Arial,Helvetica,sans-serif;color:#3d4e53} body{padding:0 0 20px 0;margin:0;font-size:13px;line-height:16.900000000000002px;font-family:"Lucida Grande","Lucida Sans Unicode","Lucida Sans",Arial,Helvetica,sans-serif;color:#3d4e53}
a{font-weight:bold;font-size:inherit;text-decoration:none;cursor:pointer;color:#6994a3} a{font-weight:bold;font-size:inherit;text-decoration:none;cursor:pointer;color:#6994a3}

View File

@ -1,5 +1,6 @@
{% load truncatechars %} {% load truncatechars %}
{% load lang_utils %} {% load lang_utils %}
{% load explore %}{% explore %}
<div class="jsmodule"> <div class="jsmodule">
<h3 class="jsmod-title"><span>Explore</span></h3> <h3 class="jsmod-title"><span>Explore</span></h3>
<div class="jsmod-content"> <div class="jsmod-content">
@ -32,8 +33,26 @@
</ul> </ul>
{% endif %} {% endif %}
</li> </li>
<li><a href="{% url 'campaign_list' 'ending' %}"><span>Active Campaigns</span></a></li> {% if top_pledge %}
<li><a href="{% url 'comment' %}"><span>Latest Comments</span></a></li> <li>Pledge to...
<ul class="menu level3">
{% for campaign in top_pledge %}
<li><a href="{% url 'work' campaign.work.id %}">
<img class="thumbnail" src="{{ campaign.work.cover_image_thumbnail }}" title="{{ campaign.work.title }}"/>
<span class="thumbnail-caption">{{ campaign.work.title }}</span>
</a></li>
{% if forloop.counter == 4 %}
<li><a href="{% url 'campaign_list' 'pledge' %}"><span>...more</span></a></li>
{% endif %}
{% endfor %}
</ul>
</li>
{% endif %}
<li><a href="{% url 'campaign_list' 'b2u' %}"><span>Buy to unglue...</span></a></li>
<li><a href="{% url 'campaign_list' 't4u' %}"><span>Thank for ...</span></a></li>
{% if not request.user.is_anonymous %}
<li><a href="{% url 'comment' %}"><span>Latest Comments</span></a></li>
{% endif %}
<li><a href="{% url 'work_list' 'popular' %}"><span>Site Favorites</span></a></li> <li><a href="{% url 'work_list' 'popular' %}"><span>Site Favorites</span></a></li>
<li><a href="{% url 'work_list' 'new' %}"><span>Latest Favorites</span></a></li> <li><a href="{% url 'work_list' 'new' %}"><span>Latest Favorites</span></a></li>
<!--<li><a href="{% url 'work_list' 'recommended' %}"><span>Noteworthy</span></a></li>--> <!--<li><a href="{% url 'work_list' 'recommended' %}"><span>Noteworthy</span></a></li>-->

View File

@ -2,13 +2,14 @@
{% load endless %} {% load endless %}
{% load lang_utils %} {% load lang_utils %}
{% load sass_tags %}
{% block title %} Browse Free Books {% endblock %} {% block title %} Browse Free Books {% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/supporter_layout.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_list.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_list.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_panel2.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/lists.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/lists.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_head %} {% block extra_head %}
<script type="text/javascript" src="/static/js/wishlist.js"></script> <script type="text/javascript" src="/static/js/wishlist.js"></script>

View File

@ -7,5 +7,11 @@
{% if facet.facet_name == 'doab' %} {% if facet.facet_name == 'doab' %}
These books are included in the <a href="http://doabooks.org/">Directory of Open Access Books</a>. This means that they have been peer-reviewed and are of interest to scholars. These books are included in the <a href="http://doabooks.org/">Directory of Open Access Books</a>. This means that they have been peer-reviewed and are of interest to scholars.
{% endif %} {% endif %}
{% if facet.facet_name == '-gtbg' %}
These books are not included in <a href="https://www.gutenberg.org">Project Gutenberg</a>.
{% endif %}
{% if facet.facet_name == '-doab' %}
These books do not seem to be included in the <a href="http://doabooks.org/">Directory of Open Access Books</a>.
{% endif %}
</p> </p>
</div> </div>

View File

@ -0,0 +1,5 @@
<div>
<p style="">
These books were faved by <a href="{% url 'supporter' facet.username %}"> {{ facet.username }}</a>
</p>
</div>

View File

@ -16,48 +16,37 @@
</dl> </dl>
{% endif %} {% endif %}
{% if location == 'basics' or location == 'faq' %} {% if location == 'basics' or location == 'faq' %}
<h3>Basics</h3> <h3>About the site</h3>
{% if sublocation == 'howitworks' or sublocation == 'all' %} {% if sublocation == 'howitworks' or sublocation == 'all' %}
<h4>How It Works</h4> <h4>About Unglue.it</h4>
<dl> <dl>
<dt>What is Unglue.it?</dt> <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="https://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 in support of free ebooks. We work together to support authors, publishers, or other rights holders who want their ebooks to be free. We work together to provide durable and effective distribution for ebooks that are free. We support <a href="https://creativecommons.org">Creative Commons</a> licensing as an enabling tool to "unglue" the ebooks. We are part of the <a href="https://ebookfoundation.org">Free Ebook Foundation</a>, a non-profit charitable organization.</dd>
<dt>What are Ungluing Campaigns?</dt> <dt>Why are all these ebooks free?</dt>
<dd>We have three types of Ungluing Campaigns: Pledge Campaigns, Buy-to-Unglue Campaigns and Thanks-for-Ungluing campaigns. <dd>The ebooks available on Unglue.it have missions to accomplish. There are textbooks to help you learn. There is literature to open up your mind. There are books on technology to help you create. There are academic books that are spreading ideas. There are books that want to change your mind. And there are books that belong to all of us, that want to live into the future.<br /><br />
<ul class="bullets">
<li> In a <i><a href="{% url 'faq_sublocation' 'campaigns' 'supporting' %}">Pledge Campaign</a></i>, book lovers pledge their support for ungluing a book. If enough support is found to reach the goal (and only then), the supporter's credit cards are charged, and an unglued ebook is released.</li> Freedom can help a book accomplish its mission, and that why the ebook is free. We call these books "unglued" and we call the process of making them free, "ungluing".</dd>
<li> In a <i><a href="{% url 'faq_sublocation' 'campaigns' 'b2u' %}">Buy-to-Unglue Campaign</a></i>, every ebook copy sold moves the book's ungluing date closer to the present. And you can donate ebooks to your local library- that's something you can't do in the Kindle or Apple Stores!</li>
<li> In a <i><a href="{% url 'faq_sublocation' 'campaigns' 't4u' %}">Thanks-for-Ungluing Campaign</a></i>, the ebook is already released with a Creative Commons license. Supporters can express their thanks by paying what they wish for the license and the ebook.</li>
</ul> <dt>So how do you make money?</dt>
We also support distribution and preservation of free ebooks through our growing catalog of free-licensed ebooks.
<dd>We don't - we're an non-profit. If you like what we do, <a href="{% url 'newdonation' %}">you can donate!</a></dd>
<dt>What about the authors and publishers, how do they make money?</dt>
<dd>That's the million dollar question. You don't make money by giving away ebooks. Many authors make money other ways - by teaching, by consulting, by getting tenure. Some authors do well be selling printed books alongside their free ebooks. We started Unglue.it because we hoped to give ebook creators additional ways to support their work: <a href="{% url 'faq_location' 'campaigns' %}">Ungluing Campaigns</a>.
<br /><br /> We hope you'll choose to support some of these Campaigns, but if you like an ebook you find on Unglue.it, don't hesitate to lend your support in other ways.
</dd> </dd>
<a id="crowdfunding"></a><dt>What is Crowdfunding?</dt>
<dd>Crowdfunding is collectively pooling contributions (or pledges) to support some cause. Using the internet for coordination means that complete strangers can work together, drawn by a common cause. This also means the number of supporters can be vast, so individual contributions can be as large or as small as people are comfortable with, and still add up to enough to do something amazing.<br /><br />
Want to see some examples? <a href="http://www.kickstarter.com/">Kickstarter</a> lets artists and inventors solicit funds to make their projects a reality. For instance, webcomic artist Rich Burlew sought $57,750 to reprint his comics in paper form -- and raised <a href="http://www.kickstarter.com/projects/599092525/the-order-of-the-stick-reprint-drive">close to a million</a>.<br /><br />
In other words, crowdfunding is working together to support something you love. By pooling resources, big and small, from all over the world, we can make huge things happen.</dd>
<dt>What does it cost?</dt>
<dd>Unglue.it is free to join. Many of the things you can do here -- discovering books, adding them to your ebook list, downloading unglued books, commenting, sharing -- are free too.
<br /><br /> If you choose to support a Campaign, please do so!.<br /><br /> If you choose to buy or download an unglue.it ebook, you can read it anywhere you like - there's no restrictive DRM.
<br /><br />
If you hold the electronic rights to a book, starting campaigns or listing your ebooks is free, too. You only pay Unglue.it a fraction of your revenue. For the basics on campaigns, see the FAQ on <a href="/faq/campaigns/">Campaigns</a>; for more details, see the <a href="/faq/rightsholders/">FAQ for Rights Holders</a>.</dd>
<dt>Who can use Unglue.it?</dt> <dt>Who can use Unglue.it?</dt>
<dd>Anyone! We're located in the United States, but we welcome participants from all over the world.<br /><br /> <dd>Anyone! We're located in the United States, but we welcome participants from all over the world.<br /><br />
To fund a campaign, or buy an ebook, you'll need a valid credit card. To start a campaign, you'll need to establish yourself with us as a rights holder, including demonstrating that you have the rights to the content you want to unglue. See the FAQs <a href="/faq/rightsholders/">for Rights Holders</a> for more.</dd> To fund a campaign, or buy an ebook, you'll need a valid credit card. To use our fund raising tools for your books, you'll need to establish yourself with us as a <a href="{% url 'rightsholders' %}">rights holder</a>. </dd>
<dt>Why should I support a book at Unglue.it when I can just buy it somewhere else?</dt>
<dd>When you buy a book, you get a copy for yourself. When you unglue it, you give a copy to yourself, reward its creators and contribute towards making it free to everyone.</dd>
<dt>What do I get when I help unglue a book?</dt> <dt>What do I get when I help unglue a book?</dt>
<dd>You get a better ebook. Your unglued ebook has no restrictive DRM: you can put it on any device you want, in any format you want. You can read it on the network or off, with no one tracking your reading.<br /><br /> <dd>You get a better ebook. Your unglued ebook has no restrictive DRM: you can put it on any device you want, in any format you want. You can read it on the network or off, with no one tracking your reading.<br /><br />
@ -65,48 +54,31 @@ 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> <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="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 /> <dd>No. Books available on unglue.it are licensed by the the copyright owner. <a href="https://creativecommons.org">Creative Commons</a> licenses are licensing agreements. They grant certain rights but do not affect the status of the copyright. You can read more about these licenses at the <a href="https://wiki.creativecommons.org/Frequently_Asked_Questions">Creative Commons FAQ</a>.</dd>
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 /> <dt>If I'm a rights holder and unglue my book, can I still offer it for sale?</dt>
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> <dd>Yes. You are free to enter into additional licensing agreements for the work and its derivatives, including translation and film adaptations. You may continue to sell both print and ebook editions. Unglued ebooks can be free samples to market you and your work -- for instance, later works in the same series.
<dt>If I'm a rights holder and my book is unglued, does that mean I can never make money from it again?</dt>
<dd>No! You are free to enter into additional licensing agreements for other, non-unglued, editions of the work, including translation and film rights. You may continue to sell both print and ebook editions. You may use your unglued books as free samples to market your other works -- for instance, later works in the same series. You can use them to attract fans who may be interested in your speaking engagements, merchandise, or other materials. You absolutely may continue to profit from ungluing books -- and we hope you do!
</dd>
<dt>Why do authors, publishers, and other rights holders want their books unglued?</dt>
<dd>
Lots of reasons:
<ul class="bullets">
<li>To publicize their other books, gain new fans, or otherwise increase the value of an author's brand.</li>
<li>To get income from books that are no longer in print.</li>
<li>To have a digital strategy that pays for itself, even if they don't have expertise in producing ebooks.</li>
<li>To advance a cause, add to the public conversation, or increase human knowledge.</li>
<li>To leave a legacy that enriches everyone, now and in the future.</li>
</ul>
</dd> </dd>
<dt>Does Unglue.it publish books? Can I self-publish here?</dt> <dt>Does Unglue.it publish books? Can I self-publish here?</dt>
<dd>Unglue.it is not a publisher. If you self-publish, Unglue.it can help you distribute your ebooks, if they meet our technical quality standards. </dd> <dd>Unglue.it is a distributor, not a publisher. If you self-publish and want your books to be free, Unglue.it can help you distribute your ebooks. </dd>
<dt>What's a rights holder? How can I tell if I'm one?</dt> <dt>What's a rights holder? How can I tell if I'm one?</dt>
<dd>A rights holder is the person (or entity) that holds the legal right to copy, distribute, or sell a book in some way. There may be one entity with all the rights to a work, or the rights may be split up among different entities: for example, one who publishes the print edition in the United States, another who publishes the print edition in the EU, another who holds electronic rights, et cetera. For ungluing purposes, the rights holder is the entity who has uncontested worldwide commercial rights to a work.<br /><br /> <dd>A rights holder is a person (or entity) that has a right to copy, distribute, or sell a book in some way. There may be one entity with all the rights to a work, or the rights may be split up among different entities: for example, one who publishes the print edition in the United States, another who publishes the print edition in the EU, another who holds electronic rights, et cetera. For ungluing purposes, the rights holder is the entity who has uncontested worldwide commercial rights to a work.<br /><br />
If you have written a book and not signed any contracts about it, you hold all the rights. If you have signed a contract with a publisher to produce, distribute, publish, or sell your work in a particular format or a particular territory, whether or not you still hold rights depends on the language of that contract. We encourage you to check your contracts. It may be in your interest to explore launching an Unglue.it campaign jointly with your publisher.<br /><br /> If you have written a book and not signed any contracts about it, you hold all the rights. If you have signed a contract with a publisher to produce, distribute, publish, or sell your work in a particular format or a particular territory, whether or not you still hold rights depends on the language of that contract. We encourage you to check your contracts. It may be in your interest to explore launching an Unglue.it campaign jointly with your publisher.<br /><br />
If you haven't written a book but you have had business or family relationships with someone who has, it's possible that you are a rights holder (for instance, you may have inherited rights from the author's estate, or your company may have acquired them during a merger). Again, this all depends on the specific language of your contracts. We encourage you to read them closely. Unglue.it cannot provide legal advice.<br /><br /> If you haven't written a book but you have had business or family relationships with someone who has, it's possible that you are a rights holder (for instance, you may have inherited rights from the author's estate, or your company may have acquired them during a merger). Again, this all depends on the specific language of your contracts. We encourage you to read them closely. Unglue.it cannot provide legal advice.<br /><br />
If you believe you are a rights holder and would like to your works to be unglued, please contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</dd> If you believe you are a rights holder and would like us to help unglue your works, get started at <a href="{% url 'rightsholders' %}">the right holder tools page</a>.</dd>
<dt>I know a book that should be unglued.</dt> <dt>I know a book that should be unglued.</dt>
<dd>Great! Find it in our database (using the search box above) and add it to your favorites, so we know it should be on our list, too. And encourage your friends to add it to their favorites. The more people that fave a book on Unglue.it, the more attractive it will be for authors and publishers to launch an ungluing campaign. You can also contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</dd> <dd>Great! Find it in our database (using the search box above) and add it to your favorites, so we know it should be on our list, too. And encourage your friends to add it to their favorites. You can also contact us at <a href="mailto:rights@ebookfoundation.org">rights@ebookfoundation.org</a>.</dd>
<dt>I know a book that should be unglued, and I own the commercial rights.</dt> <dt>I know a book that should be unglued, and it's mine!</dt>
<dd>Fabulous! Please refer to the <a href="/faq/rightsholders/">FAQ for Rights Holders</a> and then contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>. Let's talk.</dd> <dd>Fabulous! Please refer to the <a href="/faq/rightsholders/">FAQ for Rights Holders</a> and then contact us at <a href="mailto:rights@ebookfoundation.org">rights@ebookfoundation.org</a>. </dd>
<dt>Is there a widget that can be put on my own site to share my favorite books or campaigns?</dt> <dt>Is there a widget that can be put on my own site to share my favorite books or campaigns?</dt>
@ -150,33 +122,40 @@ If you receive our newsletter, there's a link at the bottom of every message to
<dt>Is my personal information shared?</dt> <dt>Is my personal information shared?</dt>
<dd>Short version: no, except as absolutely required to deliver services you have opted into. If you have pledged to a campaign and are owed a premium, we will share your email with campaign managers so that they can deliver premiums to you; you are not required to share any further information and they are required to comply with our privacy policy in handling your information. We do use third-party providers to support some of our business functions, but they are only allowed to use your information in order to enable your use of the service. For the long version, please read our <a href="/privacy/">privacy policy</a>. <dd>Short version: no, except as absolutely required to deliver services you have opted into. If you have pledged to a campaign and are owed a premium, we will share your email with campaign managers so that they can deliver premiums to you; you are not required to share any further information and they are required to comply with our privacy policy in handling your information. We do use third-party providers to support some of our business functions, but they are only allowed to use your information in order to enable your use of the service. For a long, opinionated version of this answer, please read our <a href="{% url 'privacy' %}">privacy policy</a>.
</dl> </dl>
{% endif %} {% endif %}
{% if sublocation == 'company' or sublocation == 'all' %} {% if sublocation == 'company' or sublocation == 'all' %}
<h4>The Company</h4> <h4>The Organization</h4>
<dl> <dl>
<dt>How can I contact Unglue.it?</dt> <dt>How can I contact Unglue.it?</dt>
<dd>For support requests, <a href="{% url 'feedback' %}?page=need+support">use our feedback form</a>. For general inquiries, use our Ask Questions Frequently account, <a href="mailto:aqf@gluejar.com">aqf@gluejar.com</a>. For rights inquiries, <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>. Media requests, <a href="mailto:press@gluejar.com">press@gluejar.com</a></dd> <dd>For support requests, <a href="{% url 'feedback' %}?page=need+support">use our feedback form</a>. You can also contact us on <a href="mailto:info@ebookfoundation.org">email</a>, <a href="https://twitter.com/unglueit">Twitter</a> or <a href="https://facebook/com/unglueit">Facebook</a>.
<dt>Who is Unglue.it?</dt> </dd>
<dd>We are a small group that believes strongly that ebooks can make the world richer in new and diverse ways, and that we all benefit from the hard work of their creators. We come from the worlds of entrepreneurship, linked data, physics, publishing, education, and library science, to name a few. You can learn more about us at our <a href="/about">about page</a> or our Unglue.it supporter profiles: <a href="{% url 'supporter' 'eric' %}">Eric Hellman</a>, <a href="{% url 'supporter' 'AmandaM' %}">Amanda Mecke</a> and <a href="{% url 'supporter' 'rdhyee' %}">Raymond Yee</a>. Our alumni include <a href="{% url 'supporter' 'luclewitanski' %}">Luc Lewitanski</a> and <a href="{% url 'supporter' 'andromeda' %}">Andromeda Yelton</a>.</dd>
<dt>Are you a non-profit company?</dt> <dt>Are you a non-profit company?</dt>
<dd>Yes. Unglue.it is a program of the Free Ebook Foundation, which is a not-for-profit corporation. We work with both non-profit and commercial partners.</dd> <dd>Yes. Unglue.it is a program of the <a href="https://ebookfoundation.org">Free Ebook Foundation</a>, which is a charitable, not-for-profit corporation. We work with both non-profit and commercial partners.</dd>
<dt>Why does Unglue.it exist?</dt>
<dd>Because legal stickiness means you can't reliably lend or share your ebooks, borrow them from the library, or read them on the device of your choice. Because -- like public radio -- ebooks are expensive to create but cheap to distribute, so covering their fixed costs and reasonable profit up front can be an appealing system for authors, publishers, readers, and libraries. Because we all have books we love so much we want to give them to the world.</dd>
</dl> </dl>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if location == 'campaigns' or location == 'faq' %} {% if location == 'campaigns' or location == 'faq' %}
<h3>Campaigns</h3> <h3>Campaigns</h3>
<dl>
<dt>What are Ungluing Campaigns?</dt>
<dd>Unglue.it has three programs that support ebook creators: Pledge Campaigns, Buy-to-Unglue Campaigns and Thanks-for-Ungluing Campaigns.
<ul class="bullets">
<li> In a <i><a href="{% url 'faq_sublocation' 'campaigns' 'supporting' %}">Pledge Campaign</a></i>, book lovers pledge their support for ungluing a book. If enough support is found to reach the goal (and only then), the supporter's credit cards are charged, and an unglued ebook is released.</li>
<li> In a <i><a href="{% url 'faq_sublocation' 'campaigns' 'b2u' %}">Buy-to-Unglue Campaign</a></i>, every ebook copy sold moves the book's ungluing date closer to the present. And you can donate ebooks to your local library- that's something you can't do in the Kindle or Apple Stores!</li>
<li> In a <i><a href="{% url 'faq_sublocation' 'campaigns' 't4u' %}">Thanks-for-Ungluing Campaign</a></i>, the ebook is already released with a Creative Commons license. Supporters can express their thanks by paying what they wish for the license and the ebook.</li>
</ul>
We also support distribution and preservation of free ebooks through our growing catalog of free-licensed ebooks.
</dd>
</dl>
{% if sublocation == 'all' %} {% if sublocation == 'all' %}
{% include "faq_t4u.html" %} {% include "faq_t4u.html" %}
@ -191,6 +170,7 @@ If you receive our newsletter, there's a link at the bottom of every message to
{% endif %} {% endif %}
{% if sublocation == 'supporting' or sublocation == 'all' %} {% if sublocation == 'supporting' or sublocation == 'all' %}
<a class="more_featured_books short" href="{% url 'campaign_list' 'pledge' %}" title="Pledge to Unglue list"><i class="fa fa-arrow-circle-o-right fa-3x"></i></a> <a class="more_featured_books short" href="{% url 'campaign_list' 'pledge' %}" title="Pledge to Unglue list"><i class="fa fa-arrow-circle-o-right fa-3x"></i></a>
<h4>Pledge Campaigns</h4> <h4>Pledge Campaigns</h4>
<div class="faq_tldr"> <div class="faq_tldr">
Support the books you've loved and make them free to everyone. Support the books you've loved and make them free to everyone.
@ -206,23 +186,23 @@ Support the books you've loved and make them free to everyone.
<dt>How do I pledge?</dt> <dt>How do I pledge?</dt>
<dd>When the creators of a book have been persuaded to launch a campaign, there will be a "Support" button on the book's unglue.it page. You'll be asked to select your premium and specify your pledge amount; choose how you'd like to be acknowledged, if your pledge is $25 or more; and enter your credit card information.</dd> <dd>When the creators of a book have been persuaded to launch a campaign, there will be a "Support" button on the book's unglue.it page. You'll be asked to select your premium and specify your pledge amount; choose how you'd like to be acknowledged, if your pledge is $25 or more; and enter your credit card information. If tax deductions are important to you, consider donating instead.</dd>
<dt>How do I support a campaign with a donation?</dt>
<dd>Campaigns that meet the Free Ebook Foundation's guidelines for charitable support are eligible for support via donations to the Foundation. Essentially, the Foundation will pledge to the campaign based on your direction. </dd>
<dt>When will I be charged?</dt> <dt>When will I be charged?</dt>
<dd>In most cases, your account will be charged by the end of the next business day after a Pledge Campaign succeeds. If a campaign doesn't succeed, you won't be charged.</dd> <dd>For pledges, your account will be charged by the end of the next business day after a Pledge Campaign succeeds. If a campaign doesn't succeed, you won't be charged. For donations, your account will be charged immediately. If a campaign doesn't succeed, the Foundation will use your donation in support of other ebooks.</dd>
<dt>What if I want to change or cancel a pledge?</dt> <dt>What if I want to change or cancel a pledge?</dt>
<dd>You can modify your pledge by going to the book's page and clicking on the "Modify Pledge" button. (The "Support" button you clicked on to make a pledge is replaced by the "Modify Pledge" button after you pledge.)</dd> <dd>You can modify your pledge by going to the book's page and clicking on the "Modify Pledge" button. (The "Support" button you clicked on to make a pledge is replaced by the "Modify Pledge" button after you pledge.) You can't modify a donation, but you can make another.</dd>
<dt>What happens to my pledge if a Pledge Campaign does not succeed?</dt>
<dd>Your credit card will only be charged when campaigns succeed. If they do not, your pledge will expire and you will not be charged.</dd>
<dt>How will I be recognized for my support?</dt> <dt>How will I be recognized for my support?</dt>
<dd>Pledgers are indicated on the book's page. In addition, when ungluing campaigns are successful, you'll be acknowledged in the unglued ebook as follows:<br /> <dd>Pledgers and donors are indicated on the book's page. In addition, when ungluing campaigns are successful, you'll be acknowledged in the unglued ebook as follows:<br />
<ul class="terms"> <ul class="terms">
<li>$25+ // Your name under "supporters" in the acknowledgements section.</li> <li>$25+ // Your name under "supporters" in the acknowledgements section.</li>
<li>$50+ // Your name and link to your profile page under "benefactors"</li> <li>$50+ // Your name and link to your profile page under "benefactors"</li>
@ -233,13 +213,9 @@ Support the books you've loved and make them free to everyone.
<dd>Yes. Anonymous donors are included in the total count of supporters, but their names and links are not included in the acknowledgements.</dd> <dd>Yes. Anonymous donors are included in the total count of supporters, but their names and links are not included in the acknowledgements.</dd>
<dt>If I have pledged to a suspended or withdrawn Pledge Campaign, what happens to my pledge?</dt>
<dd>Your pledge will time out according to its original time limit. If the campaign is resolved and reactivated before your pledge has timed out, your pledge will become active again. If the campaign is not reactivated before your pledge's time limit, your pledge will expire and you will not be charged. As always, you will only be charged if a campaign is successful, within its original time limit.</dd>
<dt>Is my contribution tax deductible?</dt> <dt>Is my contribution tax deductible?</dt>
<dd>If the campaign manager for the work you're supporting is a nonprofit, please contact them directly to inquire. Unglue.it cannot offer tax deductions.</dd> <dd>Donations to the Free Ebook Foundation are tax-deductible. Pledges to campaigns might be tax-deductible if the recipient of funds is a charity, otherwise probably not. Consult your tax advisor. </dd>
</dl> </dl>
{% endif %} {% endif %}
{% if sublocation == 'premiums' or sublocation == 'all' %} {% if sublocation == 'premiums' or sublocation == 'all' %}
@ -260,6 +236,10 @@ Support the books you've loved and make them free to everyone.
<dt>Is there a minimum or maximum for how much a premium can cost?</dt> <dt>Is there a minimum or maximum for how much a premium can cost?</dt>
<dd>There's a $1 minimum and a $2000 maximum.</dd> <dd>There's a $1 minimum and a $2000 maximum.</dd>
<dt>Do I get a premium for a donation?</dt>
<dd>No, just the good feeling you get for doing your part.</dd>
</dl> </dl>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -271,30 +251,15 @@ Support the books you've loved and make them free to everyone.
<dl> <dl>
<dt>So what is an unglued ebook?</dt> <dt>So what is an unglued ebook?</dt>
<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 /> <dd>
An unglued ebook is a <i>free</i> ebook, not an ebook that you <i>don't pay for</i>!
<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. Unglued ebooks use a <a href="https://creativecommons.org">Creative Commons</a> or other free license to counteract the stucked-ness of "all rights reserved" copyrights. Sometimes, payments are made to the author, publisher, or other rights holder to facilitate the ungluing.<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 retain copyright in your work.
</dd> </dd>
<dt>Is the unglued ebook edition the same as one of the existing editions?</dt>
<dd>Not necessarily. Sometimes the existing editions include content that we can't get the rights to include in the unglued ebook, especially cover art or illustrations. Sometimes the unglued ebook edition will have improvements, like copy-editing fixes or bonus features. These differences, if any, will be specified in the More... tab of the campaign page. In addition, unglued ebooks may include acknowledgements of supporters and information on their Creative Commons license.</dd>
<dt>How much does a book need to earn?</dt>
<dd>For Pledge and Buy-to-Unglue Campaigns, the author or publisher sets an earnings total for making the book free. Once you and your fellow ungluers raise enough money to meet that price through pledges or spend enough money on paid copies, the unglued ebook becomes available at no charge, for everyone, everywhere! Thanks-for-Ungluing books can be downloaded for free now, but the creators would love to have your support.</dd>
<dt>Will Unglue.it have campaigns for all kinds of books?</dt>
<dd>Yes. You choose what books to support. </dd>
<dt>Does an unglued book have to be out-of-print?</dt>
<dd>No. Unglued books can be anything in copyright, whether 75 years or 7 days old, whether they sell in paperback or hardcover and or can only be found in used bookstores -- or nowhere. But the copyright owners and any one else with a claim on the book need to agree.</dd>
<dt>Will Unglue.it help raise funds for books not yet written?</dt>
<dd>No. There are other crowdfunding sites devoted to the creative process, and we encourage you to investigate them. Once your book is ready to sell as an ebook, we'd love to talk to you.</dd>
</dl> </dl>
{% endif %} {% endif %}
{% if sublocation == 'using' or sublocation == 'all' %} {% if sublocation == 'using' or sublocation == 'all' %}
@ -302,7 +267,7 @@ What does this mean for you? If you're a book lover, you can read unglued ebook
<dl> <dl>
<dt>What can I do, legally, with an unglued ebook? What can I <I>not</I> do?</dt> <dt>What can I do, legally, with an unglued ebook? What can I <I>not</I> do?</dt>
<dd>All unglued ebooks are released under a Creative Commons license. The rights holder chooses which Creative Commons license to apply. Books that we distribute in a Buy-to-Unglue Campaign also have creative commons licenses, but the effective date of these licenses is set in the future.<br /><br /> <dd>All unglued ebooks are released under a Creative Commons or other free license. The rights holder chooses which license to apply. Books that we distribute in a Buy-to-Unglue Campaign have creative commons licenses, but the effective date of these licenses is set in the future.<br /><br />
Creative Commons licenses mean that once the license is effective you <b>can</b>: make copies; keep them for as long as you like; shift them to other formats (like .mobi or PDF); share them with friends or on the internet; download them for free.<br /><br /> Creative Commons licenses mean that once the license is effective you <b>can</b>: make copies; keep them for as long as you like; shift them to other formats (like .mobi or PDF); share them with friends or on the internet; download them for free.<br /><br />
@ -312,49 +277,41 @@ Under ND (no derivatives) licenses, you <b>cannot</b>: make derivative works, s
Under all CC licenses (except CC0), you <b>cannot</b>: remove the author's name from the book, or otherwise pass it off as someone else's work; or remove or change the CC license.</dd> Under all CC licenses (except CC0), you <b>cannot</b>: remove the author's name from the book, or otherwise pass it off as someone else's work; or remove or change the CC license.</dd>
<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="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>
<dt>Are the unglued ebooks compatible with my device? Do I need to own an ereader, like a Kindle or a Nook, to read them?</dt> <dt>Are the unglued ebooks compatible with my device? Do I need to own an ereader, like a Kindle or a Nook, to read them?</dt>
<dd>Unglued ebooks are distributed with NO RESTRICTIVE DRM, so they'll work on Kindle, iPad, Kobo, Nook, Android, Mac, Windows, Linux... you get the idea. Whether you have an ereader, a tablet, a desktop or laptop computer, or a smartphone, there are reading apps that work for you. Ebook editions issued through Unglue.it will be available in ePub format. If this format doesn't work for you, today or in the future, you are welcome to shift unglued books to one that does, and to copy and distribute it in that format.</dd> <dd>Unglued ebooks are distributed with NO RESTRICTIVE DRM, so they'll work on Kindle, iPad, Kobo, Nook, Android, Mac, Windows, Linux... you get the idea. Whether you have an ereader, a tablet, a desktop or laptop computer, or a smartphone, there are reading apps that work for you. </dd>
<dt>Do I need to have a library card to read an unglued ebook?</dt> <dt>Do I need to have a library card to read an unglued ebook?</dt>
<dd>No. (Though we hope you have a library card anyway!) <br /><br />If your library has bought an ebook as part of a Buy-to-Unglue Campaign, you may need your library card if you want to borrow them. </dd> <dd>No. (Though we hope you have a library card anyway!) <br /><br />If your library has bought an ebook as part of a Buy-to-Unglue Campaign, you may need your library card if you want to borrow them. </dd>
<dt>How long do I have to read my unglued book? When does it expire?</dt> <dt>How long do I have to read my "buy-to-unglue" ebook? When does it expire?</dt>
<dd>If you're reading a book purchased from unglue.it by a library, your borrowing license expires after 2 weeks. But otherwise there's no expiration. And you delete the file on the honor system.<br /><br /> <dd>If you're reading a "buy-to-unglue" book purchased by a library, your borrowing license expires after 2 weeks. But otherwise there's no expiration. And you delete the file on the honor system.<br /><br />
If you're reading a book you've purchased from unglue.it, you may keep it as long as you like. There's no need to return it, and it won't stop working after some deadline. You own it.<br /><br /> If you're reading a "buy-to-unglue" book you've purchased , you may keep it as long as you like. There's no need to return it, and it won't stop working after some deadline. You own it.<br /><br />
If you're reading a book that's been unglued, you can keep the file forever AND you can make copies for friends.</dd> If you're reading a book that's been unglued, you can keep the file forever AND you can make copies for friends.</dd>
<dt>Can I download ebooks directly from Unglue.it?</dt> <dt>Can I download ebooks directly from Unglue.it?</dt>
<dd>If you've purchased an ebook from unglue.it, you'll by able to download it through the website. All formats are included (where available), and you can download the ebooks for your personal use as many times as you need to.<br /><br /> <dd>If you've purchased an ebook from unglue.it, you'll by able to download it through the website. All formats are included (where available), and you can download the ebooks for your personal use as many times as you need to.<br /><br />
Unglue.it also provides links to download previously unglued books, creative commons licensed books, and public domain ebooks. You don't need our permission to host these free ebooks on your own site.</dd> Unglue.it provides links to download thousands of previously unglued books, creative commons licensed books, and public domain ebooks. You don't need our permission to host these free ebooks on your own site, but you must abide by the book's license terms</dd>
</dl> </dl>
{% endif %} {% endif %}
{% if sublocation == 'copyright' or sublocation == 'all' %} {% if sublocation == 'copyright' or sublocation == 'all' %}
<h4>Ungluing and Copyright</h4> <h4>Ungluing and Copyright</h4>
<dl> <dl>
<dt>Why does an unglued book have to be in copyright?</dt> <dt>Are Unglued Ebooks in the Public Domain?</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="https://en.wikipedia.org/wiki/Public_domain">public domain</a>, it doesn't need to be unglued.</dd> <dd>No. Books in the public domain, because their copyrights have expired, or because they were created by the US government, 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, it's already unglued. But lots of other books are free-licensed even though their copyrights are still in force; if you don't adhere to the license terms, you are infringing that copyright.</dd>
<dt>How can I tell if a book is in copyright or not?</dt> <dt>How can I tell if a book is in copyright or not?</dt>
<dd>Unfortunately, it can be complicated -- which is why Unglue.it wants to simplify things, by making unglued ebooks unambiguously legal to use in certain ways. The laws governing copyright and the public domain vary by country, so a book can be copyrighted in one place and not in another. In the United States, the <a href="http://www.librarycopyright.net/digitalslider/">Library Copyright Slider</a> gives a quick estimate. <br /><br /> <dd>Unfortunately, it can be complicated -- which is why Unglue.it wants to simplify things, by making unglued ebooks unambiguously legal to use in certain ways. The laws governing copyright and the public domain vary by country, so a book can be copyrighted in one place and not in another. In the United States, the <a href="http://www.librarycopyright.net/digitalslider/">Library Copyright Slider</a> gives a quick estimate. <br /><br />
Unglue.it signs agreements assuring the copyright status of every work we unglue, so you can be confident when reading and sharing an unglued ebook.</dd>
<dt>Unglue.it lists a book that I know is already Creative Commons licensed or available in the public domain. How can I get the link included?</dt> <dt>Unglue.it lists a book that I know is already Creative Commons licensed or available in the public domain. How can I get the link included?</dt>
<dd>If you are logged in to Unglue.it, the "Rights" page for every work lists the edition we know about. There a link for every edition that you can use to tell us about Creative Commons or public domain downloads. We do require that these books be hosted at certain known, reputable repositories: Internet Archive, Wikisources, Hathi Trust, Project Gutenberg, or Google Books.</dd></dl> <dd>If you are logged in to Unglue.it, the "More..." tab for every work lists the edition we know about. There is a link for every edition that you can use to tell us about Creative Commons or public domain downloads. We require that these books be hosted at certain known, reputable repositories such as Internet Archive, Wikisources, Hathi Trust, Project Gutenberg, Github or Google Books.</dd></dl>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -363,23 +320,15 @@ Unglue.it signs agreements assuring the copyright status of every work we unglue
{% if sublocation == 'authorization' or sublocation == 'all' %} {% if sublocation == 'authorization' or sublocation == 'all' %}
<h4>Becoming Authorized</h4> <h4>Becoming Authorized</h4>
<dl> <dl>
<dt>Who is eligible to start a campaign or sell ebooks on Unglue.it?</dt> <dt>What do I need to do to become a Rights Holder on Unglue.it?</dt>
<dd>To start a campaign, you need to be a verified rights holder who has signed a Platform Services Agreement with us. </dd> <dd>To start a campaign, you need to be a verified rights holder who has signed a Rights Holder Agreement with us. <br /><br />
<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="https://creativecommons.org">Creative Commons</a> license, or to raise money for a book you've already released under such a license.</dd> Start by visiting the <a href="{% url 'rightsholders' %}">Rights Holders Tools</a> page.You'll sign our <a href="{% url 'agree' %}">Rights Holder Agreement (RHA)</a> and learn about our various programs.</dd>
<dt>I haven't signed an agreement, but my books are listed on Unglue.it. What's going on?</dt> <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. <dd>If your book is listed on Unglue.it, it's either because an unglue.it user has indicated some interested in the book, or it's because we've determined that your book is already available with a free license.
</dd> </dd>
<dt>I didn't give you permission to offer my CC-licensed book for download. Why is it there?</dt>
<dd>When we don't have an agreement with a rights-holder, our download links lead to sites such as Internet Archive, Project Gutenberg, or Google Books that facilitate this linking and free use of the works by our users. We strive to keep our information accurate, so if the license we report is inaccurate, please <a href="mailto:rights@gluejar.com?subject=corrections">let us know</a> immediately. If for some reason you don't want us to provide download links, let us know using the "feedback" tab next to your book's page and we'll make a notation to that effect in our metadata. We may require evidence that you are authorized to act for the rights holders of the book.
</dd>
<dt>Do I need to know the exact titles I might want to unglue before I sign the Platform Services Agreement?</dt>
<dd>No. You only need to agree to the terms under which you will use Unglue.it to raise money. You can decide which specific titles you wish to make available for licensing later. However, before you start a campaign, you must claim a specific title (through the More... tab of its book page) and plan how you will attract ungluers to pledge toward your goal.</dd>
<dt>Can I run more than one campaign?</dt> <dt>Can I run more than one campaign?</dt>
<dd>You can run campaigns for as many, or as few, titles at a time as you like. </dd> <dd>You can run campaigns for as many, or as few, titles at a time as you like. </dd>
@ -389,22 +338,17 @@ Unglue.it signs agreements assuring the copyright status of every work we unglue
<h4>Claiming Works</h4> <h4>Claiming Works</h4>
<dl> <dl>
<dt>How can I claim a work for which I am the rights holder?</dt> <dt>How can I claim a work for which I am a rights holder?</dt>
<dd>On every book page there is a More... tab. If you have a signed Platform Services Agreement on file, one of the options on the More... tab will be "Claim This Work". If you represent more than one rights holder, choose the correct one for this work and click "Claim". <dd>On every book page there is a More... tab. If you have a signed Rights Holder Agreement on file, one of the options on the More... tab will be "Claim This Work". If you represent more than one rights holder, choose the correct one for this work and click "Claim".
<br /><br />If the book is not already in Unglue.it or in Google Books, you'll need to enter the metatadata for the book by hand or work with us to load the data from a spreadsheet or Onix file. <br /><br />If the book is not already in Unglue.it or in Google Books, you'll need to enter the metatadata for the book by hand or work with us to load the data from a spreadsheet or Onix file.
<ul class="bullets"> <ul class="bullets">
<li>Use <a href="{% url 'new_edition' '' '' %}">this form</a> to enter the metadata.</li> <li>Use <a href="{% url 'new_edition' '' '' %}">this form</a> to enter the metadata.</li>
<li>Your ebooks must have ISBNs or OCLC numbers assigned.</li> <li>Your metadata should have title, identifiers, authors, language, description.</li>
<li>Your metadata should have title, authors, language, description.</li>
<li>If you have a lot of books to enter, <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">contact us</a> to load ONIX or CSV files for you.</li> <li>If you have a lot of books to enter, <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">contact us</a> to load ONIX or CSV files for you.</li>
</ul> </ul>
<br /><br />If you expect to see a "Claim" button and do not, either we do not have a PSA from you yet, or we have not yet verified and filed it. Please contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</dd> <br /><br />If you expect to see a "Claim" button and do not, either we do not have a RHA from you yet, or we have not yet verified and filed it. Please contact us at <a href="mailto:rights@ebookfoundation.org">rights@ebookfoundation.org</a>.</dd>
<dt>Why should I claim my works?</dt>
<dd>You need to claim a work before you will be able to start a campaign for it. Additionally, we're adding features for verified rights holders which will help you show off your works and connect to your readers. Claiming your works will let you take advantage of these features in the future.</dd>
<dt>Where can I find a campaign page?</dt> <dt>Where can I find a campaign page?</dt>
@ -418,15 +362,12 @@ If you want to find an interesting campaign and don't have a specific book in mi
<dl> <dl>
<dt>What do I need to create a campaign?</dt> <dt>What do I need to create a campaign?</dt>
<dd>First, you need to be an authorized rights holder with a signed Platform Services Agreement on file. Please contact <a href="mailto:rights@gluejar.com">rights@gluejar.com</a> to start this process. Once we have your PSA on file, you'll be able to claim your works and you'll have access to tools for launching and monitoring campaigns. We'll be happy to walk you through the process personally.</dd> <dd>First, you need to be an authorized rights holder. To get authorized, <a href="{% url 'agree' %}">sign a Rights Holder Agreement</a>. Please contact <a href="mailto:rights@ebookfoundation.org">rights@ebookfoundation.org</a> if you have any questions. Once we have approved your Agreement, you'll be able to claim works and you'll have access to tools for launching and monitoring campaigns.
</dd>
<dt>Can I unglue only one of my books? Can I unglue all of them?</dt>
<dd>Yes! It's entirely up to you. Each Campaign is for a individual title and a separate fundraising parameters.</dd>
<dt>Can I raise any amount of money I want?</dt> <dt>Can I raise any amount of money I want?</dt>
<dd>You can set any goal that you want in a Campaign. But don't be ridiculous! Unglue.it cannot guarantee that a goal will be reached.</dd> <dd>You can set any goal that you want in a Campaign. But don't be ridiculous! Unglue.it cannot guarantee that a goal will be reached. Also, Campaigns with excessive goals will not be eligible for support via donations.</dd>
<dt>What should I include in my campaign page?</dt> <dt>What should I include in my campaign page?</dt>
@ -444,6 +385,20 @@ If you want to find an interesting campaign and don't have a specific book in mi
<dd>You can offer anything you are capable of delivering, so long as it is not illegal or otherwise restricted in the United States. For instance, your premiums may not include alcohol, drugs, or firearms. For full details, consult our <a href="/terms/">Terms of Use</a>.</dd> <dd>You can offer anything you are capable of delivering, so long as it is not illegal or otherwise restricted in the United States. For instance, your premiums may not include alcohol, drugs, or firearms. For full details, consult our <a href="/terms/">Terms of Use</a>.</dd>
<dt id='donation_support'>Is my campaign eligible for charitable donation support?</dt>
<dd>
The Free Ebook Foundation may provide support for some campaigns using donations. These campaigns are subject to the following guidelines:
<ol>
<li>Proceeds of a campaign must not benefit a candidate for political office and books to be unglued shall not be primarily aimed at influencing legislation.</li>
<li>Proceeds of a campaign must not benefit organizations or individuals subject to regulation by the US Treasurys Office of Foreign Assets Control.</li>
<li>Books to be unglued with Foundation support should clearly benefit the public at large - by advancing scholarship and learning, spreading scientific knowledge, achieving artistic, literary or cultural goals, educating students, promoting literacy and reading, documenting history, meeting the needs of underserved communities, raising awareness and understanding of the world around us. </li>
<li>The amount of support requested should be limited to the reasonable costs of producing the book and procuring associated rights. When a campaign is offered using a license with a “non-commercial” restriction, it is expected that the rights holders will bear part of these expenses themselves. When the campaign beneficiary is not a US-based non-profit, documentation of expenses may be requested.</li>
</ol>
</dd>
<dt>Who is responsible for making sure a rights holder delivers premiums?</dt> <dt>Who is responsible for making sure a rights holder delivers premiums?</dt>
<dd>The rights holder.</dd> <dd>The rights holder.</dd>
@ -534,15 +489,15 @@ Need more ideas? We're happy to work with rights holders personally to craft a
<dl> <dl>
<dt>I am a copyright holder. Do I need to already have a digital file for a book I want to unglue?</dt> <dt>I am a copyright holder. Do I need to already have a digital file for a book I want to unglue?</dt>
<dd>For a Buy-to-Unglue Campaign, yes, you'll need to upload an epub file for each book. For Pledge Campaigns, no, you may run campaigns for books that exist only in print copies or are out of print. Any print book can be scanned to create a digital file that can then become an ePub-format unglued ebook. For Thanks-for-Ungluing campaigns, you can use ePub, Mobi, pdf and html files.</dd> <dd>For a Buy-to-Unglue Campaign, yes, you'll need to upload an EPUB file for each book. For Pledge Campaigns, no, you may run campaigns for books that exist only in print copies or are out of print. Any print book can be scanned to create a digital file that can then become an EPUB-format unglued ebook. For Thanks-for-Ungluing campaigns, you can use EPUB, MOBI, pdf and html files.</dd>
<dt>Will Unglue.it scan books and produce ePub files?</dt> <dt>Will Unglue.it scan books and produce EPUB files?</dt>
<dd>No. We can help you find third parties who will contract for conversion services.</dd> <dd>No. We can help you find third parties who will contract for conversion services.</dd>
<dt>Will Unglue.it raise money to help pay for conversion costs?</dt> <dt>Will Unglue.it raise money to help pay for conversion costs?</dt>
<dd>A Pledge campaign target should include conversion costs. For a Buy-to-Unglue and Thanks-for-Ungluing campaigns, you'll need to have pre-existing epub files.</dd> <dd>A Pledge campaign target should include conversion costs. For a Buy-to-Unglue and Thanks-for-Ungluing campaigns, you'll need to have pre-existing files.</dd>
<dt>What are the requirements for my ebook files?</dt> <dt>What are the requirements for my ebook files?</dt>
@ -569,12 +524,12 @@ Need more ideas? We're happy to work with rights holders personally to craft a
<dt>Are contributions refundable?</dt> <dt>Are contributions refundable?</dt>
<dd>Unglue.it contributions are non-refundable. Once a campaign succeeds, supporters' credit cards will be charged the full amount of their pledge. However, they will not be charged until, and unless, the campaign succeeds. Before that time they may modify or cancel their pledges without charge. Buy-to-Unglue license purchases are not returnable or refundable by Unglue.it, but rights holders need to keep customers happy with respect to the products they buy, as customers are free to post comments on the work page. <dd>Unglue.it contributions are non-refundable. Once a campaign succeeds, supporters' credit cards will be charged the full amount of their pledge. However, they will not be charged until, and unless, the campaign succeeds. Before that time they may modify or cancel their pledges without charge. Buy-to-Unglue license purchases are not returnable or refundable by Unglue.it, but rights holders need to keep customers happy with respect to the products they buy, as customers are free to post comments on the work page. Donations are not refundable.
</dd> </dd>
<dt>What if an ungluer contests a charge?</dt> <dt>What if an ungluer contests a charge?</dt>
<dd>Ungluers may contest charges, either with the payment processor or with their credit card issuer. Unglue.it is not liable for this and rights holders are liable for any chargebacks. We encourage rights holders to provide clear, timely communication to their supporters throughout the campaign to ensure they remember what they pledged to and why.</dd> <dd>Ungluers may contest charges, either with the payment processor or with their credit card issuer. Unglue.it is not liable for this and rights holders are liable for any pledge chargebacks. We encourage rights holders to provide clear, timely communication to their supporters throughout the campaign to ensure they remember what they pledged to and why.</dd>
<dt>What happens if a supporter's credit card is declined?</dt> <dt>What happens if a supporter's credit card is declined?</dt>
@ -582,19 +537,19 @@ Need more ideas? We're happy to work with rights holders personally to craft a
<dt>What happens if a buyer wants a refund?</dt> <dt>What happens if a buyer wants a refund?</dt>
<dd>Our policy is for 100% buyer satisfaction. We hold back a 30% reserve for 90 days to cover possible customer refunds.</dd> <dd>Our policy is for 100% supporter satisfaction. We may hold back a 30% reserve for 90 days to cover possible customer refunds.</dd>
<dt>Can I offer tax deductions to people who pledge to my campaign?</dt> <dt>Can I offer tax deductions to people who pledge to my campaign?</dt>
<dd>If you are a 501(c)3 or similar, please consult your own attorneys and accountants about the tax deductibility of ungluers' contributions. Unglue.it cannot offer tax deductions or advice.</dd> <dd>If you are a 501(c)3 or similar, please consult your own attorneys and accountants about the tax deductibility of ungluers' pledged. Donations to the Free Ebook Foundation are tax deductible in the USA; rights holders are not involved. </dd>
<dt>How do I know when I have started fundraising?</dt> <dt>How do I know when I have started fundraising?</dt>
<dd>Unglue.it will be in communication with you about when campaigns should go live. On your <a href="/rightsholders/">rights holder dashboard</a>, you will be able to see all works you have claimed and the status of any associated campaigns.</dd> <dd>Unglue.it will be in communication with you about when campaigns should go live. On your <a href="{% url 'rightsholders' %}">rights holder tools page</a>, you will be able to see all works you have claimed and the status of any associated campaigns.</dd>
<dt>When and how will I be paid?</dt> <dt>When and how will I be paid?</dt>
<dd><ul><li>For <i>Pledge Campaigns</i>: After we reach the threshold price Unglue.it will issue you a closing memo, which will cover your gross and net proceeds. Unglue.it will hold the proceeds until you deliver an ePub file meeting Unglue.it's quality standards, at which point Unglue.it will send you a check. If Unglue.it does not receive a suitable ePub file within 90 days, we will deduct conversion costs from your funds and release the rest to you.</li> <dd><ul><li>For <i>Pledge Campaigns</i>: After we reach the threshold price Unglue.it will issue you a closing memo, which will cover your gross and net proceeds. Unglue.it will hold the proceeds until you deliver an EPUB file meeting Unglue.it's quality standards, at which point Unglue.it will send you a check. If Unglue.it does not receive a suitable EPUB file within 90 days, we will deduct conversion costs from your funds and release the rest to you.</li>
<li>For <i>Buy-to-Unglue Campaigns</i> and <i>Thanks-for-Ungluing Campaigns</i>: We make payments quarterly to rights holders who have accrued more than $100 in earnings. If we are able to set you up with ACH transfers, we will send payments more frequently. 70% of earnings accrue immediately; the rest accrues after 90 days.</li> <li>For <i>Buy-to-Unglue Campaigns</i> and <i>Thanks-for-Ungluing Campaigns</i>: We make payments quarterly to rights holders who have accrued more than $100 in earnings. If we are able to set you up with ACH transfers, we will send payments more frequently. 70% of earnings accrue immediately; the rest accrues after 90 days.</li>
</ul></dd> </ul></dd>
@ -615,17 +570,9 @@ If you're concerned a campaign may not reach its goal you have several options.
<dd>Yes, the minimum funding goal is $500.</dd> <dd>Yes, the minimum funding goal is $500.</dd>
<dt>What fees does Unglue.it charge in a Pledge Campaign?</dt> <dt>What fees does Unglue.it charge in a Fundraising Campaign?</dt>
<dd>When a campaign succeeds, Unglue.it will deduct a 6% commission (or $60, whichever is greater) on the funds raised. Our payment processor also charges a separate small fee on each transaction, plus (where relevant) currency conversion costs. If you do not have a suitable ePub version of your book available, you will also need to cover ebook conversion costs; please price this into your goal for a campaign. Details are spelled out in the Platform Services Agreement that rights holders must sign before launching an Unglue.it campaign.</dd> <dd>Fees and terms for each type of campaign are detailed on the <a href="{% url 'programs'%}">program terms page</a>.</dd>
<dt>What fees does Unglue.it charge in a Buy-to-Unglue Campaign?</dt>
<dd>For Buy-to-Unglue Campaigns, Unglue.it charges (1) a flat 25% of of revenue from ebook licenses it provides through its site or through partner sites and (2) a per-transaction charge of $0.25. These amounts <i>include</i> payment processor fees and any fees due to partner sites.</dd>
<dt>What fees does Unglue.it charge in a Thanks-for-Ungluing Campaign?</dt>
<dd>For Buy-to-Unglue Campaigns, Unglue.it charges (1) a flat 8% of of revenue from "thanks" payments it receives through its site and (2) a per-transaction charge of $0.25. These amounts <i>include</i> payment processor fees.</dd>
<dt>Does it cost money to start a campaign on Unglue.it?</dt> <dt>Does it cost money to start a campaign on Unglue.it?</dt>
@ -648,9 +595,9 @@ If you're concerned a campaign may not reach its goal you have several options.
<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> <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> <dt>If I am a publisher, but I do not have an ebook royalty in my contract, can I sign your Rights Holder Agreement?</dt>
<dd>We can't interpret your particular contract regarding subsidiary rights and your ability to use a Creative Commons license. Please seek qualified independent advice regarding the terms of your contract. In any event, you will also want the author to cooperate with you on a successful fundraising campaign, and you can work together to meet the warranties of the PSA.</dd> <dd>We can't interpret your particular contract regarding subsidiary rights and your ability to use a Creative Commons license. Please seek qualified independent advice regarding the terms of your contract. In any event, you will also want the author to cooperate with you on a successful fundraising campaign, and you can work together to meet the warranties of the RHA.</dd>
<dt>Are the copyright holder, and the rights holder who is eligible to start an Unglue.it campaign, the same person?</dt> <dt>Are the copyright holder, and the rights holder who is eligible to start an Unglue.it campaign, the same person?</dt>

View File

@ -1,12 +1,26 @@
<div class="jsmodule"> <div class="jsmodule">
<h3 class="jsmod-title"><span>Pledging FAQs</span></h3> <h3 class="jsmod-title"><span>Campaign Support FAQs</span></h3>
<div class="jsmod-content"> <div class="jsmod-content">
<ul class="menu level1"> <ul class="menu level1">
<li class="first parent"> <li class="first parent">
<span class="faq">How do I pledge?</span> <span class="faq">How do I pledge?</span>
<span class="menu level2 answer"> <span class="menu level2 answer">
Enter your pledge amount and select a premium. (You may select a premium at any level up to and including the amount you pledge.) If you pledge enough, you're also eligible to be credited in the unglued ebook and to include a dedication, and toward the bottom of this page you can specify what you'd like those to say. If this is your first pledge, we'll collect your card information after you click Pledge Now. Otherwise, we'll use the card you used last time -- no need to type in your info again! Enter your pledge amount and select a premium. (You may select a premium at any level up to and including the amount you pledge.) If you pledge enough, you're also eligible to be credited in the unglued ebook and to include a dedication, and toward the bottom of this page you can specify what you'd like those to say. If this is your first time supporting a campaign, we'll collect your card information after you click Pledge Now. Otherwise, we'll use the card you used last time -- no need to type in your info again!
</span>
</li>
<li class="parent">
<span class="faq">Donation or Pledge?</span>
<span class="menu level2 answer">
Donations are fully tax-deductible in the US, but we
</span>
</li>
<li class="parent">
<span class="faq">How do I donate?</span>
<span class="menu level2 answer">
Enter your donation amount and check the donation box. If you donate enough, you're also eligible to be credited in the unglued ebook and to include a dedication, and toward the bottom of this page you can specify what you'd like those to say. If this is your first time supporting a campaign, we'll collect your card information after you click Donate Now. Otherwise, we'll use the card you used last time -- no need to type in your info again! Remember, donations are tax-deductible in the US.
</span> </span>
</li> </li>
@ -20,12 +34,19 @@
<li class="parent"> <li class="parent">
<span class="faq">When will I be charged?</span> <span class="faq">When will I be charged?</span>
<span class="menu level2 answer"> <span class="menu level2 answer">
If this campaign reaches its target before its deadline ({{ campaign.deadline }}), you'll be charged within a day of when the target is reached. Otherwise, your pledge will expire at midnight on {{ campaign.deadline }} (Eastern US time) and you will not be charged. Donations will be charged right away. Pledges aren't charged unless the campaign succeeds. If this campaign reaches its target before its deadline ({{ campaign.deadline }}), you'll be charged within a day of when the target is reached. Otherwise, your pledge will expire at midnight on {{ campaign.deadline }} (Eastern US time) and you will not be charged.
</span> </span>
</li> </li>
<li class="parent"> <li class="parent">
<span class="faq">Will I be charged if the campaign doesn't succeed?</span> <span class="faq">Will donations be refunded if the campaign doesn't succeed?</span>
<span class="menu level2 answer">
Sorry, no. Your donation will be used to support other qualifying Unglue.it comapigns.
</span>
</li>
<li class="parent">
<span class="faq">Will pledges be charged if the campaign doesn't succeed?</span>
<span class="menu level2 answer"> <span class="menu level2 answer">
Nope! Nope!
</span> </span>

View File

@ -7,15 +7,24 @@
<a href="/faq/"><span>All</span></a> <a href="/faq/"><span>All</span></a>
</li> </li>
<li class="parent {% if location != 'basics' %}collapse{% endif %}"> <li class="parent {% if location != 'basics' %}collapse{% endif %}">
<a href="{% url 'faq_location' 'basics' %}"><span>Basics</span></a> <a href="{% url 'faq_location' 'basics' %}"><span>About the site</span></a>
<ul class="menu level2"> <ul class="menu level2">
<li class="first"><a href="{% url 'faq_sublocation' 'basics' 'howitworks' %}"><span>How it Works</span></a></li> <li class="first"><a href="{% url 'faq_sublocation' 'basics' 'howitworks' %}"><span>About Unglue.it</span></a></li>
<li><a href="{% url 'faq_sublocation' 'basics' 'account' %}"><span>Your Account</span></a></li> <li><a href="{% url 'faq_sublocation' 'basics' 'account' %}"><span>Your Account</span></a></li>
<li class="last"><a href="{% url 'faq_sublocation' 'basics' 'company' %}"><span>The Company</span></a></li> <li class="last"><a href="{% url 'faq_sublocation' 'basics' 'company' %}"><span>The Organization</span></a></li>
</ul> </ul>
</li> </li>
<li class="parent {% if location != 'unglued_ebooks' %}collapse{% endif %}">
<a href="{% url 'faq_location' 'unglued_ebooks' %}"><span>Unglued Ebooks</span></a>
<ul class="menu level2">
<li class="first"><a href="{% url 'faq_sublocation' 'unglued_ebooks' 'general' %}"><span>General Questions</span></a></li>
<li><a href="{% url 'faq_sublocation' 'unglued_ebooks' 'using' %}"><span>Using Your Unglued Ebook</span></a></li>
<li class="last"><a href="{% url 'faq_sublocation' 'unglued_ebooks' 'copyright' %}"><span>Ungluing and Copyright</span></a></li>
</ul>
</li>
<li class="parent {% if location != 'campaigns' %}collapse{% endif %}"> <li class="parent {% if location != 'campaigns' %}collapse{% endif %}">
<a href="{% url 'faq_location' 'campaigns' %}"><span>Campaigns</span></a> <a href="{% url 'faq_location' 'campaigns' %}"><span>Campaigns</span></a>
<ul class="menu level2"> <ul class="menu level2">
@ -26,14 +35,6 @@
</ul> </ul>
</li> </li>
<li class="parent {% if location != 'unglued_ebooks' %}collapse{% endif %}">
<a href="{% url 'faq_location' 'unglued_ebooks' %}"><span>Unglued Ebooks</span></a>
<ul class="menu level2">
<li class="first"><a href="{% url 'faq_sublocation' 'unglued_ebooks' 'general' %}"><span>General Questions</span></a></li>
<li><a href="{% url 'faq_sublocation' 'unglued_ebooks' 'using' %}"><span>Using Your Unglued Ebook</span></a></li>
<li class="last"><a href="{% url 'faq_sublocation' 'unglued_ebooks' 'copyright' %}"><span>Ungluing and Copyright</span></a></li>
</ul>
</li>
<li class="parent {% if location != 'rightsholders' and 'smashwords' not in request.path %}collapse{% endif %}"> <li class="parent {% if location != 'rightsholders' and 'smashwords' not in request.path %}collapse{% endif %}">
<a href="{% url 'faq_location' 'rightsholders' %}"><span>For Rights Holders</span></a> <a href="{% url 'faq_location' 'rightsholders' %}"><span>For Rights Holders</span></a>

View File

@ -6,7 +6,7 @@
<p>Love something? Hate something? Found something broken or confusing? Thanks for telling us!</p> <p>Love something? Hate something? Found something broken or confusing? Thanks for telling us!</p>
To: support@gluejar.com<br /><br /> To: support@ebookfoundation.org<br /><br />
<form method="POST" action="{% url 'feedback' %}"> <form method="POST" action="{% url 'feedback' %}">
{% csrf_token %} {% csrf_token %}
{{ form.sender.errors }} {{ form.sender.errors }}
@ -29,5 +29,5 @@
<input type="submit" value="Submit" /> <input type="submit" value="Submit" />
</form> </form>
<p>If for some reason this form doesn't work, you can send email to unglue.it support at <a href="mailto:support@gluejar.com">support@gluejar.com</a>.</p> <p>If for some reason this form doesn't work, you can send email to unglue.it support at <a href="mailto:unglueit@ebookfoundation.org">unglueit@ebookfoundation.org</a>.</p>
{% endblock %} {% endblock %}

View File

@ -1,12 +1,21 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load truncatechars %} {% load truncatechars %}
{% load sass_tags %}
{% block title %}&#151; Support Free eBooks{% endblock %} {% block title %}&#151; Support Free eBooks{% endblock %}
{% block extra_meta %}
<meta property="og:title" content="Unglue.it - A Community Supporting Free eBooks" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://unglue.it" />
<meta property="og:image" content="https://unglue.it/static/images/logo.png" />
{% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/landingpage4.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/landingpage4.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/searchandbrowse2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/searchandbrowse2.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_panel2.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
@ -211,7 +220,7 @@ function put_un_in_cookie2(){
<div class="jsmodule"> <div class="jsmodule">
<h3 class="module-title">News</h3> <h3 class="module-title">News</h3>
<div class="jsmod-content"> <div class="jsmod-content">
Unglue.it is now <a href="https://blog.unglue.it/2017/01/19/unglue-it-website-is-now-open-source/"> Open Source</a> <a href="https://blog.unglue.it/2018/01/24/unglue-it-has-resumed-crowdfunding/">Unglue.it has resumed crowdfunding</a>
</div> </div>
</div> </div>
<div class="jsmodule"> <div class="jsmodule">
@ -286,12 +295,12 @@ function put_un_in_cookie2(){
<h3 class="featured_books">As seen on</h3> <h3 class="featured_books">As seen on</h3>
<ul id="as_seen_on"> <ul id="as_seen_on">
<li><a href="http://boingboing.net/2012/06/28/release-a-deadly-monster-a-dr.html"><img src="{{ STATIC_URL }}images/press_logos/boingboing_logo.png"></a></li> <li><a href="http://boingboing.net/2012/06/28/release-a-deadly-monster-a-dr.html"><img alt="boingboing" src="{{ STATIC_URL }}images/press_logos/boingboing_logo.png"></a></li>
<li><a href="http://www.zeit.de/digital/internet/2012-07/unglue-ebook-creative-commons"><img src="{{ STATIC_URL }}images/press_logos/die_zeit_logo.png"></a></li> <li><a href="http://www.zeit.de/digital/internet/2012-07/unglue-ebook-creative-commons"><img alt="die zeit" src="{{ STATIC_URL }}images/press_logos/die_zeit_logo.png"></a></li>
<li><a href="http://www.huffingtonpost.com/2012/05/21/unglueit-free-ebooks-crowdfunding_n_1532644.html"><img src="{{ STATIC_URL }}images/press_logos/huffington_post_logo.png"></a></li> <li><a href="http://www.huffingtonpost.com/2012/05/21/unglueit-free-ebooks-crowdfunding_n_1532644.html"><img alt="huffington post" src="{{ STATIC_URL }}images/press_logos/huffington_post_logo.png"></a></li>
<li><a href="http://techcrunch.com/2014/05/06/unglue-it-sets-books-free-after-authors-get-paid/"><img src="{{ STATIC_URL }}images/press_logos/techcrunch_logo.png"></a></li> <li><a href="http://techcrunch.com/2014/05/06/unglue-it-sets-books-free-after-authors-get-paid/"><img alt="techcrunch" src="{{ STATIC_URL }}images/press_logos/techcrunch_logo.png"></a></li>
<li><a href="http://www.thedigitalshift.com/2014/02/ebooks/buy-unglue-ebook-crowdfunding-model-goes-beta/"><img src="{{ STATIC_URL }}images/press_logos/library_journal_logo.png"></a></li> <li><a href="http://www.thedigitalshift.com/2014/02/ebooks/buy-unglue-ebook-crowdfunding-model-goes-beta/"><img alt="library journal" src="{{ STATIC_URL }}images/press_logos/library_journal_logo.png"></a></li>
<li><a href="http://www.networkworld.com/community/node/85329"><img src="{{ STATIC_URL }}images/press_logos/networkworld_logo.png"></a></li> <li><a href="http://www.networkworld.com/community/node/85329"><img alt="networkworld" src="{{ STATIC_URL }}images/press_logos/networkworld_logo.png"></a></li>
</ul> </ul>
<div class="speech_bubble"><span>For readers its a gold mine of great books they can have a say in bringing to market.</span></div> <div class="speech_bubble"><span>For readers its a gold mine of great books they can have a say in bringing to market.</span></div>

View File

@ -17,7 +17,7 @@
</div> </div>
</div> </div>
<div {%if request.user.is_authenticated or hide_learn_more %}id="user-block-hide" {% endif %}class="user-block-hide learnmore_block "> <div {%if request.user.is_authenticated or hide_learn_more %}id="user-block-hide" {% endif %}class="user-block-hide learnmore_block ">
<h1 style="text-align: center; padding-top: 1em; width: 70%">3 ways <i>we</i> can make ebooks <i>free</i></h1> <h1 style="text-align: center;padding-top: 1em;width: 70%; line-height: 1.2em;">Find over 10,000 <i>free</i> ebooks here.<br />Help us make more ebooks <i>free</i>!</h1>
<div class="quicktour panelview" > <div class="quicktour panelview" >
<div class="panelview panelfront side1" > <div class="panelview panelfront side1" >

View File

@ -1,9 +1,11 @@
{% extends 'basedocumentation.html' %} {% extends 'basedocumentation.html' %}
{% load sass_tags %}
{% block title %} &#x2665; Libraries{% endblock %} {% block title %} &#x2665; Libraries{% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/campaign2.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/libraries.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/libraries.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
@ -60,7 +62,7 @@ The library license gives download access to one library member at a time for 14
<dt>Support <a href="{% url 'campaign_list' 'ending' %}">our active campaigns</a>.</dt> <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> <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> <dt>Give feedback and ask questions.</dt>
<dd>Want to know more? Need help? Have ideas for how we could improve the site or make it more library-friendly? Contact us at: <a href="mailto:libraries@gluejar.com">libraries@gluejar.com</a>.</dd> <dd>Want to know more? Need help? Have ideas for how we could improve the site or make it more library-friendly? Contact us at: <a href="mailto:info@ebookfoundation.org">info@ebookfoundation.org</a>.</dd>
</dl> </dl>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,13 +1,15 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load endless %} {% load endless %}
{% load sass_tags %}
{% load truncatechars %} {% load truncatechars %}
{% block title %} &#8212; {{ library }}{% endblock %} {% block title %} &#8212; {{ library }}{% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/supporter_layout.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/searchandbrowse2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/searchandbrowse2.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_list.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_list.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/book_panel2.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
<script type="text/javascript" src="/static/js/wishlist.js"></script> <script type="text/javascript" src="/static/js/wishlist.js"></script>

View File

@ -1,10 +1,11 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load sass_tags %}
{% load libraryauthtags %} {% load libraryauthtags %}
{% block title %} Libraries {% endblock %} {% block title %} Libraries {% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/supporter_layout.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/liblist.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/liblist.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_head %} {% block extra_head %}
<script type="text/javascript" src="{{ jquery_ui_home }}"></script> <script type="text/javascript" src="{{ jquery_ui_home }}"></script>

View File

@ -1,9 +1,10 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load sass_tags %}
{% block title %} Users of {{ library }} {% endblock %} {% block title %} Users of {{ library }} {% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/supporter_layout.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/liblist.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/liblist.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_head %} {% block extra_head %}
<script type="text/javascript" src="{{ jquery_ui_home }}"></script> <script type="text/javascript" src="{{ jquery_ui_home }}"></script>

View File

@ -1,9 +1,10 @@
{% extends 'basedocumentation.html' %} {% extends 'basedocumentation.html' %}
{% load sass_tags %}
{% block title %}Your Unglue.it Account{% endblock %} {% block title %}Your Unglue.it Account{% endblock %}
{% block extra_extra_head %} {% block extra_extra_head %}
{{ block.super }} {{ block.super }}
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/pledge.scss' %}" />
{% include "stripe_stuff.html" %} {% include "stripe_stuff.html" %}
<script> <script>

View File

@ -1,6 +1,7 @@
{% extends 'basedocumentation.html' %} {% extends 'basedocumentation.html' %}
{% load humanize %} {% load humanize %}
{% load sass_tags %}
{% block title %}Campaign Management{% endblock %} {% block title %}Campaign Management{% endblock %}
@ -11,8 +12,8 @@ textarea {
width: 90%; width: 90%;
} }
</style> </style>
<link type="text/css" rel="stylesheet" href="/static/css/manage_campaign.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/manage_campaign.scss' %}" />
<link type="text/css" rel="stylesheet" href="/static/css/campaign2.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/campaign2.scss' %}" />
<script type="text/javascript" src="/static/js/tabs.js"></script> <script type="text/javascript" src="/static/js/tabs.js"></script>
@ -83,21 +84,30 @@ Please fix the following before launching your campaign:
<div class="pledged-info"> <div class="pledged-info">
<div class="pledged-group"> <div class="pledged-group">
{% ifequal work.last_campaign.type 1 %} {% if work.last_campaign.type == 1 %}
{{ work.last_campaign.supporters_count }} Ungluers have pledged ${{ work.last_campaign.current_total|intcomma }} {{ work.last_campaign.supporters_count }} Ungluers have pledged ${{ work.last_campaign.current_total|intcomma }}
{% else %} {% else %}
Total revenue: ${{ work.last_campaign.current_total|intcomma }} from {{ work.last_campaign.supporters_count }} Ungluers and {{ work.last_campaign.anon_count }} others Total revenue: ${{ work.last_campaign.current_total|intcomma }} from {{ work.last_campaign.supporters_count }} Ungluers and {{ work.last_campaign.anon_count }} others
{% endifequal %} {% endif %}
</div> </div>
</div> </div>
{% if campaign.charitable %}
<div class="pledged-info">
This campaign is eligible for <a href="{% url 'faq_sublocation' 'rightsholders' 'campaigns' %}#donation_support">charitable donation support</a>.
</div>
{% elif campaign.type == 1 %}
<div class="pledged-group">
If you believe your campaign meets <a href="{% url 'faq_sublocation' 'rightsholders' 'campaigns' %}#donation_support">the criteria for charitable donation support</a>, use <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">the feedback form</a> to request a review by Free Ebook Foundation staff.
</div>
{% endif %}
</div> </div>
<div class="preview_campaign"> <div class="preview_campaign">
{% ifequal campaign_status 'INITIALIZED' %} {% if campaign_status == 'INITIALIZED' %}
<a href="{% url 'work_preview' campaign.work_id %}" class="manage" target="_blank">Preview Your Campaign</a> <a href="{% url 'work_preview' campaign.work_id %}" class="manage" target="_blank">Preview Your Campaign</a>
{% else %} {% else %}
<a href="{% url 'work' campaign.work_id %}" class="manage" target="_blank">See Your Campaign</a> <a href="{% url 'work' campaign.work_id %}" class="manage" target="_blank">See Your Campaign</a>
{% endifequal %} {% endif %}
</div> </div>
</div> </div>
@ -105,7 +115,7 @@ Please fix the following before launching your campaign:
<div class="content-block-heading" id="tabs"> <div class="content-block-heading" id="tabs">
<ul class="tabs"> <ul class="tabs">
<li class="tabs1 {% if activetab == '1' %}active{% endif %}"><a href="#">Description</a></li> <li class="tabs1 {% if activetab == '1' %}active{% endif %}"><a href="#">Description</a></li>
<li class="tabs2 {% if activetab == '2' %}active{% endif %}"><a href="#">{% ifequal campaign.type 1 %}Premiums{% endifequal %}{% ifequal campaign.type 2 %}Pricing{% endifequal %}{% ifequal campaign.type 3 %}Amounts{% endifequal %}</a></li> <li class="tabs2 {% if activetab == '2' %}active{% endif %}"><a href="#">{% if campaign.type == 1 %}Premiums{% elif campaign.type == 2 %}Pricing{% elif campaign.type == 3 %}Amounts{% endif %}</a></li>
<li class="tabs3 {% if activetab == '3' %}active{% endif %}"><a href="#">{% if campaign_status == 'ACTIVE' or campaign_status == 'SUCCESSFUL' %}Progress{% else %}Launch{% endif %}</a></li> <li class="tabs3 {% if activetab == '3' %}active{% endif %}"><a href="#">{% if campaign_status == 'ACTIVE' or campaign_status == 'SUCCESSFUL' %}Progress{% else %}Launch{% endif %}</a></li>
</ul> </ul>
</div> </div>
@ -117,35 +127,30 @@ Please fix the following before launching your campaign:
{% csrf_token %} {% csrf_token %}
{{ form.media }} {{ form.media }}
<h3>Edit the editions (if needed)</h3> <h3>Edit the editions (if needed)</h3>
{% if campaign.rh.can_sell %}
{% if campaign.work.ebookfiles.0 %} {% if campaign.work.ebookfiles.0 %}
<p>You have uploaded ebook files for this work. </p> <p>You have uploaded ebook files for this work. </p>
{% else %} {% else %}
{% ifequal work.last_campaign.type 2 %} {% if work.last_campaign.type == 2 %}
<p> To sell ebooks as part of a buy to unglue campaign, you will need to upload an EPUB file for the ebook you want to sell. </p> <p> To sell ebooks as part of a buy to unglue campaign, you will need to upload an EPUB file for the ebook you want to sell. </p>
{% endifequal %} {% elif work.last_campaign.type == 3 %}
{% ifequal work.last_campaign.type 3 %}
<p> To distribute ebooks as part of a thanks for ungluing campaign, you will need to upload the ebook files to unglue.it. </p> <p> To distribute ebooks as part of a thanks for ungluing campaign, you will need to upload the ebook files to unglue.it. </p>
{% endifequal %} {% endif %}
{% endif %} {% endif %}
{% endif %}
<p> Please choose the edition that most closely matches the edition to be unglued. This is the edition whose cover image will display on your book's page. Your unglued edition should be identical to this edition if possible; you should note any differences under Rights Details below.</p> <p> Please choose the edition that most closely matches the edition to be unglued. This is the edition whose cover image will display on your book's page. Your unglued edition should be identical to this edition if possible; you should note any differences under Rights Details below.</p>
{{ form.edition.errors }} {{ form.edition.errors }}
{% for edition in campaign.work.editions.all %} {% for edition in campaign.work.editions.all %}
<div class="edition_form" id="edition_{{edition.id}}"> <div class="edition_form" id="edition_{{edition.id}}">
<p> Edition {{ edition.id }}: <input type="radio" {% ifequal edition.id form.edition.value %}checked="checked" {% endifequal %}id="id_edition_{{forloop.counter}}" value="{{edition.id}}" name="edition" /><label for="id_edition_{{forloop.counter}}"> Prefer this edition </label> <p> Edition {{ edition.id }}: <input type="radio" {% if edition.id == form.edition.value %}checked="checked" {% endif %}id="id_edition_{{forloop.counter}}" value="{{edition.id}}" name="edition" /><label for="id_edition_{{forloop.counter}}"> Prefer this edition </label>
<ul style="text-indent:1em"> <ul style="text-indent:1em">
<li style="text-indent:2.5em"><a href="{% url 'new_edition' edition.work_id edition.id %}"> Edit </a> this edition</li> <li style="text-indent:2.5em"><a href="{% url 'new_edition' edition.work_id edition.id %}"> Edit </a> this edition</li>
{% ifnotequal campaign.type 1 %} {% if campaign.type != 1 %}
{% if campaign.rh.can_sell %}
{% if edition.ebook_files.all.0 %} {% if edition.ebook_files.all.0 %}
<li style="text-indent:2.5em">You have uploaded ebook files for this edition. You can <a href="{% url 'edition_uploads' edition.id %}"> manage its ebooks or upload another</a></li> <li style="text-indent:2.5em">You have uploaded ebook files for this edition. You can <a href="{% url 'edition_uploads' edition.id %}"> manage its ebooks or upload another</a></li>
{% else %} {% else %}
<li style="text-indent:2.5em">You can <a href="{% url 'edition_uploads' edition.id %}"> Manage ebooks</a> for this edition.</li> <li style="text-indent:2.5em">You can <a href="{% url 'edition_uploads' edition.id %}"> Manage ebooks</a> for this edition.</li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endifnotequal %}
</ul> </ul>
</p> </p>
{% with managing='True' %}{% include "edition_display.html" %}{% endwith %} {% with managing='True' %}{% include "edition_display.html" %}{% endwith %}
@ -159,7 +164,7 @@ Please fix the following before launching your campaign:
{% endif %} {% endif %}
{% if campaign.work.epubfiles.0 %} {% if campaign.work.epubfiles.0 %}
{% for ebf in campaign.work.epubfiles %} {% for ebf in campaign.work.epubfiles %}
<p>{% if ebf.active %}<span class="yikes">ACTIVE</span> {% elif ebf.ebook.active %} MIRROR {% endif %}EPUB file: <a href="{{ebf.file.url}}">{{ebf.file}}</a> <br />created {{ebf.created}} for edition <a href="#edition_{{ebf.edition_id}}">{{ebf.edition_id}}</a> {% if ebf.asking %}(This file has had the campaign 'ask' added.){% endif %}<br />{% if ebf.active %}{% ifequal action 'mademobi' %}<span class="yikes">A MOBI file is being generated. </span> (Takes a minute or two.) {% else %}You can <a href="{% url 'makemobi' campaign.id ebf.id %}">generate a MOBI file.</a> {% endifequal %}{% endif %}</p> <p>{% if ebf.active %}<span class="yikes">ACTIVE</span> {% elif ebf.ebook.active %} MIRROR {% endif %}EPUB file: <a href="{{ebf.file.url}}">{{ebf.file}}</a> <br />created {{ebf.created}} for edition <a href="#edition_{{ebf.edition_id}}">{{ebf.edition_id}}</a> {% if ebf.asking %}(This file has had the campaign 'ask' added.){% endif %}<br />{% if ebf.active %}{% if action == 'mademobi' %}<span class="yikes">A MOBI file is being generated. </span> (Takes a minute or two.) {% else %}You can <a href="{% url 'makemobi' campaign.id ebf.id %}">generate a MOBI file.</a> {% endif %}{% endif %}</p>
{% endfor %} {% endfor %}
{% if campaign.work.test_acqs.0 %} {% if campaign.work.test_acqs.0 %}
<ul> <ul>
@ -178,8 +183,8 @@ Please fix the following before launching your campaign:
<p>{% if ebf.active %}<span class="yikes">ACTIVE</span> {% endif %}PDF file: <a href="{{ebf.file.url}}">{{ebf.file}}</a> <br />created {{ebf.created}} for edition <a href="#edition_{{ebf.edition_id}}">{{ebf.edition_id}}</a> {% if ebf.asking %}(This file has had the campaign 'ask' added.){% endif %}</p> <p>{% if ebf.active %}<span class="yikes">ACTIVE</span> {% endif %}PDF file: <a href="{{ebf.file.url}}">{{ebf.file}}</a> <br />created {{ebf.created}} for edition <a href="#edition_{{ebf.edition_id}}">{{ebf.edition_id}}</a> {% if ebf.asking %}(This file has had the campaign 'ask' added.){% endif %}</p>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% ifnotequal campaign_status 'ACTIVE' %} {% if campaign_status != 'ACTIVE' %}
{% ifnotequal campaign.type 3 %} {% if campaign.type != 3 %}
<h3>License being offered</h3> <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="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> <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 %} {% else %}
@ -188,11 +193,11 @@ Please fix the following before launching your campaign:
Once your campaign is active, you'll be able to switch to a less restrictive license, but not a more restrictive one. 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. 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> </p>
{% endifnotequal %} {% endif %}
<div> <div>
{{ form.license.errors }}{{ form.license }} {{ form.license.errors }}{{ form.license }}
</div> </div>
{% ifequal campaign.type 1 %} {% if campaign.type == 1 %}
<h3>Target Price</h3> <h3>Target Price</h3>
<p>This is the target price for your campaign. The <i>minimum</i> target is ${{form.minimum_target|intcomma}}.</p> <p>This is the target price for your campaign. The <i>minimum</i> target is ${{form.minimum_target|intcomma}}.</p>
@ -209,8 +214,7 @@ Please fix the following before launching your campaign:
<p>The ending date can't be more than six months away- that's a practical limit for credit card authorizations. The <i>latest</i> ending you can choose <i>right now</i> is {{ campaign.latest_ending }}</p> <p>The ending date can't be more than six months away- that's a practical limit for credit card authorizations. The <i>latest</i> ending you can choose <i>right now</i> is {{ campaign.latest_ending }}</p>
{{ form.deadline.errors }}{{ form.deadline }} {{ form.deadline.errors }}{{ form.deadline }}
{% endifequal %} {% elif campaign.type == 2 %}
{% ifequal campaign.type 2 %}
<h3>Revenue Goal</h3> <h3>Revenue Goal</h3>
<p>This is the initial revenue goal for your campaign. Once the campaign starts, the actual revenue goal will decrement every day. When your actual revenue meets your actual revenue goal, your book gets released immediately, for free, under the Creative Commons License that you've specified. </p> <p>This is the initial revenue goal for your campaign. Once the campaign starts, the actual revenue goal will decrement every day. When your actual revenue meets your actual revenue goal, your book gets released immediately, for free, under the Creative Commons License that you've specified. </p>
@ -230,43 +234,40 @@ Please fix the following before launching your campaign:
<p>Before launching a campaign, you'll need to select Your initial Ungluing Date. Together with your campaign revenue goal, this will define when your book becomes "unglued". Check out the <a href="{% url 'faq_sublocation' 'campaigns' 'b2u' %}#calculator">the ungluing date calculator</a> to see how this works. Your starting Ungluing Date must be before {{ form.max_cc_date }}</p> <p>Before launching a campaign, you'll need to select Your initial Ungluing Date. Together with your campaign revenue goal, this will define when your book becomes "unglued". Check out the <a href="{% url 'faq_sublocation' 'campaigns' 'b2u' %}#calculator">the ungluing date calculator</a> to see how this works. Your starting Ungluing Date must be before {{ form.max_cc_date }}</p>
{{ form.cc_date_initial.errors }}{{ form.cc_date_initial }} {{ form.cc_date_initial.errors }}{{ form.cc_date_initial }}
<!--{{ form.deadline.errors }}--> <!--{{ form.deadline.errors }}-->
{% endifequal %} {% elif campaign.type == 3 %}
{% ifequal campaign.type 3 %}
<!--{{ form.target.errors }}--><!--{{ form.cc_date_initial.errors }}--><!--{{ form.deadline.errors }}--> <!--{{ form.target.errors }}--><!--{{ form.cc_date_initial.errors }}--><!--{{ form.deadline.errors }}-->
{% endifequal %} {% endif %}
{% else %} {% else %}
<h3>License being offered</h3> <h3>License being offered</h3>
{% ifnotequal campaign.type 3 %} {% if campaign.type != 3 %}
<p>If your campaign succeeds, you will be offering your ebook under a <b><a href="{{campaign.license_url }}">{{ campaign.license }}</a></b> license.</p> <p>If your campaign succeeds, you will be offering your ebook under a <b><a href="{{campaign.license_url }}">{{ campaign.license }}</a></b> license.</p>
{% else %} {% else %}
<p>You are offering your ebook under a <b><a href="{{campaign.license_url }}">{{ campaign.license }}</a></b> license.</p> <p>You are offering your ebook under a <b><a href="{{campaign.license_url }}">{{ campaign.license }}</a></b> license.</p>
{% endifnotequal %} {% endif %}
<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> <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> <div>
{{ form.license.errors }}<span>{{ form.license }}</span> {{ form.license.errors }}<span>{{ form.license }}</span>
</div> </div>
{% ifequal campaign.type 1 %} {% if campaign.type == 1 %}
<h3>Target Revenue</h3> <h3>Target Revenue</h3>
<p>The current target revenue for your campaign is <b>${{ campaign.target|intcomma }}</b>. Since your campaign is active, you may lower, but not raise, this target. You can get a feel for the interplay between revenue target and ungluing date with the <a href="{% url 'faq_sublocation' 'campaigns' 'b2u' %}#calculator">the ungluing date calculator</a></p> <p>The current target revenue for your campaign is <b>${{ campaign.target|intcomma }}</b>. Since your campaign is active, you may lower, but not raise, this target. You can get a feel for the interplay between revenue target and ungluing date with the <a href="{% url 'faq_sublocation' 'campaigns' 'b2u' %}#calculator">the ungluing date calculator</a></p>
<div class="std_form"> <div class="std_form">
${{ form.target.errors }}{{ form.target }} ${{ form.target.errors }}{{ form.target }}
</div> </div>
{% endifequal %} {% elif campaign.type == 2 %}
{% ifequal campaign.type 2 %}
<h3>Initial Revenue Goal</h3> <h3>Initial Revenue Goal</h3>
<p>The initial revenue goal for your campaign was <b>${{ campaign.target|intcomma }}</b>; the current amount remaining is <b>${{ campaign.left|intcomma }}</b>. Since your campaign is active, you may lower, but not raise, this goal. Before you change this, try different parameters with <a href="{% url 'faq_sublocation' 'campaigns' 'b2u' %}#calculator">the ungluing date calculator</a>. </p> <p>The initial revenue goal for your campaign was <b>${{ campaign.target|intcomma }}</b>; the current amount remaining is <b>${{ campaign.left|intcomma }}</b>. Since your campaign is active, you may lower, but not raise, this goal. Before you change this, try different parameters with <a href="{% url 'faq_sublocation' 'campaigns' 'b2u' %}#calculator">the ungluing date calculator</a>. </p>
<div class="std_form"> <div class="std_form">
${{ form.target.errors }}{{ form.target }} ${{ form.target.errors }}{{ form.target }}
</div> </div>
{% endifequal %} {% endif %}
{% ifequal campaign.type 1 %} {% if campaign.type == 1 %}
<h3>Ending date</h3> <h3>Ending date</h3>
<p>The ending date of your campaign is <b>{{ campaign.deadline }}</b>. Your campaign will conclude on this date or when you meet your target price, whichever is earlier. You may not change the ending date of an active campaign.</p> <p>The ending date of your campaign is <b>{{ campaign.deadline }}</b>. Your campaign will conclude on this date or when you meet your target price, whichever is earlier. You may not change the ending date of an active campaign.</p>
<div class="std_form"> <div class="std_form">
{{ form.deadline.errors }}<span style="display: none">{{ form.deadline }}</span> {{ form.deadline.errors }}<span style="display: none">{{ form.deadline }}</span>
</div> </div>
{% endifequal %} {% elif campaign.type == 2 %}
{% ifequal campaign.type 2 %}
<h3>Ungluing Date</h3> <h3>Ungluing Date</h3>
<p> This campaign was launched with a Ungluing Date of {{ campaign.cc_date_initial }}.</p> <p> This campaign was launched with a Ungluing Date of {{ campaign.cc_date_initial }}.</p>
<p> Based on a total revenue of {{ campaign.current_total }} the Ungluing Date has been advanced to {{ campaign.cc_date }}.</p> <p> Based on a total revenue of {{ campaign.current_total }} the Ungluing Date has been advanced to {{ campaign.cc_date }}.</p>
@ -276,20 +277,19 @@ Please fix the following before launching your campaign:
<!--{{ form.deadline.errors }}--> <!--{{ form.deadline.errors }}-->
{% endifequal %} {% elif campaign.type == 3 %}
{% ifequal campaign.type 3 %}
<!--{{ form.deadline.errors }}--><!--{{ form.cc_date_initial.errors }}--> <!--{{ form.deadline.errors }}--><!--{{ form.cc_date_initial.errors }}-->
{% endifequal %} {% endif %}
{% endifnotequal %} {% endif %}
{% ifequal campaign.type 2 %} {% if campaign.type == 2 %}
<h3>Personalization</h3> <h3>Personalization</h3>
<p>If you do not want Unglue.it to use digital watermarking techniques to encode a transaction number into the ebook files, uncheck this box. Either way, ebook files will be personalized; the difference is whether personalization is easy or hard to remove.</p> <p>If you do not want Unglue.it to use digital watermarking techniques to encode a transaction number into the ebook files, uncheck this box. Either way, ebook files will be personalized; the difference is whether personalization is easy or hard to remove.</p>
<div class="std_form">Use watermarking: {{ form.do_watermark }}</div> <div class="std_form">Use watermarking: {{ form.do_watermark }}</div>
{% endifequal %}{{ form.do_watermark.errors }} {% endif %}{{ form.do_watermark.errors }}
<h3>Your Pitch</h3> <h3>Your Pitch</h3>
{% ifequal campaign.type 3 %} {% if campaign.type == 3 %}
<p>In a "thanks for ungluing" campaign, you want to first "motivate" your book- that is, you want to get the user to download the book. <p>In a "thanks for ungluing" campaign, you want to first "motivate" your book- that is, you want to get the user to download the book.
Once the user has clicked a "Download" button or a "Read it Now" button, you have a chance for an "ask" - that's where a user can decide to also make a thank-you contribution. Once the user has clicked a "Download" button or a "Read it Now" button, you have a chance for an "ask" - that's where a user can decide to also make a thank-you contribution.
The "ask" will be displayed to a user who has clicked a "Download" button. It's your chance to ask for their support. The "ask" will be displayed to a user who has clicked a "Download" button. It's your chance to ask for their support.
@ -299,40 +299,38 @@ Please fix the following before launching your campaign:
{% else %} {% else %}
<p>This will be displayed in the Campaign tab for your work. It's your main pitch to supporters/purchasers, and your chance to share your passion for this work, and to inspire readers..</p> <p>This will be displayed in the Campaign tab for your work. It's your main pitch to supporters/purchasers, and your chance to share your passion for this work, and to inspire readers..</p>
<p>A strong ask:</p> <p>A strong ask:</p>
{% endifequal %} {% endif %}
<ul class="terms"> <ul class="terms">
{% ifequal campaign.type 3 %} {% if campaign.type == 3 %}
<li>Thanks the user for their interest in the book.</li> <li>Thanks the user for their interest in the book.</li>
<li>Makes a connection to the user while explaining how a contribution makes your work possible.</li> <li>Makes a connection to the user while explaining how a contribution makes your work possible.</li>
<li>Speaks visually to the user (photos and/or videos). The FAQ has <a hef="{% url 'faq_sublocation' 'campaigns' 'addingmedia' %}">instruction on adding media</a>.</li> <li>Speaks visually to the user (photos and/or videos). The FAQ has <a href="{% url 'faq_sublocation' 'rightsholders' 'addingmedia' %}">instructions on adding media</a>.</li>
{% else %} {% else %}
<li>Introduces the work. What's this book like?</li> <li>Introduces the work. What's this book about?</li>
<li>Shows why it matters. How will someone or something -- the world, readers, some cause that matters -- be better off if this book becomes freely available? What kind of impact has the book had already? What will ungluers get out of supporting this campaign?</li> <li>Introduces you, the person or organization who will receive support. Who (or what) are you?</li>
<li>Has visual appeal (photos and/or videos). The FAQ has <a hef="{% url 'faq_sublocation' 'campaigns' 'addingmedia' %}">instruction on adding media</a>.</li> <li>Says why it matters. How will someone or something -- the world, readers, some cause that matters -- be better off if this book becomes freely available? What will ungluers get out of supporting this campaign?</li>
<li>Defines important but potentially unfamiliar things. What's an ungluing campaign, and why are you running one? Is there anything unusual about the book, its genre, et cetera? For those who aren't already familiar with you (or the author), who are you? {% ifequal campaign.type 1 %}Are you offering any particularly great premiums you want to call people's attention to?{% endifequal %}</li> {% if campaign.type == 1 %}<li>Motivates support. Are you offering any premiums you want to call people's attention to?</li>{% endif %}
<li>Gives examples of the author's work. This could be links to your site or places people can find more information about you or the book. You can also add quotes, embed a free sample chapter or story, display images, et cetera. These work samples might be from the campaign book itself, or from your (or the author's) other creative works.</li> <li>Gives a taste of the work. This could be links to more information about you or the book. You can also add quotes, embed a free sample chapter or story, display images, et cetera. These work samples might be from the campaign book itself, or from your (or the author's) other creative works.</li>
{% endifequal %} <li>Has visual appeal (photos and/or videos). The FAQ has <a href="{% url 'faq_sublocation' 'rightsholders' 'addingmedia' %}">instruction on adding media</a>.</li>
<li>Has personality. The writing should be thoroughly proofread but it should have a point of view. This is the place to be conversational, opinionated, funny, quirky -- whatever reflects your style. Be you.</li> <li>Explains how the funds to be raised will be used.</li>
{% endif %}
{% ifequal campaign.type 1 %}
<li>Optionally, provides extra incentives (like new or improved premiums) that will kick in if the campaign is unusually successful. Will you do something special when you reach 10% or 50% or 75% of your goal? Or if you reach that milestone before a particular deadline (e.g. in the first week of your campaign)?</li>
{% endifequal %}
</ul> </ul>
<br /> <br />
{% ifequal campaign.type 3 %} {% if campaign.type == 3 %}
<div style="padding-top:2em;padding-bottom:0.5em">First, the <b>Motivation</b>. Note that users who immediately click on a "Read it Now" button won't see this.:</div> <div style="padding-top:2em;padding-bottom:0.5em">First, the <b>Motivation</b>. Note that users who immediately click on a "Read it Now" button won't see this.:</div>
{{ form.work_description.errors }}{{ form.work_description }} {{ form.work_description.errors }}{{ form.work_description }}
<div style="padding-top:2em;padding-bottom:0.5em">Next, the <b>Ask</b>. A "thank you" form will float right in this area, so don't use wide graphic elements.</div> <div style="padding-top:2em;padding-bottom:0.5em">Next, the <b>Ask</b>. A "thank you" form will float right in this area, so don't use wide graphic elements.</div>
{% endifequal %} {% endif %}
{{ form.description.errors }}{{ form.description }} {{ form.description.errors }}{{ form.description }}
{% ifequal campaign.type 3 %} {% if campaign.type == 3 %}
<h3>Enable "Thanks" in your ebook files</h3> <h3>Enable "Thanks" in your ebook files</h3>
<p> Unglue.it can add your "Ask" along with a link to your book's "thank the creators" page into your ebook files. That way, people who download the book without making a contribution will be reminded that they can do it later.</p> <p> Unglue.it can add your "Ask" along with a link to your book's "thank the creators" page into your ebook files. That way, people who download the book without making a contribution will be reminded that they can do it later.</p>
<div class="std_form">Add your ask to files: {{ form.use_add_ask.errors }}{{ form.use_add_ask }}</div> <div class="std_form">Add your ask to files: {{ form.use_add_ask.errors }}{{ form.use_add_ask }}</div>
{% endifequal %} {% endif %}
<h3>Edition and Rights Details</h3> <h3>Edition and Rights Details</h3>
<p>This will be displayed on the More... tab for your work. It's the fine print for your campaign. {% ifequal campaign.type 1 %}Make sure to disclose any ways the unglued edition will differ from the existing edition; for example: <p>This will be displayed on the More... tab for your work. It's the fine print for your campaign. {% if campaign.type == 1 %}Make sure to disclose any ways the unglued edition will differ from the existing edition; for example:
<ul class="bullets"> <ul class="bullets">
<li>Any material that may have to be excluded due to permissions issues: illustrations, forewords, etc.</li> <li>Any material that may have to be excluded due to permissions issues: illustrations, forewords, etc.</li>
<li>Any additional materials that will be included, if not already covered in your pitch -- but we encourage you to cover them there to show supporters the value of ungluing!</li> <li>Any additional materials that will be included, if not already covered in your pitch -- but we encourage you to cover them there to show supporters the value of ungluing!</li>
@ -342,7 +340,7 @@ Please fix the following before launching your campaign:
<ul class="bullets"> <ul class="bullets">
<li>If the text is to be CC BY, but illustrations are only licensed as part of the ebook.</li> <li>If the text is to be CC BY, but illustrations are only licensed as part of the ebook.</li>
</ul> </ul>
{% endifequal %} {% endif %}
<p>In short, is there a fact about this campaign that you think would matter to your agent or another publishing wonk, but that no one else is likely to care about? Put it here. If your campaign doesn't have any fine print, you can leave this blank.</p> <p>In short, is there a fact about this campaign that you think would matter to your agent or another publishing wonk, but that no one else is likely to care about? Put it here. If your campaign doesn't have any fine print, you can leave this blank.</p>
{{ form.details.errors }}{{ form.details }} {{ form.details.errors }}{{ form.details }}
@ -359,12 +357,12 @@ Please fix the following before launching your campaign:
<p>If you are set up as an unglue.it publisher (send us a url, logo, description and list of ways your name might appear) you can link your campaign by selecting the publisher here: <p>If you are set up as an unglue.it publisher (send us a url, logo, description and list of ways your name might appear) you can link your campaign by selecting the publisher here:
<p class="std_form">{{ form.publisher.errors }}{{ form.publisher }}</p> <p class="std_form">{{ form.publisher.errors }}{{ form.publisher }}</p>
{% endif %} {% endif %}
{% ifequal campaign_status 'ACTIVE' %} {% if campaign_status == 'ACTIVE' %}
<div class="yikes">When you click this button, your changes will be visible to supporters immediately. Make sure to proofread!</div><br /> <div class="yikes">When you click this button, your changes will be visible to supporters immediately. Make sure to proofread!</div><br />
<input type="submit" name="save" value="Modify Campaign" /> <input type="submit" name="save" value="Modify Campaign" />
{% else %} {% else %}
<br /><br /><input type="submit" name="save" value="Save Campaign" /> <br /><br /><input type="submit" name="save" value="Save Campaign" />
{% endifequal %} {% endif %}
{% if not is_preview or request.user.is_staff %} {% if not is_preview or request.user.is_staff %}
{% if campaign_status == 'INITIALIZED' %} {% if campaign_status == 'INITIALIZED' %}
@ -375,20 +373,20 @@ Please fix the following before launching your campaign:
</div> </div>
<div class="tabs-2"> <div class="tabs-2">
{% ifequal campaign.type 1 %} {% if campaign.type == 1 %}
<h3>Premiums</h3> <h3>Premiums</h3>
<div class="jsmod-content"> <div class="jsmod-content">
<form action="#" method="POST"> <form action="#" method="POST">
{% csrf_token %} {% csrf_token %}
<ul class="support menu"> <ul class="support menu">
{% for premium in premiums %} {% for premium in premiums %}
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}"> <li class="{% if forloop.first %}first{% elif forloop.last %}last{% endif %}">
<i> <i>
<span class="menu-item-price">${{ premium.amount|intcomma }}</span> <span class="menu-item-price">${{ premium.amount|intcomma }}</span>
<span class="menu-item-desc">{{ premium.description }}</span> <span class="menu-item-desc">{{ premium.description }}</span>
</i> </i>
{% ifnotequal premium.limit 0 %}<br />Limit: {{ premium.limit }}{% endifnotequal %} {% if premium.limit != 0 %}<br />Limit: {{ premium.limit }}{% endif %}
{% ifequal premium.type 'CU' %}<br />Deactivate? <input type="checkbox" name="premium_id" value="{{ premium.id }}" />{% endifequal %} {% if premium.type == 'CU' %}<br />Deactivate? <input type="checkbox" name="premium_id" value="{{ premium.id }}" />{% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -417,13 +415,14 @@ Please fix the following before launching your campaign:
<p>A few things to keep in mind:</p> <p>A few things to keep in mind:</p>
<ul class="bullets"> <ul class="bullets">
<li>For tax status reasons, premiums are not currently available to supporters who use donations instead of pledges.</li>
<li>Are your premiums cumulative? That is, if you have a $10 and a $25 premium, does the $25 pledger get everything that the $10 pledger gets also? Either cumulative or not-cumulative is fine, but make sure you've communicated clearly</li> <li>Are your premiums cumulative? That is, if you have a $10 and a $25 premium, does the $25 pledger get everything that the $10 pledger gets also? Either cumulative or not-cumulative is fine, but make sure you've communicated clearly</li>
<li>Adding new premiums during your campaign is a great way to build momentum. If you do, make sure to leave a comment in the Comments tab of your campaign page to tell supporters (it will be automatically emailed to them). Some of them may want to change (hopefully increase) their pledge to take advantage of it.</li> <li>Adding new premiums during your campaign is a great way to build momentum. If you do, make sure to leave a comment in the Comments tab of your campaign page to tell supporters (it will be automatically emailed to them). Some of them may want to change (hopefully increase) their pledge to take advantage of it.</li>
<li>Also make sure to think about how your new premiums interact with old ones. If you add a new premium at $10, will people who have already pledged $25 be automatically eligible for it or not? Again, you can choose whatever you want; just be sure to communicate clearly.</li> <li>Also make sure to think about how your new premiums interact with old ones. If you add a new premium at $10, will people who have already pledged $25 be automatically eligible for it or not? Again, you can choose whatever you want; just be sure to communicate clearly.</li>
</ul> </ul>
<h4>Acknowledgements</h4> <h4>Acknowledgements</h4>
<p>Your ungluers will also automatically receive the following acknowledgements:</p> <p>Your ungluers (including thos who use donations, will also automatically receive the following acknowledgements:</p>
<ul class="terms"> <ul class="terms">
<li><em>Any amount</em> &#8212; The unglued ebook</li> <li><em>Any amount</em> &#8212; The unglued ebook</li>
<li><em>$25 and above</em> &#8212; Their name in the acknowledgements section under "supporters"</li> <li><em>$25 and above</em> &#8212; Their name in the acknowledgements section under "supporters"</li>
@ -431,8 +430,7 @@ Please fix the following before launching your campaign:
<li><em>$100 and above</em> &#8212; Their name, profile link, &amp; a dedication under "bibliophiles"</li> <li><em>$100 and above</em> &#8212; Their name, profile link, &amp; a dedication under "bibliophiles"</li>
</ul> </ul>
<p>Your premium values may be any amounts -- you do not need to offer premiums at $25/$50/$100. For example, if you offer a $30 premium, anyone pledging to it will be eligible for the $25 reward. This will be communicated to them during the pledging process; you do not need to explain it in your pitch.</p> <p>Your premium values may be any amounts -- you do not need to offer premiums at $25/$50/$100. For example, if you offer a $30 premium, anyone pledging to it will be eligible for the $25 reward. This will be communicated to them during the pledging process; you do not need to explain it in your pitch.</p>
{% endifequal %} {% elif campaign.type == 2 %}
{% ifequal campaign.type 2 %}
<h3> Offers to sell </h3> <h3> Offers to sell </h3>
{% if not campaign.work.ebookfiles.0 %} {% if not campaign.work.ebookfiles.0 %}
<p> <b>An EPUB file for this work <a class="tabs1">needs to be loaded</a>!</b></p> <p> <b>An EPUB file for this work <a class="tabs1">needs to be loaded</a>!</b></p>
@ -455,8 +453,7 @@ Please fix the following before launching your campaign:
<p /> <p />
{% endfor %} {% endfor %}
</div> </div>
{% endifequal %} {% elif campaign.type == 3 %}
{% ifequal campaign.type 3 %}
<h3> Suggested Contributions </h3> <h3> Suggested Contributions </h3>
{% if not campaign.work.ebooks.0 %} {% if not campaign.work.ebooks.0 %}
<p> <b>ebook files for this work <a class="tabs1">need to be loaded</a>!</b></p> <p> <b>ebook files for this work <a class="tabs1">need to be loaded</a>!</b></p>
@ -476,10 +473,10 @@ Please fix the following before launching your campaign:
{% endfor %} {% endfor %}
<p>When a contribution>$1 is made by a library, the library's verified users on unglue.it are not asked to make another contribution.</p> <p>When a contribution>$1 is made by a library, the library's verified users on unglue.it are not asked to make another contribution.</p>
</div> </div>
{% endifequal %} {% endif %}
</div> </div>
{% ifequal campaign_status 'INITIALIZED' %} {% if campaign_status == 'INITIALIZED' %}
<div class="tabs-3"> <div class="tabs-3">
{% if campaign.launchable %} {% if campaign.launchable %}
<p>Before you hit launch:</p> <p>Before you hit launch:</p>
@ -494,47 +491,45 @@ Please fix the following before launching your campaign:
<div id="launchme"><a href="#" class="manage">Launch Campaign</a></div> <div id="launchme"><a href="#" class="manage">Launch Campaign</a></div>
{% else %} {% else %}
{% ifequal campaign.type 1 %} {% if campaign.type == 1 %}
<p>Please make sure you've selected your campaign's edition and entered its description, funding goal, deadline, premiums, and previewed your campaign, before launching.</p> <p>Please make sure you've selected your campaign's edition and entered its description, funding goal, deadline, premiums, and previewed your campaign, before launching.</p>
{% endifequal %} {% elif campaign.type == 2 %}
{% ifequal campaign.type 2 %}
<p>Please make sure you've selected your campaign's edition and entered its description, funding goal, initial ungluing date, prices, and previewed your campaign, before launching.</p> <p>Please make sure you've selected your campaign's edition and entered its description, funding goal, initial ungluing date, prices, and previewed your campaign, before launching.</p>
<p> Buy To Unglue campaigns can't be launched until ebook files <a class="tabs1">have been loaded</a> and <a class="tabs2">pricing has been set and made active</a></p> <p> Buy To Unglue campaigns can't be launched until ebook files <a class="tabs1">have been loaded</a> and <a class="tabs2">pricing has been set and made active</a></p>
{% endifequal %} {% elif campaign.type == 3 %}
{% ifequal campaign.type 3 %}
<p>Please make sure you've selected your campaign's edition and entered its description and previewed your campaign, before launching.</p> <p>Please make sure you've selected your campaign's edition and entered its description and previewed your campaign, before launching.</p>
<p> Thanks for Ungluing campaigns can't be launched until ebook files <a class="tabs1">have been loaded</a> and <a class="tabs2">a suggested contribution has been set </a></p> <p> Thanks for Ungluing campaigns can't be launched until ebook files <a class="tabs1">have been loaded</a> and <a class="tabs2">a suggested contribution has been set </a></p>
{% endifequal %} {% endif %}
{% endif %} {% endif %}
</div> </div>
{% endifequal %} {% endif %}
<div class="tabs-3"> <div class="tabs-3">
{% if campaign_status == 'ACTIVE' or campaign_status == 'SUCCESSFUL' %} {% if campaign_status == 'ACTIVE' or campaign_status == 'SUCCESSFUL' %}
{% if campaign_status == 'ACTIVE' %} {% if campaign_status == 'ACTIVE' %}
<h2 class="thank-you">Your campaign is now active! Hooray!</h2> <h2 class="thank-you">Your campaign is now active! Hooray!</h2>
<h3>What to do next</h3> <h3>What to do next</h3>
<ul class="bullets"> <ul class="bullets">
<li>Tell your friends, relatives, media contacts, professional organizations, social media networks -- everyone!</li> <li>Tell your friends, relatives, media contacts, professional organizations, social media networks -- everyone!</li>
{% ifequal campaign.type 1 %} {% if campaign.type == 1 %}
<li>Check in with your campaign frequently. Use comments, description updates, and maybe new premiums to spark additional interest, keep supporters engaged, and keep the momentum going.</li> <li>Check in with your campaign frequently. Use comments, description updates, and maybe new premiums to spark additional interest, keep supporters engaged, and keep the momentum going.</li>
{% else %} {% else %}
<li>Check in on your sales frequently. Remember, you control the per-copy pricing, so think about the promotional value of a temporary price reduction.</li> <li>Check in on your sales frequently. Remember, you control the per-copy pricing, so think about the promotional value of a temporary price reduction.</li>
{% endifequal %} {% endif %}
<li>Watch media and social networks for mentions of your campaign, and engage in those conversations.</li> <li>Watch media and social networks for mentions of your campaign, and engage in those conversations.</li>
<li>Need help doing any of this? Talk to us.</li> <li>Need help doing any of this? Talk to us.</li>
</ul> </ul>
{% endif %} {% endif %}
{% ifequal campaign.type 1 %} {% if campaign.type == 1 %}
<h3>Acknowledgements</h3> <h3>Acknowledgements</h3>
<p>When you're logged in, the "Ungluers" tab on the <a href="{% url 'work' work.id %}">campaign page</a> will tell you a bit about each ungluer- when they last pledged, for example, and you can send individual messages to each ungluer. Use this tool with care! You can see who your biggest supporters are by looking at the <a href="{% url 'work_acks' campaign.work_id %}">sample acknowledgement page</a>. <p>When you're logged in, the "Ungluers" tab on the <a href="{% url 'work' work.id %}">campaign page</a> will tell you a bit about each ungluer- when they last pledged, for example, and you can send individual messages to each ungluer. Use this tool with care! You can see who your biggest supporters are by looking at the <a href="{% url 'work_acks' campaign.work_id %}">sample acknowledgement page</a>.
After your campaign succeeds, you can used this page to generate epub code for the acknowledgements section of your unglued ebook. After your campaign succeeds, you can used this page to generate epub code for the acknowledgements section of your unglued ebook.
</p> </p>
{% else %} {% else %}
{% comment %}This might be a good place to put a sales report. {% endcomment %} {% comment %}This might be a good place to put a sales report. {% endcomment %}
{% endifequal %} {% endif %}
{% endif %} {% endif %}
</div> </div>

View File

@ -5,7 +5,7 @@ You are subscribed to the Unglue.it Newsletter. It comes roughly twice a month.
<input type="submit" name="ml_unsubscribe" value="Unsubscribe" /> <input type="submit" name="ml_unsubscribe" value="Unsubscribe" />
</form> </form>
{% else %} {% else %}
You are NOT subscribed to the Unglue.it Newsletter. It comes roughly twice a month. If you have just become an ungluer, your list invitation should be on its way. Put "gluenews@gluejar.com" in your contact list to make sure you get it.<br /> You are NOT subscribed to the Unglue.it Newsletter. It comes roughly twice a month. If you have just become an ungluer, your list invitation should be on its way. Put "unglueit@ebookfoundation.org" in your contact list to make sure you get it.<br />
<form id="ml_subscribe" action="{% url 'ml_subscribe' %}" method="POST"> <form id="ml_subscribe" action="{% url 'ml_subscribe' %}" method="POST">
{% csrf_token %} {% csrf_token %}
<input type="submit" name="ml_subscribe" value="Subscribe" /> <input type="submit" name="ml_subscribe" value="Subscribe" />

View File

@ -1,7 +1,7 @@
{% load humanize %} {% load humanize %}
As you requested, we've updated your account with the payment method you provided. As you requested, we've updated your account with the payment method you provided.
If you have any questions, we are happy to help. Simply email us at support@gluejar.com. If you have any questions, we are happy to help. Simply email us at unglueit@ebookfoundation.org.
{% if user.profile.account %} {% if user.profile.account %}
The current card we have on file: The current card we have on file:

View File

@ -3,7 +3,7 @@ We want to let you know that your {{ user.profile.account.card_type }} card endi
When you receive your new card, simply go to https://{{ site.domain }}{% url 'manage_account' %} to enter your card information. Thank you! When you receive your new card, simply go to https://{{ site.domain }}{% url 'manage_account' %} to enter your card information. Thank you!
If you have any questions, we are happy to help. Simply email us at support@gluejar.com. If you have any questions, we are happy to help. Simply email us at unglueit@ebookfoundation.org.
{% if user.profile.account %} {% if user.profile.account %}
The current card we have on file: The current card we have on file:

View File

@ -3,7 +3,7 @@ We want to give you advance notice that your {{ user.profile.account.card_type }
When you receive your new card, simply go to https://{{ site.domain }}{% url 'manage_account' %} to enter your card information. Thank you! When you receive your new card, simply go to https://{{ site.domain }}{% url 'manage_account' %} to enter your card information. Thank you!
If you have any questions, we are happy to help. Simply email us at support@gluejar.com. If you have any questions, we are happy to help. Simply email us at unglueit@ebookfoundation.org.
{% if user.profile.account %} {% if user.profile.account %}
The current card we have on file: The current card we have on file:

View File

@ -7,7 +7,7 @@
{% endblock %} {% endblock %}
{% block comments_graphical %} {% block comments_graphical %}
{% ifequal transaction.host 'credit' %} {% if transaction.host == 'credit' %}
Your Unglue.it transaction has completed and ${{transaction.max_amount|floatformat:2|intcomma}} has been deducted from your Unglue.it credit balance. Your Unglue.it transaction has completed and ${{transaction.max_amount|floatformat:2|intcomma}} has been deducted from your Unglue.it credit balance.
You have ${{transaction.user.credit.available|default:"0"}} of credit left. You have ${{transaction.user.credit.available|default:"0"}} of credit left.
{% else %} {% else %}
@ -19,7 +19,7 @@
{% else %} {% else %}
Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}. Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}.
{% endif %} {% endif %}
{% endifequal %} {% endif %}
{% endblock %} {% endblock %}
{% block comments_textual %} {% block comments_textual %}

View File

@ -1,12 +1,13 @@
{% extends 'notification/base.html' %} {% extends 'notification/base.html' %}
{% load i18n %} {% load i18n %}
{% load sass_tags %}
{% load truncatechars %} {% load truncatechars %}
{% block title %}{% trans "Notification Settings" %}{% endblock %} {% block title %}{% trans "Notification Settings" %}{% endblock %}
{% block extra_css %} {% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/notices.css" /> <link type="text/css" rel="stylesheet" href="{% sass_src 'scss/notices.scss' %}" />
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}

View File

@ -1,4 +1,19 @@
{% load humanize %}An Ungluing! {% load humanize %}{% if transaction.donation %}{% ifequal transaction.host 'credit' %}Your Unglue.it transaction has completed and ${{transaction.max_amount|default:"0"}} has been deducted from your Unglue.it credit balance. You have ${{transaction.user.credit.available|default:"0"}} of credit left. {% else %}{% if transaction.max_amount > transaction.amount %}Your transaction for ${{transaction.max_amount|default:"0"}} has completed. Your credit card has been charged ${{transaction.amount}} and the rest has been deducted from your unglue.it credit balance. You have ${{transaction.user.credit.available|default:"0"}} of credit left. {% else %}Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|default:"0" }}. {% endif %}{% endifequal %}
Your donation of ${{transaction.max_amount|default:"0"}} to the Free Ebook Foundation will support our effort to release {{ transaction.campaign.work.title }} to the world in an unglued ebook edition. We'll email you if the campaign succeeds, and when the ebook is available for download. If you'd like to visit the campaign page, click here:
https://{{ current_site.domain }}{% url 'work' transaction.campaign.work_id %}
In case the campaign for {{ transaction.campaign.work.title }} does not succeed, we'll use your donation in support of other ungluing campaigns which qualify for charitable support.
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 https://ebookfoundation.org/
Thank you again for your generous support.
{{ transaction.campaign.rightsholder }} and the Unglue.it team
{% else %}An Ungluing!
Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. Your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}. Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. Your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}.
@ -13,4 +28,4 @@ https://{{ current_site.domain }}{% url 'work' transaction.campaign.work_id %}
Thank you again for your support. Thank you again for your support.
{{ transaction.campaign.rightsholder }} and the Unglue.it team {{ transaction.campaign.rightsholder }} and the Unglue.it team
{% endif %}

View File

@ -7,10 +7,37 @@
{% endblock %} {% endblock %}
{% block comments_graphical %} {% block comments_graphical %}
Hooray! The campaign for <a href="{% url 'work' transaction.campaign.work_id %}">{{ transaction.campaign.work.title }}</a> has succeeded. Your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}. Thank you again for your help. {% if transaction.donation %}
{% if transaction.host == 'credit' %}
Your Unglue.it transaction has completed and ${{transaction.max_amount|floatformat:2|intcomma}} has been deducted from your Unglue.it credit balance.
You have ${{transaction.user.credit.available|default:"0"}} of credit left.
{% elif transaction.max_amount > transaction.amount %}
Your transaction for ${{transaction.max_amount|floatformat:2|intcomma}} has completed.
Your credit card has been charged ${{transaction.amount}} and the
rest has been deducted from your unglue.it credit balance.
You have ${{transaction.user.credit.available|intcomma}} of credit left.
{% else %}
Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}.
{% endif %}
{% else %} Hooray! The campaign for <a href="{% url 'work' transaction.campaign.work_id %}">{{ transaction.campaign.work.title }}</a> has succeeded. Your credit card has been charged ${{ transaction.amount|floatformat:2|intcomma }}. Thank you again for your help.
{% endif %}
{% endblock %} {% endblock %}
{% block comments_textual %} {% block comments_textual %}
{% if transaction.donation %}
<p>Your donation of ${{transaction.max_amount|default:"0"}} to the Free Ebook Foundation will support our effort to release {{ transaction.campaign.work.title }} to the world in an unglued ebook edition. We'll email you if the campaign succeeds, and when the ebook is available for download. If you'd like to visit the campaign page, <a href="{% url 'work' transaction.campaign.work_id %}">click here</a>. </p>
<p>In case the campaign for {{ transaction.campaign.work.title }} does not succeed, we'll use your donation in support of other ungluing campaigns which qualify for charitable support.</p>
<p>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="https://ebookfoundation.org/">https://ebookfoundation.org/</a></p>
<p>Thank you again for your generous support.</p>
<p>{{ transaction.campaign.rightsholder }} and the Unglue.it team</p>
{% else %}
<p>Congratulations!</p> <p>Congratulations!</p>
<p>Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. {{ transaction.host|capfirst }} has been charged to your credit card.</p> <p>Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be released to the world in an unglued ebook edition. {{ transaction.host|capfirst }} has been charged to your credit card.</p>
@ -28,4 +55,5 @@
</p> </p>
<p>{{ transaction.campaign.rightsholder }} and the Unglue.it team <p>{{ transaction.campaign.rightsholder }} and the Unglue.it team
</p> </p>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -1 +1 @@
Your pledge to the campaign to unglue {{transaction.campaign.work.title}} has been charged. Your {% if transaction.donation %}donation{% else %}pledge{% endif %} for the campaign to unglue {{transaction.campaign.work.title}} has been charged.

View File

@ -17,7 +17,7 @@ Or the best idea: talk about it with those you love. We'll need lots of help fr
If you want to change your pledge, just use the button at https://{{ current_site.domain }}{% url 'work' transaction.campaign.work_id %} If you want to change your pledge, just use the button at https://{{ current_site.domain }}{% url 'work' transaction.campaign.work_id %}
If you have any problems with your pledge, don't hesitate to contact us at support@gluejar.com If you have any problems with your pledge, don't hesitate to contact us at unglueit@ebookfoundation.org
Thanks for being part of Unglue.it. Thanks for being part of Unglue.it.

View File

@ -14,7 +14,7 @@ You can send the link yourself to make sure that it gets to the right place.
You can also "regift" the ebook to a different email address. To do so, FIRST log in to the {{ gift.giver }} account on Unglue.it, and then click on You can also "regift" the ebook to a different email address. To do so, FIRST log in to the {{ gift.giver }} account on Unglue.it, and then click on
https://{{ current_site.domain }}{% url 'receive_gift' gift.acq.nonce %} https://{{ current_site.domain }}{% url 'receive_gift' gift.acq.nonce %}
If you have any problems or questions, don't hesitate to contact Unglue.it support at support@gluejar.com If you have any problems or questions, don't hesitate to contact Unglue.it support at unglueit@ebookfoundation.org
the Unglue.it team the Unglue.it team

View File

@ -0,0 +1,14 @@
The Rights Holder Agreement, reproduced in plain text below, for {{ rights_holder.rights_holder_name }} has been accepted and is now an official Unglue.it rights holder.
Here's what to do next: Find on Unglue.it. On the More... tab of the book page, you'll now see an option to claim the book. Once you've claimed the book, you can edit its metadata.
If you can't find your books Unglue.it, that's okay. You can add your books to Unglue.it directly - use this link: https://unglue.it{% url 'rightsholders' %}#add_your_books
Need help with any of this? Email us at rights@ebookfoundation.org and we'll do our best to help.
The Unglue.it team
##################
{{ agreement }}
##################
{{ signature }}

View File

@ -0,0 +1,4 @@
{% extends "notification/notice_template.html" %}
{% block comments_textual %}
You are now an approved rights holder on Unglue.it. For your next step, <a href="{% url 'rightsholders' %}#add_your_books">find your works in our database and claim them or add them directly</a>.
{% endblock %}

View File

@ -0,0 +1 @@
You're now an accepted rights holder on Unglue.it.

View File

@ -3,7 +3,7 @@
You are now free to start a campaign to sell or unglue your work. If you're logged in, you will see the option to open a campaign at https://{{ current_site.domain }}/rightsholders . (You can also find this page by clicking on "Rights Holder Tools" at the bottom of any Unglue.it page.) You are now free to start a campaign to sell or unglue your work. If you're logged in, you will see the option to open a campaign at https://{{ current_site.domain }}/rightsholders . (You can also find this page by clicking on "Rights Holder Tools" at the bottom of any Unglue.it page.)
To run a campaign, you'll need to set up campaign parameters. You'll also need to write a pitch. For Pledge-to-Unglue and Buy-to-Unglue campaigns, this will appear in the Description tab on your book's page (https://{{ current_site.domain }}{% url 'work' claim.work_id %}). Think about who your book's audience is, and remind them why they'll love this book -- your pitch is not a catalog page! We encourage video, audio, and links to make your pitch come alive. For Thanks-for-Ungluing, your pitch will occur when the user clicks a Download button. You should emphasize how the ungluer's support enables you to keep doing what you do. Feel free to email us (rights@gluejar.com) if you need any help with this. To run a campaign, you'll need to set up campaign parameters. You'll also need to write a pitch. For Pledge-to-Unglue and Buy-to-Unglue campaigns, this will appear in the Description tab on your book's page (https://{{ current_site.domain }}{% url 'work' claim.work_id %}). Think about who your book's audience is, and remind them why they'll love this book -- your pitch is not a catalog page! We encourage video, audio, and links to make your pitch come alive. For Thanks-for-Ungluing, your pitch will occur when the user clicks a Download button. You should emphasize how the ungluer's support enables you to keep doing what you do. Feel free to email us (rights@ebookfoundation.org) if you need any help with this.
If you're running a Buy-to-Unglue or Thanks-for-Ungluing Campaign, now is the time to upload your digital files. For Buy-to-Unglue, you need to decide on revenue targets and pricing for individual and library licenses. If you're running a Buy-to-Unglue or Thanks-for-Ungluing Campaign, now is the time to upload your digital files. For Buy-to-Unglue, you need to decide on revenue targets and pricing for individual and library licenses.
@ -11,13 +11,12 @@ If you're running a Pledge Campaign, you need to decide on a funding target, and
Finally, think about how you're going to publicize your campaign: social media, newsletters, media contacts, professional organizations, et cetera. Have a plan for how to reach out to these potential supporters before you launch your campaign. Your supporters' sense of connection with you and your book is key to your campaign's success. Again, email us if you'd like help. Finally, think about how you're going to publicize your campaign: social media, newsletters, media contacts, professional organizations, et cetera. Have a plan for how to reach out to these potential supporters before you launch your campaign. Your supporters' sense of connection with you and your book is key to your campaign's success. Again, email us if you'd like help.
We're thrilled to be working with you.
{% endifequal %} {% endifequal %}
{% ifequal claim.status 'pending' %} {% ifequal claim.status 'pending' %}
{{ claim.rights_holder }}'s claim to {{ claim.work.title }} (https://{{ current_site.domain }}{% url 'work' claim.work_id %}) on Unglue.it has been entered. Our team will examine the claim and get back to you soon. {{ claim.rights_holder }}'s claim to {{ claim.work.title }} (https://{{ current_site.domain }}{% url 'work' claim.work_id %}) on Unglue.it has been entered.
{% endifequal %} {% endifequal %}
{% ifequal claim.status 'release' %} {% ifequal claim.status 'release' %}
{{ claim.rights_holder }}'s claim to {{ claim.work.title }} (https://{{ current_site.domain }}{% url 'work' claim.work_id %}) on Unglue.it has been released. email us (rights@gluejar.com) if you have any questions about this. {{ claim.rights_holder }}'s claim to {{ claim.work.title }} (https://{{ current_site.domain }}{% url 'work' claim.work_id %}) on Unglue.it has been released. email us (rights@ebookfoundation.org) if you have any questions about this.
{% endifequal %} {% endifequal %}
The Unglue.it team The Unglue.it team

View File

@ -17,7 +17,7 @@
<br /><br /> <br /><br />
You are now free to start a campaign to sell or unglue your work. If you're logged in, you can <a href="{% url 'rightsholders' %}">open a campaign</a>. (You can also find this page by clicking on "Rights Holder Tools" at the bottom of any Unglue.it page.) You are now free to start a campaign to sell or unglue your work. If you're logged in, you can <a href="{% url 'rightsholders' %}">open a campaign</a>. (You can also find this page by clicking on "Rights Holder Tools" at the bottom of any Unglue.it page.)
<br /><br /> <br /><br />
To run a campaign, you'll need to set up campaign parameters. You'll also need to write a pitch. For Pledge-to-Unglue and Buy-to-Unglue campaigns, this will appear in the Description tab on your book's <a href="{% url 'work' claim.work_id %}">work page</a>. Think about who your book's audience is, and remind them why they'll love this book -- your pitch is not a catalog page! We encourage video, audio, and links to make your pitch come alive. For Thanks-for-Ungluing, your pitch will occur when the user clicks a Download button. You should emphasize how the ungluer's support enables you to keep doing what you do. Feel free to email us (rights@gluejar.com) if you need any help with this. To run a campaign, you'll need to set up campaign parameters. You'll also need to write a pitch. For Pledge-to-Unglue and Buy-to-Unglue campaigns, this will appear in the Description tab on your book's <a href="{% url 'work' claim.work_id %}">work page</a>. Think about who your book's audience is, and remind them why they'll love this book -- your pitch is not a catalog page! We encourage video, audio, and links to make your pitch come alive. For Thanks-for-Ungluing, your pitch will occur when the user clicks a Download button. You should emphasize how the ungluer's support enables you to keep doing what you do. Feel free to email us (rights@ebookfoundation.org) if you need any help with this.
<br /><br /> <br /><br />
If you're running a Buy-to-Unglue or Thanks-for-Ungluing Campaign, now is the time to upload your digital files. For Buy-to-Unglue, you need to decide on revenue targets and pricing for individual and library licenses. If you're running a Buy-to-Unglue or Thanks-for-Ungluing Campaign, now is the time to upload your digital files. For Buy-to-Unglue, you need to decide on revenue targets and pricing for individual and library licenses.
<br /><br /> <br /><br />
@ -25,12 +25,11 @@ If you're running a Pledge Campaign, you need to decide on a funding target, and
<br /><br /> <br /><br />
Finally, think about how you're going to publicize your campaign: social media, newsletters, media contacts, professional organizations, et cetera. Have a plan for how to reach out to these potential supporters before you launch your campaign. Your supporters' sense of connection with you and your book is key to your campaign's success. Again, email us if you'd like help. Finally, think about how you're going to publicize your campaign: social media, newsletters, media contacts, professional organizations, et cetera. Have a plan for how to reach out to these potential supporters before you launch your campaign. Your supporters' sense of connection with you and your book is key to your campaign's success. Again, email us if you'd like help.
We're thrilled to be working with you.
{% endifequal %} {% endifequal %}
{% ifequal claim.status 'pending' %} {% ifequal claim.status 'pending' %}
The claim for <a href="{% url 'work' claim.work_id %}">{{ claim.work.title }}</a> will be examined, and we'll email you. <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">Contact us</a> if you need any help. The claim for <a href="{% url 'work' claim.work_id %}">{{ claim.work.title }}</a> will be examined, and we'll email you. <a href="{% url 'feedback' %}?page={{request.build_absolute_uri|urlencode:""}}">Contact us</a> if you need any help.
{% endifequal %} {% endifequal %}
{% ifequal claim.status 'release' %} {% ifequal claim.status 'release' %}
The claim for <a href="{% url 'work' claim.work_id %}">{{ claim.work.title }}</a> has been released. Contact us at rights@gluejar.com if you have questions. The claim for <a href="{% url 'work' claim.work_id %}">{{ claim.work.title }}</a> has been released. Contact us at rights@ebookfoundation.org if you have questions.
{% endifequal %} {% endifequal %}
{% endblock %} {% endblock %}

View File

@ -1,11 +1,20 @@
Your Platform Services Agreement has been accepted and you're now an official Unglue.it rights holder. Your Rights Holder Agreement for {{ rights_holder.rights_holder_name }} has been received and Unglue.it staff is reviewing it.
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. Here's the information we received - we'll use this to verify that you really exist and can be relied upon to fulfill your obligations. Once we've reviewed and approved the agreement, you'll receive by email a digitally signed copy for your reference.
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. Rights Holder: {{ rights_holder.rights_holder_name }}
Unglue.it Username for Rights Holder: {{ rights_holder.owner.username }}
Business Address:
{{ rights_holder.address }}
Mailing Address:
{{ rights_holder.mailing }}
Tel: {{ rights_holder.telephone }}
Email: {{ rights_holder.email }}
Signer Name: {{ rights_holder.signer }}
Signer Title: {{ rights_holder.signer_title }}
Signature: {{ rights_holder.signature }}
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.
Need help with any of this? We'd be delighted. Email us at rights@gluejar.com. We're thrilled to be working with you. Need help with any of this? Email us at rights@ebookfoundation.org.
The Unglue.it team The Unglue.it team

View File

@ -1,4 +1,4 @@
{% extends "notification/notice_template.html" %} {% extends "notification/notice_template.html" %}
{% block comments_textual %} {% block comments_textual %}
You are now an approved rights holder on Unglue.it. For your next step, find your works in our database and claim them (under the More... tab). See your email for more details. The Unglue.it rights holder agreement for {{ rights_holder.rights_holder_name }} has been received. Unglue.it staff will use the information you've supplied to verify that you really exist and can be relied upon to fulfill your obligations. Once we've reviewed and approved the agreement, you'll receive by email a digitally signed copy for your reference.
{% endblock %} {% endblock %}

View File

@ -1 +1 @@
You're now a confirmed rights holder on Unglue.it. Your Unglue.it rights holder agreement has been received.

View File

@ -1,4 +1,4 @@
{% if pledged %}You pledged toward it{% else %}You put it on your list{% endif %}, and now the campaign for {{ campaign.work.title}} (https://{{current_site.domain}}{% url 'work' campaign.work_id %}) has succeeded. {% if pledged %}You supported it{% else %}You put it on your list{% endif %}, and now the campaign for {{ campaign.work.title}} (https://{{current_site.domain}}{% url 'work' campaign.work_id %}) has succeeded.
{% ifequal campaign.type 1 %} {% ifequal campaign.type 1 %}
You will notified when an Unglued ebook edition is available, within 90 days. You will notified when an Unglued ebook edition is available, within 90 days.
{% if pledged %} {% if pledged %}

View File

@ -24,9 +24,9 @@ The Creative Commons licensing terms for {{ work.title }} allow you to redistrib
{% endif %} {% endif %}
{% if work.last_campaign_status == 'SUCCESSFUL' %} {% if work.last_campaign_status == 'SUCCESSFUL' %}
If you have any problems with this unglued ebook, please don't hesitate to let us know at support@gluejar.com. And if you love being able to give this ebook for free to all of your friends, please consider supporting other ebooks for ungluing. If you have any problems with this unglued ebook, please don't hesitate to let us know at unglueit@ebookfoundation.org. And if you love being able to give this ebook for free to all of your friends, please consider supporting other ebooks for ungluing.
{% else %} {% else %}
If you have any problems with these ebook files, please don't hesitate to let us know at support@gluejar.com. For example, if the file isn't what it says it is, or if the licensing or copyright status is misrepresented, we want to know as soon as possble. If you have any problems with these ebook files, please don't hesitate to let us know at unglueit@ebookfoundation.org. For example, if the file isn't what it says it is, or if the licensing or copyright status is misrepresented, we want to know as soon as possble.
{% endif %} {% endif %}
Thanks, Thanks,

View File

@ -2,9 +2,7 @@ Alas. The campaign to unglue {{ campaign.work.title }} (https://{{current_site.
If you pledged toward this work, your pledge will expire shortly and your credit card will not be charged, nor will you receive any premiums. If you pledged toward this work, your pledge will expire shortly and your credit card will not be charged, nor will you receive any premiums.
Still want to give {{ campaign.work.title }} to the world? Don't despair. Keep it on your wishlist and tell everyone why you love this book. The rights holder, {{ campaign.rightsholder }}, may run a campaign with different terms in the future. With your help, we may yet be able to unglue {{ campaign.work.title }}. If you donated in support of this work, your donation will be used to support other campaigns that qualify for charitable support.
There are also other books with active campaigns that need your help: https://unglue.it/campaigns/ending .
Thank you for your support. Thank you for your support.

View File

@ -10,11 +10,7 @@
{% endblock %} {% endblock %}
{% block comments_textual %} {% block comments_textual %}
If you pledged toward this work, your pledge will expire shortly and your credit card will not be charged, nor will you receive any premiums. If you pledged toward this work, your pledge will expire shortly and your credit card will not be charged, nor will you receive any premiums. If you donated in support of this work, your donation will be used to support other campaigns that qualify for charitable support.
Still want to give {{ campaign.work.title }} to the world? Don't despair. Keep it on your faves and tell everyone why you love this book. The rights holder, {{ campaign.rightsholder }}, may run a campaign with different terms in the future. With your help, we may yet be able to unglue {{ campaign.work.title }}.
There are also <a href="https://unglue.it/campaigns/ending">other books with active campaigns</a> that need your help.
Thank you for your support. Thank you for your support.
{% endblock %} {% endblock %}

View File

@ -25,10 +25,6 @@
<style type="text/css"> <style type="text/css">
.header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em} .header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em}
.panelborders{border-width:1px 0;border-style:solid none;border-color:#fff} .panelborders{border-width:1px 0;border-style:solid none;border-color:#fff}
.roundedspan{border:1px solid #d4d4d4;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;padding:1px;color:#fff;margin:0 8px 0 0;display:inline-block}
.roundedspan>span{padding:7px 7px;min-width:15px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;text-align:center;display:inline-block}
.roundedspan>span .hovertext{display:none}
.roundedspan>span:hover .hovertext{display:inline}
.mediaborder{padding:5px;border:solid 5px #edf3f4} .mediaborder{padding:5px;border:solid 5px #edf3f4}
.actionbuttons{width:auto;height:36px;line-height:36px;background:#8dc63f;-moz-border-radius:32px;-webkit-border-radius:32px;border-radius:32px;color:white;cursor:pointer;font-size:13px;font-weight:bold;padding:0 15px;border:0;margin:5px 0} .actionbuttons{width:auto;height:36px;line-height:36px;background:#8dc63f;-moz-border-radius:32px;-webkit-border-radius:32px;border-radius:32px;color:white;cursor:pointer;font-size:13px;font-weight:bold;padding:0 15px;border:0;margin:5px 0}
.header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em} .header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em}
@ -52,15 +48,12 @@
.border{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;border:solid 2px #d6dde0;margin:5px auto;padding-right:5px;padding-left:5px} .border{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;border:solid 2px #d6dde0;margin:5px auto;padding-right:5px;padding-left:5px}
.btn_support.kindle{height:40px} .btn_support.kindle{height:40px}
.btn_support.kindle a{width:auto;font-size:15px} .btn_support.kindle a{width:auto;font-size:15px}
.preview{border:solid 3px #e35351;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;clear:both;padding:5px 10px;font-size:13px;width:90%}
.preview a{color:#8dc63f}
.launch_top{border:solid 3px #e35351;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;clear:both;padding:5px 10px;font-size:13px;width:90%;border-color:#8dc63f;margin:10px auto 0 auto;font-size:15px;line-height:22.5px} .launch_top{border:solid 3px #e35351;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;clear:both;padding:5px 10px;font-size:13px;width:90%;border-color:#8dc63f;margin:10px auto 0 auto;font-size:15px;line-height:22.5px}
.launch_top a{color:#8dc63f} .launch_top a{color:#8dc63f}
.launch_top.pale{border-color:#d6dde0;font-size:13px} .launch_top.pale{border-color:#d6dde0;font-size:13px}
.launch_top.alert{border-color:#e35351;font-size:13px} .launch_top.alert{border-color:#e35351;font-size:13px}
.preview_content{border:solid 3px #e35351;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;clear:both;padding:5px 10px;font-size:13px;width:90%;width:80%;margin:10px auto} .preview_content{border:solid 3px #e35351;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;clear:both;padding:5px 10px;font-size:13px;width:90%;width:80%;margin:10px auto}
.preview_content a{color:#8dc63f} .preview_content a{color:#8dc63f}
.utilityheaders{text-transform:uppercase;color:#3d4e53;font-size:15px;display:block}
html,body{height:100%} html,body{height:100%}
body{padding:0 0 20px 0;margin:0;font-size:13px;line-height:16.900000000000002px;font-family:"Lucida Grande","Lucida Sans Unicode","Lucida Sans",Arial,Helvetica,sans-serif;color:#3d4e53} body{padding:0 0 20px 0;margin:0;font-size:13px;line-height:16.900000000000002px;font-family:"Lucida Grande","Lucida Sans Unicode","Lucida Sans",Arial,Helvetica,sans-serif;color:#3d4e53}
a{font-weight:bold;font-size:inherit;text-decoration:none;cursor:pointer;color:#6994a3} a{font-weight:bold;font-size:inherit;text-decoration:none;cursor:pointer;color:#6994a3}

Some files were not shown because too many files have changed in this diff Show More