Merge branch 'master' into jkace

pull/1/head
icellama21 2012-01-26 11:23:10 -05:00
commit 8b6b038fe3
121 changed files with 4675 additions and 901 deletions

View File

@ -38,10 +38,10 @@ Production Deployment
Below are the steps for getting regluit running on EC2 with Apache and mod_wsgi, and talking to an Amazon Relational Data Store instance.
1. create an ubuntu natty ec2 instance using ami-1aad5273
1. create an ubuntu ec2 instance (e.g, go http://alestic.com/ to find various ubuntu images)
1. `sudo aptitude update`
1. `sudo aptitude upgrade`
1. `sudo aptitude install git apache libapache2-mod-wsgi mysql-client python-virtualenv python-mysqldb redis-server python-lxml postfix`
1. `sudo aptitude install git-core apache libapache2-mod-wsgi mysql-client python-virtualenv python-mysqldb redis-server python-lxml postfix`
1. `sudo mkdir /opt/regluit`
1. `sudo chown ubuntu:ubuntu /opt/regluit`
1. `cd /opt`
@ -54,32 +54,32 @@ Below are the steps for getting regluit running on EC2 with Apache and mod_wsgi,
1. create an Amazon RDS instance
1. connect to it, e.g. `mysql -u root -h gluejardb.cboagmr25pjs.us-east-1.rds.amazonaws.com -p`
1. `CREATE DATABASE unglueit CHARSET utf8;`
1. `GRANT ALL ON unglueit.\* TO unglueit@ip-10-244-250-168.ec2.internal IDENTIFIED BY 'unglueit' REQUIRE SSL`
1. `GRANT ALL ON unglueit.\* TO unglueit@ip-10-244-250-168.ec2.internal IDENTIFIED BY 'unglueit' REQUIRE SSL;`
1. update settings/prod.py with database credentials
1. `virtualenv ENV`
1. `source ENV/bin/activate`
1. `pip install -r requirements.pip`
1. `echo "/opt/" > ENV/lib/python2.7/site-packages/regluit.pth`
1. `django-admin.py syncdb --migrate --settings regluit.settings.prod`
1. `sudo mkdir /var/www/static`
1. `sudo chown ubuntu:ubuntu /var/www/static`
1. `django-admin.py collectstatic --settings regluit.settings.prod`
1. `sudo ln -s /opt/regluit/deploy/regluit.conf /etc/apache2/sites-available/regluit`
1. `sudo a2ensite regluit`
1. `sudo a2enmod ssl`
1. 'sudo a2ensite default-ssl`
1. edit /etc/apache2/sites-available/default-ssl to use correct ssl certificate if necessary
1. `sudo a2enmod ssl rewrite`
1. `cd /home/ubuntu`
1. copy SSL server key to `/etc/ssl/private/server.key`
1. copy SSL certificate to `/etc/ssl/certs/server.crt`
1. `sudo /etc/init.d/apache2 restart`
1. `sudo adduser --no-create-home celery --disabled-password --disabled-login`
1. `sudo adduser --no-create-home celery --disabled-password --disabled-login` (just enter return for all?)
1. `sudo cp deploy/celeryd /etc/init.d/celeryd`
1. `sudo chmod 755 /etc/init.d/celeryd`
1. `sudo cp deploy/celeryd.conf /etc/default/celeryd`
1. `sudo mkdir /var/log/celery`
1. `sudo chown celery:celery /var/log/celery`
1. `sudo mkdir /var/run/celery`
1. `sudo chown celery:celery /var/run/celery`
1. `sudo /etc/init.d/celeryd start`
OS X Develper Notes
OS X Developer Notes
-------------------
To run regluit on OS X you should have XCode installed

View File

@ -23,21 +23,52 @@ class UserResource(ModelResource):
fields = ['username', 'first_name', 'last_name']
class EditionResource(ModelResource):
work = fields.ForeignKey('regluit.api.resources.WorkResource', 'work')
identifiers = fields.ToManyField('regluit.api.resources.IdentifierResource', 'identifiers')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Edition.objects.all()
resource_name = 'edition'
filtering = {
"isbn_13": ALL,
"isbn_13": ALL, "identifiers": ALL_WITH_RELATIONS,
}
def build_filters(self, filters = None):
if filters is None:
filters = {}
for filter_expr, value in filters.items():
if filter_expr.startswith('isbn_13'):
filters['identifiers__type'] = 'isbn'
if len(filter_expr)>7:
filters['identifiers__value'+filter_expr[7:]] = value
else:
filters['identifiers__value'] = value
del filters[ filter_expr ]
return super(EditionResource, self).build_filters(filters)
class IdentifierResource(ModelResource):
work = fields.ForeignKey('regluit.api.resources.WorkResource', 'work')
edition = fields.ForeignKey('regluit.api.resources.EditionResource', 'edition')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Identifier.objects.all()
resource_name = 'identifier'
filtering = {
"value": ALL, "type" : ALL,
}
class WorkResource(ModelResource):
editions = fields.ToManyField(EditionResource, 'editions')
identifiers = fields.ToManyField(IdentifierResource, 'identifiers')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Work.objects.all()
resource_name = 'work'
filtering = {'editions': ALL_WITH_RELATIONS, 'id': ALL}
filtering = {'editions': ALL_WITH_RELATIONS, 'id': ALL, 'identifiers': ALL_WITH_RELATIONS}
class CampaignResource(ModelResource):
work = fields.ToOneField(WorkResource, 'work')

View File

@ -20,7 +20,11 @@
{% if user.is_authenticated %}
<h3>Campaign info</h3>
<p>JSON to get data on all campaigns</p>
<a href="/api/v1/campaign/?format=json&api_key={{api_key}}&username={{user.username}}">{{base_url}}/api/v1/campaign/?format=json&api_key={api_key}&username={username}</a>
<a href="/api/v1/campaign/?format=json&amp;api_key={{api_key}}&amp;username={{user.username}}">{{base_url}}/api/v1/campaign/?format=json&amp;api_key={api_key}&amp;username={username}</a>
<h3>Identifier Resolution</h3>
<p>JSON to get work/edition data for an isbn</p>
<a href="/api/v1/identifier/?format=json&amp;api_key={{api_key}}&amp;type=isbn&amp;value=9780441012039">{{base_url}}/api/v1/identifier/?format=json&amp;api_key={api_key}&amp;ype=isbn&amp;value=9780441012039</a>
<p> In addition to isbn, you can use 'goog' if you have a google books id, and 'oclc' for oclc numbers.</p>
{% endif %}
<h3>Campaign Widgets</h3>

View File

@ -33,7 +33,7 @@
<!--- editions --->
{% if work %}
{% with work.editions.all.0.googlebooks_id as googlebooks_id %}
{% with work.googlebooks_id as googlebooks_id %}
{% include "book_panel.html" %}
{% endwith %}
{% else %}

View File

@ -12,7 +12,7 @@ import regluit.core.isbn
class ApiTests(TestCase):
def setUp(self):
edition = bookloader.add_by_isbn(isbn='0441012035')
edition = bookloader.add_by_isbn_from_google(isbn='0441012035')
campaign = models.Campaign.objects.create(
name=edition.work.title,
work=edition.work,
@ -57,7 +57,8 @@ class ApiTests(TestCase):
def test_campaign_lookup_by_isbn(self):
r = self.client.get('/api/v1/campaign/', data={
'format': 'json',
'work__editions__isbn_13': regluit.core.isbn.convert_10_to_13('0441012035'),
'work__identifiers__value': regluit.core.isbn.convert_10_to_13('0441012035'),
'work__identifiers__type': 'isbn',
'username': self.user.username,
'api_key': self.user.api_key.key
})
@ -75,7 +76,8 @@ class ApiTests(TestCase):
r = self.client.get('/api/v1/campaign/', data={
'format': 'json',
'work__editions__isbn_13': regluit.core.isbn.convert_10_to_13('0441012035'),
'work__identifiers__value': regluit.core.isbn.convert_10_to_13('0441012035'),
'work__identifiers__type': 'isbn',
'username': self.user.username,
'api_key': self.user.api_key.key
})
@ -83,11 +85,12 @@ class ApiTests(TestCase):
self.assertEqual(j['meta']['logged_in_username'], 'test')
self.assertEqual(j['objects'][0]['in_wishlist'], False)
w = models.Work.objects.get(editions__isbn_13=regluit.core.isbn.convert_10_to_13('0441012035'))
w = models.Work.objects.get(identifiers__value=regluit.core.isbn.convert_10_to_13('0441012035'), identifiers__type='isbn')
self.user.wishlist.add_work(w,'test')
r = self.client.get('/api/v1/campaign/', data={
'format': 'json',
'work__editions__isbn_13': regluit.core.isbn.convert_10_to_13('0441012035'),
'work__identifiers__value': regluit.core.isbn.convert_10_to_13('0441012035'),
'work__identifiers__type': 'isbn',
'username': self.user.username,
'api_key': self.user.api_key.key
})

View File

@ -8,6 +8,7 @@ from regluit.api import resources
v1_api = Api(api_name='v1')
v1_api.register(resources.UserResource())
v1_api.register(resources.WorkResource())
v1_api.register(resources.IdentifierResource())
v1_api.register(resources.EditionResource())
v1_api.register(resources.CampaignResource())
v1_api.register(resources.AuthorResource())

View File

@ -13,9 +13,11 @@ from tastypie.models import ApiKey
def isbn(request,isbn):
if len(isbn)==10:
isbn=regluit.core.isbn.convert_10_to_13(isbn)
editions = models.Edition.objects.filter( Q(isbn_13 = isbn))
# models.Campaign.objects.filter(work__editions__isbn_13='9780811216999')
try:
edition = models.Identifier.objects.get( Q(type = 'isbn', value = isbn)).edition
editions = [edition]
except models.Identifier.DoesNotExist:
editions = []
return render_to_response('isbn.html',
{'isbn':isbn, 'editions':editions},
context_instance=RequestContext(request)
@ -44,23 +46,14 @@ def widget(request,isbn):
Current implementation is to supply info for current book panel design
"""
# presumably 0 or 1 Edition will match
if len(isbn)==10:
isbn = regluit.core.isbn.convert_10_to_13(isbn)
editions = models.Edition.objects.filter( Q(isbn_13 = isbn))
# if 1 edition: should be 0 or 1 corresponding Work
# for 1 Work, there will be a Campaign or not
assert len(editions) < 2
if len(editions):
edition = editions[0]
try:
work = edition.work
identifier = models.Identifier.objects.get( Q( type = 'isbn', value = isbn ))
work = identifier.work
edition = identifier.edition
campaigns = work.campaigns.all()
except Exception, e:
work = None
campaigns = []
else:
except models.Identifer.DoesNotExist:
edition = None
work = None
campaigns = []
@ -104,10 +97,7 @@ class ApiHelpView(TemplateView):
campaigns = models.Campaign.objects.all()
if len(campaigns):
c = campaigns[0]
try:
isbn = c.work.editions.all()[0].isbn_13
except IndexError:
isbn = ''
isbn = c.work.first_isbn_13
context["campaign"] = campaigns[0]
context["campaign_isbn"] = isbn

3
context_processors.py Normal file
View File

@ -0,0 +1,3 @@
def is_preview(request):
from django.conf import settings
return {'is_preview': settings.IS_PREVIEW}

View File

@ -15,22 +15,29 @@ import regluit.core.isbn
logger = logging.getLogger(__name__)
def add_by_oclc(oclc):
logger.info("adding book by oclc %s", oclc)
for edition in models.Edition.objects.filter(oclc=oclc):
return edition
def add_by_oclc(isbn, work=None):
# this is indirection in case we have a data source other than google
return add_by_oclc_from_google(isbn)
def add_by_oclc_from_google(oclc):
if oclc:
logger.info("adding book by oclc %s" , oclc)
else:
return None
try:
return models.Identifier.objects.get(type='oclc', value=oclc).edition
except:
url = "https://www.googleapis.com/books/v1/volumes"
results = _get_json(url, {"q": '"OCLC%s"' % oclc})
if not results.has_key('items') or len(results['items']) == 0:
logger.warn("no google hits for %s" % oclc)
logger.warn("no google hits for %s" , oclc)
return None
try:
e = add_by_googlebooks_id(results['items'][0]['id'])
e.oclc = oclc
e.save()
e = add_by_googlebooks_id(results['items'][0]['id'], results=results['items'][0])
models.Identifier(type='oclc', value=oclc, edition=e, work=e.work).save()
return e
except LookupFailure, e:
logger.exception("failed to add edition for %s", oclc)
@ -38,9 +45,43 @@ def add_by_oclc(oclc):
logger.exception("google books data for %s didn't fit our db", oclc)
return None
def add_by_isbn(isbn, work=None):
"""add a book to the UnglueIt database based on ISBN. The work parameter
if not isbn:
return None
e = add_by_isbn_from_google(isbn, work=work)
if e:
return e
logger.info("null came back from add_by_isbn_from_google: %s", isbn)
if not work or not work.title:
return None
# if there's a work with a title, we want to create stub editions and
# works, even if google doesn't know about it # but if it's not valid,
# forget it!
try:
isbn = regluit.core.isbn.ISBN(isbn)
except:
logger.exception("invalid isbn: %s", isbn)
return None
if not isbn.valid:
return None
isbn = isbn.to_string()
# we don't know the language ->'xx'
w = models.Work(title=work.title, language='xx')
w.save()
e = models.Edition(title=work.title,work=w)
e.save()
e.new = True
models.Identifier(type='isbn', value=isbn, work=w, edition=e).save()
return e
def add_by_isbn_from_google(isbn, work=None):
"""add a book to the UnglueIt database from google based on ISBN. The work parameter
is optional, and if not supplied the edition will be associated with
a stub work.
"""
@ -50,9 +91,9 @@ def add_by_isbn(isbn, work=None):
isbn = regluit.core.isbn.convert_10_to_13(isbn)
logger.info("adding book by isbn %s", isbn)
# save a lookup to google if we already have this isbn
has_isbn = Q(isbn_13=isbn)
for edition in models.Edition.objects.filter(has_isbn):
# check if we already have this isbn
edition = get_edition_by_id(type='isbn',value=isbn)
if edition:
edition.new = False
return edition
@ -60,31 +101,48 @@ def add_by_isbn(isbn, work=None):
results = _get_json(url, {"q": "isbn:%s" % isbn})
if not results.has_key('items') or len(results['items']) == 0:
logger.warn("no google hits for %s" % isbn)
logger.warn("no google hits for %s" , isbn)
return None
try:
return add_by_googlebooks_id(results['items'][0]['id'], work)
return add_by_googlebooks_id(results['items'][0]['id'], work=work, results=results['items'][0])
except LookupFailure, e:
logger.exception("failed to add edition for %s", isbn)
except IntegrityError, e:
logger.exception("google books data for %s didn't fit our db", isbn)
return None
def get_work_by_id(type,value):
if value:
try:
return models.Identifier.objects.get(type=type,value=value).work
except models.Identifier.DoesNotExist:
return None
def add_by_googlebooks_id(googlebooks_id, work=None):
def get_edition_by_id(type,value):
if value:
try:
return models.Identifier.objects.get(type=type,value=value).edition
except models.Identifier.DoesNotExist:
return None
def add_by_googlebooks_id(googlebooks_id, work=None, results=None):
"""add a book to the UnglueIt database based on the GoogleBooks ID. The
work parameter is optional, and if not supplied the edition will be
associated with a stub work.
"""
"""
# don't ping google again if we already know about the edition
try:
e = models.Edition.objects.get(googlebooks_id=googlebooks_id)
return e
except models.Edition.DoesNotExist:
return models.Identifier.objects.get(type='goog', value=googlebooks_id).edition
except models.Identifier.DoesNotExist:
pass
# if google has been queried by caller, don't call again
if results:
item =results
else:
logger.info("loading metadata from google for %s", googlebooks_id)
url = "https://www.googleapis.com/books/v1/volumes/%s" % googlebooks_id
item = _get_json(url)
@ -92,27 +150,41 @@ def add_by_googlebooks_id(googlebooks_id, work=None):
# don't add the edition to a work with a different language
# https://www.pivotaltracker.com/story/show/17234433
language = d.get('language')
language = d['language']
if work and work.language != language:
logger.warn("ignoring %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))
return
work = None
isbn = None
for i in d.get('industryIdentifiers', []):
if i['type'] == 'ISBN_10' and not isbn:
isbn = regluit.core.isbn.convert_10_to_13(i['identifier'])
elif i['type'] == 'ISBN_13':
isbn = i['identifier']
e = models.Edition(googlebooks_id=googlebooks_id)
# now check to see if there's an existing Work
if not work:
work = get_work_by_id(type='isbn',value=isbn)
if not work:
work = models.Work.objects.create(title=d['title'], language=language)
work.new = True
work.save()
# because this is a new google id, we have to create a new edition
e = models.Edition(work=work)
e.title = d.get('title')
e.description = d.get('description')
e.publisher = d.get('publisher')
e.publication_date = d.get('publishedDate', '')
for i in d.get('industryIdentifiers', []):
if i['type'] == 'ISBN_13':
e.isbn_13 = i['identifier']
elif i['type'] == 'ISBN_13':
e.isbn_13 = i['identifier']
e.save()
e.new = True
# create identifier where needed
models.Identifier(type='goog',value=googlebooks_id,edition=e,work=work).save()
if isbn:
models.Identifier.get_or_add(type='isbn',value=isbn,edition=e,work=work)
for a in d.get('authors', []):
a, created = models.Author.objects.get_or_create(name=a)
a.editions.add(e)
@ -134,15 +206,6 @@ def add_by_googlebooks_id(googlebooks_id, work=None):
provider='google')
ebook.save()
# if we know what work the edition should be attached to, attach it
if work:
work.editions.add(e)
# otherwise we need to create a stub work
else:
w = models.Work.objects.create(title=e.title, language=language)
w.editions.add(e)
return e
@ -156,17 +219,33 @@ def add_related(isbn):
# this is the work everything will hang off
work = edition.work
new_editions = []
other_editions = {}
for other_isbn in thingisbn(isbn):
# 979's come back as 13
if len(other_isbn)==10:
other_isbn = regluit.core.isbn.convert_10_to_13(other_isbn)
related_edition = add_by_isbn(other_isbn, work)
if related_edition and related_edition.work != edition.work:
merge_works(edition.work, related_edition.work)
related_edition = add_by_isbn(other_isbn, work=work)
if related_edition:
related_language = related_edition.work.language
if edition.work.language == related_language:
new_editions.append(related_edition)
if related_edition.work != edition.work:
merge_works(edition.work, related_edition.work)
else:
if other_editions.has_key(related_language):
other_editions[related_language].append(related_edition)
else:
other_editions[related_language]=[related_edition]
# group the other language editions together
for lang_group in other_editions.itervalues():
if len(lang_group)>1:
lang_edition = lang_group[0]
for related_edition in lang_group[1:]:
if lang_edition.work != related_edition.work:
merge_works(lang_edition.work, related_edition.work)
return new_editions
@ -175,7 +254,7 @@ def thingisbn(isbn):
"""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')
"""
logger.info("looking up %s at ThingISBN" % isbn)
logger.info("looking up %s at ThingISBN" , isbn)
url = "http://www.librarything.com/api/thingISBN/%s" % isbn
xml = requests.get(url, headers={"User-Agent": settings.USER_AGENT}).content
doc = ElementTree.fromstring(xml)
@ -185,7 +264,10 @@ def thingisbn(isbn):
def merge_works(w1, w2):
"""will merge the second work (w2) into the first (w1)
"""
logger.info("merging work %s into %s", w1, w2)
logger.info("merging work %s into %s", w2, w1)
for identifier in w2.identifiers.all():
identifier.work = w1
identifier.save()
for edition in w2.editions.all():
edition.work = w1
edition.save()
@ -202,6 +284,10 @@ def merge_works(w1, w2):
def add_openlibrary(work):
if work.openlibrary_lookup is not None:
# don't hit OL if we've visited in the past month or so
if datetime.datetime.now()- work.openlibrary_lookup < datetime.timedelta(days=30):
return
work.openlibrary_lookup = datetime.datetime.now()
work.save()
@ -217,11 +303,11 @@ def add_openlibrary(work):
for edition in work.editions.all():
isbn_key = "ISBN:%s" % edition.isbn_13
params['bibkeys'] = isbn_key
e = _get_json(url, params)
e = _get_json(url, params, type='ol')
if e.has_key(isbn_key) and e[isbn_key]['details'].has_key('works'):
work_key = e[isbn_key]['details']['works'].pop(0)['key']
logger.info("got openlibrary work %s for isbn %s", work_key, isbn_key)
w = _get_json("http://openlibrary.org" + work_key)
w = _get_json("http://openlibrary.org" + work_key,type='ol')
if w.has_key('subjects'):
found = True
break
@ -235,24 +321,32 @@ def add_openlibrary(work):
logger.info("adding subject %s to work %s", s, work.id)
subject, created = models.Subject.objects.get_or_create(name=s)
work.subjects.add(subject)
work.openlibrary_id = w['key']
work.save()
models.Identifier.get_or_add(type='olwk',value=w['key'],work=work)
if e[isbn_key]['details'].has_key('identifiers'):
ids = e[isbn_key]['details']['identifiers']
if ids.has_key('goodreads'):
models.Identifier.get_or_add(type='gdrd',value=ids['goodreads'][0],work=work,edition=edition)
if ids.has_key('librarything'):
models.Identifier.get_or_add(type='ltwk',value=ids['librarything'][0],work=work)
# TODO: add authors here once they are moved from Edition to Work
# TODO: add LCCN, LibraryThing, GoodReads to appropriate models
def _get_json(url, params={}):
def _get_json(url, params={}, type='gb'):
# TODO: should X-Forwarded-For change based on the request from client?
headers = {'User-Agent': settings.USER_AGENT,
'Accept': 'application/json',
'X-Forwarded-For': '69.174.114.214'}
if type == 'gb':
params['key'] = settings.GOOGLE_BOOKS_API_KEY
response = requests.get(url, params=params, headers=headers)
if response.status_code == 200:
return json.loads(response.content)
else:
logger.error("unexpected HTTP response: %s" % response)
if response.content:
logger.error("response content: %s" % response.content)
raise LookupFailure("GET failed: url=%s and params=%s" % (url, params))

View File

@ -23,6 +23,14 @@
"name": "unglue.it local development"
}
},
{
"pk": 4,
"model": "sites.site",
"fields": {
"domain": "ry-dev.dyndns.org",
"name": "ry-dev development"
}
},
{
"pk": 1,
"model": "core.premium",

View File

@ -13,6 +13,7 @@ import django.utils.encoding
import regluit.core
from regluit.core import bookloader
from regluit.core import models
# import parse_qsl from cgi if it doesn't exist in urlparse
try:
@ -286,8 +287,7 @@ def load_goodreads_shelf_into_wishlist(user, shelf_name='all', goodreads_user_id
link = review['book']['link']
match = re.search('/show/(\d+)', link)
if match:
edition.goodreads_id = match.group(1)
edition.save()
identifier= models.Identifier.get_or_add(type = 'gdrd', value = match.group(1), edition = edition, work = edition.work)
user.wishlist.add_work(edition.work, 'goodreads')
logger.info("Work with isbn %s added to wishlist.", isbn)
else:

View File

@ -6,6 +6,7 @@ import HTMLParser
import logging
import re
from datetime import datetime
from regluit.core import models
logger = logging.getLogger(__name__)
@ -200,6 +201,7 @@ def load_librarything_into_wishlist(user, lt_username, max_books=None):
"""
from regluit.core import bookloader
from regluit.core import tasks
from itertools import islice
logger.info("Entering into load_librarything_into_wishlist")
@ -214,13 +216,13 @@ def load_librarything_into_wishlist(user, lt_username, max_books=None):
if not edition:
continue
# add the librarything ids to the db since we know them now
edition.librarything_id = book['book_id']
edition.save()
edition.work.librarything_id = book['work_id']
edition.work.save()
identifier= models.Identifier.get_or_add(type = 'thng', value = book['book_id'], edition = edition, work = edition.work)
identifier= models.Identifier.get_or_add(type = 'ltwk', value = book['work_id'], work = edition.work)
if book['lc_call_number']:
identifier= models.Identifier.get_or_add(type = 'lccn', value = book['lc_call_number'], edition = edition, work = edition.work)
user.wishlist.add_work(edition.work, 'librarything')
if edition.new:
regluit.core.tasks.populate_edition.delay(edition)
tasks.populate_edition.delay(edition)
logger.info("Work with isbn %s added to wishlist.", isbn)
except Exception, e:
logger.info ("error adding ISBN %s: %s", isbn, e)

View File

@ -0,0 +1,21 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from regluit.core import bookloader
class Command(BaseCommand):
help = "populate a user's wishlist with books from a file of isbns"
args = "<filename> <username>"
def handle(self, filename, username, **options):
user = User.objects.get(username=username)
wishlist = user.wishlist
for isbn in open(filename):
isbn = isbn.strip()
edition = bookloader.add_by_isbn(isbn)
bookloader.add_related(isbn)
if edition:
user.wishlist.add_work(edition.work, source="user")
print "loaded %s as %s for %s" % (isbn, edition, user)
else:
print "failed to load book for %s" % isbn

View File

@ -1,19 +0,0 @@
from django.db.models import Count
from django.core.management.base import BaseCommand
from regluit.core import models
class Command(BaseCommand):
help = "remove duplicates"
def handle(self, *args, **options):
q = models.Edition.objects.values("googlebooks_id")
q = q.annotate(Count("googlebooks_id"))
for r in q:
if r['googlebooks_id__count'] == 1:
continue
gb = r['googlebooks_id']
editions = models.Edition.objects.filter(googlebooks_id=gb)
for e in editions[1:]:
print "removing duplicate edition: %s" % e
e.delete()

View File

@ -0,0 +1,254 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Identifier'
db.create_table('core_identifier', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('type', self.gf('django.db.models.fields.CharField')(max_length=4)),
('value', self.gf('django.db.models.fields.CharField')(max_length=31)),
('work', self.gf('django.db.models.fields.related.ForeignKey')(related_name='identifiers', to=orm['core.Work'])),
('edition', self.gf('django.db.models.fields.related.ForeignKey')(related_name='identifiers', null=True, to=orm['core.Edition'])),
))
db.send_create_signal('core', ['Identifier'])
# Adding unique constraint on 'Identifier', fields ['type', 'value']
db.create_unique('core_identifier', ['type', 'value'])
# migrating data
if not db.dry_run:
for work in orm.Work.objects.all():
if work.librarything_id:
identifier = orm.Identifier.objects.create(type='ltwk',value=work.librarything_id, work=work)
identifier.save()
if work.openlibrary_id:
identifier = orm.Identifier.objects.create(type='olwk',value=work.openlibrary_id, work=work)
identifier.save()
for edition in work.editions.all():
if edition.googlebooks_id:
identifier = orm.Identifier.objects.create(type='goog',value=edition.googlebooks_id, work=work, edition=edition)
identifier.save()
if edition.goodreads_id:
identifier = orm.Identifier.objects.create(type='gdrd',value=edition.goodreads_id, work=work, edition=edition)
identifier.save()
if edition.librarything_id:
identifier = orm.Identifier.objects.create(type='thng',value=edition.librarything_id, work=work, edition=edition)
identifier.save()
if edition.isbn_13:
identifier = orm.Identifier.objects.create(type='isbn',value=edition.isbn_13, work=work, edition=edition)
identifier.save()
if edition.oclc:
identifier = orm.Identifier.objects.create(type='oclc',value=edition.oclc, work=work, edition=edition)
identifier.save()
def backwards(self, orm):
# Removing unique constraint on 'Identifier', fields ['type', 'value']
db.delete_unique('core_identifier', ['type', 'value'])
# Deleting model 'Identifier'
db.delete_table('core_identifier')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.author': {
'Meta': {'object_name': 'Author'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.campaignaction': {
'Meta': {'object_name': 'CampaignAction'},
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['core.Campaign']"}),
'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '15'})
},
'core.celerytask': {
'Meta': {'object_name': 'CeleryTask'},
'active': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 1, 7, 17, 53, 55, 504045)', 'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True'}),
'function_args': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'function_name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'task_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'null': 'True', 'to': "orm['auth.User']"})
},
'core.claim': {
'Meta': {'object_name': 'Claim'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'rights_holder': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.RightsHolder']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '7'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['auth.User']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.Work']"})
},
'core.ebook': {
'Meta': {'object_name': 'Ebook'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebooks'", 'to': "orm['core.Edition']"}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'rights': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
},
'core.edition': {
'Meta': {'object_name': 'Edition'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
'goodreads_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'googlebooks_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'isbn_13': ('django.db.models.fields.CharField', [], {'max_length': '13', 'null': 'True'}),
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'oclc': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True'}),
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
},
'core.identifier': {
'Meta': {'unique_together': "(('type', 'value'),)", 'object_name': 'Identifier'},
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
'value': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['core.Work']"})
},
'core.premium': {
'Meta': {'object_name': 'Premium'},
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
'core.rightsholder': {
'Meta': {'object_name': 'RightsHolder'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rights_holder'", 'to': "orm['auth.User']"}),
'rights_holder_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
},
'core.subject': {
'Meta': {'ordering': "['name']", 'object_name': 'Subject'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", 'symmetrical': 'False', 'to': "orm['core.Work']"})
},
'core.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'facebook_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'goodreads_auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'goodreads_auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'goodreads_user_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'goodreads_user_link': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'goodreads_user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
'pic_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'tagline': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
'twitter_id': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'core.wishes': {
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Work']"})
},
'core.wishlist': {
'Meta': {'object_name': 'Wishlist'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'openlibrary_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
}
}
complete_apps = ['core']

View File

@ -0,0 +1,267 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'Work.librarything_id'
db.delete_column('core_work', 'librarything_id')
# Deleting field 'Work.openlibrary_id'
db.delete_column('core_work', 'openlibrary_id')
# Deleting field 'Edition.oclc'
db.delete_column('core_edition', 'oclc')
# Deleting field 'Edition.isbn_13'
db.delete_column('core_edition', 'isbn_13')
# Deleting field 'Edition.goodreads_id'
db.delete_column('core_edition', 'goodreads_id')
# Deleting field 'Edition.googlebooks_id'
db.delete_column('core_edition', 'googlebooks_id')
# Deleting field 'Edition.librarything_id'
db.delete_column('core_edition', 'librarything_id')
def backwards(self, orm):
# Adding field 'Work.librarything_id'
db.add_column('core_work', 'librarything_id', self.gf('django.db.models.fields.CharField')(max_length=50, null=True), keep_default=False)
# Adding field 'Work.openlibrary_id'
db.add_column('core_work', 'openlibrary_id', self.gf('django.db.models.fields.CharField')(max_length=50, null=True), keep_default=False)
# Adding field 'Edition.oclc'
db.add_column('core_edition', 'oclc', self.gf('django.db.models.fields.CharField')(max_length=25, null=True), keep_default=False)
# Adding field 'Edition.isbn_13'
db.add_column('core_edition', 'isbn_13', self.gf('django.db.models.fields.CharField')(max_length=13, null=True), keep_default=False)
# Adding field 'Edition.goodreads_id'
db.add_column('core_edition', 'goodreads_id', self.gf('django.db.models.fields.CharField')(max_length=50, null=True), keep_default=False)
# Adding field 'Edition.googlebooks_id'
db.add_column('core_edition', 'googlebooks_id', self.gf('django.db.models.fields.CharField')(default='0', max_length=50, unique=True), keep_default=False)
# Adding field 'Edition.librarything_id'
db.add_column('core_edition', 'librarything_id', self.gf('django.db.models.fields.CharField')(max_length=50, null=True), keep_default=False)
# migrating data
if not db.dry_run:
for identifier in orm.Identifier.objects.all():
if identifier.type=='ltwk':
identifier.work.librarything_id = identifier.value
identifier.work.save()
elif identifier.type=='olwk':
identifier.work.openlibrary_id = identifier.value
identifier.work.save()
elif identifier.type=='goog':
identifier.edition.googlebooks_id = identifier.value
identifier.edition.save()
elif identifier.type=='gdrd':
identifier.edition.goodreads_id = identifier.value
identifier.edition.save()
elif identifier.type=='thng':
identifier.edition.librarything_id = identifier.value
identifier.edition.save()
elif identifier.type=='isbn':
identifier.edition.isbn_13 = identifier.value
identifier.edition.save()
elif identifier.type=='oclc':
identifier.edition.oclc = identifier.value
identifier.edition.save()
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.author': {
'Meta': {'object_name': 'Author'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.campaignaction': {
'Meta': {'object_name': 'CampaignAction'},
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['core.Campaign']"}),
'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '15'})
},
'core.celerytask': {
'Meta': {'object_name': 'CeleryTask'},
'active': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 1, 7, 21, 27, 56, 524980)', 'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True'}),
'function_args': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'function_name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'task_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'null': 'True', 'to': "orm['auth.User']"})
},
'core.claim': {
'Meta': {'object_name': 'Claim'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'rights_holder': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.RightsHolder']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '7'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['auth.User']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.Work']"})
},
'core.ebook': {
'Meta': {'object_name': 'Ebook'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebooks'", 'to': "orm['core.Edition']"}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'rights': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
},
'core.edition': {
'Meta': {'object_name': 'Edition'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
},
'core.identifier': {
'Meta': {'unique_together': "(('type', 'value'),)", 'object_name': 'Identifier'},
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
'value': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['core.Work']"})
},
'core.premium': {
'Meta': {'object_name': 'Premium'},
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
'core.rightsholder': {
'Meta': {'object_name': 'RightsHolder'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rights_holder'", 'to': "orm['auth.User']"}),
'rights_holder_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
},
'core.subject': {
'Meta': {'ordering': "['name']", 'object_name': 'Subject'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", 'symmetrical': 'False', 'to': "orm['core.Work']"})
},
'core.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'facebook_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'goodreads_auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'goodreads_auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'goodreads_user_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'goodreads_user_link': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'goodreads_user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
'pic_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'tagline': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
'twitter_id': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'core.wishes': {
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Work']"})
},
'core.wishlist': {
'Meta': {'object_name': 'Wishlist'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
}
}
complete_apps = ['core']

View File

@ -0,0 +1,211 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Edition.publisher'
db.alter_column('core_edition', 'publisher', self.gf('django.db.models.fields.CharField')(max_length=255, null=True))
# Changing field 'Edition.publication_date'
db.alter_column('core_edition', 'publication_date', self.gf('django.db.models.fields.CharField')(max_length=50, null=True))
def backwards(self, orm):
# Changing field 'Edition.publisher'
db.alter_column('core_edition', 'publisher', self.gf('django.db.models.fields.CharField')(default='', max_length=255))
# Changing field 'Edition.publication_date'
db.alter_column('core_edition', 'publication_date', self.gf('django.db.models.fields.CharField')(default=datetime.date(1901, 1, 1), max_length=50))
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.author': {
'Meta': {'object_name': 'Author'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.campaignaction': {
'Meta': {'object_name': 'CampaignAction'},
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['core.Campaign']"}),
'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '15'})
},
'core.celerytask': {
'Meta': {'object_name': 'CeleryTask'},
'active': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 1, 16, 14, 5, 44, 457292)', 'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True'}),
'function_args': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
'function_name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'task_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'null': 'True', 'to': "orm['auth.User']"})
},
'core.claim': {
'Meta': {'object_name': 'Claim'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'rights_holder': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.RightsHolder']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '7'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['auth.User']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.Work']"})
},
'core.ebook': {
'Meta': {'object_name': 'Ebook'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebooks'", 'to': "orm['core.Edition']"}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'rights': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'url': ('django.db.models.fields.CharField', [], {'max_length': '1024'})
},
'core.edition': {
'Meta': {'object_name': 'Edition'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
},
'core.identifier': {
'Meta': {'unique_together': "(('type', 'value'),)", 'object_name': 'Identifier'},
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
'value': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['core.Work']"})
},
'core.premium': {
'Meta': {'object_name': 'Premium'},
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
'core.rightsholder': {
'Meta': {'object_name': 'RightsHolder'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rights_holder'", 'to': "orm['auth.User']"}),
'rights_holder_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
},
'core.subject': {
'Meta': {'ordering': "['name']", 'object_name': 'Subject'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", 'symmetrical': 'False', 'to': "orm['core.Work']"})
},
'core.userprofile': {
'Meta': {'object_name': 'UserProfile'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'facebook_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
'goodreads_auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'goodreads_auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'goodreads_user_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'goodreads_user_link': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'goodreads_user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
'pic_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'tagline': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
'twitter_id': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'core.wishes': {
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishes'", 'to': "orm['core.Work']"})
},
'core.wishlist': {
'Meta': {'object_name': 'Wishlist'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
}
}
complete_apps = ['core']

View File

@ -209,11 +209,28 @@ class Campaign(models.Model):
premiums = Premium.objects.filter(campaign__isnull=True)
return premiums
class Identifier(models.Model):
# olib, ltwk, goog, gdrd, thng, isbn, oclc, olwk, olib
type = models.CharField(max_length=4, null=False)
value = models.CharField(max_length=31, null=False)
work = models.ForeignKey("Work", related_name="identifiers", null=False)
edition = models.ForeignKey("Edition", related_name="identifiers", null=True)
class Meta:
unique_together = ("type", "value")
@classmethod
def get_or_add(klass, type='goog', value=None, edition=None, work=None):
try:
return Identifier.objects.get(type=type, value=value)
except Identifier.DoesNotExist:
i=Identifier(type=type, value=value, edition=edition, work=work)
i.save()
return i
class Work(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=1000)
openlibrary_id = models.CharField(max_length=50, null=True)
librarything_id = models.CharField(max_length=50, null=True)
language = models.CharField(max_length=2, default="en", null=False)
openlibrary_lookup = models.DateTimeField(null=True)
@ -226,29 +243,47 @@ class Work(models.Model):
@property
def googlebooks_id(self):
# may want to denormalize this at some point to avoid an extra query
try:
return self.editions.all()[0].googlebooks_id
return self.identifiers.filter(type='goog')[0].value
except IndexError:
return ''
@property
def googlebooks_url(self):
if self.googlebooks_id:
return "http://books.google.com/books?id=%s" % self.googlebooks_id
else:
return ''
@property
def goodreads_id(self):
for e in self.editions.filter(goodreads_id__isnull=False):
return e.goodreads_id
try:
return self.identifiers.filter(type='gdrd')[0].value
except IndexError:
return ''
@property
def goodreads_url(self):
return "http://www.goodreads.com/book/show/%s" % self.goodreads_id
@property
def librarything_id(self):
try:
return self.identifiers.filter(type='ltwk')[0].value
except IndexError:
return ''
@property
def librarything_url(self):
return "http://www.librarything.com/work/%s" % self.librarything_id
@property
def openlibrary_id(self):
try:
return self.identifiers.filter(type='olwk')[0].value
except IndexError:
return ''
@property
def openlibrary_url(self):
return "http://openlibrary.org" + self.openlibrary_id
@ -287,6 +322,9 @@ class Work(models.Model):
campaign = self.last_campaign()
if campaign:
status = campaign.status
else:
if self.first_ebook():
status = "Available"
else:
status = "No campaign yet"
return status
@ -348,9 +386,13 @@ class Work(models.Model):
return None
def first_ebook(self, ebook_format=None):
if ebook_format:
for ebook in Ebook.objects.filter(edition__work=self,
format=ebook_format):
return ebook
else:
for ebook in Ebook.objects.filter(edition__work=self):
return ebook
return None
def wished_by(self):
@ -366,8 +408,17 @@ class Work(models.Model):
return description
def first_isbn_13(self):
for e in self.editions.filter(isbn_13__isnull=False):
return e.isbn_13
try:
return self.identifiers.filter(type='isbn')[0].value
except IndexError:
return ''
@property
def publication_date(self):
for edition in Edition.objects.filter(work=self):
if edition.publication_date:
return edition.publication_date
return ''
def __unicode__(self):
return self.title
@ -395,41 +446,77 @@ class Subject(models.Model):
class Edition(models.Model):
googlebooks_id = models.CharField(max_length=50, null=False, unique=True)
goodreads_id = models.CharField(max_length=50, null=True)
librarything_id = models.CharField(max_length=50, null=True)
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=1000)
description = models.TextField(default='', null=True)
publisher = models.CharField(max_length=255)
publication_date = models.CharField(max_length=50)
publisher = models.CharField(max_length=255, null=True)
publication_date = models.CharField(max_length=50, null=True)
public_domain = models.NullBooleanField(null=True)
isbn_13 = models.CharField(max_length=13, null=True)
oclc = models.CharField(max_length=25, null=True)
work = models.ForeignKey("Work", related_name="editions", null=True)
def __unicode__(self):
return "%s (%s)" % (self.title, self.isbn_13)
def cover_image_small(self):
if self.googlebooks_id:
server_id = random.randint(0, 9)
return "http://bks%i.books.google.com/books?id=%s&printsec=frontcover&img=1&zoom=5" % (server_id, self.googlebooks_id)
else:
return ''
def cover_image_thumbnail(self):
if self.googlebooks_id:
server_id = random.randint(0, 9)
return "http://bks%s.books.google.com/books?id=%s&printsec=frontcover&img=1&zoom=1" % (server_id, self.googlebooks_id)
else:
return ''
@property
def isbn_10(self):
return regluit.core.isbn.convert_13_to_10(self.isbn_13)
@property
def isbn_13(self):
try:
return self.identifiers.filter(type='isbn')[0].value
except IndexError:
return ''
@property
def googlebooks_id(self):
try:
return self.identifiers.filter(type='goog')[0].value
except IndexError:
return ''
@property
def librarything_id(self):
try:
return self.identifiers.filter(type='thng')[0].value
except IndexError:
return ''
@property
def oclc(self):
try:
return self.identifiers.filter(type='oclc')[0].value
except IndexError:
return ''
@property
def goodreads_id(self):
try:
return self.identifiers.filter(type='gdrd')[0].value
except IndexError:
return ''
@classmethod
def get_by_isbn(klass, isbn):
if length(isbn)==10:
isbn=regluit.core.isbn.convert_10_to_13(isbn)
for e in Edition.objects.filter( Q(isbn_13=isbn) ):
return e
try:
return Identifier.objects.get( type='isbn', value=isbn ).edition
except Identifier.DoesNotExist:
return None
class Ebook(models.Model):
@ -474,7 +561,7 @@ class Wishes(models.Model):
created = models.DateTimeField(auto_now_add=True)
source = models.CharField(max_length=15, blank=True)
wishlist = models.ForeignKey('Wishlist')
work = models.ForeignKey('Work')
work = models.ForeignKey('Work', related_name='wishes')
class Meta:
db_table = 'core_wishlist_works'

View File

@ -28,7 +28,7 @@ class BookLoaderTests(TestCase):
edition = bookloader.add_by_isbn('0441012035')
self.assertEqual(edition.title, 'Neuromancer')
self.assertEqual(edition.publication_date, '2004')
self.assertEqual(edition.publisher, 'Ace Books')
self.assertEqual(edition.publisher, u'Ace Hardcover')
self.assertEqual(edition.isbn_10, '0441012035')
self.assertEqual(edition.isbn_13, '9780441012039')
self.assertEqual(edition.googlebooks_id, "2NyiPwAACAAJ")
@ -40,6 +40,7 @@ class BookLoaderTests(TestCase):
# work
self.assertTrue(edition.work)
def test_double_add(self):
bookloader.add_by_isbn('0441012035')
bookloader.add_by_isbn('0441012035')
@ -48,7 +49,7 @@ class BookLoaderTests(TestCase):
self.assertEqual(models.Work.objects.all().count(), 1)
def test_missing_isbn(self):
e = bookloader.add_by_isbn('0139391401')
e = bookloader.add_by_isbn_from_google('0139391401')
self.assertEqual(e, None)
def test_thingisbn(self):
@ -62,22 +63,22 @@ class BookLoaderTests(TestCase):
edition = bookloader.add_by_isbn('0441012035')
self.assertEqual(models.Edition.objects.count(), 1)
self.assertEqual(models.Work.objects.count(), 1)
lang=edition.work.language
# ask for related editions to be added using the work we just created
bookloader.add_related('0441012035')
self.assertTrue(models.Edition.objects.count() > 15)
self.assertEqual(models.Work.objects.count(), 1)
self.assertTrue(edition.work.editions.count() > 15)
self.assertEqual(models.Work.objects.filter(language=lang).count(), 1)
self.assertTrue(edition.work.editions.count() > 10)
# all the editions in the db should be tied to the work
self.assertEqual(models.Edition.objects.count(),
edition.work.editions.count())
def test_populate_edition(self):
edition = bookloader.add_by_googlebooks_id('c_dBPgAACAAJ')
edition = tasks.populate_edition.run(edition)
self.assertTrue(edition.work.editions.all().count() > 20)
self.assertTrue(edition.work.subjects.all().count() > 10)
self.assertTrue(edition.work.publication_date)
edition.publication_date = None
self.assertTrue(edition.work.publication_date)
def test_merge_works(self):
# add two editions and see that there are two stub works
@ -159,6 +160,8 @@ class BookLoaderTests(TestCase):
self.assertTrue(len(subjects) > 10)
self.assertTrue('Science fiction' in subjects)
self.assertEqual(work.openlibrary_id, '/works/OL27258W')
self.assertEqual(work.goodreads_id, '14770')
self.assertEqual(work.librarything_id, '609')
class SearchTests(TestCase):
@ -237,6 +240,7 @@ class CampaignTests(TestCase):
t.amount = D('1234.00')
t.type = PAYMENT_TYPE_AUTHORIZATION
t.status = 'ACTIVE'
t.approved = True
t.campaign = c4
t.save()
self.assertTrue(c4.update_success())

View File

@ -25,13 +25,11 @@ def supporting_users(work, how_many):
return user_list
def work_list_users(work_list, how_many):
users = User.objects.filter(wishlist__works__in=work_list).distinct().reverse()
count = users.count()
if count <= how_many :
user_list = users[0: count]
else :
user_list = users[0: how_many]
return user_list
"""return up to how_many users with one of the works on work_list in their wishlist"""
#users = User.objects.filter(wishlist__works__in=work_list).distinct().reverse()
# for MySQL, avoiding a nested query is more efficient: https://docs.djangoproject.com/en/dev/ref/models/querysets/#in
users = User.objects.filter(wishlist__works__in=list(work_list)).distinct().reverse()
return users.all()[0:how_many]
def campaign_list_users(campaign_list, how_many):
users = User.objects.filter(wishlist__works__campaigns__in=campaign_list).distinct().reverse()

View File

@ -1,8 +1,7 @@
CELERYD_NODES="w1"
CELERYD_CHDIR="/opt/regluit/"
CELERYD_LOG_FILE="/var/log/celery/%n.log"
CELERYD_PID_FILE="/var/run/celery/%n.pid"
CELERYD_PID_FILE="/var/log/celery/%n.pid"
CELERYD_USER="celery"
CELERYD_GROUP="celery"
CELERYD="/opt/regluit/ENV/bin/django-admin.py celeryd"

View File

@ -0,0 +1,11 @@
CELERYD_NODES="w1"
CELERYD_CHDIR="/opt/regluit/"
CELERYD_LOG_FILE="/var/log/celery/%n.log"
CELERYD_PID_FILE="/var/log/celery/%n.pid"
CELERYD_USER="celery"
CELERYD_GROUP="celery"
CELERYD="/opt/regluit/ENV/bin/django-admin.py celeryd"
CELERYD_MULTI="/opt/regluit/ENV/bin/django-admin.py celeryd_multi"
VIRTUALENV_ACTIVATE="/opt/regluit/ENV/bin/activate"
export DJANGO_SETTINGS_MODULE="regluit.settings.please"

View File

@ -12,7 +12,7 @@ RewriteRule /admin(.*) https://please.unglueit.com/admin$1 [R=301]
RewriteRule /accounts(.*) https://please.unglueit.com/accounts$1 [R=301]
WSGIDaemonProcess regluit processes=4 threads=4 python-eggs=/tmp/regluit-python-eggs
WSGIScriptAlias / /opt/regluit/deploy/regluit.wsgi
WSGIScriptAlias / /opt/regluit/deploy/please.wsgi
<Directory /opt/regluit/static>
Options Indexes FollowSymLinks
@ -31,10 +31,10 @@ Alias /static /var/www/static
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCertificateChainFile /etc/ssl/certs/gd_bundle.crt
#SSLCertificateChainFile /etc/ssl/certs/gd_bundle.crt
WSGIDaemonProcess regluit-ssl processes=4 threads=4 python-eggs=/tmp/regluit-python-eggs
WSGIScriptAlias / /opt/regluit/deploy/regluit.wsgi
WSGIScriptAlias / /opt/regluit/deploy/please.wsgi
<Directory /opt/regluit/static>
Options Indexes FollowSymLinks

9
deploy/please.wsgi Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
import os
import django.core.handlers.wsgi
os.environ['CELERY_LOADER'] = 'django'
os.environ['DJANGO_SETTINGS_MODULE'] = 'regluit.settings.please'
application = django.core.handlers.wsgi.WSGIHandler()

73
deploy/prod.conf Normal file
View File

@ -0,0 +1,73 @@
WSGIPythonHome /opt/regluit/ENV
WSGISocketPrefix /opt/regluit
<VirtualHost _default_:80>
ServerAdmin info@gluejar.com
RewriteEngine On
RewriteCond %{SERVER_NAME} !^unglue.it$ [NC]
RewriteRule (.*) http://unglue.it$1 [R=301,L]
RewriteRule ^/$ https://unglue.it/ [R=301]
RewriteRule /admin(.*) https://unglue.it/admin$1 [R=301]
RewriteRule /accounts(.*) https://unglue.it/accounts$1 [R=301]
WSGIDaemonProcess regluit processes=4 threads=4 python-eggs=/tmp/regluit-python-eggs
WSGIScriptAlias / /opt/regluit/deploy/regluit.wsgi
<Directory /opt/regluit/static>
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
Alias /static /var/www/static
ErrorLog ${APACHE_LOG_DIR}/unglue.it-error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/unglue.it-access.log combined
</VirtualHost>
<VirtualHost _default_:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCertificateChainFile /etc/ssl/certs/gd_bundle.crt
RewriteEngine On
RewriteCond %{SERVER_NAME} !^unglue.it$ [NC]
RewriteRule (.*) https://unglue.it$1 [R=301,L]
WSGIDaemonProcess regluit-ssl processes=4 threads=4 python-eggs=/tmp/regluit-python-eggs
WSGIScriptAlias / /opt/regluit/deploy/regluit.wsgi
<Directory /opt/regluit/static>
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
Alias /static /var/www/static
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
# MSIE 7 and newer should be able to use keepalive
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
ErrorLog ${APACHE_LOG_DIR}/unglue.it-ssl-error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/unglue.it-ssl-access.log combined
</VirtualHost>

View File

@ -9,9 +9,9 @@
cd /opt/regluit
sudo -u ubuntu /usr/bin/git pull
source ENV/bin/activate
pip install -r requirements.pip
django-admin.py syncdb --migrate --settings regluit.settings.prod
django-admin.py collectstatic --noinput --settings regluit.settings.prod
#pip install -r requirements.pip
django-admin.py syncdb --migrate --settings regluit.settings.please
django-admin.py collectstatic --noinput --settings regluit.settings.please
sudo /etc/init.d/apache2 restart
sudo /etc/init.d/celeryd restart
touch /opt/regluit/deploy/last-update

View File

@ -83,9 +83,36 @@ def get_or_create(session, model, defaults=None, **kwargs):
Base = declarative_base()
class GutenbergText(object):
"""
CREATE TABLE `GutenbergText` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`etext_id` int(10) unsigned NOT NULL,
`title` varchar(1024) DEFAULT NULL,
`friendly_title` varchar(1024) DEFAULT NULL,
`lang` char(5) DEFAULT NULL,
`rights` varchar(512) DEFAULT NULL,
`created` date DEFAULT NULL,
`creator` varchar(1024) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `etext_id` (`etext_id`)
) ENGINE=MyISAM AUTO_INCREMENT=37874 DEFAULT CHARSET=utf8;
"""
pass
class GutenbergFile(object):
"""
CREATE TABLE `GutenbergFile` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`about` varchar(300) NOT NULL DEFAULT '',
`format` varchar(256) DEFAULT NULL,
`extent` int(11) unsigned DEFAULT NULL,
`modified` date DEFAULT NULL,
`is_format_of` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `about_index` (`about`),
KEY `is_format_of` (`is_format_of`)
) ENGINE=MyISAM AUTO_INCREMENT=463211 DEFAULT CHARSET=utf8;
"""
pass
class WikipediaLink(Base):
@ -171,11 +198,13 @@ class GluejarDB(object):
def rollback(self):
self.session.rollback()
def gutenberg_texts(self):
"""generator for all records in the GutenbergText table"""
items = self.session.query(GutenbergText).all()
for item in items:
yield item
def filtered_wikipedia_links(self):
"""generate wikipedia links that are in the main Wikipedia namespace"""
# eliminate pages in the TO_FILTER namespace
TO_FILTER = ['File:%', 'Portal:%', 'Portal talk:%', "Talk:%",
'Template:%', 'Template talk:%', 'User:%','User talk:%',
'Wikipedia:%', 'Wikipedia talk:%']
@ -742,14 +771,21 @@ def suite():
if __name__ == '__main__':
#walk through and parse catalogs
#walk_through_catalog(fname='/Users/raymondyee/D/Document/Gluejar/gutenberg/catalog_texts.rdf',max=100)
#walk_through_catalog(fname='/Users/raymondyee/D/Document/Gluejar/gutenberg/catalog_files.rdf',max=1000)
#load_texts_to_db(max=10)
#load_files_to_db(max=None)
#load_wikipedia_external_links_into_db(None)
#map_wikipedia_links_to_freebase_ids(None, page_size=10)
# in between: here we have to do some manual work in Google Refine
#map_refine_fb_links_to_openlibrary_work_ids(max=None)
#compute_ol_title_from_work_id(max=1000)
export_gutenberg_to_ol_mapping(fname="gutenberg_openlibrary.json")
import_gutenberg_json(fname="gutenberg_openlibrary.json")
#unittest.main()

View File

@ -0,0 +1,12 @@
from zoteroconf import user_id, user_key
# http://pyzotero.readthedocs.org/en/latest/index.html
from pyzotero import zotero
zot = zotero.Zotero(user_id, user_key)
items = zot.top()
for item in items:
try:
print 'Author: %s | Title: %s' % (item['creators'][0]['lastName'] if item['creators'] else '', item['title'])
except Exception, e:
print "Error: %s " % (e)

View File

@ -108,7 +108,7 @@ class MyZotero(Zotero2):
logger.info ("error adding ISBN %s: %s", isbn, e)
if __name__ == '__main__':
def get_unglue_collection():
zot = MyZotero()
#zot.compare_keys(24,7,3)
to_unglue = list(zot.items_in_unglue_it_collection())
@ -117,3 +117,18 @@ if __name__ == '__main__':
print b
#zot.upload_to_unglue_it('RaymondYee',5000)
#print zot.get_all_items()
def hello_world():
zot = Zotero(user_id, user_key)
items = zot.top()
for item in items:
try:
print 'Author: %s | Title: %s' % (item['creators'][0]['lastName'] if item['creators'] else '', item['title'])
except Exception, e:
print "Error: %s " % (e)
if __name__ == '__main__':
get_unglue_collection()

View File

@ -62,9 +62,10 @@ class RightsHolderForm(forms.ModelForm):
class ProfileForm(forms.ModelForm):
clear_facebook=forms.BooleanField(required=False)
clear_twitter=forms.BooleanField(required=False)
clear_goodreads=forms.BooleanField(required=False)
class Meta:
model = UserProfile
fields = 'tagline', 'librarything_id', 'home_url', 'clear_facebook', 'clear_twitter'
fields = 'tagline', 'librarything_id', 'home_url', 'clear_facebook', 'clear_twitter', 'clear_goodreads'
widgets = {
'tagline': forms.Textarea(attrs={'cols': 25, 'rows': 5}),
}
@ -201,3 +202,22 @@ class EmailShareForm(forms.Form):
# we can't rely on POST or GET since the emailshare view handles both
# and may iterate several times as it catches user errors, losing URL info
next = forms.CharField(widget=forms.HiddenInput())
class FeedbackForm(forms.Form):
sender = forms.EmailField(widget=forms.TextInput(attrs={'size':50}))
subject = forms.CharField(max_length=500, widget=forms.TextInput(attrs={'size':50}))
message = forms.CharField(widget=forms.Textarea())
page = forms.CharField(widget=forms.HiddenInput())
notarobot = forms.IntegerField(label="Please prove you're not a robot")
answer = forms.IntegerField(widget=forms.HiddenInput())
num1 = forms.IntegerField(widget=forms.HiddenInput())
num2 = forms.IntegerField(widget=forms.HiddenInput())
def clean(self):
cleaned_data = self.cleaned_data
notarobot = str(cleaned_data.get("notarobot"))
answer = str(cleaned_data.get("answer"))
if notarobot!=answer:
raise forms.ValidationError(_("Whoops, try that sum again."))
return cleaned_data

View File

@ -0,0 +1,52 @@
{% extends "basedocumentation.html" %}
{% block doccontent %}
<h2>About</h2>
<p><a href="http://unglue.it">Unglue.It</a> is a service provided by <a href="http://gluejar.com">Gluejar, Inc.</a> It's a place for individuals and institutions to join together to liberate specific ebooks and other types of digital content by paying rights holders to relicense their works under <a href="http://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="{{ faqurl }}">FAQs</a> and our <a href="{{ pressurl }}">press page</a>.
<h3>Team</h3>
<div class="pressimages">
<div class="outer">
<div><img src="/static/images/headshots/eric.jpg" class="mediaborder"></div>
<div class="text"><b>Eric Hellman</b>, President of Gluejar, 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="http://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"></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 will continue to represent original work by her literary agency clients.<br /><br />
Although our founding team will be be playing many roles at once, Amanda will be spending much of her time reaching out to 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. Her experience in both trade and academic publishing, together with her keen insight into the world of book rights, stood her above a lot of great people who expressed interest in working for Gluejar.</div>
</div>
<div class="outer">
<div><img src="/static/images/headshots/raymond.jpg" class="mediaborder"></div>
<div class="text"><b>Raymond Yee</b> is a data architect, author, consultant, and teacher. He is author of the leading book on web mashups, <a href="http://blog.mashupguide.net/2008/02/29/the-book-is-available-now/">Pro Web 2.0 Mashups: Remixing Data and Web Services</a> (published by Apress and licensed under a Creative Commons license), and has numerous blogs at his <a href="http://raymondyee.net/">personal site</a>. At the UC Berkeley School of Information, he taught Mixing and Remixing Information, a course on using APIs to create mashups. An open data and open government afficionado, he recently co-wrote three influential reports on how the US government can improve its efforts to make data and services available through APIs. Raymond served as the Integration Advisor for the Zotero Project (a widely used open source research tool) and managed the <a href="http://www.archive.org/details/zoterocommons">Zotero Commons</a>, a collaboration between George Mason University and the Internet Archive. Raymond has been an invited speaker about web technology at the Library of Congress, Fashion Institute of Technology, the O'Reilly Emerging Technology Conference, American Library Association, the Open Education conference, Code4lib, Educause, and NISO. While earning a Ph.D. in biophysics, he taught computer science, philosophy, and personal development to middle and high school students in the Academic Talent Development Program on the Berkeley campus. Raymond is an erstwhile tubaist, admirer of J. S. Bach, and son of industrious Chinese-Canadian restaurateurs.<br /><br />
At Gluejar, the interfaces we will expose to other websites and the data mash-ups we'll support will be just as important as the website itself. Expect that any webpage -- book blog, Facebook page, or library online catalog -- will be able to combine book data and user interaction with the effort of nudging the book towards Open Access. Raymond's expertise will make this possible.</div>
</div>
<div class="outer">
<div><img src="/static/images/headshots/andromeda.jpg" class="mediaborder"></div>
<div class="text"><b>Andromeda Yelton</b> is former Latin teacher and recent library science graduate (with a background in mathematics) who's quickly made a name for herself in the library world. She has a BA in Mathematics from Harvey Mudd College, an MA in Classics from Tufts, and recently completed her MLS from Simmons. She blogs at <a href="http://andromedayelton.com/">Across Divided Networks</a> and at <a href="http://www.alatechsource.org/blog">ALA TechSource</a>, and won the 2010 <a href="http://www.ala.org/ala/mgrps/divs/lita/litaresources/litascholarships/exlibris/index.cfm">LITA/Ex Libris Student Writing Award</a> for the article "A Simple Scheme for Book Classification Using Wikipedia". She is a 2011 American Library Association <a href="http://americanlibrariesmagazine.org/news/ala/ala-announces-2011-emerging-leader-participants">Emerging Leader</a>.
Andromeda was one of the leaders of the crowdsourced philanthropy project <a href="http://buyindiaalibrary.wordpress.com/">Buy India a Library</a>. She also has first-hand experience with public broadcasting- she was once a <a href="http://andromedayelton.com/2009/12/how-to-get-carl-kasells-voice-on-your-home-answering-machine/">listener contestant</a> on <a href="http://www.npr.org/programs/wait-wait-dont-tell-me/">Wait, Wait, Don't Tell Me</a>.</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

@ -5,6 +5,8 @@
{% url regluit.frontend.views.edit_user as editurl %}
{% url rightsholders as rhtoolsurl %}
{% url faq as faqurl %}
{% url about as abouturl %}
{% url press as pressurl %}
{% url rh_admin as adminurl %}
{% load truncatechars %}
@ -16,6 +18,8 @@
{% block extra_css %}{% endblock %}
{% block base_js %}
<script type="text/javascript" src="/static/js/jquery-1.6.3.min.js"></script>
{% endblock %}
{% block extra_js %}
{% endblock %}
<script type="text/javascript" src="/static/js/watermark_init.js"></script>
<script type="text/javascript" src="/static/js/watermark_change.js"></script>
@ -25,6 +29,9 @@
<body>
<div id="feedback">
<p><a href="/feedback/?page={{request.build_absolute_uri|urlencode:""}}" class="nounderline">Feedback</a></p>
</div>
<div id="js-page-wrap">
<div id="js-header">
<div class="js-main">
@ -51,9 +58,9 @@
{% else %}
<li class="first"><a href="{% url auth_login %}?next={% firstof request.path '/' %}"><span>Sign In</span></a></li>
{% endif %}
<li><a href="/stub/tour"><span>Tour</span></a></li>
<li><a href="{{ faqurl }}"><span>FAQs</span></a></li>
<li><a href="/stub/Help"><span>Help</span></a></li>
<li><a href="{{ pressurl }}"><span>Press</span></a></li>
<li><a href="/"><span>Home</span></a></li>
{% if not user.is_authenticated %}
<li class="last" id="expander"><a href="{% url registration_register %}"><span>sign up</span></a></li>
{% endif %}
@ -62,18 +69,23 @@
</div>
</div>
{% if is_preview %}
<div class="preview_top">
Welcome to the alpha version of Unglue.It. This site is a preview of our full functionality; some things (including pledging) aren't turned on yet. If something seems broken or confusing -- or if you find something you love! -- please give us <a href="/feedback">feedback</a>. Thank you for your interest, and have fun.
</div>
{% endif %}
{% block topsection %}{% endblock %}
{% block content %}{% endblock %}
</div>
<div id="footer">
<div class="js-main">
<div class="column">
<span>About unglue.it</span>
<ul>
<li><a href="/stub/about">About</a></li>
<li><a href="{{ abouturl }}">About</a></li>
<li><a href="http://www.gluejar.com/Blog">Blog</a></li>
<li><a href="/stub/press">Press</a></li>
<li><a href="/stub/newsletter">Newsletter</a></li>
<li><a href="{{ pressurl }}">Press</a></li>
<li><a href="http://eepurl.com/fKLfI">Newsletter</a></li>
</ul>
</div>
<div class="column">
@ -93,8 +105,8 @@
<div class="column">
<span>Help</span>
<ul>
<li><a href="{{faqurl}}">FAQ</a></li>
<li><a href="/stub/rhfaq">Rights Holder FAQ</a></li>
<li><a href="{{faqurl}}">General FAQ</a></li>
<li><a href="/faq/rightsholders/">Rights Holder FAQ</a></li>
<li><a href="{% url api_help %}">API</a></li>
<li><a href="mailto:support@gluejar.com">support@gluejar.com</a>
</ul>
@ -110,24 +122,20 @@
</div>
</div>
</div>
</div>
{% block counter %}
<script type="text/javascript">
var sc_project=7447776;
var sc_invisible=1;
var sc_security="9ae2ba93";
var sc_https=1;
var scJsHost = (("https:" == document.location.protocol) ?
"https://secure." : "http://www.");
document.write("<sc"+"ript type='text/javascript' src='" +
scJsHost +
"statcounter.com/counter/counter_xhtml.js'></"+"script>");</script>
<noscript><div class="statcounter"><a title="web
statistics" href="http://statcounter.com/free-web-stats/"
class="statcounter"><img class="statcounter"
src="https://c.statcounter.com/7447776/0/9ae2ba93/1/"
alt="web statistics" /></a></div></noscript>
{% endblock %}
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-28369982-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>{% endblock %}
</body>
</html>

View File

@ -1,6 +1,4 @@
{% extends "base.html" %}
{% block extra_head %}
<link href="/static/css/documentation.css" rel="stylesheet" type="text/css" />
{% extends "registration/registration_base.html" %}
{% block extra_js %}
<script type="text/javascript" src="/static/js/definitions.js"></script>
@ -8,7 +6,6 @@
{% block extra_extra_head %}
<!-- extra head content in descendants goes in extra_extra_head, not extra_head, to avoid overwriting the documentation.css include -->
{% endblock %}
{% endblock %}
{% block title %}{% endblock %}

View File

@ -1,4 +1,4 @@
<div class="thewholebook listview tabs {% if status == 'SUCCESSFUL' %}tabs-1{% else %}{% if status == 'ACTIVE' %}tabs-2{% else %}tabs-3{% endif %}{% endif %}">
<div class="thewholebook listview tabs {% if status == 'SUCCESSFUL' or work.first_pdf_url or work.first_epub_url %}tabs-1{% else %}{% if status == 'ACTIVE' %}tabs-2{% else %}tabs-3{% endif %}{% endif %}">
<div class="listview book-list">
<div class="listview panelback side2">
<div class="greenpanel2">
@ -123,24 +123,24 @@
<div class="listview panelfront side1 icons">
{% if status == 'No campaign yet' or status == 'INITIALIZED' %}
<span class="rounded"><span class="grey"><span class="panelnope">Wished by&nbsp;</span>{{ work.wished_by.count }}</span></span>
{% else %}{% if work.first_pdf_url or work.first_epub_url %}
<span class="listview boolist-ebook">
{% if work.first_epub_url %}
<a href="{{ work.first_epub_url }}">EPUB</a>
{% endif %}
{% if work.first_pdf_url %}
<a href="{{ work.first_pdf_url }}">PDF</a>
{% endif %}
</span>
{% else %}
<div class="booklist-status-img">
<img src="/static/images/images/icon-book-37by25-{{ work.percent_unglued }}.png" title="book list status" alt="book list status" />
</div>
<div class="booklist-status-label">{{ work.percent_unglued_number }}%</div>
{% endif %}
{% endif %}{% endif %}
</div>
<div class="listview panelfront side1 ebooks">
{% if work.first_epub_url %}
<span class="listview boolist-ebook">
<a href="{{ work.first_epub_url }}">epub</a>
</span>
{% endif %}
{% if work.first_pdf_url %}
<span class="listview boolist-ebook">
<a href="{{ work.first_pdf_url }}">pdf</a>
</span>
{% endif %}
</div>
<div class="unglue-this panelfront side1 none">
<div class="unglue-this-inner1">

View File

@ -27,10 +27,10 @@
<p>Target: {{campaign.target}}</p>
<p>Deadline: {{campaign.deadline}}</p>
<p>Status: {{campaign.status}}</p>
<p>ISBN: {{campaign.work.editions.all.0.isbn_10}} | {{campaign.work.editions.all.0.isbn_13}} </p>
<p><a href="{% url widget isbn=campaign.work.editions.all.0.isbn_13 %}">Widget Link</a></p>
<p>ISBN: {{campaign.work.editions.all.0.isbn_10}} | {{campaign.work.first_isbn_13}} </p>
<p><a href="{% url widget isbn=campaign.work.first_isbn_13 %}">Widget Link</a></p>
<p>Embed a widget:</p>
<textarea rows="2" cols="80">&lt;iframe src="{{base_url}}/api/widget/{{campaign.work.editions.all.0.isbn_13}}/" width="152" height="325" frameborder="0"&gt;</iframe></textarea>
<textarea rows="2" cols="80">&lt;iframe src="{{base_url}}/api/widget/{{campaign.work.first_isbn_13}}/" width="152" height="325" frameborder="0"&gt;</iframe></textarea>
<p>PayPal receiver: {{campaign.paypal_receiver}}</p>
<p>Current total (pledged/authorized): {{campaign.current_total}}</p>
<form method="POST" action="{% url campaign_by_id pk=campaign.id %}" onsubmit="test()">

View File

@ -75,15 +75,22 @@
</ul>
</div>
<div class="content-block-content">
{% if is_preview == 1 %}
<div class="preview_content">
We're not running campaigns during our alpha phase.<br /><br />
After we've implemented a few more features and improved the site based on <a href="/feedback">your feedback</a>, you'll see a list of active campaigns here.<br /><br />
If you're a rights holder and you'd like us to run campaigns to unglue your works, please contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>. Want more info? We have a <a href="/faq/rightsholders">rights holder FAQ</a>.
</div>
{% else %}
{% ifequal campaign_list.count 0 %}
There aren't any ungluing campaigns active. Poor Unglueman has nothing to do.
There aren't any ungluing campaigns active yet. If you're a rights holder, you can <a href="/faq/rightsholders">start one</a>.
{% else %}
{% paginate 20 campaign_list %}
{% for campaign in campaign_list %}
<div class="{% cycle 'row1' 'row2' %}">
{% with campaign.status as status %}
{% with campaign.deadline as deadline %}
{% with campaign.work.editions.all.0.googlebooks_id as googlebooks_id %}
{% with campaign.work.googlebooks_id as googlebooks_id %}
{% with campaign.work as work %}
{% include "book_panel.html" %}
{% endwith %}{% endwith %}{% endwith %}{% endwith %}
@ -94,6 +101,7 @@
{% show_pages %}
</div>
{% endifequal %}
{% endif %}
</div>
</div>
</div>

View File

@ -5,28 +5,35 @@
<ul class="menu level1">
<li class="first parent">
<a href="#"><span>Show me...</span></a>
<span>Show me...</span>
<ul class="menu level2">
{% if is_preview %}
<li class="first"><a href="{% url work_list 'recommended' %}"><span>Recommended</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>Popular</span></a></li>
<li><a href="{% url campaign_list 'pledges' %}"><span>Most pledges</span></a></li>
<li><a href="{% url campaign_list 'pledged' %}"><span>Biggest campaigns</span></a></li>
<li><a href="{% url unglued_list 'recent' %}"><span>Recently unglued</span></a></li>
<li><a href="{% url campaign_list 'ending' %}"><span>Ending Soon</span></a></li>
<li class="last"><a href="{% url campaign_list 'newest' %}"><span>Just Listed</span></a></li>
<li class="last"><a href="{% url work_list 'new' %}"><span>Newly Wished</span></a></li>
{% else %}
<li class="first"><a href="{% url work_list 'recommended' %}"><span>Recommended</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>Popular</span></a></li>
<li><a href="{% url campaign_list 'pledges' %}" class="comingsoon"><span>Most pledges</span></a></li>
<li><a href="{% url campaign_list 'pledged' %}" class="comingsoon"><span>Biggest campaigns</span></a></li>
<li><a href="{% url unglued_list 'recent' %}" class="comingsoon"><span>Recently unglued</span></a></li>
<li><a href="{% url campaign_list 'ending' %}" class="comingsoon"><span>Ending Soon</span></a></li>
<li class="last"><a href="{% url campaign_list 'newest' %}" class="comingsoon"><span>Just Listed</span></a></li>
{% endif %}
</ul>
</li>
<li class="parent">
<a href="#"><span>Ungluers</span></a>
<span>Ungluers</span>
<ul class="menu level2">
{% for ungluer in ungluers %}
<li class="first"><a href="{% url supporter supporter_username=ungluer %}">
{% if ungluer.profile.pic_url %}
<img src="{{ungluer.profile.pic_url}}" height="30" width="30" alt="{{ungluer}}" title="{{ungluer}}" />
<img src="{{ungluer.profile.pic_url}}" height="30" width="30" alt="{{ungluer}}" title="{{ungluer}}" /></a>
{% else %}
<img src="/static/images/header/avatar.png" height="30" width="30" alt="Generic Ungluer Avatar" />
<img src="/static/images/header/avatar.png" height="30" width="30" alt="Generic Ungluer Avatar" /></a>
{% endif %}
<span class="ungluer-name">{{ungluer|truncatechars:20}}</span>
<a href="{% url supporter supporter_username=ungluer %}"><span class="ungluer-name">{{ungluer|truncatechars:20}}</span>
</a></li>
{% endfor %}
</ul>

View File

@ -5,12 +5,14 @@
<h2>Frequently Asked Questions</h2>
<dl>
<dt>Help! My question isn't covered in the FAQs!</dt>
<dt class="background: #8dc63f; color: white; font-weight: bold;">Help! My question isn't covered in the FAQs!</dt>
<dd>Please email us at <a href="mailto:support@gluejar.com">support@gluejar.com</a>. Especially during our alpha phase, we want to make sure everything on the site runs as smoothly as possible. Thanks for helping us do that.</dd>
{% if location == 'basics' or location == 'faq' %}
<h3>How It Works</h3>
<h3>Basics</h3>
{% if sublocation == 'howitworks' or sublocation == 'all' %}
<h4>How It Works</h4>
<dt>What is Unglue.It?</dt>
<dd>TBA</dd>
@ -23,13 +25,13 @@
<dd>Unglue.It is free to join. Most of the things you can do here -- discovering books, adding them them to your wishlist, commenting, sharing -- are free too. If you choose to support a campaign, you may pledge whatever amount you're comfortable with.<br /><br />
If you're a rights holder, starting campaigns is free, too. You only pay Unglue.It if your campaign succeeds. For more details, see the FAQs on <a href="/faq/starting/">Starting a Campaign</a>.</dd>
If you're a rights holder, starting campaigns is free, too. You only pay Unglue.It if your campaign succeeds. 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>
<dd>Anyone! We're located in the United States, but we welcome participants from all over the world.<br /><br />
To fund a campaign, 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 on <a href="/faq/starting/">Starting a Campaign</a> for more.</dd>
To fund a campaign, 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>
<dt>Does Unglue.It own the copyright of unglued books?</dt>
@ -39,11 +41,30 @@ Ungluing involves licensing rights, just like traditional publishing transaction
If you are a copyright holder, you will retain your copyright when you unglue a book. CC licenses are non-exclusive, so you also retain the right to enter into separate licensing agreements. You can read more about these licenses at the <a href="http://wiki.creativecommons.org/Frequently_Asked_Questions">Creative Commons FAQ</a>.</dd>
<dt>If I'm a rights holder and I unglue my book, 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!<br /><br />
For some examples of how authors and publishers have made free ebooks work for their business plans, see (TBA: with a little help, o'reilly, baen, ...?) If unglued books are working for you, we'd love to hear your story. Tell us at <a href="mailto:support@gluejar.com">support@gluejar.com</a>.</dd>
<dt>Why do rights holders want to unglue their books?</dt>
<dd>Lots of reasons! Unglued ebooks may be part of a marketing strategy to publicize other books or increase the value of an author's brand. Or they may be books that are no longer selling through conventional channels, and ungluing them is a low-risk way to get some extra value out of them. Or ungluing provides a simple digital strategy that pays for itself. Or the books may have been written to advance a cause, add to the public conversation, or increase human knowledge. These books succeed by having as many readers as possible.</dd>
<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 wishlist, so we know it should be on our list, too. You can also contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a></dd>
<dt>I know a book that should be unglued, and I own its electronic rights.</dt>
<dd>Fabulous! Please refer to the FAQ <a href="/faq/rightsholders/">for Rights Holders</a> and then contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</dd>
<dt>Is there a widget that can be put on my own site to share my favorite books or campaigns?</dt>
<dd>Yes! Every book page has an "Embed" option. Click that and copy/paste the HTML code wherever you like.</dd>
<h3>Your Account</h3>
{% endif %}
{% if sublocation == 'account' or sublocation == 'all' %}
<h4>Your Account</h4>
<dt>I forgot my password. What do I do?</dt>
There's a <a href="/accounts/password/reset/">forgot your password</a> link at the bottom of the <a href="/accounts/login/">sign in page</a>. Enter the email address you use for your account and we'll send you an email to help you reset your password.
@ -60,7 +81,7 @@ There's a <a href="/accounts/password/reset/">forgot your password</a> link at t
Click on "My Settings" near the top of your user profile page. (If you're logged in, your profile page is <a href="/">here</a>.) Some new options will appear. You can enter your personal URL and your LibraryThing username here. There are one-click options to connect your Twitter, Facebook, and GoodReads accounts. (You'll be asked to log in to those accounts, if you aren't logged in already.) Make sure to click on "Save Settings".
<dt>Why should I connect those account?</dt>
<dt>Why should I connect those accounts?</dt>
<dd>If you connect your Facebook or Twitter accounts, we'll use your user pics from those sites for your avatar here. If you connect LibraryThing or GoodReads, you can import your books there onto your wishlist here, using the My Settings area.</dd>
@ -85,50 +106,36 @@ If you receive our newsletter, there's a link at the bottom of every message to
<dt>Is my personal information shared?</dt>
<dd>Short version: no. You may share information with rights holders so they can deliver rewards to you, but this is not required. For the long version, please read our <a href="/privacy/">privacy policy</a>.
<h3>The Company</h3>
<dd>Short version: no. You may share information with Rights Holders so they can deliver premiums to you, but this is not required. For the long version, please read our <a href="/privacy/">privacy policy</a>.
{% endif %}
{% if sublocation == 'company' or sublocation == 'all' %}
<h4>The Company</h4>
<dt>How can I contact Unglue.It?</dt>
<dd>For support requests, <a href="mailto:support@gluejar.com">support@gluejar.com</a>. For general inquiries, use our Ask Questions Frequently account, <a href="mailto:aqf@gluejar.com">. For rights inquiries, <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</dd>
<dd>For support requests, <a href="mailto:support@gluejar.com">support@gluejar.com</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>.</dd>
<dt>Who is Unglue.It?</dt>
<dd>We are <a href="http://go-to-hellman.blogspot.com">Eric Hellman</a>, <a href="http://www.ameckeco.com/">Amanda Mecke</a>, <a href="http://raymondyee.net/">Raymond Yee</a>, and <a href="http://andromedayelton.com">Andromeda Yelton</a>. (TBA: include contractors?) 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 personal home pages (linked above) or <a href="http://gluejar.com/team">the team page</a> of our corporate site.</dd>
<dd>We are <a href="http://go-to-hellman.blogspot.com">Eric Hellman</a>, <a href="http://www.ameckeco.com/">Amanda Mecke</a>, <a href="http://raymondyee.net/">Raymond Yee</a>, and <a href="http://andromedayelton.com">Andromeda Yelton</a>, with help from designer <a href="http://www.vonfab.com/">Stefan Fabry</a> and software developers Jason Kace and <a href="http://inkdroid.org/">Ed Summers</a>. 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 personal home pages (linked above) or <a href="http://gluejar.com/team">the team page</a> of our corporate site.</dd>
<dt>Are you a non-profit company?</dt>
<dd>No. Gluejar is a for-profit company with a public-spirited mission. We work with both non-profit and commercial partners.</dd>
<dt>Why does Unglue.It exist?</dt>
<dd>TBA</dd>
{% endif %}
{% if location == 'starting_campaigns' or location == 'faq' %}
If you were on the Starting Campaigns or general FAQ page, you'd be able to read this. Oh hey! You are!<br />
{% endif %}
{% if location == 'supporting_campaigns' or location == 'faq' %}
If you were on the Supporting Campaigns or general FAQ page, you'd be able to read this. Oh hey! You are!<br />
{% endif %}
{% if location == 'campaigns' or location == 'faq' %}
<h3>Campaigns</h3>
{% if sublocation == 'overview' or sublocation == 'all' %}
<h4>Overview</h4>
{% if location == 'unglued_ebooks' or location == 'faq' %}
If you were on the Unglued Ebooks or general FAQ page, you'd be able to read this. Oh hey! You are!<br />
{% endif %}
<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 wishlist, so we know it should be on our list, too. You can also contact us: <!-- info? --></dd>
<dt>I know a book that should be unglued, and I own its electronic rights.</dt>
<dd>Fabulous! Please refer to the <a href="/stub/rhfaq">Rights Holder FAQ</a> and then contact us.</dd><!-- which contact info? -->
<dt>How much does a book cost?</dt>
<dd>The author or publisher set a price for giving the book to the world. Once you and your fellow ungluers raise enough money to meet that price, the Unglued ebook is available at no charge, for everyone, everywhere!</dd>
<dt>Are the unglued ebooks compatible with my reader?</dt>
<dd>Unglued ebooks are distributed with NO DRM, so they'll work on Kindle, iPad, Kobo, Mac, Windows, Linux... you get the idea. And if the ePub format isn't best for your device, you're free to shift unglued books to a format that works better for you.</dd>
{% comment %}
These need to be put in proper order. Also this should be broken down into the general FAQ and the RH FAQ; only the basic create/manage campaign, and the supporter mechanisms of interacting with campaigns, should be in the basic FAQ; more technical questions about running campaigns should be in the RH FAQ.
<dt>Why does it say a book's campaign has been suspended or withdrawn?</dt>
@ -139,11 +146,386 @@ If you were on the Unglued Ebooks or general FAQ page, you'd be able to read thi
<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>What if I want to change or cancel a pledge?</dt>
{% endcomment %}
<dt>Who is eligible to start a campaign 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. If you hold the electronic rights for one or more works, please contact <a href="mailto:rights@gluejar.com">rights@gluejar.com</a> to start the process.</dd>
<dt>How can I claim a work for which I am the rights holder?</dt>
<dd>On every book page there is a Details tab. If you have a signed Platform Services Agreement on file, one of the options on the Details tab will be "Claim This Work". If you represent more than one rights holder, choose the correct one for this work and click "Claim".<br /><br />If you expect to see that and do not, either we do not have a PSA from you yet, or we have not yet verified and filed it. Please contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</dd>
<dt>Why should I claim my works?</dt>
<dd>You need to claim a work before you will be able to start a campaign for it. Additionally, we're working new 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>Can I have more than one campaign?</dt>
<dd>Yes!</dd>
<dt>Where can I find a campaign page?</dt>
<dd>If you're looking for a specific book, search for it. The book's page becomes a campaign page when its campaign starts. Your bookmarks and widgets for that book will still work.<br /><br />
If you want to find an interesting campaign and don't have a specific book in mind, see the Explore sidebar (on almost any page except the FAQs) for some of our favorites.</dd>
<h4>Campaign Pages</h4>
<dt>Can a campaign be edited after launching? (combine with changing funding goal/deadline?)</dt>
<dd>TBA</dd>
<dt>Can campaigns be edited after funding is completed?</dt>
<dd>TBA</dd>
<dt>What is a campaign page?</dt>
<dd>TBA</dd>
{% endif %}
{% if sublocation == 'funding' or sublocation == 'all' %}
<h4>Funding</h4>
<dt>Is a Paypal account required to launch a campaign?</dt>
<dd>TBA</dd>
<dt>Are a funding goal and deadline required?</dt>
<dd>Yes.</dd>
<dt>Can I change my funding goal?</dt>
<dd>TBA</dd>
<dt>Can I change my deadline?</dt>
<dd>TBA</dd>
<dt>Can funding be canceled?</dt>
<dd>TBA</dd>
<dt>Can I offer tax deductions to people who pledge to my campaign?</dt>
<dd>TBA</dd>
<dt>Are contributions refundable?</dt>
<dd>TBA</dd>
<dt>How do I know when I have started fundraising?</dt>
<dd>TBA</dd>
<dt>How do I collect money for my campaign? (include registering & verifying info as needed)</dt>
<dd>TBA</dd>
<dt>Where can I check the status of my funds?</dt>
<dd>TBA</dd>
<dt>What happens if my campaign reaches its funding goal before its deadline? Can campaigns raise more money than their goal?</dt>
<dd>TBA</dd>
<dt>Can people contribute anonymously?</dt>
<dd>We intend to implement this feature, but have not yet.</dd>
<dt>What happens if a supporter's credit card is declined?</dt>
Can campaigns raise more money than their goal?
<dt>What fees does Unglue.It charge?</dt>
<dd>TBA</dd>
<dt>Does Paypal charge any fees?</dt>
<dd>TBA</dd>
<dt>Does it cost money to start a campaign on Unglue.It?</dt>
<dd>No.</dd>
<dt>I'm having problems verifying my account with Paypal. What do I need to do?</dt>
<dd>TBA</dd>
<dt>Will Paypal send me a 1099-K tax form?</dt>
<dd>TBA</dd>
<dt>What happens to my pledge if a 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>
{% endif %}
{% if sublocation == 'premiums' or sublocation == 'all' %}
<h4>Premiums</h4>
<dt>What are premiums?</dt>
<dd>Premiums are bonuses people get for supporting a successful campaign, to thank them and incentivize them to pledge. If you've ever gotten a tote bag from NPR, you know what we're talking about.</dd>
<dt>Are premiums required?</dt>
<dd>Yes. All campaigns have a required set of premiums, as follows:<br /><br />
<ul>
<li>$1 // The unglued ebook delivered to your inbox.</li>
<li>$25 // Your name under "supporters" in the acknowledgements section.</li>
<li>$50 // Your name and link of your choice under "benefactors"</li>
<li>$100 // Your name, link of your choice, and a brief message (140 characters max) under "bibliophiles"</li>
</ul>
Rights holders are encouraged to offer additional premiums to engage supporters' interest. Think about things that uniquely represent yourself or your work, that are appealing, and that you can deliver quickly and within your budget.</dd>
<dt>What can be offered as a premium? What items are prohibited as premiums?</dt>
<dd>TBA</dd>
<dt>Who is responsible for making sure Rights Holders deliver premiums?</dt>
<dd>The rights holder. (TBA: is this correct? do we need to say more?</dd>
<dt>Who creates the premiums for each campaign?</dt>
<dd>Unglue.It determines the required premiums; the rights holder determines any additional premiums for a campaign.</dd>
<dt>Is there a minimum or maximum for how much a premium can cost?</dt>
<dd>TBA</dd>
<dt>How can I get my supporters' information (mailing address, T-shirt size, etc.) to fulfill premiums?</dt>
<dd>TBA</dd>
(TBA: questions about specifying estimated delivery dates/what the process timeline should be)
{% endif %}
{% endif %}
{% if location == 'unglued_ebooks' or location == 'faq' %}
<h3>Unglued Ebooks</h3>
{% if sublocation == 'general' or sublocation == 'all' %}
<h4>General Questions</h4>
<dt>So what is an unglued ebook?</dt>
<dd>An unglued ebook is one that's been released under a <a href="http://creativecommons.org">Creative Commons</a> license, after obtaining permission and compensating the rights holder.<br /><br />
What does this mean for you? If you're a book lover, you can read unglued ebooks for free, on the device of your choice, in the format of your choice, and share them with all your friends. If you're a library, you can lend them to your patrons with no checkout limits or simultaneous user restrictions, and preserve them however you think best. If you're a rights holder, you get a guaranteed up-front payment in lieu of royalties on this edition, while retaining copyright and all interests in other editions of your work.<br /><br />
Unglued ebooks are a win-win solution for readers, libraries, and authors, publishers.
</dd>
<dt>How much does a book cost?</dt>
<dd>The author or publisher set a price for giving the book to the world. Once you and your fellow ungluers raise enough money to meet that price, the Unglued ebook is available at no charge, for everyone, everywhere!</dd>
<dt>Will Unglue.It have campaigns for all kinds of books?</dt>
<dd>Yes. You choose what books to wishlist and what books to support. Your passion will drive the campaigns. We aim to host campaigns for diverse subjects genres, from romance novels to poetry, from history to medicine, from the lectures of famous scentists to your favorite chapter book when you were 8 years old.</dd>
<dt>Will you raise money for books already for sale as an ebook?</dt>
<dd>Yes. Books available as ebooks can still be unglued, to make them available more easily to more people.</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.</dd>
<dt>Does Gluejar help to fund self-published books?</dt>
<dd>No.</dd><!-- should we provide pointers to other resources? encourage people to CC-license their own works?-->
<dd>No.</dd>{% comment %}should we provide pointers to other resources? encourage people to CC-license their own works?{% endcomment %}
{% endif %}
{% if sublocation == 'using' or sublocation == 'all' %}
<h4>Using Your Unglued Ebook</h4>
<dt>What can I do, legally, with an unglued ebook? What can I NOT do?</dt>
<dd>Unless otherwise specified for specific books, unglued ebooks are released under a (TBA:link)Creative Commons Attribution-NonCommercial-NoDerivatives license (CC BY-NC-ND). (TBA: improve with input from CC site)<br /><br />
This means that 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 />
You <b>cannot</b>: sell them, or otherwise use them commercially, without permission from the rights holder; make derivative works, such as translations or movies, without permission from the rights holder; 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>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 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. And if the ePub format isn't best for your device, you're free to shift unglued books to a format that works better for you. (TBA: I think ePub does NOT work on Kindle so we need to address that, & how people can deal)</dd>
<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!) While your library may make unglued ebooks available, they will be available from other sources as well.</dd>
<dt>How long do I have to read my unglued book? When does it expire?</dt>
<dd>It doesn't! 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.</dd>
<dt>I love my unglued ebook and I want to loan it to my friends. Can I?</dt>
<dd>Yes! Because everything with a <a href="http://creativecommons.org">Creative Commons</a> license is free to copy and share, you can copy your unglued ebook file and give it to anyone you want. You can all read and discuss it on Good Reads or LibraryThing at the same time. The Rights Holder has given you permission to spread the word to readers everywhere.<br /><br />
Just don't sell, adapt, or abridge it. Unglued ebooks are only licensed for noncommercial use. Of course, you're welcome to still buy other editions -- paperbacks, hardcovers, or audio -- too.</dd>
<dt>Will I be able to dowload an unglued ebook directly from Unglue.It?</dt>
<dd>Unglue.It will provide links to places where you can find unglued and public domain ebooks. We will not host them ourselves. We encourage you to look for them at bookstores, libraries, literary blogs, social reading sites, and all kinds of places people go to read and talk about books.</dd>
{% endif %}
{% if sublocation == 'copyright' or sublocation == 'all' %}
<h4>Ungluing and Copyright</h4>
<dt>Why does an unglued book have to be in copyright?</dt>
<dd>Because books out of copyright are already free for you to copy, remix, and share! If a book is in the (TBA: link) public domain, it's already unglued.</dd>
<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. (TBA: link, library copyright slider; other?) In the United States, the Library Copyright Slider gives a quick estimate. (TBA: more links for people who want to learn more?)<br /><br />
Unglue.It signs agreements concerning the copyright status of every work we unglue, so you can be confident when reading and sharing an unglued ebook.</dd>
{% endif %}
{% endif %}
{% if location == 'rightsholders' %}
<h3>For Rights Holders</h3>
{% if sublocation == 'authorization' or sublocation == 'all' %}
<h4>Becoming Authorized</h4>
<dt>What do I need to do to become an authorized Rights Holder on Unglue.it?</dt>
<dd>Contact Amanda Mecke, <a href="mailto:amecke@gluejar.com">amecke@gluejar.com</a>, to discuss signing our Platform Services Agreement. This is the first step in being able to make an offer to license, set a monetary goal, and run a campaign on Unglue.it.</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 to release an ebook using the <a href="http://creativecommons.org">Creative Commons</a> license. You can decide which specific titles you wish to make available for licensing later. You can run campaigns for as many, or as few, titles at a time as you like.</dt>
{% endif %}
{% if sublocation == 'campaigns' or sublocation == 'all' %}
<h4>Launching Campaigns</h4>
<dt>What do I need to create a campaign?</dt>
<dd>TBA</dd>
<dt>How do I launch my first campaign?</dt>
<dd>TBA</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 goal.</dd>
<dt>Can raise any amount of money I want?</dt>
<dd>You can set any goal that you want in a Campaign. Unglue.It cannot guarantee that a goal will be reached.</dd>
<dt>What should I include in my campaign page?</dt>
<dd>Show us why we should love your book. What makes it powerful, intriguing, moving, thought-provoking, important? How does it capture readers' imaginations, engage their minds, or connect to their emotions? Remind readers who loved the book why they loved it, and give people who haven't read it reasons they want to.<br /><br />
We strongly encourage you to include video. You can upload it to YouTube and embed it here. We also strongly encourage links to help readers explore further -- authors' home pages, organizations which endorse the book, positive reviews, et cetera. Think about blurbs and awards which showcase your book. But don't just write a catalog entry or a laundry list: be relatable, and be concise.</dd>
<dt>What should I ask my supporters to do?</dt>
<dd>(TBA: support campaign and share the word)</dd>
{% endif %}
{% if sublocation == 'publicity' or sublocation == 'all' %}
<h4>Publicizing Campaigns</h4>
<dt>I need help using social media channels to publicize my campaign.</dt>
<dd>We're developing a social media toolkit for rights holders. Please tell us what you think should be in it so we can serve you better: <a href="mailto:andromeda@gluejar.com">andromeda@gluejar.com</a>. In the meantime we're happy to help you one-on-one, at the same address.</dd>
<dt>Can I contact my supporters directly?</dt>
<dd>TBA</dd>
<dt>How do I get feedback from supporters or fans about my campaign?</dt>
<dd>TBA</dd>
<dt>How do I share my campaign?</dt>
<dd>(TBA: use widget, share functions on page, own social media networks, friends, their friends...)</dd>
<dt>How can I get my campaign featured on the home page or the Explore menu pages?</dt>
<dd>TBA</dd>
<dt>How can I get press coverage or social media buzz for my campaign?</dt>
<dd>We're developing a social media toolkit to help you with this; stay tuned. We're also happy to work with you one-on-one. Email <a href="mailto:support@gluejar.com">support@gluejar.com</a>.
{% endif %}
{% if sublocation == 'conversion' or sublocation == 'all' %}
<h4>Ebook Conversion</h4>
<dt>I am a copyright holder. Do I need to already have a digital file for a book I want to nominate for a pledge drive?</dt>
<dd>No. You may run campaigns for any of your books, even those 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 unglued ebook.</dd>
<dt>Will Unglue.It scan the books and produce ePub files?</dt>
<dd>No. We will help you find third parties who will contract for conversion services. (TBA: is this true?)</dd>
<dt>Will Unglue.It raise money to help pay for conversion costs?</dt>
<dd>Yes. Your campaign target should include conversion costs.</dd>
{% endif %}
{% if sublocation == 'rights' or sublocation == 'all' %}
<h4>Rights</h4>
<dt>If I am an author with my book still in print, can I still start a Campaign to unglue it?</dt>
<dd>This depends entirely on your original contract with your publisher. You should seek independent advice about your royalty clauses and the "Subsidiary Rights" clauses of your contract. The Authors' Guild provides guidance for its members.</dd>
<dt>If I am an author do I have to talk to my publisher or agent first?</dt>
<dd>It is your responsibility to get advice on the current status of any contracts you may have ever had for the right to publish your work, whether or not a book is in print now. <a href="http://creativecommons.org">Creative Commons</a> licenses are media neutral and worldwide (English). You may need waivers from other parties who have exclusive licenses for this book.</dd>
<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>
<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>
<dt>Is the copyright holder the same as a Rights Holder?</dt>
<dd>Not necessarily. If you are the author and copyright holder but have a contract with a publisher, the publisher may be the authorized Rights Holder with respect to electronic rights, and they may be able to sign a licensing agreement on your behalf. Again, you must get advice about your individual contract, especially subsidiary rights clauses and exclusivity.</dd>
Questions about the Unglue.it license
<dt>Can I offer a book to be Unglued even if I cannot include all the illustrations from the print edition?</dt>
<dd>Yes. If permission to reprint cannot not be obtained for items such as photographs, drawings, color plates, as well as quotations from lyrics and poetry, we can produce an unglued edition which leaves them out. Please indicate the difference between the editions on your Campaign page.</dd>
<dt>What impact does ungluing a book have on the rights status of my other editions?</dt>
<dd>The Creative Commons license is non-exclusive and will apply only to the unglued edition, not to the print, audio, or any other editions. It does not affect the rights status of those other editions.</dd>
<dt>Can an Unglued Ebook be issued only in the US?</dt>
<dd>No. An Unglued Ebook is released to the world. It uses a <a href="http://creativecommons.org">Creative Commons</a> license which is territory-neutral. That is, the unglued ebook can be read by anyone, anywhere in the world, subject always to the non-commercial limitations of the license.</dd>
{% endif %}
<dt>What is my responsibility for answering questions from supporters and non-supporters?</dt>
<dd>TBA</dd>
<dt>If I am unable to complete my campaign as listed, what should I do?</dt>
<dd>TBA</dd>
</dl>
{% endif %}
{% endblock %}
{% comment %}
-- stuff that needs to be organized --
Will you tell me when campaigns I have pledged to succeed?
What does it mean when I wish for a book to be Unglued?
Can I share my wish list with others?
I see where I can comment on a campaign, but where can I talk about it with other readers?
See our links to Goodreads, Library Thing...etc where you can join conversations in progress.
-- stuff that will only go on the RH FAQ (if it's above, needs to be copy/pasted over to there)
Questions about becoming an authorized Rights Holder
Does a Creative Commons license mean the Rights Holders doesn't get paid?
No. An Unglued Ebook is pre-paid. A CC BY NC ND license means no one else can sell or adapt the ebook for profit. The Rights Holder can set as high a fee as wanted as a fundraising goal, instead of waiting decades for royalties.
<dt>How do I keep track of my contributors and the fulfillment of premiums?</dt>
<dd>TBA</dd>
{% endcomment %}

View File

@ -6,38 +6,38 @@
<li class="first parent {% if location != 'basics' %}collapse{% endif %}">
<a href="/faq/basics/"><span>Basics</span></a>
<ul class="menu level2">
<li class="first"><a href="{% url work_list 'recommended' %}"><span>How it Works</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>Your Account</span></a></li>
<li class="last"><a href="{% url campaign_list 'newest' %}"><span>The Company</span></a></li>
<li class="first"><a href="/faq/basics/howitworks"><span>How it Works</span></a></li>
<li><a href="/faq/basics/account"><span>Your Account</span></a></li>
<li class="last"><a href="/faq/basics/company/"><span>The Company</span></a></li>
</ul>
</li>
<li class="parent {% if location != 'starting_campaigns' %}collapse{% endif %}">
<a href="/faq/starting_campaigns/"><span>Starting Campaigns</span></a>
<li class="parent {% if location != 'campaigns' %}collapse{% endif %}">
<a href="/faq/campaigns/"><span>Campaigns</span></a>
<ul class="menu level2">
<li class="first"><a href="{% url work_list 'recommended' %}"><span>The Basics</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>Funding</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>Rewards</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>Campaign Pages</span></a></li>
<li class="last"><a href="{% url campaign_list 'newest' %}"><span>Supporter Relationships</span></a></li>
</ul>
</li>
<li class="parent {% if location != 'supporting_campaigns' %}collapse{% endif %}">
<a href="/faq/supporting_campaigns/"><span>Supporting Campaigns</span></a>
<ul class="menu level2">
<li class="first"><a href="{% url work_list 'recommended' %}"><span>Stuff</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>More Stuff</span></a></li>
<li class="last"><a href="{% url campaign_list 'newest' %}"><span>Totally Spurious Stuff</span></a></li>
<li class="first"><a href="/faq/campaigns/overview"><span>Overview</span></a></li>
<li><a href="/faq/campaigns/funding"><span>Funding</span></a></li>
<li class="last"><a href="/faq/campaigns/premiums"><span>Premiums</span></a></li>
</ul>
</li>
<li class="parent {% if location != 'unglued_ebooks' %}collapse{% endif %}">
<a href="/faq/unglued_ebooks/"><span>Unglued Ebooks</span></a>
<ul class="menu level2">
<li class="first"><a href="{% url work_list 'recommended' %}"><span>DRM is lame</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>CC is awesome</span></a></li>
<li class="last"><a href="{% url campaign_list 'newest' %}"><span>These Links Also Spurious</span></a></li>
<li class="first"><a href="/faq/unglued_ebooks/general"><span>General Questions</span></a></li>
<li><a href="/faq/unglued_ebooks/using"><span>Using Your Unglued Ebook</span></a></li>
<li class="last"><a href="/faq/unglued_ebooks/copyright"><span>Ungluing and Copyright</span></a></li>
</ul>
</li>
<li class="parent {% if location != 'rightsholders' %}collapse{% endif %}">
<a href="/faq/rightsholders/"><span>For Rights Holders</span></a>
<ul class="menu level2">
<li class="first"><a href="/faq/rightsholders/authorization"><span>Becoming Authorized</span></a></li>
<li><a href="/faq/rightsholders/campaigns"><span>Launching Campaigns</span></a></li>
<li><a href="/faq/rightsholders/publicity"><span>Publicizing Campaigns</span></a></li>
<li><a href="/faq/rightsholders/conversion"><span>Ebook Conversion</span></a></li>
<li class="last"><a href="/faq/rightsholders/rights/"><span>Rights</span></a></li>
</ul>
</li>

View File

@ -0,0 +1,31 @@
{% extends "basedocumentation.html" %}
{% block title %}Feedback{% endblock %}
{% block doccontent %}
<p>Love something? Hate something? Found something broken or confusing? Thanks for telling us!</p>
To: support@gluejar.com<br /><br />
<form method="POST" action="/feedback/">
{% csrf_token %}
{{ form.sender.label_tag }}</br>
{{ form.sender.errors }}
{{ form.sender }}</br><br />
{{ form.subject.label_tag }}</br>
{{ form.subject.errors }}
{{ form.subject }}</br><br />
{{ form.message.label_tag }}</br>
{{ form.message.errors }}
{{ form.message }}</br><br />
{{ form.notarobot.errors }}
{{ form.non_field_errors }}
Please prove you're not a robot. {{num1}} + {{num2}} =
{{ form.notarobot }}</br /><br />
{{ form.answer }}
{{ form.num1 }}
{{ form.num2 }}
{{ form.page }}
<input type="submit" value="Submit" />
</form>
{% endblock %}

View File

@ -98,7 +98,7 @@ var $j = jQuery.noConflict();
</div>
<div style="clear:right;">
<div class="item-content">
<h2 class="page-heading">Give books to the world.</h2>
<h2 class="page-heading">Give ebooks to the world.</h2>
</div>
<div class="jsmod-content">
<img src="/static/images/landingpage/icon-group.png" alt="How does it work?" title="How does it work?" height="269" width="268" />
@ -116,7 +116,7 @@ var $j = jQuery.noConflict();
</div>
<div id="js-maincontainer-faq">
<div class="js-maincontainer-faq-inner">
Questions? Read our <a href="/stub/tour">FAQs</a> or take the <a href="/stub/tour">Tour</a>
Questions? Read our <a href="/faq/">general FAQ</a> or <a href="/faq/rightsholders/">FAQ for rights holders</a>.</a>
</div>
</div>
</div>
@ -177,7 +177,7 @@ var $j = jQuery.noConflict();
<div id="js-slide">
<div class="js-main">
<div class="jsmodule">
<h3><span>Top ungluing campaigns</span></h3>
<h3><span>{% if is_preview %}Most wishlisted{% else %}Top ungluing campaigns{% endif %}</span></h3>
<div class="jsmod-content">
<div id="js-slideshow">
<div class="slides_container">

View File

@ -12,7 +12,7 @@
<div><span class="def">unglue</span> (v. t.) 5. To give your favorite books to everyone on earth.</div>
<div><span class="def">unglue</span> (v. t.) 6. To reward authors and publishers for sharing books with the world.</div>
</div>
<a class="my-setting readon"><span>Learn more</span></a>
<a class="my-setting readon nounderline"><span>Learn more</span></a>
</div>
</div>

View File

@ -23,7 +23,7 @@ Please fix the following before launching your campaign:
<div class="book-detail-info">
<h2 class="book-name">Title: <a href="{% url work campaign.work.id %}">{{ campaign.work.title }}</a></h2>
<h3 class="book-author">Authors: {{ campaign.work.author }}</h3>
<h3 class="book-year">Published: {{ campaign.work.editions.all.0.publication_date }}</h3>
<h3 class="book-year">Published: {{ campaign.work.publication_date }}</h3>
<h3 class="book-author">Language: {{ campaign.work.editions.all.0.language }}</h3>
<p>Target Price: {{ campaign.pretarget }}</p>
<p>End Date: {{ campaign.predeadline }}</p>

View File

@ -9,7 +9,7 @@
{% block doccontent %}
<h2>Campaign: {{campaign.name}}</h2>
<p>Wonderful: We're glad that you would like to support this campaign.</p>
<div class="thank-you">Thank you!</div>
<div class="book-detail">
<div class="book-detail-img"><a href="#">
@ -18,7 +18,7 @@
<div class="book-detail-info">
<h2 class="book-name">{{ work.title }}</h2>
<h3 class="book-author">{{ work.author }}</h3>
<h3 class="book-year">{{ work.editions.all.0.publication_date }}</h3>
<h3 class="book-year">{{ work.publication_date }}</h3>
<div class="find-book">
<label>Find it here</label>
<!-- todo: these should be a real thing -->

View File

@ -0,0 +1,23 @@
{% extends "basedocumentation.html" %}
{% block title %}Pledge Cancelled{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
{% endblock %}
{% block doccontent %}
{% if transaction %}
<div>You were about to pledge ${{transaction.amount}} to <a href="{% url work work.id %}">{{work.title}}</a> but hit the cancel link.
Naturally, we won't be charging your PayPal account for this campaign unless you give permission.</div>
<div>However, the campaign can definitely make use of your pledge -- so won't you reconsider?</div>
<div>You <a href="{{try_again_url}}">can finish the pledge transaction</a>.</div>
{% else %}
<div>What transaction are you talking about?</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends "basedocumentation.html" %}
{% block title %}Pledge Completed{% endblock %}
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
{% endblock %}
{% block doccontent %}
<div class="thank-you">Thank you!</div>
<div>You just pledged ${{transaction.amount}} to <a href="{% url work work.id %}">{{work.title}}</a>.</div>
<div>If the campaign, which is slated to end at {{campaign.deadline}} reaches its target of ${{campaign.target}},
your PayPal account will be charged soon after the deadline.</div>
<div>Tell your friends about this campaign!</div>
{% endblock %}

View File

@ -0,0 +1,157 @@
{% extends "basedocumentation.html" %}
{% block doccontent %}
<h1>Press</h1>
<div class="presstoc">
<div>
<a href="#overview">Overview</a><br />
<a href="#press">Press Coverage</a><br />
<a href="#blogs">Blog Coverage (Highlights)</a><br />
</div>
<div>
<a href="#video">Video</a><br />
<a href="#newsletters">Newsletters</a><br />
<a href="#images">Logos &amp; Images</a><br />
</div>
<div class="pressemail">
Additional press questions? Please email <a href="mailto:press@gluejar.com">press@gluejar.com</a>.
</div>
<div class="spacer"></div>
<div class="pressemail">
Thanks for your interest! As of January 2011 Unglue.It is in alpha release. Things are mostly working but they're rough around the edges and may change without notice. Please do kick the tires and forgive us any mess.
</div>
</div>
<a name="overview"></a><h2>Overview</h2>
<dl>
<dt>What?</dt>
<dd>Unglue.It offers a win-win solution to readers, who want to read and share their favorite books conveniently, and rights holders, who want to be rewarded for their work.<br /><br />
We will run <a href"http://en.wikipedia.org/wiki/Crowdfunding">crowdfunding</a> campaigns to raise money for specific, already-published books. When we reach goals set by the rights holders, we'll pay them to unglue their work. They'll issue an electronic edition with a <a href="http://creativecommons.org"</a>Creative Commons</a> <a href="http://creativecommons.org/licenses/by-nc-nd/3.0/">BY-NC-ND</a> license. This license will make the edition free and legal for everyone to read, copy, and share, noncommercially, worldwide.<br /><br />
At present, in our alpha phase, we're not running live campaigns (though you may see some fake campaign data for testing purposes). However, most of the other features of the site -- such as searching for books, adding them to your wishlist, and personalizing your user profile -- work. We invite you to try them out and give us feedback.<br/><br/>
Once we've fully tested our payment processes and user experience, we'll have a beta launch. At this point we'll announce our founding rights holders, run live campaigns, and invite everyone to join the site.</dd>
<dt>Why?</dt>
<dd>As ereaders proliferate, more and more people are enjoying the ereading experience. However, their favorite books may not be available as ebooks. Their ebooks may come with DRM which makes them unreadable on certain devices, and difficult or impossible to lend to friends. Or they may not be able to tell if they have the legal right to use the book as they'd like. The situation is even more challenging for libraries, which may not be able to acquire ebooks at all, or can only acquire them under legal terms and DRM restrictions which run counter to library lending.<br /><br />
When books have a clear, established legal license which promotes use, they can be read more widely, leading to enjoyment, scholarship, and innovation. By raising money to compensate authors and publishers up front, Unglue.It encourages the benefits of openness while ensuring sustainability for creators.<br /><br />
For more background, read our president Eric Hellman's thoughts on <a href="http://go-to-hellman.blogspot.com/2011/04/public-broadcasting-model-for-ebooks.html">a public broadcasting model for ebooks</a> (on why the numbers work) and <a href="http://go-to-hellman.blogspot.com/search/label/Unglue.it">the development of Unglue.It</a>.</dd>
<dt>Who?</dt>
<dd>Unglue.It is a service of Gluejar, Inc. We are <a href="http://go-to-hellman.blogspot.com">Eric Hellman</a>, <a href="http://www.ameckeco.com/">Amanda Mecke</a>, <a href="http://raymondyee.net/">Raymond Yee</a>, and <a href="http://andromedayelton.com">Andromeda Yelton</a>, with help from designer <a href="http://www.vonfab.com/">Stefan Fabry</a> and software developers Jason Kace and <a href="http://inkdroid.org/">Ed Summers</a>. 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 personal home pages (linked above) or <a href="http://gluejar.com/team">the team page</a> of our corporate site.</dd>
<dt>When?</dt>
<dd>Unglue.It is in alpha -- a limited release for testing purposes -- as of January 2011. If you <a href="http://eepurl.com/fKLfI">sign up for our newsletter</a>, we'll tell you the moment we're in beta. At that point we'll have active campaigns and we will open account signups to everyone.</dd>
<dt>Where?</dt>
<dd>Gluejar is a New Jersey corporation, but its employees and contractors live and work across North America. The best way to contact us is by email, <a mailto="press@gluejar.com">press@gluejar.com</a>.</dd>
<dt>What does it cost?</dt>
<dd>Unglue.It is free to join and explore. Supporters pay only if they choose to support campaigns, and the amount is up to them. Unglue.It takes a small percentage from successful campaigns, with the remainder going to the rights holders.</dd>
<dt>What's your technology?</dt>
<dd>Unglue.It is built using <a href="http://python.org/">Python</a> and the <a href="https://www.djangoproject.com/">Django framework</a>. We use data from the <a href="http://code.google.com/apis/books/docs/v1/getting_started.html">Google Books</a>, <a href="http://openlibrary.org/developers/api">Open Library</a>, <a href="http://www.librarything.com/api">LibraryThing</a>, and <a href="http://www.goodreads.com/api">GoodReads</a> APIs [TBA: public?]; we appreciate that they've made these APIs available, and we're returning the favor with <a href="/api/help">our own API</a>. You're welcome to use it. We use <a href="http://lesscss.org/">Less</a> to organize our CSS. We collaborate on our code at <a href="https://github.com/">GitHub</a> and deploy to the cloud with <a href="http://aws.amazon.com/ec2/">Amazon EC2</a>.</dd>
<dt>I have more questions...</dt>
<dd>Please consult our <a href="/faq/">FAQ</a> (sidebar at left); join the site and explore its features for yourself; or email us, <a href="press@gluejar.com">press@gluejar.com</a>.</dd>
<a name="press"></a><h2>Press Coverage</h2>
<div class="pressarticles">
<div>
<a href="http://publishingperspectives.com/2011/11/books-in-browsers-2011/">“Our Friends in Seattle” and Other Euphemisms from Books in Browsers 2011</a><br />
Publishing Perspectives - November 2, 2011
</div>
<div>
<a href="http://www.publishersweekly.com/pw/by-topic/digital/conferences/article/49344-digital-transition-questions-examined-at-books-in-browsers-conference.html">Digital Transition Questions Examined at Books in Browsers Conference</a><br />
Publishers Weekly - November 1, 2011
</div>
<div>
<a href="http://bits.blogs.nytimes.com/2011/10/28/books-unbound/">Books Unbound</a><br />
New York Times (Bits blog) - October 28, 2011
</div>
<div>
<a href="http://americanlibrariesmagazine.org/features/07132011/new-technologies-new-directions-emerge-ala-2011">New Technologies, New Directions Emerge at ALA 2011</a><br />
American Libraries - August 13, 2011
</div>
<div>
<a href="http://radar.oreilly.com/2011/04/pubwir-bookcasting-piracy-book-trailers.html">Publishing News: Week in Review</a><br />
O'Reilly Radar - 8 April 2011
</div>
</div>
<a name="blogs"></a><h2>Blog Coverage (Highlights)</h2>
<div class="pressarticles">
<div>
<a href="http://pluggedinlibrarian.blogspot.com/2011/11/ebooks-brief-fix-on-moving-target.html">Ebooks: A Brief Fix On a Moving Target</a><br />
The Plugged-In Librarian - November 1, 2011
</div>
<div>
<a href="http://www.chip.ro/blog/catalina-zorojanu/17883-vom-mai-citi-cri-sau-altceva">Vom mai citi cărți? Sau altceva?</a> <I>(Romanian)</I><br />
Chip Online - October 31, 2011
</div>
<div>
<a href="http://www.lecturalab.org/story/Nueva-propuesta-para-el-acceso-abierto-a-los-libros-electrnicos_2489">Nueva propuesta para el acceso abierto a los libros electrónicos</a> <I>(Spanish)</I><br />
I+D de la Lectura - September 7, 2011
</div>
<div>
<a href="http://edlab.tc.columbia.edu/index.php?q=node/6451">EdLab Review: Gluejar</a><br />
EdLab blog - September 2, 2011
</div>
<div>
<a href="http://scinfolex.wordpress.com/2011/07/28/gluejar-ou-comment-desengluer-les-livres-numeriques/">Gluejar ou comment désengluer les livres numériques</a> <I>(French)</I><br />
S.I.Lex - July 28, 2011
</div>
<a name="video"></a><h2>Video</h2>
<div class="pressvideos">
<div>
<iframe width="480" height="274" src="http://www.youtube-nocookie.com/embed/YNGPCR-iM24?rel=0" frameborder="0" allowfullscreen></iframe><br />
<I>November 2011</I><br />
Eric Hellman, "The Network is Overrated"; talk at <a href="http://bib.archive.org/">Books in Browsers</a> 2011.
</div>
<div>
<iframe width="480" height="274" src="http://www.youtube-nocookie.com/embed/mhfr-GWlo0M?rel=0" frameborder="0" allowfullscreen></iframe><br />
<I>June 2011</I><br />
Eric Hellman explains the Gluejar model to David Weinberger at <a href="http://lod-lam.net/summit/">LOD-LAM</a> 2011.
</div>
</div>
<div class="spacer"></div>
<a name="newsletters"></a><h2>Newsletters</h2>
{% comment %}autogenerated by mailchimp{% endcomment %}
<script language="javascript" src="http://us2.campaign-archive1.com/generate-js/?u=15472878790f9faa11317e085&fid=28161&show=5" type="text/javascript"></script>
<p>You can also <a href="http://eepurl.com/fKLfI">subscribe</a> to our newsletter.</p>
<a name="images"></a><h2>Logos &amp; Images</h2>
<div class="pressimages">
<div class="outer">
<div><a href="/static/images/logo.png"><img src="/static/images/logo.png"></a></div>
<div class="text"><p>Full logo, 161px x 70px</p></div>
</div>
<div class="outer">
<div><a href="/static/images/unglued.png"><img src="/static/images/unglued.png"></a></div>
<div class="text"><p>Logo image only, 44px x 30px</p></div>
</div>
<div class="outer">
<div><a href="/static/images/workpage.png"><img src="/static/images/workpage_thumb.png" class="screenshot"></a></div>
<div class="text"><p>300 ppi screenshot of a book page on Unglue.It. Features include links to the book, where available, at Google Books, Open Library, GoodReads, and LibraryThing; social sharing options; tabs with user comments and more information; and an explore bar linking to other books and users. The user list is dynamically generated, reflecting others interested in the same book.</p></div>
</div>
<div class="outer">
<div><a href="/static/images/supporter_listview.png"><img src="/static/images/supporter_listview_thumb.png" class="screenshot"></a></div>
<div class="text"><p>300 ppi screenshot of a supporter page. Supporters can readily see all the books on their wishlist and filter for active or successful campaigns. Icons show how many other ungluers have wishlisted a book or, for active campaigns, their progress toward completion. Unglued and public domain books have links to freely available copies. Numbered badges show how many successful, active, and wishlisted books a user is supporting. Supporters can link to their home page and accounts on Facebook, Twitter, GoodReads, and LibraryThing (note icons in upper right), as well as import books from their GoodReads and LibraryThing accounts to their wishlist.</p></div>
</div>
<div class="outer">
<div><a href="/static/images/supporter_panelview.png"><img src="/static/images/supporter_panelview_thumb.png" class="screenshot"></a></div>
<div class="text"><p>300 ppi screenshot of a supporter page in panel view. This displays the same information as the list view, but in a less information-dense, more visually arresting format. Supporters can toggle between views.</p></div>
</div>
<div class="outer">
<div><a href="/static/images/search_listview.png"><img src="/static/images/search_listview_thumb.png" class="screenshot"></a></div>
<div class="text"><p>300 ppi screenshot of a search result page, powered by Google Books. Users can add books to their wishlist with one click, or click through for more information. Books with active campaigns display a progress meter. Books that are already unglued or in the public domain link to freely available copies.</p></div>
</div>
<div class="outer">
<div><a href="/static/images/search_panelview.png"><img src="/static/images/search_panelview_thumb.png" class="screenshot"></a></div>
<div class="text"><p>300 ppi screenshot of a search result page, in panel view. As with the supporter page, users can toggle between views.</p></div>
</div>
</div>
<br /><br /><br /><br /><br /><br />
<div>
{% comment %}
should have logos at a variety of resolutions and also in greyscale
should also have high-res screencaps
{% endcomment %}
</div>
{% endblock %}

View File

@ -1,12 +1,10 @@
{% extends "registration/registration_base.html" %}
{% block title %}{% if account %}Activation complete{% else %}Activation problem{% endif %}{% endblock %}
{% block content %}
<div id="registration">
{% block doccontent %}
{% if account %}
Thanks {{ account }}, activation complete! You may now <a href='{% url auth_login %}'>login</a> using the username and password you set at registration.
Thanks {{ account }}, activation complete! You may now <a href='{% url auth_login %}'>sign in</a> using the username and password you set at registration.
{% else %}
Oops &ndash; it seems that your activation key is invalid. Please check the url again.
{% endif %}
</div>
{% endblock %}

View File

@ -1,16 +1,13 @@
{% extends "registration/registration_base.html" %}
{% block title %}Activation complete{% endblock %}
{% block doccontent %}
<div id="registration">
{% if request.user.is_authenticated %}
Your password has been changed.
{% else %}
Thanks, your activation is complete! You may now sign in using the username and password you set at registration:
<div>
<p>Thanks, your activation is complete! You may now sign in using the username and password you set at registration:</p>
{% include "login_form.html" %}
{% endif %}
<a href="{% url auth_password_reset %}">Already forgot</a> your password?
</div>
<br />
<div>
<a href="{% url auth_password_reset %}">Forgot</a> your password?
</div>
{% endblock %}

View File

@ -2,28 +2,19 @@
{% block title %}login{% endblock %}
{% block doccontent %}
<div id="registration">
{% if form.errors %}
<p class="errorlist">Your username and password didn't match. Please try again.</p>
{% endif %}
<h3>Login to your account:</h3>
<h3>Sign in to your account</h3>
{% include "login_form.html" %}
<p>
<br>
<br>
<h3>Or, login with:</h3>
<a href="/socialauth/login/google?next=http://{{request.get_host}}{{ next }}"><img src="{{ STATIC_URL }}/images/auth/google_64.png"</a>
</p>
<p>
<br>
<br>
<br />
<a href="{% url auth_password_reset %}">Forgot</a> your password? <a href="{% url registration_register %}">Need an account</a>?
</p>
<br /><br />
<div class="google_signup">
<a href="/socialauth/login/google?next=/"><img src="{{ STATIC_URL }}images/auth/google_32_noborder.png"></a><div>Or: <a href="/socialauth/login/google?next=/">Sign in with Google</a></div>
</div>
{% endblock %}

View File

@ -1,20 +1,10 @@
<!-- login_form.html -->
<form method="post" action="{% url django.contrib.auth.views.login %}">{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="login" /></td>
</tr>
</table>
{{ form.username.label_tag }}
{{ form.username }}
{{ form.password.label_tag }}
{{ form.password }}
<input type="submit" value="sign in" />
<input type="hidden" name="next" value="{% if next %}{{ next }}{% else %}/{% endif %}" />
</form>

View File

@ -1,6 +1,12 @@
{% extends "registration/registration_base.html" %}
{% block title %}Password changed{% endblock %}
{% block doccontent %}
Password successfully changed!
<div id="registration">
<div>
<h3>Password successfully changed!</h3>
Please write it down in a safe place.
</div><br /><br />
<a href="/" class="nounderline"><p class="actionbutton">Back to my account</p></a>
</div>
{% endblock %}

View File

@ -1,9 +1,13 @@
{% extends "registration/registration_base.html" %}
{% block title %}Change password{% endblock %}
{% block doccontent %}
{% if request.user.is_authenticated %}
{% ifequal request.user.password '!' %}
Because you registered using another site such as google, you'll need to <a href="{% url django.contrib.auth.views.password_reset %}">"reset" your password</a> before you can change it.
<div>
Because you registered using your account on another site (such as Google), you'll need to <a href="{% url django.contrib.auth.views.password_reset %}">reset your password</a> before you can change it.
</div>
<a href="{% url django.contrib.auth.views.password_reset %}" class="nounderline"><div class="actionbutton">Reset password</div></a>
{% else %}
<form method='post' action=''>{% csrf_token %}
@ -13,6 +17,9 @@
</form>
{% endifequal %}
{% else %}
You must be logged in to change your password.
<div>You must be logged in to change your password.</div>
<a href="{% url auth_login %}?next={% firstof request.path '/' %}" class="nounderline"><div class="actionbutton">Log in</div></a>
{% endif %}
<p>Want to <a href="/accounts/edit/">change your username</a> instead?</p>
{% endblock %}

View File

@ -1,7 +1,15 @@
{% extends "registration/registration_base.html" %}
{% block title %}Password reset complete{% endblock %}
{% block doccontent %}
<div id="registration">
Your password has been reset! {% if not request.user.is_authenticated %} You may now <a href="{{ login_url }}">log in</a>. {% endif %}
<div>
<h3>Password successfully reset!</h3>
</div>
<div>
{% if not request.user.is_authenticated %}
<a href="{{ login_url }}" class="nounderline"><div class="actionbutton">Log in</div></a>
{% else %}
<a href="/" class="nounderline"><div class="actionbutton">Back to my account</div></a>
{% endif %}
</div>
{% endblock %}

View File

@ -1,13 +1,10 @@
{% extends "registration/registration_base.html" %}
{% block title %}Confirm password reset{% endblock %}
{% block doccontent %}
<div id="registration">
Enter your new password below to reset your password:
Enter your new password below to reset your password.<br /><br />
<form method="post" action="">{% csrf_token %}
<table>
{{ form.as_table }}
<tr><td></ td><td><input type="submit" value="Set password" /></td></tr>
</table>
<input type="submit" value="Set password" />
</form>
</div>
{% endblock %}

View File

@ -1,10 +1,9 @@
{% extends "registration/registration_base.html" %}
{% block title %}Password reset{% endblock %}
{% block doccontent %}
<div id="registration">
<p>
We have sent you an email with a link to reset your password. Please check
We've sent you an email with a link to reset your password. Please check
your email and click the link to continue.
</p>
</div>
<p>If you don't see the email soon, please check your spam folder and make sure accounts@gluejar.com is on your whitelist.</p>
{% endblock %}

View File

@ -1,7 +1,6 @@
{% extends "registration/registration_base.html" %}
{% block title %}Reset password{% endblock %}
{% block doccontent %}
<div id="registration">
{% if request.user.is_authenticated and request.user.password == '!' %}
<p>
Sorry for the extra step, but just click and we'll email you
@ -19,11 +18,8 @@ Forgot your password? Enter your email in the form below and we'll send you
instructions for creating a new one.
</p>
<form method='post' action=''>{% csrf_token %}
<table>
{{ form }}
<tr><td></td><td><input type='submit' value="Reset password" /></td></tr>
</table>
<input type='submit' value="Reset password" />
</form>
{% endif %}
</div>
{% endblock %}

View File

@ -1 +1,20 @@
{% extends "basedocumentation.html" %}
{% extends "base.html" %}
{% block extra_head %}
<link href="/static/css/documentation.css" rel="stylesheet" type="text/css" />
{% block extra_extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/registration.css" />
{% endblock %}
{% endblock %}
{% block content %}
<div id="registration">
<div id="login_centerer">
<div id="login">
{% block doccontent %}
{% endblock %}
</div>
</div>
</div>
<div class="spacer"></div>
{% endblock %}

View File

@ -1,8 +1,6 @@
{% extends "registration/registration_base.html" %}
{% block title %}Activation email sent{% endblock %}
{% block doccontent %}
<div id="registration">
An activation email has been sent. Please check your email and click on the link to activate your account.
</div>
{% endblock %}

View File

@ -3,24 +3,18 @@
{% block title %}Register for an account{% endblock %}
{% block doccontent %}
<div id="registration">
<h3>Sign up for a Unglue It account:</h3>
<table>
<form method='post' action=''>{% csrf_token %}
{{ form }}
<tr><td></td><td><input type="submit" value="Send activation email" /></td>
<input type="submit" value="Send activation email" />
</form>
</table>
<p>
<br>
<br>
<div>
<div class="google_signup">
<h3>Or use an account you already have:</h3>
<a href="/socialauth/login/google"><img src="{{ STATIC_URL }}/images/auth/google_64.png"</a>
</p>
<a href="/socialauth/login/google?next=/"><img src="{{ STATIC_URL }}images/auth/google_32_noborder.png"><div>Sign in with Google</div></a>
</div>
</div>
{% endblock %}

View File

@ -1,19 +1,17 @@
{% extends "registration/registration_base.html" %}
{% block title %}Change User Data{% endblock %}
{% block doccontent %}
<div id="registration">
<h1>Changing your Username.</h1>
<h1>Changing Your Username</h1>
<p> If you change your Username, the web address for your profile page will change as well.</p>
<p> If you change your username, the web address for your profile page will change as well.</p>
<p> <b>Your current Username:</b> {{ user.username }}</p>
<p> <b>Your current username:</b> {{ user.username }}</p>
<form method="POST" action="">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="submit" value="Change" id="submit">
<input type="submit" name="submit" value="Change username" id="submit">
</form>
<p><a href="/accounts/password/change/">Change your password</a></p>
</div>
<p>Want to <a href="/accounts/password/change/">change your password</a> instead?</p>
{% endblock %}

View File

@ -10,6 +10,11 @@
{% block doccontent %}
<h1>unglue.it Tools for Rightsholders</h1>
<div class="presstoc"><div class="pressemail">
Any questions not covered here? Please email us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.
</div></div>
{% if request.user.campaigns.all %}
<h2>Campaigns You Manage</h2>
<dl>
@ -106,7 +111,7 @@
<h2>Rightsholder social media tools</h2>
<a href="/stub/rightsholder_social_media_toolkit">Needs to be written.</a>
Needs to be written. What would you find helpful in a social media toolkit? <a href="/feedback">Send us feedback.</a>
<h2>Rewards</h2>

View File

@ -1,2 +1,8 @@
# Hi Robot, welcome to UnglueIt :-)
{% ifequal request.META.HTTP_HOST "unglue.it" %}
User-agent: *
Disallow: /nothing--please-crawl-us
{% else %}
User-agent: *
Disallow: /
{% endifequal %}

View File

@ -8,7 +8,7 @@
<div class="book-detail-info">
<h2 class="book-name">Title: {{ work.title }}</h2>
<h3 class="book-author">Authors: {{ work.author }}</h3>
<h3 class="book-year">Published: {{ work.editions.all.0.publication_date }}</h3>
<h3 class="book-year">Published: {{ work.publication_date }}</h3>
<h3 class="book-author">Language: {{ work.editions.all.0.language }}</h3>
</div>
<form action="#">

View File

@ -53,7 +53,7 @@ how do I integrate the your wishlist thing with the tabs thing?
{% ifequal supporter request.user %}
<div class="user-block1">
<div class="block-inner">
<a class="my-setting" href="#">My Settings</a>
<span class="my-setting">My Settings</a>
</div>
</div>
<div class="user-block2">
@ -92,27 +92,27 @@ how do I integrate the your wishlist thing with the tabs thing?
<div class="user-block4">
<div class="social">
{% if supporter.profile.home_url %}
<a href="{{ supporter.profile.home_url }}">
<a href="{{ supporter.profile.home_url }}" class="nounderline">
<img src="/static/images/supporter_icons/home_square.png" alt="{{ supporter }}'s homepage" title="{{ supporter }}'s Homepage" />
</a>
{% endif %}
{% if supporter.profile.facebook_id %}
<a href="http://www.facebook.com/profile.php?id={{supporter.profile.facebook_id}}">
<a href="http://www.facebook.com/profile.php?id={{supporter.profile.facebook_id}}" class="nounderline">
<img src="/static/images/supporter_icons/facebook_square.png" alt="{{ supporter }}'s Facebook" title="{{ supporter }}'s Facebook" />
</a>
{% endif %}
{% if supporter.profile.twitter_id %}
<a href="https://twitter.com/#!/{{ supporter.profile.twitter_id }}">
<a href="https://twitter.com/#!/{{ supporter.profile.twitter_id }}" class="nounderline">
<img src="/static/images/supporter_icons/twitter_square.png" alt="{{ supporter }}'s Twitter" title="{{ supporter }}'s Twitter" />
</a>
{% endif %}
{% if supporter.profile.goodreads_user_link %}
<a href="{{supporter.profile.goodreads_user_link}}">
<a href="{{supporter.profile.goodreads_user_link}}" class="nounderline">
<img src="/static/images/supporter_icons/goodreads_square.png" alt="{{ supporter }}'s profile on GoodReads" title="{{ supporter }}'s page on GoodReads" />
</a>
{% endif %}
{% if supporter.profile.librarything_id %}
<a href="http://www.librarything.com/profile/{{ supporter.profile.librarything_id }}">
<a href="http://www.librarything.com/profile/{{ supporter.profile.librarything_id }}" class="nounderline">
<img src="/static/images/supporter_icons/librarything_square.png" alt="{{ supporter }}'s profile on LibraryThing" title="{{ supporter }}'s page on LibraryThing" />
</a>
{% endif %}
@ -125,7 +125,7 @@ how do I integrate the your wishlist thing with the tabs thing?
{% csrf_token %}
<div class="block block1">
<div class="block-inner">
<h3><a class="profile-edit" href="#">Your Tagline</a></h3> (140 characters max)
<h3 class="title">Your Tagline</h3> (140 characters max)
{{ profile_form.tagline.errors }}
{{ profile_form.tagline }}
@ -155,7 +155,7 @@ how do I integrate the your wishlist thing with the tabs thing?
</div>
<div class="check-list">
{% if user.profile.goodreads_user_id %}
<a href="{{goodreads_auth_url}}">Update your GoodReads connection</a>
<a href="{{goodreads_auth_url}}">Update your GoodReads connection</a> <br/> or disconnect GoodReads {{ profile_form.clear_goodreads }}
{% else %}
<a href="{{goodreads_auth_url}}">Connect your GoodReads account</a> to Unglue.it
{% endif %}
@ -168,15 +168,16 @@ how do I integrate the your wishlist thing with the tabs thing?
</form>
<div class="block block3">
<h3 class="title">Import your books</h3>
{% if goodreads_shelf_load_form %}
{% if goodreads_id %}
<form id="load_shelf_form" method="post" action="#">
{% csrf_token %}
{{ goodreads_shelf_load_form.non_field_errors }}
<div class="fieldWrapper">
<div id="loadgr"><span><div>{{ goodreads_shelf_load_form.goodreads_shelf_name_number.errors }}
{{ goodreads_shelf_load_form.goodreads_shelf_name_number }}</div>
<input type="submit" value="Add this shelf" />
</div></span>
<div id="loadgr"><span>
<div id="goodreads_shelves"></div>
<input id="goodreads_input" type="submit" value="Display your GoodReads shelves" />
</span>
</div>
</div>
</form>
{% else %}
@ -239,16 +240,13 @@ how do I integrate the your wishlist thing with the tabs thing?
{% ifequal wishlist.works.all.count 0 %}
{% ifequal request.user supporter %}
<div class="empty-wishlist">
Your wishlist is currently empty.<br><br>
Go ahead and <span class="bounce-search">find</span> some books to give to the world, and add them to your Wishlist!<br><br>
We double dog dare you...
Your wishlist is currently empty.<br /><br />
Why not <span class="bounce-search">find</span> your favorite books, and add them to your Wishlist?<br /><br />
We'd also love to hear your <a href="/feedback">feedback</a>.
</div>
{% else %}
<div class="empty-wishlist">
It looks like {{ supporter.username }} is just
getting started, and hasn't added anything to their
wishlist just yet.<br><br>
Nudge, nudge, say no more.
It looks like {{ supporter.username }} is just getting started, and hasn't added books just yet.<br /><br />
{% endifequal %}
{% else %}
{% paginate 20 works %}
@ -256,7 +254,7 @@ how do I integrate the your wishlist thing with the tabs thing?
<div class="{% cycle 'row1' 'row2' %}">
{% with work.last_campaign_status as status %}
{% with work.last_campaign.deadline as deadline %}
{% with work.editions.all.0.googlebooks_id as googlebooks_id %}
{% with work.googlebooks_id as googlebooks_id %}
{% include "book_panel.html" %}
{% endwith %}{% endwith %}{% endwith %}
</div>
@ -274,11 +272,3 @@ how do I integrate the your wishlist thing with the tabs thing?
</div>
{% endblock %}
{% block counter %}
<div class="statcounter"><a title="web
statistics" href="http://statcounter.com/free-web-stats/"
class="statcounter"><img class="statcounter"
src="https://c.statcounter.com/7447776/0/9ae2ba93/1/"
alt="web statistics" /></a></div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "basedocumentation.html" %}
{% block title %}Feedback{% endblock %}
{% block doccontent %}
<p>Thanks for helping us make Unglue.It better!</p>
<p>Would you like to <a href="{{ page }}">go back to the page you were on</a>?
{% endblock %}

View File

@ -0,0 +1,102 @@
{% extends "base.html" %}
{% load endless %}
{% block title %} Works {% endblock %}
{% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" />
<link type="text/css" rel="stylesheet" href="/static/css/book_list.css" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel.css" />
{% endblock %}
{% block extra_head %}
<script type="text/javascript" src="/static/js/wishlist.js"></script>
<script type="text/javascript" src="/static/js/jquery-1.6.3.min.js"></script>
<script type="text/javascript" src="/static/js/jquery-ui-1.8.16.custom.min.js"></script>
<script type="text/javascript" src="/static/js/greenpanel.js"></script>
<script type="text/javascript" src="/static/js/toggle.js"></script>
<script type="text/javascript" src="/static/js/tabs.js"></script>
{% endblock %}
{% block topsection %}
<div id="js-topsection">
<div class="js-main">
<div class="js-topnews">
<div class="js-topnews1">
<div class="js-topnews2">
<div class="js-topnews3">
<div class="user-block">
<div class="user-block1">
<div class="block-intro-text"><span class="special-user-name">{{ facet|capfirst }}</span></div>
</div>
<div class="user-block2"><span class="user-short-info">These ebooks are ready to read- either they're in the public domain, or they've been 'unglued' by ungluers like you.</span>
</div>
<div class="user-block3">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block content %}
<div id="main-container">
<div class="js-main">
<div id="js-leftcol">
{% include "explore.html" %}
</div>
<div id="js-maincol-fr">
<div class="js-maincol-inner">
<div class="content-block">
<div class="content-block-heading ungluing" id="tabs">
<ul class="tabs">
<li class="tabs1"><a href="#">Unglued</a></li>
</ul>
<div class="badges listspage">
<span class="rounded"><span class="blue tabs1" title="Enjoy {{ counts.unglued }} unglued {% if counts.unglued == 1 %}book{% else %}books{% endif %}."><span class="hovertext">Enjoy&nbsp;</span>{{ counts.unglued }}</span></span>
</div>
<ul class="book-list-view">
<li>View As:</li>
<li class="view-list">
<a href="#" id="toggle-list">
<img src="/static/images/booklist/view-list.png" alt="view list" title="view list" height="21" width="24" />
</a>
</li>
<li class="view-list">
<a href="#" id="toggle-panel">
<img src="/static/images/booklist/view-icon.png" alt="view icon" title="view icon" height="22" width="22" />
</a>
</li>
</ul>
</div>
<div class="content-block-content">
{% ifequal work_list.count 0 %}
There aren't any works in this list. Why don't you add some?
{% else %}
{% paginate 20 work_list %}
{% for work in work_list %}
<div class="{% cycle 'row1' 'row2' %}">
{% with work.last_campaign_status as status %}
{% with work.last_campaign.deadline as deadline %}
{% with work.googlebooks_id as googlebooks_id %}
{% include "book_panel.html" %}
{% endwith %}{% endwith %}{% endwith %}
</div>
{% endfor %}
<br>
<div class="pagination content-block-heading">
{% show_pages %}
</div>
{% endifequal %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -28,14 +28,14 @@ jQuery(document).ready(function(){
<div class="js-main">
<div id="js-leftcol">
<div class="jsmodule rounded">
{% if work.last_campaign %}
{% if status == 'ACTIVE' %}
<div class="jsmod-content active{{ work.percent_unglued}}">
<div class="jsmod-content{% if status == 'ACTIVE' %} active{{ work.percent_unglued}}{% endif %}">
<span>
{% if work.first_epub_url or work.first_pdf_url %}
AVAILABLE!
{% else %}{% if work.last_campaign %}
{% if status == 'ACTIVE' %}
Campaign in Progress: <br />${{ work.last_campaign.current_total }}/${{ work.last_campaign.target }}
{% else %}
<div class="jsmod-content">
<span>
{% if status == 'SUCCESSFUL' %}
Unglued on {{ work.last_campaign.deadline|date:"M j, Y"}}! <br />${{ work.last_campaign.current_total }} raised of ${{ work.last_campaign.target }} goal
{% else %}{% if status == 'INITIALIZED' %}
@ -48,16 +48,15 @@ jQuery(document).ready(function(){
{{ wishers }} Ungluer is WISHING
{% else %}
{{ wishers }} Ungluers are WISHING
{% endif %}{% endif %}{% endif %}{% endif %}{% endif %}{% endif %}
{% endif %}{% endif %}{% endif %}{% endif %}{% endif %}
{% endif %}
{% else %}
<div class="jsmod-content">
<span>
{% if wishers == 1 %}
{{ wishers }} Ungluer is WISHING
{% else %}
{{ wishers }} Ungluers are WISHING
{% endif %}
{% endif %}
{% endif %}{% endif %}
</span>
<span class="spacer">&nbsp;<br />&nbsp;</span>
</div>
@ -79,7 +78,13 @@ jQuery(document).ready(function(){
<h3 class="book-author">{{ work.author }}</h3>
<h3 class="book-year">{{ pubdate }}</h3>
</div>
{% if status == 'ACTIVE' %}<div class="btn_support"><form action="{% url pledge work_id=work.id %}" method="get"><input type="submit" value="Support"/></form></div>{% endif %}
{% if status == 'ACTIVE' %}
{% if pledged %}
<div class="btn_support modify"><form action="/stub/modify_pledge" method="get"><input type="submit" value="Change Pledge"/></form></div>
{% else %}
<div class="btn_support"><form action="{% url pledge work_id=work.id %}" method="get"><input type="submit" value="Support"/></form></div>
{% endif %}
{% endif %}
</div>
</div>
<div class="find-book">
@ -97,17 +102,41 @@ jQuery(document).ready(function(){
{% endif %}
</div>
</div>
{% if status == 'ACTIVE' %}
<div class="pledged-info"><div class="pledged-group">{% if work.last_campaign.supporters.count == 1 %}One Ungluer has {% else %} {{ work.last_campaign.supporters.count }} Ungluers have {% endif %}pledged ${{ work.last_campaign.current_total }}<br />toward a ${{ work.last_campaign.target }} goal </div><div class="status"><img src="/static/images/images/icon-book-37by25-{{ work.percent_unglued }}.png" /></div></div>
{% else %}
<div class="pledged-info"><div class="pledged-group">
{% if work.first_epub_url or work.first_pdf_url %}
{% if wishers == 1 %}
{{ wishers }} Ungluer has
1 Ungluer is
{% else %}
{{ wishers }} Ungluers are
{% endif %} enjoying this Work
{% else %}{% if status == 'ACTIVE' %}
{% if work.last_campaign.supporters.count == 1 %}
One Ungluer has
{% else %}
{{ work.last_campaign.supporters.count }} Ungluers have
{% endif %}
pledged ${{ work.last_campaign.current_total }}<br />toward a ${{ work.last_campaign.target }} goal
{% else %}
{% if wishers == 1 %}
1 Ungluer has
{% else %}
{{ wishers }} Ungluers have
{% endif %}wished for this Work</div><div class="status"><img src="/static/images/images/icon-book-37by25-{{ work.percent_unglued }}.png" /></div></div>
{% endif %}
{% endif %} wished for this Work
{% endif %}{% endif %}
</div>
<div class="status"><img src="/static/images/images/icon-book-37by25-{% if work.first_epub_url or work.first_pdf_url %}6{%else%}{{ work.percent_unglued }}{%endif%}.png" /></div>
</div>
<div class="btn_wishlist">
{% if work.first_epub_url or work.first_pdf_url %}
<span class="boolist-ebook">
{% if work.first_epub_url %}
<a href="{{ work.first_epub_url }}">EPUB</a>
{% endif %}
{% if work.first_pdf_url %}
<a href="{{ work.first_pdf_url }}">PDF</a>
{% endif %}
</span>
{% endif %}
{% if request.user.is_anonymous %}
<div class="create-account">
<span title="{% url work work.id %}">Login to Add</span>
@ -122,7 +151,7 @@ jQuery(document).ready(function(){
</div>
{% else %}
<div class="add-wishlist">
<span id="{{ work.googlebooks_id }}">Add to Wishlist</span>
<span id="{{ work.googlebooks_id }}">Add to {% if work.first_epub_url or work.first_pdf_url %}Enjoying{% else %}Wishlist{% endif %}</span>
</div>
{% endif %}{% endif %}{% endif %}
</div>
@ -191,25 +220,39 @@ jQuery(document).ready(function(){
{{ work.last_campaign.details|safe }}
{% endif %}
{% if work.claim.count %}
<h4> Rights Information </h4>
{% if work.claim.count %}
<p> This work has been claimed by:</p>
<ul>
{% for claim in work.claim.all %}
<li>{{ claim.rights_holder.rights_holder_name }} </li>
{% endfor %}
</ul>
{% endif %}
{% if not user.is_anonymous %}
{% if request.user.rights_holder.all.count %}
<h4> Claim this work:</h4>
Should someone else should be authorized to run campaigns for this work? Select a rights holder:<br /><br />
<form method="GET" action="{% url claim %}">
{% csrf_token %}
{{ claimform.user }}
{{ claimform.work }}
{{ claimform.rights_holder }}
<input type="submit" name="submit" value="Claim" id="submit">
</form>
</form><br />
{% endif %}
Need to talk to us about claim status? Please email <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.
{% else %}
{% if request.user.rights_holder.all.count %}
Is this work yours? Claim it: <br /><br />
<form method="GET" action="{% url claim %}">
{% csrf_token %}
{{ claimform.user }}
{{ claimform.work }}
{{ claimform.rights_holder }}
<input type="submit" name="submit" value="Claim" id="submit">
</form><br />
{% else %}
Are you the rights holder for this work? Please email <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.
{% endif %}
{% endif %}
@ -243,7 +286,7 @@ jQuery(document).ready(function(){
<a href="https://twitter.com/intent/tweet?url={{request.build_absolute_uri|urlencode:"" }}&text=I'm%20ungluing%20{{ work.title|urlencode }}%20at%20%40unglueit"><li class="twitter"><span>Twitter</span></li></a>
{% if request.user.is_authenticated %}<a href="{% url emailshare %}?next={{request.build_absolute_uri|urlencode:""}}"><li class="email"><span>Email</span></li></a>{% endif %}
<a href="#" id="embed"><li class="embed"><span>Embed</span></li></a>
<div id="widgetcode">Copy/paste this into your site:<br /><textarea rows="7" cols="22">&lt;iframe src="https://{{request.META.HTTP_HOST}}/api/widget/{{work.editions.all.0.isbn_13}}/" width="152" height="325" frameborder="0"&gt;&lt;/iframe&gt;</textarea></div>
<div id="widgetcode">Copy/paste this into your site:<br /><textarea rows="7" cols="22">&lt;iframe src="https://{{request.META.HTTP_HOST}}/api/widget/{{work.first_isbn_13}}/" width="152" height="325" frameborder="0"&gt;&lt;/iframe&gt;</textarea></div>
</ul>
</div>
</div>

View File

@ -87,7 +87,7 @@
<div class="{% cycle 'row1' 'row2' %}">
{% with work.last_campaign_status as status %}
{% with work.last_campaign.deadline as deadline %}
{% with work.editions.all.0.googlebooks_id as googlebooks_id %}
{% with work.googlebooks_id as googlebooks_id %}
{% include "book_panel.html" %}
{% endwith %}{% endwith %}{% endwith %}
</div>

View File

@ -47,6 +47,8 @@ class PageTests(TestCase):
# logged in
r = self.client.get("/supporter/test/")
self.assertEqual(r.status_code, 200)
r = self.client.get("/search/?q=sverige")
self.assertEqual(r.status_code, 200)
def test_view_by_other(self):
# someone else's supporter page
@ -60,6 +62,8 @@ class PageTests(TestCase):
self.assertEqual(r.status_code, 200)
r = anon_client.get("/")
self.assertEqual(r.status_code, 200)
r = anon_client.get("/search/?q=sverige")
self.assertEqual(r.status_code, 200)
class GoogleBooksTest(TestCase):

View File

@ -3,16 +3,16 @@ from django.views.generic.simple import direct_to_template
from django.views.generic.base import TemplateView
from django.views.generic import ListView, DetailView
from django.contrib.auth.decorators import login_required
from django.conf import settings
from regluit.core.models import Campaign
from regluit.frontend.views import CampaignFormView, GoodreadsDisplayView, LibraryThingView, PledgeView, FAQView
from regluit.frontend.views import CampaignListView, DonateView, WorkListView
from regluit.frontend.views import CampaignFormView, GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeCancelView, FAQView
from regluit.frontend.views import CampaignListView, DonateView, WorkListView, UngluedListView
urlpatterns = patterns(
"regluit.frontend.views",
url(r"^$", "home", name="home"),
url(r"^supporter/(?P<supporter_username>.+)/$", "supporter", {'template_name': 'supporter.html'}, name="supporter"),
url(r"^supporter2/(?P<supporter_username>.+)/$", "supporter", {'template_name': 'supporter_panel.html'}, name="supporter2"),
url(r"^search/$", "search", name="search"),
url(r"^privacy/$", TemplateView.as_view(template_name="privacy.html"),
name="privacy"),
@ -23,21 +23,19 @@ urlpatterns = patterns(
url(r"^rightsholders/claim/$", "claim", name="claim"),
url(r"^rh_admin/$", "rh_admin", name="rh_admin"),
url(r"^campaign_admin/$", "campaign_admin", name="campaign_admin"),
url(r"^faq/$", FAQView.as_view(), {'location':'faq'}, name="faq"),
url(r"^faq/(?P<location>\w*)/$", FAQView.as_view()),
url(r"^faq/$", FAQView.as_view(), {'location':'faq', 'sublocation':'all'}, name="faq"),
url(r"^faq/(?P<location>\w*)/$", FAQView.as_view(), {'sublocation':'all'}),
url(r"^faq/(?P<location>\w*)/(?P<sublocation>\w*)/$", FAQView.as_view()),
url(r"^wishlist/$", "wishlist", name="wishlist"),
url(r"^campaigns/(?P<pk>\d+)/$",CampaignFormView.as_view(), name="campaign_by_id"),
url(r"^campaigns/(?P<facet>\w*)$", CampaignListView.as_view(), name='campaign_list'),
url(r"^lists/(?P<facet>\w*)$", WorkListView.as_view(), name='work_list'),
url(r"^unglued/(?P<facet>\w*)$",
ListView.as_view( model=Campaign,template_name="campaign_list.html", context_object_name="campaign_list"),
name='unglued_list'),
url(r"^goodreads/$", login_required(GoodreadsDisplayView.as_view()), name="goodreads_display"),
url(r"^unglued/(?P<facet>\w*)$", UngluedListView.as_view(), name='unglued_list'),
url(r"^goodreads/auth/$", "goodreads_auth", name="goodreads_auth"),
url(r"^goodreads/auth_cb/$", "goodreads_cb", name="goodreads_cb"),
url(r"^goodreads/flush/$","goodreads_flush_assoc", name="goodreads_flush_assoc"),
url(r"^goodreads/load_shelf/$","goodreads_load_shelf", name="goodreads_load_shelf"),
url(r"^goodreads/clear_wishlist/$","clear_wishlist", name="clear_wishlist"),
url(r"^goodreads/shelves/$","goodreads_calc_shelves", name="goodreads_calc_shelves"),
url(r"^stub/", "stub", name="stub"),
url(r"^work/(?P<work_id>\d+)/$", "work", name="work"),
url(r"^work/(?P<work_id>\d+)/librarything/$", "work_librarything", name="work_librarything"),
@ -47,13 +45,29 @@ urlpatterns = patterns(
#may want to deprecate the following
url(r"^setup/work/(?P<work_id>\d+)/$", "work", {'action':'setup_campaign'}, name="setup_campaign"),
url(r"^pledge/(?P<work_id>\d+)/$", login_required(PledgeView.as_view()), name="pledge"),
url(r"^celery/clear/$","clear_celery_tasks", name="clear_celery_tasks"),
url(r"^pledge/cancel/$", login_required(PledgeCancelView.as_view()), name="pledge_cancel"),
url(r"^pledge/complete/$", PledgeCompleteView.as_view(), name="pledge_complete"),
url(r"^subjects/$", "subjects", name="subjects"),
url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"),
url(r"^librarything/load/$","librarything_load", name="librarything_load"),
url(r"^donate/$", DonateView.as_view(), name="donate"),
url('^404testing/$', direct_to_template, {'template': '404.html'}),
url('^500testing/$', direct_to_template, {'template': '500.html'}),
url('^robots.txt$', direct_to_template, {'template': 'robots.txt'}),
url('^robots.txt$', direct_to_template, {'template': 'robots.txt', 'mimetype': 'text/plain'}),
url(r"^emailshare/$", "emailshare", name="emailshare"),
url(r"^feedback/$", "feedback", name="feedback"),
url(r"^feedback/thanks/$", TemplateView.as_view(template_name="thanks.html")),
url(r"^press/$", TemplateView.as_view(template_name="press.html"),
name="press"),
url(r"^about/$", TemplateView.as_view(template_name="about.html"),
name="about"),
)
if not settings.IS_PREVIEW:
urlpatterns += patterns(
"regluit.frontend.views",
url(r"^goodreads/$", login_required(GoodreadsDisplayView.as_view()), name="goodreads_display"),
url(r"^goodreads/clear_wishlist/$","clear_wishlist", name="clear_wishlist"),
url(r"^donate/$", DonateView.as_view(), name="donate"),
url(r"^celery/clear/$","clear_celery_tasks", name="clear_celery_tasks"),
)

View File

@ -4,6 +4,7 @@ import json
import urllib
import logging
import datetime
from random import randint
from re import sub
from itertools import islice
from decimal import Decimal as D
@ -39,10 +40,11 @@ from regluit.core.search import gluejar_search
from regluit.core.goodreads import GoodreadsClient
from regluit.frontend.forms import UserData, ProfileForm, CampaignPledgeForm, GoodreadsShelfLoadingForm
from regluit.frontend.forms import RightsHolderForm, UserClaimForm, LibraryThingForm, OpenCampaignForm
from regluit.frontend.forms import ManageCampaignForm, DonateForm, CampaignAdminForm, EmailShareForm
from regluit.frontend.forms import ManageCampaignForm, DonateForm, CampaignAdminForm, EmailShareForm, FeedbackForm
from regluit.payment.manager import PaymentManager
from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN, TARGET_TYPE_DONATION
from regluit.payment.paypal import Preapproval, IPN_PAY_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_COMPLETED, IPN_PAY_STATUS_CANCELED
from regluit.payment.models import Transaction
from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN, TARGET_TYPE_DONATION, PAYMENT_TYPE_AUTHORIZATION
from regluit.payment.paypal import Preapproval, IPN_PAY_STATUS_NONE, IPN_PAY_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_COMPLETED, IPN_PAY_STATUS_CANCELED, IPN_TYPE_PREAPPROVAL
from regluit.core import goodreads
from tastypie.models import ApiKey
from regluit.payment.models import Transaction
@ -61,6 +63,15 @@ def home(request):
works=[]
works2=[]
count=ending.count()
# on the preview site there are no active campaigns, so we should show most-wished books instead
is_preview = settings.IS_PREVIEW
if is_preview:
# django related fields and distinct() interact poorly, so we need to do a song and dance to get distinct works
worklist = models.Work.objects.annotate(num_wishes=Count('wishes')).order_by('-num_wishes')
works = worklist[:6]
works2 = worklist[6:12]
else:
while i<12 and count>0:
if i<6:
works.append(ending[j].work)
@ -82,7 +93,11 @@ def work(request, work_id, action='display'):
editions = work.editions.all().order_by('-publication_date')
campaign = work.last_campaign()
try:
pubdate = work.editions.all()[0].publication_date[:4]
pledged = campaign.transactions().filter(user=request.user, status="ACTIVE")
except:
pledged = None
try:
pubdate = work.publication_date[:4]
except IndexError:
pubdate = 'unknown'
if not request.user.is_anonymous():
@ -111,6 +126,7 @@ def work(request, work_id, action='display'):
'base_url': base_url,
'editions': editions,
'pubdate': pubdate,
'pledged':pledged,
})
def manage_campaign(request, id):
@ -140,8 +156,8 @@ def manage_campaign(request, id):
def googlebooks(request, googlebooks_id):
try:
edition = models.Edition.objects.get(googlebooks_id=googlebooks_id)
except models.Edition.DoesNotExist:
edition = models.Identifier.objects.get(type='goog',value=googlebooks_id).edition
except models.Identifier.DoesNotExist:
edition = bookloader.add_by_googlebooks_id(googlebooks_id)
# we could populate_edition(edition) to pull in related editions here
# but it is left out for now to lower the amount of traffic on
@ -172,7 +188,8 @@ class WorkListView(ListView):
def work_set_counts(self,work_set):
counts={}
counts['unglued'] = work_set.annotate(ebook_count=Count('editions__ebooks')).filter(ebook_count__gt=0).count()
# counts['unglued'] = work_set.annotate(ebook_count=Count('editions__ebooks')).filter(ebook_count__gt=0).count()
counts['unglued'] = work_set.filter(editions__ebooks__isnull=False).distinct().count()
counts['unglueing'] = work_set.filter(campaigns__status='ACTIVE').count()
counts['wished'] = work_set.count() - counts['unglued'] - counts['unglueing']
return counts
@ -194,6 +211,32 @@ class WorkListView(ListView):
context['facet'] =self.kwargs['facet']
return context
class UngluedListView(ListView):
template_name = "unglued_list.html"
context_object_name = "work_list"
def work_set_counts(self,work_set):
counts={}
counts['unglued'] = work_set.annotate(ebook_count=Count('editions__ebooks')).filter(ebook_count__gt=0).count()
return counts
def get_queryset(self):
facet = self.kwargs['facet']
if (facet == 'popular'):
return models.Work.objects.annotate(ebook_count=Count('editions__ebooks')).annotate(wished=Count('wishlists')).filter(ebook_count__gt=0).order_by('-wished')
else:
#return models.Work.objects.annotate(ebook_count=Count('editions__ebooks')).filter(ebook_count__gt=0).order_by('-created')
return models.Work.objects.filter(editions__ebooks__isnull=False).distinct().order_by('-created')
def get_context_data(self, **kwargs):
context = super(UngluedListView, self).get_context_data(**kwargs)
qs=self.get_queryset()
context['counts'] = self.work_set_counts(qs)
context['ungluers'] = userlists.work_list_users(qs,5)
context['facet'] =self.kwargs['facet']
return context
class CampaignListView(ListView):
template_name = "campaign_list.html"
context_object_name = "campaign_list"
@ -271,20 +314,23 @@ class PledgeView(FormView):
if not self.embedded:
return_url = self.request.build_absolute_uri(reverse('work',kwargs={'work_id': str(work_id)}))
return_url = None
cancel_url = None
# the recipients of this authorization is not specified here but rather by the PaymentManager.
# set the expiry date based on the campaign deadline
expiry = campaign.deadline + datetime.timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, preapproval_amount, expiry=expiry, campaign=campaign, list=None, user=user,
return_url=return_url, anonymous=anonymous)
return_url=return_url, cancel_url=cancel_url, anonymous=anonymous)
else: # embedded view -- which we're not actively using right now.
# embedded view triggerws instant payment: send to the partnering RH
receiver_list = [{'email':settings.PAYPAL_NONPROFIT_PARTNER_EMAIL, 'amount':preapproval_amount}]
#redirect the page back to campaign page on success
return_url = self.request.build_absolute_uri(reverse('campaign_by_id',kwargs={'pk': str(pk)}))
return_url = None
cancel_url = None
t, url = p.pledge('USD', TARGET_TYPE_CAMPAIGN, receiver_list, campaign=campaign, list=None, user=user,
return_url=return_url, anonymous=anonymous)
return_url=return_url, cancel_url=cancel_url, anonymous=anonymous)
if url:
logger.info("PledgeView paypal: " + url)
@ -295,6 +341,138 @@ class PledgeView(FormView):
logger.info("PledgeView paypal: Error " + str(t.reference))
return HttpResponse(response)
class PledgeCompleteView(TemplateView):
"""A callback for PayPal to tell unglue.it that a payment transaction has completed successfully.
Possible things to implement:
after pledging, supporter receives email including thanks, work pledged, amount, expiry date, any next steps they should expect; others?
study other confirmation emails for their contents
after pledging, supporters are returned to a thank-you screen
should have prominent "thank you" or "congratulations" message
should have prominent share options
should suggest other works for supporters to explore (on what basis?)
link to work page? or to page on which supporter entered the process? (if the latter, how does that work with widgets?)
should note that a confirmation email has been sent to $email from $sender
should briefly note next steps (e.g. if this campaign succeeds you will be emailed on date X)
"""
template_name="pledge_complete.html"
def get_context_data(self):
# pick up all get and post parameters and display
context = super(PledgeCompleteView, self).get_context_data()
output = "pledge complete"
output += self.request.method + "\n" + str(self.request.REQUEST.items())
context["output"] = output
if self.request.user.is_authenticated():
user = self.request.user
else:
user = None
# pull out the transaction id and try to get the corresponding Transaction
transaction_id = self.request.REQUEST.get("tid")
transaction = Transaction.objects.get(id=transaction_id)
# work and campaign in question
try:
campaign = transaction.campaign
work = campaign.work
except Exception, e:
campaign = None
work = None
# we need to check whether the user tied to the transaction is indeed the authenticated user.
correct_user = False
try:
if user.id == transaction.user.id:
correct_user = True
except Exception, e:
pass
# check that the user had not already approved the transaction
# do we need to first run PreapprovalDetails to check on the status
# is it of type=PAYMENT_TYPE_AUTHORIZATION and status is NONE or ACTIVE (but approved is false)
if transaction.type == PAYMENT_TYPE_AUTHORIZATION:
correct_transaction_type = True
else:
correct_transaction_type = False
context["transaction"] = transaction
context["correct_user"] = correct_user
context["correct_transaction_type"] = correct_transaction_type
context["work"] = work
context["campaign"] = campaign
return context
class PledgeCancelView(TemplateView):
"""A callback for PayPal to tell unglue.it that a payment transaction has been canceled by the user"""
template_name="pledge_cancel.html"
def get_context_data(self):
context = super(PledgeCancelView, self).get_context_data()
if self.request.user.is_authenticated():
user = self.request.user
else:
user = None
# pull out the transaction id and try to get the corresponding Transaction
transaction_id = self.request.REQUEST.get("tid")
transaction = Transaction.objects.get(id=transaction_id)
# work and campaign in question
try:
campaign = transaction.campaign
work = campaign.work
except Exception, e:
campaign = None
work = None
# we need to check whether the user tied to the transaction is indeed the authenticated user.
correct_user = False
try:
if user.id == transaction.user.id:
correct_user = True
except Exception, e:
pass
# check that the user had not already approved the transaction
# do we need to first run PreapprovalDetails to check on the status
# is it of type=PAYMENT_TYPE_AUTHORIZATION and status is NONE or ACTIVE (but approved is false)
if transaction.type == PAYMENT_TYPE_AUTHORIZATION:
correct_transaction_type = True
else:
correct_transaction_type = False
# status?
# give the user an opportunity to approved the transaction again
# provide a URL to click on.
# https://www.sandbox.paypal.com/?cmd=_ap-preapproval&preapprovalkey=PA-6JV656290V840615H
try_again_url = '%s?cmd=_ap-preapproval&preapprovalkey=%s' % (settings.PAYPAL_PAYMENT_HOST, transaction.preapproval_key)
context["transaction"] = transaction
context["correct_user"] = correct_user
context["correct_transaction_type"] = correct_transaction_type
context["try_again_url"] = try_again_url
context["work"] = work
context["campaign"] = campaign
return context
class DonateView(FormView):
template_name="donate.html"
form_class = DonateForm
@ -557,31 +735,28 @@ def supporter(request, supporter_username, template_name):
if request.method == 'POST':
profile_form = ProfileForm(data=request.POST,instance=profile_obj)
if profile_form.is_valid():
if profile_form.cleaned_data['clear_facebook'] or profile_form.cleaned_data['clear_twitter'] :
if profile_form.cleaned_data['clear_facebook'] or profile_form.cleaned_data['clear_twitter'] or profile_form.cleaned_data['clear_goodreads'] :
if profile_form.cleaned_data['clear_facebook']:
profile_obj.facebook_id=0
if profile_form.cleaned_data['clear_twitter']:
profile_obj.twitter_id=""
if profile_form.cleaned_data['clear_goodreads']:
profile_obj.goodreads_user_id = None
profile_obj.goodreads_user_name = None
profile_obj.goodreads_user_link = None
profile_obj.goodreads_auth_token = None
profile_obj.goodreads_auth_secret = None
profile_obj.save()
profile_form.save()
else:
profile_form= ProfileForm(instance=profile_obj)
# for now, also calculate the Goodreads shelves of user when loading this page
# we should move towards calculating this only if needed (perhaps with Ajax), caching previous results, etc to speed up
# performance
if request.user.profile.goodreads_user_id is not None:
gr_client = GoodreadsClient(key=settings.GOODREADS_API_KEY, secret=settings.GOODREADS_API_SECRET)
goodreads_shelves = gr_client.shelves_list(user_id=request.user.profile.goodreads_user_id)
goodreads_shelf_load_form = GoodreadsShelfLoadingForm()
# load the shelves into the form
choices = [('all:%d' % (goodreads_shelves["total_book_count"]),'all (%d)' % (goodreads_shelves["total_book_count"]))] + \
[("%s:%d" % (s["name"], s["book_count"]) ,"%s (%d)" % (s["name"],s["book_count"])) for s in goodreads_shelves["user_shelves"]]
goodreads_shelf_load_form.fields['goodreads_shelf_name_number'].widget = Select(choices=tuple(choices))
goodreads_id = request.user.profile.goodreads_user_id
else:
goodreads_shelf_load_form = None
goodreads_id = None
if request.user.profile.librarything_id is not None:
librarything_id = request.user.profile.librarything_id
@ -589,7 +764,7 @@ def supporter(request, supporter_username, template_name):
librarything_id = None
else:
profile_form = ''
goodreads_shelf_load_form = None
goodreads_id = None
librarything_id = None
@ -605,7 +780,7 @@ def supporter(request, supporter_username, template_name):
"profile_form": profile_form,
"ungluers": userlists.other_users(supporter, 5 ),
"goodreads_auth_url": reverse('goodreads_auth'),
"goodreads_shelf_load_form": goodreads_shelf_load_form,
"goodreads_id": goodreads_id,
"librarything_id": librarything_id
}
@ -641,9 +816,9 @@ def search(request):
works=[]
for result in results:
try:
edition = models.Edition.objects.get(googlebooks_id=result['googlebooks_id'])
works.append(edition.work)
except models.Edition.DoesNotExist:
work = models.Identifier.objects.get(type='goog',value=result['googlebooks_id']).work
works.append(work)
except models.Identifier.DoesNotExist:
works.append(result)
context = {
"q": q,
@ -746,7 +921,8 @@ class FAQView(TemplateView):
template_name = "faq.html"
def get_context_data(self, **kwargs):
location = self.kwargs["location"]
return {'location': location}
sublocation = self.kwargs["sublocation"]
return {'location': location, 'sublocation': sublocation}
class GoodreadsDisplayView(TemplateView):
template_name = "goodreads_display.html"
@ -876,6 +1052,26 @@ def goodreads_load_shelf(request):
logger.info("Error in loading shelf for user %s: %s ", user, e)
@login_required
def goodreads_calc_shelves(request):
# we should move towards calculating this only if needed (perhaps with Ajax), caching previous results, etc to speed up
# performance
if request.user.profile.goodreads_user_id is not None:
gr_client = GoodreadsClient(key=settings.GOODREADS_API_KEY, secret=settings.GOODREADS_API_SECRET)
goodreads_shelves = gr_client.shelves_list(user_id=request.user.profile.goodreads_user_id)
#goodreads_shelf_load_form = GoodreadsShelfLoadingForm()
## load the shelves into the form
#choices = [('all:%d' % (goodreads_shelves["total_book_count"]),'all (%d)' % (goodreads_shelves["total_book_count"]))] + \
# [("%s:%d" % (s["name"], s["book_count"]) ,"%s (%d)" % (s["name"],s["book_count"])) for s in goodreads_shelves["user_shelves"]]
#goodreads_shelf_load_form.fields['goodreads_shelf_name_number'].widget = Select(choices=tuple(choices))
else:
goodreads_shelf_load_form = None
return HttpResponse(json.dumps(goodreads_shelves), content_type="application/json")
@require_POST
@login_required
@csrf_exempt
@ -984,7 +1180,7 @@ def work_librarything(request, work_id):
def work_openlibrary(request, work_id):
work = get_object_or_404(models.Work, id=work_id)
isbns = ["ISBN:" + e.isbn_13 for e in work.editions.filter(isbn_13__isnull=False)]
isbns = ["ISBN:" + i.value for i in work.identifiers.filter(type='isbn')]
url = None
if work.openlibrary_id:
@ -1043,3 +1239,44 @@ def emailshare(request):
form = EmailShareForm(initial={'next':next, 'message':"I'm ungluing books at unglue.it. Here's one of my favorites: "+next, "sender":sender})
return render(request, "emailshare.html", {'form':form})
def feedback(request):
num1 = randint(0,10)
num2 = randint(0,10)
sum = num1 + num2
if request.method == 'POST':
form=FeedbackForm(request.POST)
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
recipient = 'support@gluejar.com'
page = form.cleaned_data['page']
useragent = request.META['HTTP_USER_AGENT']
if request.user.is_anonymous():
ungluer = "(not logged in)"
else:
ungluer = request.user.username
message = "<<<This feedback is about "+page+". Original user message follows\nfrom "+sender+", ungluer name "+ungluer+"\nwith user agent "+useragent+"\n>>>\n"+message
send_mail(subject, message, sender, [recipient])
return render(request, "thanks.html", {"page":page})
else:
num1 = request.POST['num1']
num2 = request.POST['num2']
else:
if request.user.is_authenticated():
sender=request.user.email;
else:
sender=''
try:
page = request.GET['page']
except:
page='/'
form = FeedbackForm(initial={"sender":sender, "subject": "Feedback on page "+page, "page":page, "num1":num1, "num2":num2, "answer":sum})
return render(request, "feedback.html", {'form':form, 'num1':num1, 'num2':num2})

View File

@ -1,8 +1,10 @@
from regluit.core.models import Campaign, Wishlist
from regluit.payment.models import Transaction, Receiver, PaymentResponse
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from regluit.payment.parameters import *
from regluit.payment.paypal import Pay, Execute, IPN, IPN_TYPE_PAYMENT, IPN_TYPE_PREAPPROVAL, IPN_TYPE_ADJUSTMENT, IPN_PAY_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE
from regluit.payment.paypal import Pay, Execute, IPN, IPN_TYPE_PAYMENT, IPN_TYPE_PREAPPROVAL, IPN_TYPE_ADJUSTMENT, IPN_PAY_STATUS_ACTIVE, IPN_PAY_STATUS_INCOMPLETE, IPN_PAY_STATUS_NONE
from regluit.payment.paypal import Preapproval, IPN_PAY_STATUS_COMPLETED, CancelPreapproval, PaymentDetails, PreapprovalDetails, IPN_SENDER_STATUS_COMPLETED, IPN_TXN_STATUS_COMPLETED
from regluit.payment.paypal import RefundPayment
import uuid
@ -12,9 +14,11 @@ from dateutil.relativedelta import relativedelta
import logging
from decimal import Decimal as D
from xml.dom import minidom
import urllib, urlparse
from django.conf import settings
logger = logging.getLogger(__name__)
def append_element(doc, parent, name, text):
@ -32,49 +36,59 @@ class PaymentManager( object ):
def __init__( self, embedded=False):
self.embedded = embedded
def checkStatus(self, past_days=3):
def update_preapproval(self, transaction):
"""Update a transaction to hold the data from a PreapprovalDetails on that transaction"""
t = transaction
p = PreapprovalDetails(t)
'''
Run through all pay transactions and verify that their current status is as we think.
'''
preapproval_status = {'id':t.id, 'key':t.preapproval_key}
#doc = minidom.Document()
#head = doc.createElement('transactions')
#doc.appendChild(head)
if p.error() or not p.success():
logger.info("Error retrieving preapproval details for transaction %d" % t.id)
preapproval_status["error"] = "An error occurred while verifying this transaction, see server logs for details"
else:
status = {'payments':[], 'preapprovals':[]}
# Check the transaction status
if t.status != p.status:
preapproval_status["status"] = {'ours':t.status, 'theirs':p.status}
t.status = p.status
t.save()
# look at all PAY transactions for stated number of past days; if past_days is not int, get all Transaction
# only PAY transactions have date_payment not None
try:
ref_date = datetime.utcnow() - relativedelta(days=int(past_days))
transactions = Transaction.objects.filter(date_payment__gte=ref_date)
except:
ref_date = datetime.utcnow()
transactions = Transaction.objects.filter(date_payment__isnull=False)
# check the currency code
if t.currency != p.currency:
preapproval_status["currency"] = {'ours':t.currency, 'theirs':p.currency}
t.currency = p.currency
t.save()
logger.info(transactions)
# Check the amount
if t.max_amount != D(p.amount):
preapproval_status["amount"] = {'ours':t.max_amount, 'theirs':p.amount}
t.max_amount = p.amount
t.save()
for t in transactions:
# Check approved
if t.approved != p.approved:
preapproval_status["approved"] = {'ours':t.approved, 'theirs':p.approved}
t.approved = p.approved
t.save()
#tran = doc.createElement('transaction')
#tran.setAttribute("id", str(t.id))
#head.appendChild(tran)
return preapproval_status
def update_payment(self, transaction):
"""Update a transaction to hold the data from a PaymentDetails on that transaction"""
t = transaction
payment_status = {'id':t.id}
p = PaymentDetails(t)
if p.error() or not p.success():
logger.info("Error retrieving payment details for transaction %d" % t.id)
#append_element(doc, tran, "error", "An error occurred while verifying this transaction, see server logs for details")
payment_status['error'] = "An error occurred while verifying this transaction, see server logs for details"
else:
# Check the transaction status
if t.status != p.status:
#append_element(doc, tran, "status_ours", t.status)
#append_element(doc, tran, "status_theirs", p.status)
payment_status['status'] = {'ours': t.status, 'theirs': p.status}
t.status = p.status
@ -95,16 +109,11 @@ class PaymentManager( object ):
# Check for updates on each receiver's status. Note that unprocessed delayed chained payments
# will not have a status code or txn id code
if receiver.status != r['status']:
#append_element(doc, tran, "receiver_status_ours", receiver.status)
#append_element(doc, tran, "receiver_status_theirs",
# r['status'] if r['status'] is not None else 'None')
receiver_status['status'] = {'ours': receiver.status, 'theirs': r['status']}
receiver.status = r['status']
receiver.save()
if receiver.txn_id != r['txn_id']:
#append_element(doc, tran, "txn_id_ours", receiver.txn_id)
#append_element(doc, tran, "txn_id_theirs", r['txn_id'])
receiver_status['txn_id'] = {'ours':receiver.txn_id, 'theirs':r['txn_id']}
receiver.txn_id = r['txn_id']
@ -117,57 +126,60 @@ class PaymentManager( object ):
receivers_status.append(receiver_status)
if len(receivers_status):
status["receivers"] = receivers_status
payment_status["receivers"] = receivers_status
if not set(["status", "receivers"]).isdisjoint(payment_status.keys()):
status["payments"].append(payment_status)
return payment_status
def checkStatus(self, past_days=None, transactions=None):
'''
Run through all pay transactions and verify that their current status is as we think.
Allow for a list of transactions to be passed in or for the method to check on all transactions within the
given past_days
'''
DEFAULT_DAYS_TO_CHECK = 3
status = {'payments':[], 'preapprovals':[]}
# look at all PAY transactions for stated number of past days; if past_days is not int, get all Transaction
# only PAY transactions have date_payment not None
if transactions is None:
if past_days is None:
past_days = DEFAULT_DAYS_TO_CHECK
try:
ref_date = datetime.utcnow() - relativedelta(days=int(past_days))
payment_transactions = Transaction.objects.filter(date_payment__gte=ref_date)
except:
ref_date = datetime.utcnow()
payment_transactions = Transaction.objects.filter(date_payment__isnull=False)
logger.info(payment_transactions)
# Now look for preapprovals that have not been paid and check on their status
transactions = Transaction.objects.filter(date_authorized__gte=ref_date, date_payment=None, type=PAYMENT_TYPE_AUTHORIZATION)
preapproval_transactions = Transaction.objects.filter(date_authorized__gte=ref_date, date_payment=None, type=PAYMENT_TYPE_AUTHORIZATION)
logger.info(preapproval_transactions)
transactions = payment_transactions | preapproval_transactions
for t in transactions:
p = PreapprovalDetails(t)
#tran = doc.createElement('preapproval')
#tran.setAttribute("key", str(t.preapproval_key))
#head.appendChild(tran)
preapproval_status = {'id':t.id, 'key':t.preapproval_key}
if p.error() or not p.success():
logger.info("Error retrieving preapproval details for transaction %d" % t.id)
#append_element(doc, tran, "error", "An error occurred while verifying this transaction, see server logs for details")
preapproval_status["error"] = "An error occurred while verifying this transaction, see server logs for details"
else:
# Check the transaction status
if t.status != p.status:
#append_element(doc, tran, "status_ours", t.status)
#append_element(doc, tran, "status_theirs", p.status)
preapproval_status["status"] = {'ours':t.status, 'theirs':p.status}
t.status = p.status
t.save()
# check the currency code
if t.currency != p.currency:
#append_element(doc, tran, "currency_ours", t.currency)
#append_element(doc, tran, "currency_theirs", p.currency)
preapproval_status["currency"] = {'ours':t.currency, 'theirs':p.currency}
t.currency = p.currency
t.save()
# Check the amount
if t.max_amount != D(p.amount):
#append_element(doc, tran, "amount_ours", str(t.amount))
#append_element(doc, tran, "amount_theirs", str(p.amount))
preapproval_status["amount"] = {'ours':t.max_amount, 'theirs':p.amount}
t.max_amount = p.amount
t.save()
# append only if there was a change in status
if not set(['status', 'currency', 'amount']).isdisjoint(set(preapproval_status.keys())):
if t.date_payment is None:
preapproval_status = self.update_preapproval(t)
if not set(['status', 'currency', 'amount', 'approved']).isdisjoint(set(preapproval_status.keys())):
status["preapprovals"].append(preapproval_status)
else:
payment_status = self.update_payment(t)
if not set(["status", "receivers"]).isdisjoint(payment_status.keys()):
status["payments"].append(payment_status)
return status
@ -261,6 +273,10 @@ class PaymentManager( object ):
# The status is always one of the IPN_PREAPPROVAL_STATUS codes defined in paypal.py
t.status = ipn.status
# capture whether the transaction has been approved
t.approved = ipn.approved
t.save()
logger.info("IPN: Preapproval transaction: " + str(t.id) + " Status: " + ipn.status)
@ -287,8 +303,10 @@ class PaymentManager( object ):
pledged_list = []
if authorized:
# return only ACTIVE transactions with approved=True
authorized_list = transaction_list.filter(type=PAYMENT_TYPE_AUTHORIZATION,
status=IPN_PAY_STATUS_ACTIVE)
status=IPN_PAY_STATUS_ACTIVE,
approved=True)
else:
authorized_list = []
@ -620,6 +638,19 @@ class PaymentManager( object ):
anonymous=anonymous
)
# we might want to not allow for a return_url or cancel_url to be passed in but calculated
# here because we have immediate access to the Transaction object.
if cancel_url is None:
cancel_path = "{0}?{1}".format(reverse('pledge_cancel'),
urllib.urlencode({'tid':t.id}))
cancel_url = urlparse.urljoin(settings.BASE_URL, cancel_path)
if return_url is None:
return_path = "{0}?{1}".format(reverse('pledge_complete'),
urllib.urlencode({'tid':t.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
p = Preapproval(t, amount, expiry, return_url=return_url, cancel_url=cancel_url)
# Create a response for this
@ -708,7 +739,7 @@ class PaymentManager( object ):
logger.info("Error, unable to start a new authorization")
return False, None
elif amount < transaction.max_amount:
elif amount <= transaction.max_amount:
# Change the amount but leave the preapproval alone
transaction.amount = amount
transaction.save()

View File

@ -0,0 +1,150 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Transaction.max_amount'
db.add_column('payment_transaction', 'max_amount', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=14, decimal_places=2), keep_default=False)
def backwards(self, orm):
# Deleting field 'Transaction.max_amount'
db.delete_column('payment_transaction', 'max_amount')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.wishes': {
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Work']"})
},
'core.wishlist': {
'Meta': {'object_name': 'Wishlist'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'openlibrary_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
},
'payment.paymentresponse': {
'Meta': {'object_name': 'PaymentResponse'},
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
},
'payment.receiver': {
'Meta': {'object_name': 'Receiver'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
},
'payment.transaction': {
'Meta': {'object_name': 'Transaction'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']", 'null': 'True'}),
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32'}),
'target': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
}
}
complete_apps = ['payment']

View File

@ -0,0 +1,151 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Transaction.approved'
db.add_column('payment_transaction', 'approved', self.gf('django.db.models.fields.NullBooleanField')(null=True, blank=True), keep_default=False)
def backwards(self, orm):
# Deleting field 'Transaction.approved'
db.delete_column('payment_transaction', 'approved')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'core.campaign': {
'Meta': {'object_name': 'Campaign'},
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
},
'core.wishes': {
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Work']"})
},
'core.wishlist': {
'Meta': {'object_name': 'Wishlist'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
},
'core.work': {
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'openlibrary_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
},
'payment.paymentresponse': {
'Meta': {'object_name': 'PaymentResponse'},
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
},
'payment.receiver': {
'Meta': {'object_name': 'Receiver'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
},
'payment.transaction': {
'Meta': {'object_name': 'Transaction'},
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']", 'null': 'True'}),
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32'}),
'target': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
}
}
complete_apps = ['payment']

View File

@ -36,6 +36,9 @@ class Transaction(models.Model):
# (RY is not sure what receipt is for)
receipt = models.CharField(max_length=256, null=True)
# whether a Preapproval has been approved or not
approved = models.NullBooleanField(null=True)
# error message from a PayPal transaction
error = models.CharField(max_length=256, null=True)

View File

@ -694,7 +694,16 @@ class PreapprovalDetails(PaypalEnvelopeRequest):
self.status = self.response.get("status", None)
self.amount = self.response.get("maxTotalAmountOfAllPayments", None)
self.currency = self.response.get("currencyCode", None)
self.approved = self.response.get("approved", None)
# a bit uncertain about how well PayPal sticks to a standard case
approved = self.response.get("approved", 'None')
if approved.lower() == 'true':
self.approved = True
elif approved.lower() == 'false':
self.approved = False
else:
self.approved = None
self.expiration = self.response.get("endingDate", None)
self.date = self.response.get("startingDate", None)
@ -738,6 +747,15 @@ class IPN( object ):
self.reason_code = request.POST.get('reason_code', None)
self.trackingId = request.POST.get('tracking_id', None)
# a bit uncertain about how well PayPal sticks to a standard case
approved = request.POST.get("approved", 'None')
if approved.lower() == 'true':
self.approved = True
elif approved.lower() == 'false':
self.approved = False
else:
self.approved = None
self.process_transactions(request)
except:

View File

@ -240,7 +240,7 @@ class TransactionTest(TestCase):
pass
def testSimple(self):
"""
create a single transaction with PAYMENT_TYPE_INSTANT / COMPLETED with a $12.34 pledge and see whether the payment
create a single transaction with PAYMENT_TYPE_AUTHORIZATION / ACTIVE with a $12.34 pledge and see whether the payment
manager can query and get the right amount.
"""
@ -253,6 +253,7 @@ class TransactionTest(TestCase):
t.amount = D('12.34')
t.type = PAYMENT_TYPE_AUTHORIZATION
t.status = 'ACTIVE'
t.approved = True
t.campaign = c
t.save()

View File

@ -1,13 +1,19 @@
from django.conf.urls.defaults import *
from django.conf import settings
urlpatterns = patterns(
"regluit.payment.views",
url(r"^paypalipn", "paypalIPN", name="PayPalIPN"),
)
if not settings.IS_PREVIEW:
urlpatterns += patterns(
"regluit.payment.views",
url(r"^testpledge", "testPledge"),
url(r"^testauthorize", "testAuthorize"),
url(r"^testexecute", "testExecute"),
url(r"^testcancel", "testCancel"),
url(r"^querycampaign", "queryCampaign"),
url(r"^paypalipn", "paypalIPN", name="PayPalIPN"),
url(r"^runtests", "runTests"),
url(r"^paymentcomplete","paymentcomplete"),
url(r"^checkstatus", "checkStatus"),

View File

@ -159,7 +159,7 @@ def testRefund(request):
return HttpResponse(message)
'''
http://BASE/testmodufy?transaction=2
http://BASE/testmodify?transaction=2
Example that modifies the amount of a transaction
'''

View File

@ -72,6 +72,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.static',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.request',
'regluit.context_processors.is_preview',
)
MIDDLEWARE_CLASSES = (
@ -180,7 +181,7 @@ AUTHENTICATION_BACKENDS = (
SOCIAL_AUTH_ENABLED_BACKENDS = ['google', 'facebook', 'twitter']
SOCIAL_AUTH_ASSOCIATE_BY_MAIL = True
SOCIAL_AUTH_NEW_USER_REDIRECT_URL = '/accounts/edit/'
SOCIAL_AUTH_NEW_USER_REDIRECT_URL = '/'
TWITTER_EXTRA_DATA = [('profile_image_url', 'profile_image_url')]
@ -201,8 +202,12 @@ GOODREADS_API_SECRET = ""
UNGLUEIT_MINIMUM_TARGET = '1000' # in US Dollars
UNGLUEIT_LONGEST_DEADLINE = '180' # number of days allowed for a campaign
UNGLUEIT_SHORTEST_DEADLINE = '7' # minimum number of days allowed for a campaign
UNGLUEIT_RECOMMENDED_USERNAME = 'recommended'
UNGLUEIT_RECOMMENDED_USERNAME = 'unglueit'
TEST_RUNNER = "djcelery.contrib.test_runner.CeleryTestSuiteRunner"
import djcelery
djcelery.setup_loader()
# this suppresses functionality that should not be visible on the alpha site
# change to 0 if you want to see it on your local machine
IS_PREVIEW = True

View File

@ -3,6 +3,12 @@ from regluit.settings.common import *
DEBUG = True
TEMPLATE_DEBUG = DEBUG
# if you're doing development work, you'll want this to be zero
IS_PREVIEW = False
# SITE_ID for your particular site -- must be configured in /core/fixtures/initial_data.json
SITE_ID = 3
ADMINS = (
('Ed Summers', 'ehs@pobox.com'),
)
@ -109,3 +115,7 @@ CELERYD_HIJACK_ROOT_LOGGER = False
INTERNAL_IPS = ('127.0.0.1',)
CELERYD_LOG_LEVEL = "INFO"
# this suppresses functionality that should not be visible on the alpha site
# change to 0 if you want to see it on your local machine
IS_PREVIEW = False

125
settings/please.py Normal file
View File

@ -0,0 +1,125 @@
from regluit.settings.common import *
DEBUG = False
TEMPLATE_DEBUG = DEBUG
SITE_ID = 2
ADMINS = (
('Ed Summers', 'ed.summers@gmail.com'),
('Raymond Yee', 'rdhyee+ungluebugs@gluejar.com'),
('Eric Hellman', 'eric@gluejar.com'),
('Andromeda Yelton', 'andromeda@gluejar.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'please',
'USER': 'please2',
'PASSWORD': 'unglueit',
'HOST': 'pleasedb.cboagmr25pjs.us-east-1.rds.amazonaws.com',
'PORT': '',
}
}
TIME_ZONE = 'America/New_York'
SECRET_KEY = '_^_off!8zsj4+)%qq623m&$7_m-q$iau5le0w!mw&n5tgt#x=t'
# settings for outbout email
# if you have a gmail account you can use your email address and password
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'accounts@gluejar.com'
EMAIL_HOST_PASSWORD = '7k3sWyzHpI'
EMAIL_PORT = 587
DEFAULT_FROM_EMAIL = 'accounts@gluejar.com'
# googlebooks
GOOGLE_BOOKS_API_KEY = 'AIzaSyBE36z7o6NUafIWcLEB8yk2I47-8_5y1_0'
# twitter auth
TWITTER_CONSUMER_KEY = 'sd9StEg1N1qB8gGb2GRX4A'
TWITTER_CONSUMER_SECRET = 'YSKHn8Du6EWqpcWZ6sp5tqDPvcOBXK0WJWVGWyB0'
# facebook auth
FACEBOOK_APP_ID = '242881179080779'
FACEBOOK_API_SECRET = '5eae483a0e92113d884c427b578ef23a'
# google auth
GOOGLE_OAUTH2_CLIENT_ID = '989608723367.apps.googleusercontent.com'
GOOGLE_OAUTH2_CLIENT_SECRET = '3UqalKyNynnaaarumUIWh8vS'
GOOGLE_DISPLAY_NAME = 'unglue it!'
# credentials from a sandbox account that Raymond set up.
PAYPAL_USERNAME = 'glueja_1317336101_biz_api1.gluejar.com'
PAYPAL_PASSWORD = '1317336137'
PAYPAL_SIGNATURE = 'AHVb0D1mzGD6zdX4XtKZbH.Kd6OhALVyiJVbNReOZEfyz79AoEnQJWTR'
PAYPAL_APPID = 'APP-80W284485P519543T' # sandbox app id -- will have to replace with production id
PAYPAL_ENDPOINT = 'svcs.sandbox.paypal.com' # sandbox
PAYPAL_PAYMENT_HOST = 'https://www.sandbox.paypal.com' # sandbox
PAYPAL_SANDBOX_LOGIN = ''
PAYPAL_SANDBOX_PASSWORD = ''
PAYPAL_BUYER_LOGIN =''
PAYPAL_BUYER_PASSWORD = ''
# The amount of the transaction that Gluejar takes
GLUEJAR_COMMISSION = 0.06
PREAPPROVAL_PERIOD = 365 # days to ask for in a preapproval
PREAPPROVAL_PERIOD_AFTER_CAMPAIGN = 90 # if we ask for preapproval time after a campaign deadline
# in live system, replace with the real Gluejar paypal email and that for our non-profit partner
PAYPAL_GLUEJAR_EMAIL = "glueja_1317336101_biz@gluejar.com"
PAYPAL_NONPROFIT_PARTNER_EMAIL = "nppart_1318957063_per@gluejar.com"
# for test purposes have a single RH paypal email
PAYPAL_TEST_RH_EMAIL = "rh1_1317336251_biz@gluejar.com"
# Goodreads API
GOODREADS_API_KEY = "vfqIO6QAhBVvlxt6hAzZJg"
GOODREADS_API_SECRET = "57tq4MpyJ15Hgm2ToZQQFWJ7vraZzOAqHLckWRXQ"
# Freebase credentials
FREEBASE_USERNAME = ''
FREEBASE_PASSWORD = ''
# send celery log to Python logging
CELERYD_HIJACK_ROOT_LOGGER = False
# BASE_URL is a hard-coding of the domain name for site and used for PayPal IPN
# Next step to try https
BASE_URL = 'http://please.unglueit.com'
# use redis for production queue
BROKER_TRANSPORT = "redis"
BROKER_HOST = "localhost"
BROKER_PORT = 6379
BROKER_VHOST = "0"
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
},
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
STATIC_ROOT = '/var/www/static'
IS_PREVIEW = False

View File

@ -2,8 +2,9 @@ from regluit.settings.common import *
DEBUG = False
TEMPLATE_DEBUG = DEBUG
IS_PREVIEW = True
SITE_ID = 2
SITE_ID = 1
ADMINS = (
('Ed Summers', 'ed.summers@gmail.com'),
@ -17,10 +18,10 @@ MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'please',
'USER': 'please',
'PASSWORD': 'unglueit',
'HOST': 'gluejardb.cboagmr25pjs.us-east-1.rds.amazonaws.com',
'NAME': 'unglueit',
'USER': 'root',
'PASSWORD': 'unglue1t',
'HOST': 'production.cboagmr25pjs.us-east-1.rds.amazonaws.com',
'PORT': '',
}
}
@ -46,8 +47,8 @@ TWITTER_CONSUMER_KEY = 'sd9StEg1N1qB8gGb2GRX4A'
TWITTER_CONSUMER_SECRET = 'YSKHn8Du6EWqpcWZ6sp5tqDPvcOBXK0WJWVGWyB0'
# facebook auth
FACEBOOK_APP_ID = '242881179080779'
FACEBOOK_API_SECRET = '5eae483a0e92113d884c427b578ef23a'
FACEBOOK_APP_ID = '211951285561911'
FACEBOOK_API_SECRET = '42efef7e540b80479dbbb69490cd902a'
# google auth
GOOGLE_OAUTH2_CLIENT_ID = '989608723367.apps.googleusercontent.com'
@ -94,7 +95,7 @@ CELERYD_HIJACK_ROOT_LOGGER = False
# BASE_URL is a hard-coding of the domain name for site and used for PayPal IPN
# Next step to try https
BASE_URL = 'http://please.unglueit.com'
BASE_URL = 'http://unglueit.com'
# use redis for production queue
BROKER_TRANSPORT = "redis"

View File

@ -13,6 +13,38 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.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: none;
margin: 5px 0;
}
/* Cross-browser language */
/* rows in listview should alternate colors */
.row1 .book-list.listview {
@ -223,9 +255,9 @@ ul.navigation li a:hover, ul.navigation li.active a {
right: 31px;
}
.listview.icons .booklist-status-img {
-moz-border-radius: 4px 4px 4px 4px;
-webkit-border-radius: 4px 4px 4px 4px;
border-radius: 4px 4px 4px 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
background-color: #fff;
margin-top: 4px;
height: 37px;

View File

@ -13,6 +13,38 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.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: none;
margin: 5px 0;
}
/* Local variables */
.greenpanelstuff {
font-family: Arial, Helvetica, sans-serif;
@ -38,9 +70,9 @@
padding: 0px 0px;
background: #FFFFFF;
margin: 0px;
-moz-border-radius: 4px 4px 4px 4px;
-webkit-border-radius: 4px 4px 4px 4px;
border-radius: 4px 4px 4px 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
border: 1px solid #81bb38;
}
/* background */
@ -170,9 +202,9 @@ div.panelview.side2 {
padding: 0px 0px;
background: #FFFFFF;
margin: 0px;
-moz-border-radius: 4px 4px 4px 4px;
-webkit-border-radius: 4px 4px 4px 4px;
border-radius: 4px 4px 4px 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
border: 1px solid #81bb38;
}
.read_itbutton a {
@ -224,9 +256,9 @@ div.panelview.side2 {
padding: 0px 0px;
background: #FFFFFF;
margin: 0px;
-moz-border-radius: 4px 4px 4px 4px;
-webkit-border-radius: 4px 4px 4px 4px;
border-radius: 4px 4px 4px 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
border: 1px solid #81bb38;
}
.read_itbutton_fail span {
@ -250,9 +282,9 @@ div.panelview.side2 {
padding: 0px 0px;
background: #FFFFFF;
margin: 0px;
-moz-border-radius: 4px 4px 4px 4px;
-webkit-border-radius: 4px 4px 4px 4px;
border-radius: 4px 4px 4px 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
border: 1px solid #81bb38;
}
.Unglue_itbutton a {

View File

@ -17,6 +17,34 @@
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.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: none;
margin: 5px 0;
}
#js-page-wrap {
overflow: hidden;
}
@ -27,9 +55,9 @@
margin-bottom: 10px;
}
#js-leftcol .jsmodule.rounded .jsmod-content {
-moz-border-radius: 20px 20px 20px 20px;
-webkit-border-radius: 20px 20px 20px 20px;
border-radius: 20px 20px 20px 20px;
-moz-border-radius: 20px;
-webkit-border-radius: 20px;
border-radius: 20px;
background: #edf3f4;
color: #3d4e53;
padding: 10px 20px;
@ -130,6 +158,9 @@
padding: 0;
cursor: pointer;
}
.book-detail-info > div.layout div.btn_support.modify input {
background: url("/static/images/btn_bg_grey.png") 0 0 no-repeat;
}
.book-detail-info .btn_wishlist span {
text-align: right;
}
@ -204,6 +235,9 @@ ul.tabs li a {
background: #d6dde0;
color: #3d4e53;
}
ul.tabs li a:hover {
text-decoration: none;
}
ul.tabs li a:hover, ul.tabs li.active a {
background: #6994a3;
color: #fff;
@ -215,9 +249,9 @@ ul.tabs li a:hover, ul.tabs li.active a {
}
#js-rightcol h3.jsmod-title {
background: #a7c1ca;
-moz-border-radius: 10px 10px 10px 10px;
-webkit-border-radius: 10px 10px 10px 10px;
border-radius: 10px 10px 10px 10px;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
padding: 10px;
height: auto;
font-style: normal;
@ -235,9 +269,9 @@ ul.tabs li a:hover, ul.tabs li.active a {
}
.js-rightcol-pad {
border: 1px solid #d6dde0;
-moz-border-radius: 10px 10px 10px 10px;
-webkit-border-radius: 10px 10px 10px 10px;
border-radius: 10px 10px 10px 10px;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
padding: 10px;
}
#js-maincol {
@ -264,14 +298,14 @@ div.content-block-content {
.tabs-content form {
margin-left: -5px;
}
ul.social a:hover {
text-decoration: none;
}
ul.social li {
padding: 5px 0 5px 30px;
height: 28px;
line-height: 28px;
}
ul.social li a {
padding-left: 10px;
}
ul.social li.facebook {
background: url("/static/images/icons/facebook.png") 10px center no-repeat;
cursor: pointer;
@ -327,9 +361,9 @@ ul.social li.embed:hover span {
#widgetcode {
display: none;
border: 1px solid #d6dde0;
-moz-border-radius: 10px 10px 10px 10px;
-webkit-border-radius: 10px 10px 10px 10px;
border-radius: 10px 10px 10px 10px;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
padding: 10px;
}
ul.support li {
@ -419,6 +453,9 @@ a {
.editions a:hover {
text-decoration: underline;
}
.thank-you {
font-size: 20px;
}
.work_supporter {
height: auto;
min-height: 50px;

View File

@ -13,6 +13,45 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.actionbuttons {
width: auto;
height: 36px;
line-height: 36px;
height: 36px;
line-height: 36px;
background: #8dc63f;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
-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: none;
margin: 5px 0;
}
/* variables and mixins used in multiple less files go here */
.header-text {
height: 36px;
@ -28,6 +67,45 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.actionbuttons {
width: auto;
height: 36px;
line-height: 36px;
height: 36px;
line-height: 36px;
background: #8dc63f;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
-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: none;
margin: 5px 0;
}
.user-block-hide {
float: left;
width: 100%;
@ -65,7 +143,10 @@
}
.block-intro-text div#active {
display: inherit;
height: 75px;
}
body {
font-size: 13px;
line-height: 16px;
}
/* Learn More area (not already styles by learnmore.less) */
.user-block {
@ -119,12 +200,12 @@
margin-top: 50px;
background: #edf3f4;
border: 1px solid #d6dde0;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
}
.have-right #js-rightcol .jsmodule {
border-bottom: 1px solid #3c4e52;
@ -155,39 +236,40 @@
}
.doc ul li {
margin-bottom: 7px;
-moz-border-radius: 14px 14px 14px 14px;
-webkit-border-radius: 14px 14px 14px 14px;
border-radius: 14px 14px 14px 14px;
-moz-border-radius: 14px 14px 14px 14px;
-webkit-border-radius: 14px 14px 14px 14px;
border-radius: 14px 14px 14px 14px;
-moz-border-radius: 14px;
-webkit-border-radius: 14px;
border-radius: 14px;
-moz-border-radius: 14px;
-webkit-border-radius: 14px;
border-radius: 14px;
padding: 10px;
background: #edf3f4;
}
.doc div.inset {
background: #edf3f4;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
padding: 10px;
font-style: italic;
}
dt {
margin: 7px 0px 7px -7px;
-moz-border-radius: 14px 14px 14px 14px;
-webkit-border-radius: 14px 14px 14px 14px;
border-radius: 14px 14px 14px 14px;
-moz-border-radius: 14px 14px 14px 14px;
-webkit-border-radius: 14px 14px 14px 14px;
border-radius: 14px 14px 14px 14px;
-moz-border-radius: 14px;
-webkit-border-radius: 14px;
border-radius: 14px;
-moz-border-radius: 14px;
-webkit-border-radius: 14px;
border-radius: 14px;
padding: 10px 0px 10px 7px;
background: #edf3f4;
}
dd {
margin: 0;
padding-bottom: 7px;
}
.doc ol li {
margin-bottom: 7px;
@ -198,3 +280,71 @@ dd {
.collapse ul {
display: none;
}
/* items on press page */
.spacer {
clear: both;
height: 0px;
}
.presstoc {
overflow: auto;
clear: both;
padding-bottom: 10px;
}
.presstoc div {
float: left;
padding-right: 15px;
margin-bottom: 7px;
}
.presstoc div.pressemail {
border: solid 2px #3d4e53;
padding: 5px;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
max-width: 678px;
margin-top: 7px;
}
.pressarticles div {
margin-bottom: 10px;
}
.pressvideos > div {
margin-bottom: 15px;
padding-bottom: 7px;
border-bottom: solid 1px #3d4e53;
float: left;
}
.pressvideos iframe {
padding: 5px;
border: solid 5px #EDF3F4;
padding: 5px;
border: solid 5px #EDF3F4;
}
.pressimages {
clear: both;
}
.pressimages .outer {
clear: both;
}
.pressimages .outer div {
float: left;
width: 25%;
padding-bottom: 10px;
}
.pressimages .outer div.text {
width: 75%;
}
.pressimages .outer div p {
margin: 0 auto;
padding-left: 10px;
padding-right: 10px;
}
.pressimages .screenshot {
width: 150px;
padding: 5px;
border: solid 5px #EDF3F4;
padding: 5px;
border: solid 5px #EDF3F4;
}

View File

@ -13,6 +13,45 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.actionbuttons {
width: auto;
height: 36px;
line-height: 36px;
height: 36px;
line-height: 36px;
background: #8dc63f;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
-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: none;
margin: 5px 0;
}
/* variables and mixins used in multiple less files go here */
.header-text {
height: 36px;
@ -28,6 +67,45 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.actionbuttons {
width: auto;
height: 36px;
line-height: 36px;
height: 36px;
line-height: 36px;
background: #8dc63f;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
-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: none;
margin: 5px 0;
}
.user-block-hide {
float: left;
width: 100%;
@ -65,7 +143,6 @@
}
.block-intro-text div#active {
display: inherit;
height: 75px;
}
#expandable {
display: none;
@ -141,14 +218,19 @@
height: 24px;
line-height: 24px;
width: 24px;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
border: solid 4px #3d4e53;
-moz-border-radius: 24px;
-webkit-border-radius: 24px;
border-radius: 24px;
-moz-border-radius: 24px;
-webkit-border-radius: 24px;
border-radius: 24px;
-moz-box-shadow: -1px 1px #3d4e53;
-webkit-box-shadow: -1px 1px #3d4e53;
box-shadow: -1px 1px #3d4e53;
border: solid 3px white;
text-align: center;
color: white;
background: #3d4e53;
font-size: 17px;
z-index: 5000;
margin-top: -12px;
@ -163,12 +245,12 @@
width: 208px;
background: #edf3f4;
border: 1px solid #d6dde0;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
margin-bottom: 10px;
padding: 0 10px 10px 10px;
}
@ -177,12 +259,12 @@
padding-bottom: 10px;
}
#js-rightcol .jsmodule input, #js-rightcol2 .jsmodule input {
-moz-border-radius: 32px 32px 32px 32px;
-webkit-border-radius: 32px 32px 32px 32px;
border-radius: 32px 32px 32px 32px;
-moz-border-radius: 32px 32px 32px 32px;
-webkit-border-radius: 32px 32px 32px 32px;
border-radius: 32px 32px 32px 32px;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
border: none;
height: 36px;
line-height: 36px;
@ -238,6 +320,14 @@ ul.ungluingwhat li > div {
ul.ungluingwhat div.user-avatar {
width: 43px;
}
ul.ungluingwhat div.user-avatar img {
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
ul.ungluingwhat .user-book-thumb {
width: 29px;
}
@ -262,12 +352,12 @@ ul.ungluingwhat .user-book-info a.user-book-name {
}
div.typo2 {
background: #edf3f4;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px 12px 12px 12px;
-webkit-border-radius: 12px 12px 12px 12px;
border-radius: 12px 12px 12px 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
-moz-border-radius: 12px;
-webkit-border-radius: 12px;
border-radius: 12px;
padding: 10px;
font-style: italic;
}
@ -338,12 +428,12 @@ h2.page-heading {
margin-top: 55px;
}
#js-maincontainer-bot-block #js-search {
-moz-border-radius: 64px 64px 64px 64px;
-webkit-border-radius: 64px 64px 64px 64px;
border-radius: 64px 64px 64px 64px;
-moz-border-radius: 64px 64px 64px 64px;
-webkit-border-radius: 64px 64px 64px 64px;
border-radius: 64px 64px 64px 64px;
-moz-border-radius: 64px;
-webkit-border-radius: 64px;
border-radius: 64px;
-moz-border-radius: 64px;
-webkit-border-radius: 64px;
border-radius: 64px;
background-color: #8dc63f;
width: 628px;
height: 80px;
@ -438,6 +528,18 @@ h3.module-title {
}
.google_signup {
padding: 14px 0;
padding: 14px 0;
}
.google_signup div {
height: 32px;
line-height: 32px;
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup img {
float: left;
}
.google_signup div {
height: 32px;

View File

@ -13,6 +13,38 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.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: none;
margin: 5px 0;
}
.user-block-hide {
float: left;
width: 100%;
@ -50,5 +82,4 @@
}
.block-intro-text div#active {
display: inherit;
height: 75px;
}

View File

@ -1,14 +1,159 @@
/* variables and mixins used in multiple less files go here */
.header-text {
height: 36px;
line-height: 36px;
display: block;
text-decoration: none;
font-weight: bold;
font-size: 13px;
letter-spacing: -0.05em;
}
.panelborders {
border-width: 1px 0px;
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.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: none;
margin: 5px 0;
}
.errorlist {
color: red;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
border: solid #e35351 4px;
width: 90%;
padding: 5px;
font-weight: bold;
padding: 7px 0;
height: 16px;
line-height: 16px;
text-align: center;
}
.errorlist li {
list-style: none;
border: none;
}
#login_centerer {
padding: 10px 25%;
width: 960px;
}
#registration {
margin-left: auto;
margin-right: auto;
width: 30%;
margin-top: 5%;
width: 960px;
padding: 10px;
margin: 0 auto;
padding: 10px 0;
font-size: 13px;
line-height: 18px;
}
#registration input[type="submit"] {
margin-left: auto;
margin-right: auto;
padding: 5px 15px;
margin: 0 auto;
padding: 5px 0;
}
#login {
border: solid 3px #d6dde0;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
margin: 10px auto;
float: left;
padding: 10px;
width: 50%;
}
#login form label {
display: block;
}
#login form input {
width: 90%;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
border: 1px solid #d6dde0;
height: 18px;
line-height: 18px;
margin-bottom: 16px;
}
#login form input:focus {
border: 1px solid #8dc63f;
outline: none;
}
#login form input[type=submit] {
text-decoration: capitalize;
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: none;
margin: 5px 0;
}
#login form span.helptext {
display: block;
margin-top: -11px;
font-style: italic;
font-size: 12px;
}
#login .google_signup {
padding: 14px 0;
}
#login .google_signup div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
#login .google_signup img {
float: left;
}
.actionbutton {
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: none;
margin: 5px 0;
float: left;
}

View File

@ -14,6 +14,82 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.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: none;
margin: 5px 0;
}
/* remove before beta */
.preview {
border: solid 3px #e35351;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
clear: both;
padding: 5px 10px;
font-size: 13px;
line-height: 17px;
}
.preview a {
color: #8dc63f;
}
.preview_top {
border: solid 3px #e35351;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
clear: both;
padding: 5px 10px;
font-size: 13px;
line-height: 17px;
width: 934px;
margin: 10px auto 0 auto;
}
.preview_top a {
color: #8dc63f;
}
.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;
line-height: 17px;
width: 80%;
margin: 10px auto;
}
.preview_content a {
color: #8dc63f;
}
/* Local variables */
.utilityheaders {
text-transform: uppercase;
@ -35,6 +111,43 @@ body {
font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Arial, Helvetica, sans-serif;
color: #3d4e53;
}
#feedback {
/* remove after alpha? */
position: fixed;
top: 50%;
left: 0;
z-index: 500;
}
#feedback p {
/* see http://scottgale.com/blog/css-vertical-text/2010/03/01/ */
writing-mode: tb-rl;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-o-transform: rotate(90deg);
white-space: nowrap;
display: block;
bottom: 0;
width: 160px;
height: 26px;
-moz-border-radius: 32px;
-webkit-border-radius: 32px;
border-radius: 32px;
background: #8dc63f;
margin-bottom: 0;
text-align: center;
margin-left: -67px;
line-height: normal;
}
#feedback p a {
color: white;
font-size: 24px;
font-weight: normal;
}
#feedback p a:hover {
color: #3d4e53;
}
a {
font-weight: bold;
font-size: 13px;
@ -42,6 +155,9 @@ a {
cursor: pointer;
color: #6994a3;
}
a:hover {
text-decoration: underline;
}
img {
border: none;
}
@ -226,13 +342,21 @@ a.readon span {
#js-leftcol a:hover {
text-decoration: underline;
}
#js-leftcol a.comingsoon:hover {
text-decoration: none;
cursor: default;
}
#js-leftcol a.comingsoon:hover:after {
content: " Coming soon!";
color: #8dc63f;
}
#js-leftcol .jsmod-content {
border: solid 1px #edf3f4;
-moz-border-radius: 0 0 10px 10px;
-webkit-border-radius: 0 0 10px 10px;
border-radius: 0 0 10px 10px;
}
#js-leftcol ul.level1 > li > a {
#js-leftcol ul.level1 > li > a, #js-leftcol ul.level1 > li > span {
border-bottom: 1px solid #edf3f4;
border-top: 1px solid #edf3f4;
text-transform: uppercase;
@ -249,6 +373,9 @@ a.readon span {
}
#js-leftcol ul.level2 li img {
vertical-align: middle;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
#js-leftcol ul.level2 li .ungluer-name {
height: 30px;
@ -360,3 +487,6 @@ div.content-block-content .cols3 .column {
padding: 1px;
background-color: #edf3f4;
}
a.nounderline {
text-decoration: none;
}

View File

@ -13,6 +13,38 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.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: none;
margin: 5px 0;
}
.block-inner {
padding-right: 10px;
}
@ -74,6 +106,9 @@
img.user-avatar {
float: left;
margin-right: 10px;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
}
.social {
width: 100%;
@ -92,7 +127,7 @@ span.user-name, span.user-date, span.user-short-info {
.user-block2 .user-short-info {
padding-right: 10px;
}
span.user-name, span.user-name a, a {
span.user-name, span.user-name a {
font-size: 12px;
color: #3d4e53;
}
@ -103,9 +138,9 @@ span.user-status-title {
}
span.rounded {
border: 1px solid #d4d4d4;
-moz-border-radius: 7px 7px 7px 7px;
-webkit-border-radius: 7px 7px 7px 7px;
border-radius: 7px 7px 7px 7px;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
padding: 1px;
color: #fff;
margin: 0 8px 0 0;
@ -114,9 +149,9 @@ span.rounded {
span.rounded > span {
padding: 7px 7px;
min-width: 15px;
-moz-border-radius: 5px 5px 5px 5px;
-webkit-border-radius: 5px 5px 5px 5px;
border-radius: 5px 5px 5px 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
text-align: center;
display: inline-block;
}
@ -154,6 +189,7 @@ input.profile-save {
display: block;
text-indent: -100000px;
border: none;
cursor: pointer;
}
#loadgr {
background: url("/static/images/supporter_icons/goodreads_square.png") left center no-repeat;
@ -178,16 +214,20 @@ input.profile-save {
.weareonthat {
background: url("/static/images/checkmark_small.png") left center no-repeat;
}
a.my-setting {
span.my-setting {
background: #d6dde0 url("/static/images/header/explane.png") 90% center no-repeat;
-moz-border-radius: 7px;
-webkit-border-radius: 7px;
border-radius: 7px;
height: 50px;
line-height: 50px;
display: block;
padding: 0 0 0 10px;
font-size: 20px;
font-weight: bold;
cursor: pointer;
}
a.my-setting.active {
span.my-setting.active {
background: #d6dde0 url("/static/images/header/collspane.png") 90% center no-repeat;
}
.badges.listspage {
@ -201,6 +241,9 @@ a.my-setting.active {
margin-top: 10px;
width: 100%;
}
#tabs ul.tabs li a:hover {
text-decoration: none;
}
#tabs.wantto {
border-bottom: 4px solid #d6dde0;
}
@ -287,6 +330,9 @@ ul.tabs li.tabs3 a:hover, ul.tabs li.active a {
div.content-block-content {
padding-left: 10px;
}
.empty-wishlist {
margin-top: 10px;
}
.js-news-text {
float: left;
width: 70%;

View File

@ -13,3 +13,35 @@
border-style: solid none;
border-color: #FFFFFF;
}
.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
}
.google_signup_div {
padding: 14px 0;
}
.google_signup_div div {
height: 32px;
line-height: 32px;
float: left;
padding-left: 5px;
}
.google_signup_div img {
float: left;
}
.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: none;
margin: 5px 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

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