Merge pull request #221 from Gluejar/add_libraries

Add libraries
pull/1/head
Raymond Yee 2013-10-22 16:02:26 -07:00
commit eacb801af9
60 changed files with 2253 additions and 244 deletions

View File

@ -41,6 +41,8 @@ from regluit.core.lookups import (
OwnerLookup,
EditionLookup
)
from regluit.libraryauth.models import Library, Block, CardPattern, EmailPattern
from regluit.libraryauth.admin import LibraryAdmin, BlockAdmin, CardPatternAdmin, EmailPatternAdmin
class RegluitAdmin(AdminSite):
login_template = 'registration/login.html'
@ -211,6 +213,10 @@ class MARCRecordAdmin(ModelAdmin):
admin_site = RegluitAdmin("Admin")
admin_site.register(User, UserAdmin)
admin_site.register(Library, LibraryAdmin)
admin_site.register(Block, BlockAdmin)
admin_site.register(CardPattern, CardPatternAdmin)
admin_site.register(EmailPattern, EmailPatternAdmin)
admin_site.register(models.Work, WorkAdmin)
admin_site.register(models.Claim, ClaimAdmin)
admin_site.register(models.RightsHolder, RightsHolderAdmin)

View File

@ -0,0 +1,328 @@
# -*- coding: 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 'Acq.refreshes'
db.add_column('core_acq', 'refreshes',
self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime(2013, 10, 17, 0, 0), auto_now_add=True, blank=True),
keep_default=False)
# Adding field 'Acq.lib_acq'
db.add_column('core_acq', 'lib_acq',
self.gf('django.db.models.fields.related.ForeignKey')(related_name='loans', null=True, to=orm['core.Acq']),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Acq.refreshes'
db.delete_column('core_acq', 'refreshes')
# Deleting field 'Acq.lib_acq'
db.delete_column('core_acq', 'lib_acq_id')
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'})
},
'booxtream.boox': {
'Meta': {'object_name': 'Boox'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'download_link_epub': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
'download_link_mobi': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
'downloads_remaining': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
'expirydays': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'referenceid': ('django.db.models.fields.CharField', [], {'max_length': '32'})
},
'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.acq': {
'Meta': {'object_name': 'Acq'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'lib_acq': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'loans'", 'null': 'True', 'to': "orm['core.Acq']"}),
'license': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'nonce': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'refreshes': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 10, 17, 0, 0)', 'auto_now_add': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'acqs'", 'to': "orm['auth.User']"}),
'watermarked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['booxtream.Boox']", 'null': 'True'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'acqs'", 'to': "orm['core.Work']"})
},
'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.badge': {
'Meta': {'object_name': 'Badge'},
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '72', 'blank': 'True'})
},
'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'}),
'cc_date_initial': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
'dollar_per_day': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
'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'}),
'publisher': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Publisher']"}),
'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'}),
'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'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(2013, 10, 17, 0, 0)', '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'}),
'download_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'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', 'db_index': 'True'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '1024'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
},
'core.ebookfile': {
'Meta': {'object_name': 'EbookFile'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebook_files'", 'to': "orm['core.Edition']"}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'core.edition': {
'Meta': {'object_name': 'Edition'},
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': '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', 'blank': 'True'}),
'publisher_name': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.PublisherName']"}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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.key': {
'Meta': {'object_name': 'Key'},
'encrypted_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
},
'core.libpref': {
'Meta': {'object_name': 'Libpref'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'marc_link_target': ('django.db.models.fields.CharField', [], {'default': "'UNGLUE'", 'max_length': '6'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'libpref'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'core.marcrecord': {
'Meta': {'object_name': 'MARCRecord'},
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'MARCrecords'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'link_target': ('django.db.models.fields.CharField', [], {'default': "'DIRECT'", 'max_length': '6'})
},
'core.offer': {
'Meta': {'object_name': 'Offer'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'license': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'offers'", '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'}),
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
},
'core.press': {
'Meta': {'object_name': 'Press'},
'date': ('django.db.models.fields.DateField', [], {}),
'highlight': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
'note': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '140'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '140'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
},
'core.publisher': {
'Meta': {'object_name': 'Publisher'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'key_publisher'", 'to': "orm['core.PublisherName']"}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'})
},
'core.publishername': {
'Meta': {'object_name': 'PublisherName'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'publisher': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'alternate_names'", 'null': 'True', 'to': "orm['core.Publisher']"})
},
'core.rightsholder': {
'Meta': {'object_name': 'RightsHolder'},
'can_sell': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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'})
},
'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'},
'avatar_source': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1', 'null': 'True'}),
'badges': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'holders'", 'symmetrical': 'False', 'to': "orm['core.Badge']"}),
'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'}),
'kindle_email': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'blank': '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.waswork': {
'Meta': {'object_name': 'WasWork'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'moved': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
'was': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'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', [], {'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'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
}
}
complete_apps = ['core']

View File

@ -56,13 +56,16 @@ from regluit.core.parameters import (
INDIVIDUAL,
LIBRARY,
BORROWED,
TESTING
TESTING,
RESERVE,
)
from regluit.booxtream import BooXtream
watermarker = BooXtream()
from regluit.libraryauth.models import Library
pm = PostMonkey(settings.MAILCHIMP_API_KEY)
logger = logging.getLogger(__name__)
@ -256,9 +259,10 @@ class Acq(models.Model):
"""
Short for Acquisition, this is a made-up word to describe the thing you acquire when you buy or borrow an ebook
"""
CHOICES = ((INDIVIDUAL,'Individual license'),(LIBRARY,'Library License'),(BORROWED,'Borrowed from Library'), (TESTING,'Just for Testing'))
CHOICES = ((INDIVIDUAL,'Individual license'),(LIBRARY,'Library License'),(BORROWED,'Borrowed from Library'), (TESTING,'Just for Testing'), (RESERVE,'On Reserve'),)
created = models.DateTimeField(auto_now_add=True)
expires = models.DateTimeField(null=True)
refreshes = models.DateTimeField(auto_now_add=True, default=now())
work = models.ForeignKey("Work", related_name='acqs', null=False)
user = models.ForeignKey(User, related_name='acqs')
license = models.PositiveSmallIntegerField(null = False, default = INDIVIDUAL,
@ -266,6 +270,9 @@ class Acq(models.Model):
watermarked = models.ForeignKey("booxtream.Boox", null=True)
nonce = models.CharField(max_length=32, null=True)
# when the acq is a loan, this points at the library's acq it's derived from
lib_acq = models.ForeignKey("self", related_name="loans", null=True)
@property
def expired(self):
if self.expires is None:
@ -274,13 +281,19 @@ class Acq(models.Model):
return self.expires < datetime.now()
def get_mobi_url(self):
if self.expired:
return ''
return self.get_watermarked().download_link_mobi
def get_epub_url(self):
if self.expired:
return ''
return self.get_watermarked().download_link_epub
def get_watermarked(self):
if self.watermarked == None or self.watermarked.expired:
if self.on_reserve:
self.borrow(self.user)
params={
'customeremailaddress': self.user.email,
'customername': self.user.username,
@ -302,13 +315,55 @@ class Acq(models.Model):
def _hash(self):
return hashlib.md5('1c1a56974ef08edc%s:%s:%s'%(self.user.id,self.work.id,self.created)).hexdigest()
def expire_in(self, delta):
self.expires = now()+delta
self.save()
if self.lib_acq:
self.lib_acq.refreshes = now()+ (timedelta(days=14))
self.lib_acq.save()
@property
def on_reserve(self):
return self.license==RESERVE
def borrow(self, user=None):
if self.on_reserve:
self.license=BORROWED
self.expire_in(timedelta(days=14))
self.user.wishlist.add_work( self.work, "borrow")
return self
elif self.borrowable and user:
user.wishlist.add_work( self.work, "borrow")
borrowed = Acq.objects.create(user=user,work=self.work,license= BORROWED, lib_acq=self)
from regluit.core.tasks import watermark_acq
watermark_acq.delay(borrowed)
return borrowed
@property
def borrowable(self):
if self.license == RESERVE and not self.expired:
return True
if self.license == LIBRARY:
return self.refreshes < datetime.now()
else:
return False
def add_acq_nonce(sender, instance, created, **kwargs):
if created:
instance.nonce=instance._hash()
instance.save()
def set_expiration(sender, instance, created, **kwargs):
if created:
if instance.license == RESERVE:
instance.expire_in(timedelta(hours=2))
if instance.license == BORROWED:
instance.expire_in(timedelta(days=14))
post_save.connect(add_acq_nonce,sender=Acq)
post_save.connect(set_expiration,sender=Acq)
class Campaign(models.Model):
LICENSE_CHOICES = settings.CCCHOICES
@ -672,15 +727,26 @@ class Campaign(models.Model):
return Premium.objects.none()
return Premium.objects.filter(campaign=self).filter(type='CU').order_by('amount')
def active_offers(self):
@property
def library_offer(self):
return self._offer(LIBRARY)
@property
def individual_offer(self):
return self._offer(INDIVIDUAL)
def _offer(self, license):
if self.type is REWARDS:
return Offer.objects.none()
return Offer.objects.filter(work=self.work,active=True).order_by('price')
return None
try:
return Offer.objects.get(work=self.work, active=True, license=license)
except Offer.DoesNotExist:
return None
@property
def days_per_copy(self):
if self.active_offers().count()>0:
return Decimal(float(self.active_offers()[0].price) / self.dollar_per_day )
if self.individual_offer:
return Decimal(float(self.individual_offer.price) / self.dollar_per_day )
else:
return Decimal(0)
@ -1045,21 +1111,82 @@ class Work(models.Model):
def create_offers(self):
for choice in Offer.CHOICES:
if not self.offers.filter(license=choice[0]):
self.offers.create(license=choice[0])
self.offers.create(license=choice[0],active=True,price=Decimal(10))
return self.offers.all()
def purchased_by(self,user):
if user==None or not user.is_authenticated():
def borrowable(self, user):
if user.is_anonymous():
return False
acqs= self.acqs.filter(user=user)
if acqs.count()==0:
return False
for acq in acqs:
if acq.expires is None:
return True
if acq.expires > now():
return True
lib_user=(lib.user for lib in user.profile.libraries)
lib_license=self.get_user_license(lib_user)
if lib_license and lib_license.borrowable:
return True
return False
def in_library(self,user):
if user.is_anonymous():
return False
lib_user=(lib.user for lib in user.profile.libraries)
lib_license=self.get_user_license(lib_user)
if lib_license and lib_license.acqs.count():
return True
return False
@property
def lib_acqs(self):
return self.acqs.filter(license=LIBRARY)
class user_license:
acqs=Acq.objects.none()
def __init__(self,acqs):
self.acqs=acqs
@property
def is_active(self):
return self.acqs.filter(expires__isnull = True).count()>0 or self.acqs.filter(expires__gt= now()).count()>0
@property
def borrowed(self):
loans = self.acqs.filter(license=BORROWED,expires__gt= now())
if loans.count()==0:
return None
else:
return loans[0]
@property
def purchased(self):
purchases = self.acqs.filter(license=INDIVIDUAL)
if purchases.count()==0:
return None
else:
return purchases[0]
@property
def lib_acqs(self):
return self.acqs.filter(license=LIBRARY)
@property
def next_acq(self):
loans = self.acqs.filter(license=LIBRARY, refreshes__gt=now()).order_by('refreshes')
if loans.count()==0:
return None
else:
return loans[0]
@property
def borrowable(self):
return self.acqs.filter(license=LIBRARY, refreshes__lt=now()).count()>0
def get_user_license(self, user):
if user==None:
return None
if isinstance(user, User):
if user.is_anonymous():
return None
return self.user_license(self.acqs.filter(user=user))
else:
# assume it's several users
return self.user_license(self.acqs.filter(user__in=user))
class Author(models.Model):
created = models.DateTimeField(auto_now_add=True)
@ -1501,6 +1628,17 @@ class UserProfile(models.Model):
for social in socials:
auths[social.provider]=True
return auths
@property
def libraries(self):
libs=[]
for group in self.user.groups.all():
try:
libs.append(group.library)
except Library.DoesNotExist:
pass
return libs
class Press(models.Model):
url = models.URLField()

View File

@ -1,3 +1,3 @@
(REWARDS, BUY2UNGLUE) = (1, 2)
(INDIVIDUAL, LIBRARY, BORROWED) = (1, 2, 3)
(INDIVIDUAL, LIBRARY, BORROWED, RESERVE) = (1, 2, 3, 4)
TESTING = 0

View File

@ -31,7 +31,8 @@ regluit imports
"""
from regluit.payment.signals import transaction_charged, transaction_failed, pledge_modified, pledge_created
from regluit.utils.localdatetime import now
from regluit.core.parameters import REWARDS, BUY2UNGLUE
from regluit.core.parameters import REWARDS, BUY2UNGLUE, LIBRARY, RESERVE
from regluit.libraryauth.models import Library
logger = logging.getLogger(__name__)
@ -184,7 +185,12 @@ def handle_transaction_charged(sender,transaction=None, **kwargs):
else:
# provision the book
Acq = get_model('core', 'Acq')
new_acq = Acq.objects.create(user=transaction.user,work=transaction.campaign.work,license= transaction.offer.license)
if transaction.offer.license == LIBRARY:
library = Library.objects.get(id=transaction.extra['library_id'])
new_acq = Acq.objects.create(user=library.user,work=transaction.campaign.work,license= LIBRARY)
reserve_acq = Acq.objects.create(user=transaction.user,work=transaction.campaign.work,license= RESERVE, lib_acq = new_acq)
else:
new_acq = Acq.objects.create(user=transaction.user,work=transaction.campaign.work,license= transaction.offer.license)
transaction.campaign.update_left()
notification.send([transaction.user], "purchase_complete", {'transaction':transaction}, True)
from regluit.core.tasks import watermark_acq

View File

@ -57,8 +57,8 @@ from regluit.core.models import (
EbookFile,
Acq,
)
from regluit.core.parameters import TESTING
from regluit.libraryauth.models import Library
from regluit.core.parameters import TESTING, LIBRARY, RESERVE
from regluit.frontend.views import safe_get_work
from regluit.payment.models import Transaction
from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION
@ -864,5 +864,27 @@ class EbookFileTests(TestCase):
self.assertRegexpMatches(url,'download.booxtream.com/')
print url
from .signals import handle_transaction_charged
class LibTests(TestCase):
class transaction:
pass
def test_purchase(self):
w = Work.objects.create(title="Work 1")
e = Edition.objects.create(title=w.title,work=w)
u = User.objects.create_user('test', 'test@example.org', 'testpass')
lu = User.objects.create_user('library', 'testu@example.org', 'testpass')
lib = Library.objects.create(user=lu)
c = Campaign.objects.create(work=w, type = parameters.BUY2UNGLUE, cc_date_initial= datetime(2020,1,1),target=1000, deadline=datetime(2020,1,1))
new_acq = Acq.objects.create(user=lib.user,work=c.work,license= LIBRARY)
self.assertTrue(new_acq.borrowable)
reserve_acq = Acq.objects.create(user=u,work=c.work,license= RESERVE, lib_acq = new_acq)
self.assertTrue(reserve_acq.borrowable)
self.assertFalse(new_acq.borrowable)
self.assertTrue(reserve_acq.expires< now()+timedelta(hours=3))
reserve_acq.borrow()
self.assertTrue(reserve_acq.expires> now()+timedelta(hours=3))

View File

@ -41,3 +41,11 @@ def campaign_list_users(campaign_list, how_many):
else :
user_list = users[0: how_many]
return user_list
def library_users(library, how_many):
count= library.group.user_set.all().count()
if count <= how_many :
user_list = library.group.user_set.all().order_by('-last_login')[0: count]
else :
user_list = library.group.user_set.all().order_by('-last_login')[0: how_many]
return user_list

View File

@ -13,7 +13,6 @@ from django import forms
from django.conf import settings
from django.conf.global_settings import LANGUAGES
from django.contrib.auth.models import User
from django.contrib.auth.forms import AuthenticationForm
from django.core.validators import validate_email
from django.db import models
from django.forms.widgets import RadioSelect
@ -48,6 +47,8 @@ from regluit.core.models import (
FACEBOOK,
GRAVATAR
)
from regluit.libraryauth.models import Library
from regluit.core.parameters import LIBRARY
from regluit.core.lookups import (
OwnerLookup,
WorkLookup,
@ -365,6 +366,8 @@ class OfferForm(forms.ModelForm):
model = Offer
widgets = {
'work': forms.HiddenInput,
'license': forms.HiddenInput,
'active': forms.HiddenInput,
}
date_selector=range(date.today().year, settings.MAX_CC_DATE.year+1)
@ -488,6 +491,9 @@ class CampaignPurchaseForm(forms.Form):
anonymous = forms.BooleanField(required=False, label=_("Make this purchase anonymous, please"))
offer_id = forms.IntegerField(required=False)
offer=None
library_id = forms.IntegerField(required=False)
library = None
def clean_offer_id(self):
offer_id = self.cleaned_data['offer_id']
try:
@ -495,13 +501,30 @@ class CampaignPurchaseForm(forms.Form):
except Offer.DoesNotExist:
raise forms.ValidationError(_("Sorry, that offer is not valid."))
def clean_library_id(self):
library_id = self.cleaned_data['library_id']
if library_id:
try:
self.library = Library.objects.get(id=library_id)
except Library.DoesNotExist:
raise forms.ValidationError(_("Sorry, that Library is not valid."))
def clean(self):
if self.offer.license == LIBRARY:
if not self.library:
raise forms.ValidationError(_("No library specified." ))
return self.cleaned_data
def amount(self):
return self.offer.price if self.offer else None
@property
def trans_extra(self):
return PledgeExtra( anonymous=self.cleaned_data['anonymous'],
pe = PledgeExtra( anonymous=self.cleaned_data['anonymous'],
offer = self.offer )
if self.library:
pe.extra['library_id']=self.library.id
return pe
class CampaignPledgeForm(forms.Form):
preapproval_amount = forms.DecimalField(
@ -622,14 +645,6 @@ class FeedbackForm(forms.Form):
return cleaned_data
class AuthForm(AuthenticationForm):
def __init__(self, request=None, *args, **kwargs):
if request and request.method == 'GET':
saved_un= request.COOKIES.get('un', None)
super(AuthForm, self).__init__(initial={"username":saved_un},*args, **kwargs)
else:
super(AuthForm, self).__init__(*args, **kwargs)
class MsgForm(forms.Form):
msg = forms.CharField(widget=forms.Textarea(), error_messages={'required': 'Please specify a message.'})

View File

@ -1,5 +1,6 @@
{% load humanize %}
{% load purchased %}
{% load lib_acqs %}
{% with work.first_ebook as first_ebook %}
{% with work.last_campaign.supporters as supporters %}
{% with work.cover_image_thumbnail as thumbnail %}
@ -10,7 +11,7 @@
{% with work.last_campaign.deadline as deadline %}
{% with work.id as workid %}
{% with request.user.wishlist.works.all as wishlist %}
{% purchased %}
{% purchased %}{% lib_acqs %}
<div class="thewholebook listview tabs {% if first_ebook or status == 'SUCCESSFUL' %}tabs-1{% else %}{% if status == 'ACTIVE' %}tabs-2{% else %}tabs-3{% endif %}{% endif %}">
<div class="listview book-list">
<div class="listview panelback side2">
@ -18,7 +19,7 @@
<div class="greenpanel2">
{% if last_campaign %}
{% comment %}top section: campaign info + optional action button. Varies by campaign status.{% endcomment %}
{% if status == 'SUCCESSFUL' %}
{% if status == 'SUCCESSFUL' or license_is_active or borrowable %}
<div class="greenpanel_top">
{% comment %}bibliographic data{% endcomment %}
<div class="white_text">
@ -29,21 +30,41 @@
<div class="moreinfo">
<a href="{% if workid %}{% url work workid %}{% else %}{% url googlebooks googlebooks_id %}{% endif %}" target="_top">More Info</a>
</div>
{% if purchased %}
<div class="unglued_white">
<b>Purchased!</b>
</div>
{% else %}{% if borrowed %}
<b>Borrowed! </b>
<p><b>until</b> {{ borrowed.expires|date:"M d, Y" }}</p>
{% else %}{% if borrowable %}
<div class="unglued_white">
<b>Library has it!</b>
<p><b>{{ lib_acqs.count }}</b>{% ifequal lib_acqs.count 1 %} copy{% else %} copies{% endifequal %}</p>
</div>
{% else %}
<div class="unglued_white">
<b>UNGLUED!</b>
<p><b>On:</b> {{ deadline|date:"M d, Y" }}</p>
<p><b>Raised:</b> {{ work.last_campaign.current_total|floatformat:0|intcomma }}</p>
</div>
</div>
{% endif %}{% endif %}{% endif %}
</div>
<div class="add_button">
{% include "book_panel_addbutton.html" %}
</div>
<div class="white_text bottom_button" >
{% if first_ebook %}
{% if purchased %}
<a href="{% url download workid %}" class="hijax"><span class="read_itbutton button_text"><span>Read it Now</span></span></a>
{% else %}{% if borrowed %}
<a href="{% url download workid %}" class="hijax"><span class="read_itbutton button_text"><span>Read it Now</span></span></a>
{% else %}{% if borrowable %}
<a href="{% url borrow workid %}?library={{library}}" class="hijax"><span class="read_itbutton button_text"><span>Borrow It</span></span></a>
{% else %}{% if first_ebook %}
<a href="{% url download workid %}" class="hijax"><span class="read_itbutton button_text"><span>Read it Now</span></span></a>
{% else %}
<a href="{% url work workid %}"><span class="read_itbutton button_text"><span>Coming Soon</span></span></a>
{% endif %}
{% endif %}{% endif %}{% endif %}{% endif %}
</div>
{% else %}{% if status == 'ACTIVE' %}
<div class="greenpanel_top">
@ -58,16 +79,24 @@
</div>
<div class="unglued_white">
<b>UNGLUE IT!</b>
{% ifequal work.last_campaign.type 1 %}
<p><b>${{ work.last_campaign.current_total|floatformat:0|intcomma }}</b> raised</p>
<p><b>${{ work.last_campaign.target|floatformat:0|intcomma }}</b> needed</p>
<p>by {{ deadline|naturalday:"M d, Y" }}</p>
{% if in_library %}
{% if borrowable %}
<p>Available in your library now!</p>
{% else %}
<p>Available in your library on<br />{{ next_acq.refreshes|date:"M j, Y" }}</p>
{% endif %}
{% else %}
<p><b>${{ work.last_campaign.left|floatformat:0|intcomma }}</b> needed</p>
<p>now ungluing on </p>
<p>{{ work.last_campaign.cc_date|naturalday:"M d, Y" }}</p>
{% endifequal %}
<b>UNGLUE IT!</b>
{% ifequal work.last_campaign.type 1 %}
<p><b>${{ work.last_campaign.current_total|floatformat:0|intcomma }}</b> raised</p>
<p><b>${{ work.last_campaign.target|floatformat:0|intcomma }}</b> needed</p>
<p>by {{ deadline|naturalday:"M d, Y" }}</p>
{% else %}
<p><b>${{ work.last_campaign.left|floatformat:0|intcomma }}</b> needed</p>
<p>will unglue on </p>
<p>{{ work.last_campaign.cc_date|naturalday:"M d, Y" }}</p>
{% endifequal %}
{% endif %}
</div>
</div>
{% if request.user.id in supporters %}
@ -80,10 +109,10 @@
</div>
<div class="white_text bottom_button" >
{% ifequal work.last_campaign.type 1 %}
<a href="{% url pledge work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Pledge</span></span></a>
<a href="{% url pledge work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Pledge</span></span></a>
{% else %}
{% if purchased %}
<a href="{% url download workid %}"><span class="read_itbutton pledge button_text"><span>Download</span></span></a>
{% if in_library %}
<a href="{% url purchase work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Reserve It</span></span></a>
{% else %}
<a href="{% url purchase work_id=workid %}"><span class="read_itbutton pledge button_text"><span>Purchase</span></span></a>
{% endif %}
@ -166,50 +195,37 @@
</div>
{% comment %}same logic as above{% endcomment %}
{% if request.user.is_anonymous %}
{% if show_pledge %}
<div class="listview panelfront side1 add-wishlist">
<span class="booklist_pledge"><a href="{% url pledge work_id=workid %}" class="fakeinput">Pledge</a></span>
</div>
{% else %}{% if show_purchase %}
<div class="listview panelfront side1 add-wishlist">
<span class="booklist_pledge"><a href="{% url purchase work_id=workid %}" class="fakeinput">Purchase</a></span>
</div>
{% else %}{% if request.user.is_anonymous %}
<div class="listview panelfront side1 create-account">
<span title="{% if workid %}{% url work workid %}{% else %}{% url googlebooks googlebooks_id %}{% endif %}">Login to Add</span>
</div>
{% else %}{% if request.user.id in supporters %}
<div class="listview panelfront side1 on-wishlist">
<span>Pledged!</span>
</div>
{% else %}{% ifequal supporter request.user %}
{% comment %} used only on your own supporter page. {% endcomment %}
{% ifequal status "ACTIVE" %}
<div class="listview panelfront side1 pledge">
{% ifequal work.last_campaign.type 1 %}
<span class="booklist_pledge"><a href="{% url pledge work_id=workid %}" class="fakeinput">Pledge</a></span>
{% else %}
{% if purchased %}
<span class="booklist_pledge"><a href="{% url download workid %}" class="fakeinput">Download</a></span>
{% else %}
<span class="booklist_pledge"><a href="{% url purchase work_id=workid %}" class="fakeinput">Purchase</a></span>
{% endif %}
{% endifequal %}
</div>
{% else %}
{% else %}{% if work in wishlist %}
{% ifequal supporter request.user %}
{% comment %} used only on your own supporter page. {% endcomment %}
<div class="listview panelfront side1 remove-wishlist">
<span id="l{{ workid }}">Un-list</span>
</div>
{% else %}
<div class="listview panelfront side1 on-wishlist">
{% if purchased %}
<span>Purchased!</span>
{% else %}{% if borrowed %}
<span>Borrowed! ...until</span>
{% else %}{% if request.user.id in supporters %}
<span>Pledged!</span>
{% else %}
<span>On My List!</span>
{% endif %}{% endif %}{% endif %}
</div>
{% endifequal %}
{% else %}{% ifequal status "ACTIVE" %}
<div class="listview panelfront side1 add-wishlist">
{% ifequal work.last_campaign.type 1 %}
<span class="booklist_pledge"><a href="{% url pledge work_id=workid %}" class="fakeinput">Pledge</a></span>
{% else %}
{% if purchased %}
<span class="booklist_pledge"><a href="{% url download workid %}" class="fakeinput">Download</a></span>
{% else %}
<span class="booklist_pledge"><a href="{% url purchase work_id=workid %}" class="fakeinput">Purchase</a></span>
{% endif %}
{% endifequal %}
</div>
{% else %}{% if work in wishlist %}
<div class="listview panelfront side1 on-wishlist">
<span>On My List!</span>
</div>
{% else %}
<div class="listview panelfront side1 add-wishlist">
{% if on_search_page %}
@ -218,17 +234,23 @@
<span class="work_id" id="l{{ workid }}">Add to My List</span>
{% endif %}
</div>
{% endif %}{% endifequal %}{% endifequal %}{% endif %}{% endif %}
{% endif %}{% endif %}{% endif %}{% endif %}
<div class="listview panelfront side1 booklist-status">
{% ifequal status "ACTIVE" %}
{% ifequal work.last_campaign.type 1 %}
<span class="booklist-status-text"><b>${{ work.last_campaign.current_total|floatformat:0|intcomma }}</b>/<b>${{ work.last_campaign.target|floatformat:0|intcomma }}</b></span>
{% if in_library %}
{% if borrowed %}
<span class="booklist-status-text">{{ borrowed.expires|date:"M j, Y" }}</span>
{% else %}
<span class="booklist-status-text" style="line-height:19px">available<br />{{ next_acq.refreshes|date:"M j, Y" }}</span>
{% endif %}
{% else %}{% ifequal work.last_campaign.type 1 %}
<span class="booklist-status-text"><b>${{ work.last_campaign.current_total|floatformat:0|intcomma }}</b>/<b>${{ work.last_campaign.target|floatformat:0|intcomma }}</b></span>
{% else %}
<span class="booklist-status-text"><b>${{ work.last_campaign.left|floatformat:0|intcomma }}</b> to go</span>
{% endifequal %}
<span class="booklist-status-text"><b>${{ work.last_campaign.left|floatformat:0|intcomma }}</b> to go</span>
{% endifequal %}{% endif %}
{% else %}{% ifequal status "INITIALIZED" %}
<span class="booklist-status-label">Status:&nbsp;</span><span class="booklist-status-text">Coming soon!</span>
<span class="booklist-status-label">Status:&nbsp;</span><span class="booklist-status-text">Coming soon!</span>
{% else %}{% ifequal status "SUCCESSFUL" %}
{% if not first_ebook %}
<span class="booklist-status-text">Ebook coming soon</span>
@ -243,22 +265,29 @@
For status icons, we should display...
If there is an ebook: options to get it
If no ebook but there is an active or successful campaign: progress toward goal
If B2U, read, borrow, reserve, purchase
Otherwise: number of wishes
{% endcomment %}
{% if first_ebook %}
<a href="{% url download workid %}" class="hijax"><div class="read_itbutton"><span>Read it Now</span></div></a>
{% if purchased or borrowed or first_ebook %}
<a href="{% url download workid %}" class="hijax" title="Download this work"><div class="read_itbutton"><span>Read it Now</span></div></a>
{% else %}{% if borrowable %}
<a href="{% url borrow workid %}?library={{library}}" class="hijax" title="Borrow this work"><div class="read_itbutton"><span>Borrow It</span></div></a>
{% else %}{% if in_library %}
<a href="{% url purchase work_id=workid %}" title="Reserve or buy this work"><div class="read_itbutton"><span>Reserve It</span></div></a>
{% else %}{% if status == 'ACTIVE' or status == 'SUCCESSFUL' %}
{% if not library or not in_library %}
<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 panel">{{ work.percent_of_goal }}%</div>
{% endif %}
{% else %}
{% if work.num_wishes %}
<a href="{% if workid %}{% url work workid %}{% else %}{% url googlebooks googlebooks_id %}{% endif %}?tab=3" class="nobold"><span class="rounded"><span class="grey"><span class="panelnope">Listed by&nbsp;</span>{{ work.num_wishes }}</span></span></a>
{% else %}
<a href="{% if workid %}{% url work workid %}{% else %}{% url googlebooks googlebooks_id %}{% endif %}?tab=3" class="nobold"><span class="rounded"><span class="grey"><span class="panelnope">Listed by&nbsp;</span>0</span></span></a>
{% endif %}
{% endif %}{% endif %}
{% endif %}{% endif %}{% endif %}{% endif %}
</div>
<div class="listview panelfront side1 ebooks">

View File

@ -23,9 +23,7 @@
<div id="user-block1">
<div id="block-intro-text"><span class="special-user-name">Latest Comments</span></div>
</div>
<div class="user-block2"><span class="user-short-info">With your help we're raising money to give these books to the world.</span>
</div>
<div class="user-block3">
<div class="user-block24"><span class="user-short-info">Ungluers have a lot to say about their books.</span>
</div>
</div>
</div>

View File

@ -99,7 +99,7 @@ $j(document).ready(function() {
</div>
{% endif %}
{% if acq %}
<h3>Download your ebook!</h3>
<h3>Download your ebook{% if acq.lib_acq %}{% if acq.on_reserve %}, on reserve for you at{% else %}, on loan to you at{% endif %} {{ acq.lib_acq.user.library }}{% endif %}</h3>
<div class="ebook_download">
<a href="{{ formats.epub }}"><img src="/static/images/epub32.png" height="32" alt="epub" title="epub" /></a>
<a href="{{ formats.epub }}">EPUB</a> (for iBooks, Nook, Kobo)

View File

@ -10,6 +10,9 @@
<ul class="menu level2">
{% if not request.user.is_anonymous %}
<li class="first"><a href="/"><span>My Books</span></a></li>
{% for library in request.user.profile.libraries %}
<li><a href="{% url library library %}"><span>{{ library }}</span></a></li>
{% endfor %}
<li>
{% else %}
<li class="first">

View File

@ -34,7 +34,7 @@
<li class="parent">
<span class="faq">Do eBooks I buy through unglue.it have DRM?</span>
<span class="menu level2 answer">
NO. However, each purchased book you download from unglue.it is personalized with your unglue.it username and your personal license certificate. You should not make copies of personal ebooks for your friends; if you do so, your license may be revoked and the rightsholders may get mad at you. To share ebooks, you can purchase Unglue.it library licenses!
NO. However, each purchased book you download from unglue.it is personalized with your unglue.it username and your personal license certificate. You should not make copies of personal ebooks for your friends; if you do so, your license may be revoked and the rightsholders may get mad at you. To share ebooks, check out Unglue.it library licenses!
</span>
</li>
@ -44,6 +44,19 @@
If you're unhappy with an Unglue.it ebook in any way, use the feedback tab at the right of this page to ask for a refund. But be sure to tell us why- that's our best way of learning how to make Ungue.it better!
</span>
</li>
<li class="parent">
<span class="faq">What is a Library License?</span>
<span class="menu level2 answer">
If your library participates in Unglue.it, you can pay for a library license instead of purchasing an individual license. The ebook is yours for the first loan period, but the license belongs to your library so other library users can use it after your loan period is up. Obviously, it costs more because more people benefit, but you get the benefit of being part of a community sharing library assets.
</span>
</li>
<li class="parent">
<span class="faq">Why can't I buy a Library License?</span>
<span class="menu level2 answer">
You need to join a library participating in Unglue.it. If your library is not on <a href="{% url library_list %}">this list</a>, contact your librarian and have them check out our <a href="{% url libraries %}">libraries FAQ</a>.
</span>
</li>
</ul>
</div>
</div>

View File

@ -0,0 +1,17 @@
{% extends "basedocumentation.html" %}
{% block title %}Join {{ library }}{% endblock %}
{% block doccontent %}
<h2> Select your library </h2>
<h2> Other Account Management Tools </h2>
<ul>
<li>Want to <a href="{% url auth_password_change %}">change your password</a>?</li>
<li>... or <a href="{% url notification_notice_settings %}">manage your contact preferences</a>?</li>
<li>... or <a href="{% url email_change %}">change your email address</a>?</li>
<li>... or <a href="{% url regluit.frontend.views.edit_user %}">change your username</a>?</li>
<li>... or <a href="{% url marc_config %}">change your MARC record preferences</a>?</li>
<li>... or <a href="{% url kindle_config %}">add or change a Send-to-Kindle email</a>?</li>
</ul>
{% endblock %}

View File

@ -0,0 +1,269 @@
{% extends "base.html" %}
{% load endless %}
{% load truncatechars %}
{% block title %} &#8212; {{ library.user.username }}{% 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/searchandbrowse2.css" />
<link type="text/css" rel="stylesheet" href="/static/css/book_list.css" />
<link type="text/css" rel="stylesheet" href="/static/css/book_panel2.css" />
{% endblock %}
{% block extra_js %}
<script type="text/javascript" src="/static/js/wishlist.js"></script>
<script type="text/javascript" src="{{ jquery_ui_home }}"></script>
<script type="text/javascript" src="/static/js/greenpanel.js"></script>
<script type="text/javascript" src="/static/js/import_books.js"></script>
<script type="text/javascript" src="/static/js/counter.js"></script>
{% if works %}
<!-- when the user's wishlist is empty, views.py gives us works for a slideshow -->
<script src="/static/js/slides.min.jquery.js"></script>
<script src="/static/js/slideshow.js"></script>
<!-- toggle to panelview instead of listview default so slideshow will look right -->
<script type="text/javascript">
var $j = jQuery.noConflict();
$j(document).ready(function($) {
$('.listview').addClass("panelview").removeClass("listview");
});
</script>
{% else %}
<!-- we only need these when there's stuff on the user's wishlist -->
<script type="text/javascript" src="/static/js/toggle.js"></script>
<script type="text/javascript" src="/static/js/tabs.js"></script>
{% endif %}
<!-- highlight LT/GR add functions when people click on import divs -->
<script type="text/javascript">
var $j = jQuery.noConflict();
function highlightTarget(targetdiv) {
var target = $j(targetdiv);
target.css({"background": "#8dc63f"}).animate(
{backgroundColor: "white"}, 1500
);
};
</script>
{% endblock %}
{% block extra_head %}
<link rel="alternate" type="application/atom+xml" title="feed for books from {{ supporter }}'s ungluing list" href="feed" />
{% endblock %}
{% block topsection %}
<div id="locationhash">{{ activetab }}</div>
{% ifequal supporter request.user %}
<div class="launch_top pale">You are logged in as the administrator of {{ library.user.username }}
</div>
{% endifequal %}
<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" itemscope itemtype="http://schema.org/Person">
<div id="user-block1">
<div class="block-inner">
<img class="user-avatar" src="{{ library.user.profile.avatar_url }}" height="50" width="50" alt="Avatar for {{ supporter }}" title="Avatar" />
<span class="user-name">
<a href="#"><span itemprop="name">{{ library.user.username }}</span></a>
</span>
</div>
<span class="user-badges">
{% if library.user.profile.badges.all %}
{% for badge in library.user.profile.badges.all %}
<img src="{{ badge.path }}" alt="{{ badge.description }}" title="{{ badge.description }}" width="26" height="26" class="{{ badge.name }}" />
{% endfor %}
{% endif %}
</span>
</div>
{% if library.group in request.user.groups.all %}
<div class="user-block23">
<i>{{ library }} is {{ request.user }}'s Library!</i>
</div>
<div class="user-block4">
<div class="social">
{% if library.user.profile.home_url %}
<a href="{{ library.user.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 library.user.profile.facebook_id %}
<a href="http://www.facebook.com/profile.php?id={{library.user.profile.facebook_id}}" class="nounderline">
<img src="/static/images/supporter_icons/facebook_square.png" alt="{{ supporter }}'s Facebook" title="{{ supporter }}'s Facebook" />
</a>
{% endif %}
{% if library.user.profile.twitter_id %}
<a href="https://twitter.com/#!/{{ library.user.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 library.user.profile.goodreads_user_link %}
<a href="{{library.user.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 library.user.profile.librarything_id %}
<a href="http://www.librarything.com/profile/{{ library.user.profile.librarything_id }}" class="nounderline">
<img src="/static/images/supporter_icons/librarything_square.png" alt="{{ supporter }}'s profile on LibraryThing" title="{{ supporter }}'s page on LibraryThing" />
</a>
{% endif %}
</div>
</div>
{% else %}
<div class="user-block24">
{% include library.join_template %}
</div>
{% endif %}
</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 id="content-block">
{% if not works %}
{% comment %}
if we're in empty-wishlist, slideshow mode, suppress tab area
{% endcomment %}
<div class="content-block-heading wantto" id="tabs">
<ul class="tabs">
<li class="tabs1"><a href="#">Unglued</a></li>
<li class="tabs2"><a href="#">Available</a></li>
</ul>
<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" />
</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" />
</a>
</li>
</ul>
</div>
{% endif %}
{% if wishlist %}
<div id="content-block-content">
{% ifequal wishlist.works.all.count 0 %}
{% ifequal request.user supporter %}
<div class="empty-wishlist">
<div><h2 style="padding-left:35px;">Add a book to your list to get started.</h2><br /><br /></div>
<div id="js-slide">
<div class="js-main">
<div class="jsmodule">
{% include "slideshow.html" %}
</div>
</div>
</div>
<div id="js-maincontainer-bot-block">
<div id="js-search">
<label>What book would you give to the world? </label>
<form action="{% url search %}" method="get">
<input type="text" id="watermark" onfocus="imgfocus()" onblur="imgblur(0)" size="25" class="inputbox" name="q" value="{{ q }}">
<input type="submit" class="greenbutton" value="Search">
</form>
</div>
</div>
<br /><br /><hr />We'd also love to hear your <a href="/feedback">feedback</a>.
</div>
{% else %}
<div class="empty-wishlist">
It looks like {{ library.user.username }} is just getting started, and hasn't added books just yet.<br /><br />
{% endifequal %}
{% else %}
{% if request.user.is_anonymous %}
<div class="tabs-1 anon_about">
{% if works_unglued %}
{{ supporter }} wants you to know about these free books. <a href="/about/unglued/" class="hijax">Find out why.</a>
{% else %}
{{ supporter }} isn't promoting any free books yet. <a href="/about/unglued_empty/" class="hijax">Find out how you can.</a>
{% endif %}
</div>
<div class="tabs-2 anon_about">
{% if works_active %}
{{ supporter }} is helping to make these books free. <a href="/about/active/" class="hijax">Find out how.</a>
{% else %}
{{ supporter }} isn't ungluing books at the moment. <a href="/about/active_empty" class="hijax">Find out how you can.</a>
{% endif %}
</div>
<div class="tabs-3 anon_about">
{% if works_wished %}
{{ supporter }} wants these books to be free. <a href="/about/wishlist/" class="hijax">Find out how to help.</a>
{% else %}
{{ supporter }} hasn't decided which books to give the world yet. <a href="/about/wishlist_empty/" class="hijax">Learn more.</a>
{% endif %}
</div>
{% endif %}
{% lazy_paginate 20 works_unglued using "works_unglued" %}
{% for work in works_unglued %}
<div class="{% cycle 'row1' 'row2' %}">
{% with work.googlebooks_id as googlebooks_id %}
{% include "book_panel.html" %}
{% endwith %}
</div>
{% endfor %}
<div class="pagination content-block-heading tabs-1">
{% get_pages %}
{% for page in pages %}
<a href="{{ page.path }}#1" class="endless_page_link">{{ page.number }}</a>
{% endfor %}
</div>
{% lazy_paginate 20 works_active using "works_active" %}
{% for work in works_active %}
<div class="{% cycle 'row1' 'row2' %}">
{% with work.googlebooks_id as googlebooks_id %}
{% include "book_panel.html" %}
{% endwith %}
</div>
{% endfor %}
<div class="pagination content-block-heading tabs-2">
{% get_pages %}
{% for page in pages %}
<a href="{{ page.path }}#2" class="endless_page_link">{{ page.number }}</a>
{% endfor %}
</div>
{% endifequal %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,72 @@
{% extends "base.html" %}
{% block title %} Comments {% 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/liblist.css" />
{% endblock %}
{% block extra_head %}
<script type="text/javascript" src="{{ jquery_ui_home }}"></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 id="user-block1">
<div id="block-intro-text"><span class="special-user-name">Unglue.it Libraries</span></div>
</div>
<div class="user-block24"><span class="user-short-info">These libraries are participating in Unglue.it. If one of these is your library, you should be able to join, borrow and donate books. If your library is not on this list, contact your librarian and have them check out our <a href="{% url libraries %}">libraries FAQ</a>.</span>
</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 id="content-block">
<div id="content-block-content">
<div style="height:46px;"></div>
{% for library in libraries %}
<div class="items {% cycle 'row1' 'row2' %}{% if library.group in request.user.groups.all %} joined{% endif %}">
<div class="avatar">
<a href="{% url library library %}">
<img class="user-avatar" src="{{ library.user.profile.avatar_url }}" height="50" width="50" alt="Avatar for {{ library }}" title="{{ library }}" />
</a>
</div>
<div class="nonavatar">
<div class="libname"><a href="{% url library library %}">{{ library }}</a> {{ library.user.tagline }}</div>
<div class="libstat">{% if library.group in request.user.groups.all %}<em>joined</em>{% else %}&nbsp;{% endif %}</div>
<div class="libstat">{{ library.library_users.count }} Unglue.it users</div>
<div class="libstat">{{ library.user.acqs.count }} Unglue.it holdings</div>
<div class="libstat">{{ library.user.wishlist.works.count }} books on list</div>
</div>
</div>
{% empty %}
No libraries yet.
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -114,29 +114,34 @@ Please fix the following before launching your campaign:
<div class="tabs-1">
<h3>Edit the editions (if needed)</h3>
{% if campaign.rh.can_sell %}
<p> If you want to sell ebooks as part of a buy to unglue campaign, you'll need to upload the ebook files that you want to sell. </p>
<p> If you want to sell ebooks as part of a buy to unglue campaign, you'll also need to upload an EPUB file for the ebook you want to sell. </p>
{% endif %}
<ul>
{% for edition in campaign.work.editions.all %}
<li> <a href="{% url new_edition edition.work.id edition.id %}"> Edit </a><i>{{ edition }}</i>
{% if campaign.rh.can_sell %}You can also <a href="{% url edition_uploads edition.id %}"> load files</a> for this edition.
<li>Edition: <i>{{ edition }}</i>
<ul>
{% for ebook_file in edition.ebook_files.all %}
<li>{{ebook_file.file}} created {{ebook_file.created}} </li>
{% endfor %}
</ul>
<li><a href="{% url new_edition edition.work.id edition.id %}"> Edit </a> the edition</li>
{% if campaign.rh.can_sell %}
<li>You can also <a href="{% url edition_uploads edition.id %}"> Load a file</a> for this edition.</li>
{% endif %}
</ul>
</li>
{% endfor %}
</ul>
{% if campaign.work.ebookfiles.0 %}
<p> <b>An Ebook file has been loaded.</b> </p>
<p>Active file: {{campaign.work.ebookfiles.0.file}} created {{campaign.work.ebookfiles.0.created}} </p>
<p>Edition: <i>{{ campaign.work.ebookfiles.0.edition }}</i> </p>
{% endif %}
<form action="#" method="POST">
{% csrf_token %}
{{ form.media }}
<h3>Select the main edition</h3>
<p> Please choose the edition that most closely matches the edition to be unglued. This is the edition whose cover image will display on your book's page. Your unglued edition should be identical to this edition if possible; you should note any differences under Rights Details below.</p>
<div class="std_form">
{{ form.edition.errors }}{{ form.edition }}
</div>
{% if campaign.edition %}
<p>If the details of the edition you've chosen aren't accurate, you can <a href="{% url rh_edition work.id campaign.edition.id %}">edit the edition</a>.</p>
{% else %}
@ -146,8 +151,9 @@ Please fix the following before launching your campaign:
{% ifnotequal campaign_status 'ACTIVE' %}
<h3>License being offered</h3>
<p> This is the license you are offering to use once the campaign succeeds. For more information on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>. Once your campaign is active, you'll be able to switch to a less restrictive license, but not a more restrictive one. We encourage you to pick the least restrictive license you are comfortable with, as this will increase the ways people can use your unglued ebook and motivate more people to donate.</p>
<div class="std_form">
{{ form.license.errors }}{{ form.license }}
</div>
{% ifequal campaign.type 1 %}
<h3>Target Price</h3>
<p>This is the target price for your campaign. The <i>minimum</i> target is ${{form.minimum_target|intcomma}}.</p>
@ -157,9 +163,9 @@ Please fix the following before launching your campaign:
<p>Once you launch the campaign, you'll be able to decrease your target, but not increase it.</p>
<p>Please email us if you want to talk about pricing strategy.</p>
<div class="std_form">
{{ form.target.errors }}${{ form.target }}
</div>
<h3>Ending date</h3>
<p> This is the ending date of your campaign. Once you launch the campaign, you won't be able to change it.</p>
@ -176,9 +182,9 @@ Please fix the following before launching your campaign:
<p>Once you launch the campaign, you'll be able to decrease your target, but not increase it.</p>
<p>Please email us if you want to talk about pricing strategy.</p>
<div class="std_form">
{{ form.target.errors }}${{ form.target }}
</div>
<h3>Initial Ungluing Date</h3>
<p>When you launch a Buy-To-Unglue campaign, you will specify a date in the future at which your book will become Creative Commons Licensed. eBooks sold via unglue.it will include a notice of this license. With every sale, the effective date of this license will advance a bit toward the present. </p>
@ -191,23 +197,30 @@ Please fix the following before launching your campaign:
<h3>License being offered</h3>
<p>If your campaign succeeds, you will be offering your ebook under a <b><a href="{{campaign.license_url }}">{{ campaign.license }}</a></b> license.</p>
<p>Since your campaign is active, you may only change the license to remove restrictions. For more information on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>.</p></p>
<div class="std_form">
{{ form.license.errors }}<span>{{ form.license }}</span>
</div>
<h3>Target Price</h3>
<p>The current target price for your campaign is <b>${{ campaign.target|intcomma }}</b>. Since your campaign is active, you may lower, but not raise, this target.</p>
<div class="std_form">
${{ form.target.errors }}{{ form.target }}
</div>
{% ifequal campaign.type 1 %}
<h3>Ending date</h3>
<p>The ending date of your campaign is <b>{{ campaign.deadline }}</b>. Your campaign will conclude on this date or when you meet your target price, whichever is earlier. You may not change the ending date of an active campaign.</p>
<div class="std_form">
{{ form.deadline.errors }}<span style="display: none">{{ form.deadline }}</span>
</div>
{% else %}
<h3>Ungluing Date</h3>
<p> This campaign was launched with a Ungluing Date of {{ campaign.cc_date_initial }}.</p>
<p> Based on a total revenue of {{ campaign.current_total }} the Ungluing Date has been advanced to {{ campaign.cc_date }}.</p>
<div class="std_form">
{{ form.cc_date_initial.errors }}{{ form.cc_date_initial }}
<h3>Ending date</h3>
<p>Your Buy-to-Unglue Campaign will run until <b>{{ campaign.deadline }}</b>. Contact unglue.it staff to extend it.</p>
</div>
<div class="std_form">
<!--{{ form.deadline.errors }}-->{{ form.deadline }}
</div>
{% endifequal %}
{% endifnotequal %}
@ -279,14 +292,14 @@ Please fix the following before launching your campaign:
<h3>e-mail contact addresses</h3>
<p>Enter the email address where notifications about this campaign should be sent. If your campaign succeeds, this email needs to work if you want to get paid! This address will not be exposed on the website.</p>
<p>{{ form.paypal_receiver.errors }}{{ form.paypal_receiver }}</p>
<p class="std_form">{{ form.paypal_receiver.errors }}{{ form.paypal_receiver }}</p>
<p>(Optional, but highly recommended). Enter an email address where ungluers with questions about the book or the campaign can contact you or someone involved. This address will not be exposed on the website.</p>
<p>{{ form.email.errors }}{{ form.email }}</p>
<p class="std_form">{{ form.email.errors }}{{ form.email }}</p>
{% if work.publishers %}
<h3>Publisher</h3>
<p>If you are set up as an unglue.it publisher (send us a url, logo, description and list of ways your name might appear) you can link your campaign by selecting the publisher here:
<p>{{ form.publisher.errors }}{{ form.publisher }}</p>
<p class="std_form">{{ form.publisher.errors }}{{ form.publisher }}</p>
{% endif %}
{% ifequal campaign_status 'ACTIVE' %}
<div class="yikes">When you click this button, your changes will be visible to supporters immediately. Make sure to proofread!</div><br />
@ -363,17 +376,21 @@ Please fix the following before launching your campaign:
{% endifequal %}
{% ifequal campaign.type 2 %}
<h3> Offers to sell </h3>
<p> Make sure that ebook files for this work <a class="tabs1">have been loaded</a>!
{% if not campaign.work.ebookfiles.0 %}
<p> <b>An EPUB file for this work <a class="tabs1">needs to be loaded</a>!</b></p>
{% endif %}
<p> Enter a per/copy price for each license type. You may change these prices after the campaign has begun.</p>
<div class="jsmod-content">
<p>
{% for offer in offers %}
<form action="#" method="POST">
<form action="#" method="POST"><div>
{% csrf_token %}
{{ offer.offer_form.as_p }}
<br />
<input type="submit" name="change_offer" value="Change Offer" />
</form>
<span class="std_form">{{ offer.get_license_display }}: ${{ offer.offer_form.price }}{{ offer.offer_form.price.errors }}
{{ offer.offer_form.active }}
{{ offer.offer_form.license }}
{{ offer.offer_form.work }}</span>
<input type="submit" name="change_offer" value="Change Price" />
</div></form>
<p />
{% endfor %}
</div>
{% endifequal %}
@ -422,10 +439,14 @@ Please fix the following before launching your campaign:
<li>Need help doing any of this? Talk to us.</li>
</ul>
{% endif %}
{% ifequal campaign.type 1 %}
<h3>Acknowledgements</h3>
<p>When you're logged in, the "Ungluers" tab on the <a href="{% url work work.id %}">campaign page</a> will tell you a bit about each ungluer- when they last pledged, for example, and you can send individual messages to each ungluer. Use this tool with care! You can see who your biggest supporters are by looking at the <a href="{% url work_acks campaign.work.id %}">sample acknowledgement page</a>.
After your campaign succeeds, you can used this page to generate epub code for the acknowledgements section of your unglued ebook.
</p>
{% else %}
{% comment %}This might be a good place to put a sales report. {% endcomment %}
{% endifequal %}
{% endif %}
</div>

View File

@ -1,9 +1,13 @@
{% load humanize %}Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|intcomma }}. If you have not already done so, download your ebook at
https://{{ current_site.domain }}{% url download transaction.campaign.work.id %}
{% load humanize %}Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|intcomma }}. {% ifequal transaction.offer.license 2 %}If you have not already done so, download your ebook at
https://{{ current_site.domain }}{% url download transaction.campaign.work.id %}{% endifequal %}
Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be eventually be released to the world in an unglued ebook edition. Thanks to your purchase, the ungluing date advanced {{ transaction.offer.days_per_copy|floatformat }} days to {{ transaction.campaign.cc_date }}.
{% ifequal transaction.offer.license 1 %}
This ebook is licensed to you personally, and your personal license has been embedded in the ebook file. You may download as many times as you need to, but you can't make copies for the use of others until the ungluing date. You can make that date come sooner by encouraging your friends to buy a copy.
{% else %}
This ebook is licensed to your library and its license has been embedded in the ebook file. If you'd like to be the first to use it, please get your copy now at
https://{{ current_site.domain }}{% url borrow transaction.campaign.work.id %}
After an hour, the ebook will be available to all of your library's users on a one-user-per two weeks basis until the ungluing date, when it will be free to all. You can make that date come sooner by encouraging your friends to buy a copy.{% endifequal %}
For more information about the book, visit the visit the book's unglue.it page at
https://{{ current_site.domain }}{% url work transaction.campaign.work.id %}

View File

@ -6,12 +6,21 @@
{% endblock %}
{% block comments_graphical %}
{% ifequal transaction.offer.license 1 %}
Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|intcomma }}. If you have not already done so, download your ebook at <a href="{% url download transaction.campaign.work.id %}">the book's download page.</a>
{% else %}
Your Unglue.it credit card transaction has completed and your credit card has been charged ${{ transaction.amount|intcomma }}.
{% endifequal %}
{% endblock %}
{% block comments_textual %}
<p>Thanks to you and other ungluers, {{ transaction.campaign.work.title }} will be eventually be released to the world in an unglued ebook edition. Thanks to your purchase, the ungluing date advanced {{ transaction.offer.days_per_copy|floatformat }} days to {{ transaction.campaign.cc_date }}.</p>
{% ifequal transaction.offer.license 1 %}
<p>This ebook is licensed to you personally, and your personal license has been embedded in the ebook file. You may download as many times as you need to, but you can't make copies for the use of others until the ungluing date. You can make that date come sooner by encouraging your friends to buy a copy.</p>
{% else %}
<p>This ebook is licensed to your library and its license has been embedded in the ebook file. If you'd like to be the first to use it, please <a href="{% url borrow transaction.campaign.work.id %}">get your copy now</a>. After an hour, the ebook will be available to all of your library's users on a one-user-per two weeks basis until the ungluing date, when it will be free to all. You can make that date come sooner by encouraging your friends to buy a copy.</p>
{% endifequal %}
<p>For more information about the book, visit the visit the <a href="{% url work transaction.campaign.work.id %}">book's unglue.it page</a>.
</p>
<p>Thank you again for your support.

View File

@ -1 +1 @@
You have purchased {{transaction.campaign.work.title}}.
You have purchased {{transaction.campaign.work.title}}{% ifequal transaction.offer.license 2 %} for your library{% endifequal %}.

View File

@ -1,5 +1,6 @@
{% extends "basepledge.html" %}
{% load humanize %}
{% load lib_acqs %}
{% block title %}Pledge{% endblock %}
@ -64,31 +65,67 @@
</div>
</div>
{% ifequal work.last_campaign.status "ACTIVE" %}
{% lib_acqs %}
<div class="jsmodule rounded clearfix">
<div class="jsmod-content">
<form class="pledgeform" method="POST" action="#">
{% if next_acq %}
<div class="pledge_amount">This ebook can be reserved from your library.</div>
<div class="bigger" style="margin:20px">Available starting {{ next_acq.refreshes|date:"M j, Y"}} at {{ next_acq.user }}.</div>
<div><a href="{% url reserve work.id%}?library={{ next_acq.user }}" class="fakeinput">Reserve Now</a> </div>
<div style="height:30px;"></div>
<div class="bigger" style="height:30px;clear:both;margin:20px">If you'd rather not wait, consider one of the purchase options.</div>
{% endif %}
{% csrf_token %}
{{ form.non_field_errors }}
{% if work.offers.all|length > 1 %}
<div class="pledge_amount premium_level">Choose your license type:</div>
<div class="pledge_amount premium_level">Purchase Options: Individual or Library?</div>
<div style="height:10px;"></div>
<ul class="support menu" id="premiums_list">
{% for offer in work.last_campaign.active_offers %}
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
<label for="offer_{{offer.id}}">
<input type="radio" name="offer_id" id="offer_{{offer.id}}" value="{{offer.id}}" {% ifequal request.REQUEST.offer_id offer.id|stringformat:"s" %}checked="checked"{% else %} {% ifequal offer_id offer.id %}checked="checked"{% endifequal %}{% endifequal %} />
<span class="menu-item-price">
${{ offer.price|intcomma }}
</span>
<span class="menu-item-desc">
{{ offer.get_license_display }}
</span>
</label></li>
{% endfor %}
{% with work.last_campaign.individual_offer as offer %}
<li class="first">
{% if user_license.purchased %}
<span class="menu-item-desc">Individual license already purchased!</span>
{% else %}
<label for="offer_{{offer.id}}">
<input type="radio" name="offer_id" id="offer_{{offer.id}}" value="{{offer.id}}" {% ifequal request.REQUEST.offer_id offer.id|stringformat:"s" %}checked="checked"{% else %} {% ifequal offer_id offer.id %}checked="checked"{% endifequal %}{% endifequal %} />
<span class="menu-item-price">
${{ offer.price|intcomma }}
</span>
<span class="menu-item-desc">
{{ offer.get_license_display }}
</span>
</label>
{% endif %}
</li>
{% endwith %}
{% with work.last_campaign.library_offer as offer %}
<li class="last">
{% if request.user.profile.libraries %}
<label for="offer_{{offer.id}}">
<input type="radio" name="offer_id" id="offer_{{offer.id}}" value="{{offer.id}}" {% ifequal request.REQUEST.offer_id offer.id|stringformat:"s" %}checked="checked"{% else %} {% ifequal offer_id offer.id %}checked="checked"{% endifequal %}{% endifequal %} />
<span class="menu-item-price">
${{ offer.price|intcomma }}
</span>
<span class="menu-item-desc">
{{ offer.get_license_display }} for
<select name="library_id" class="std_form">
{% for library in request.user.profile.libraries %}
<option value="{{library.id}}">{{ library }}</option>
{% endfor %}
</select>
</span>
</label>
{% else %}
<a href="{% url library_list %}"><span class="menu-item-desc">
Join a Library to share and borrow unglue.it ebooks
</span></a>
{% endif %}
</li>
{% endwith %}
</ul>
{% else %}
<div style="height:10px;"></div>

View File

@ -70,25 +70,13 @@ function highlightTarget(targetdiv) {
<link rel="alternate" type="application/atom+xml" title="feed for books from {{ supporter }}'s ungluing list" href="feed" />
{% endblock %}
{% comment %}
To do:
create topsection file for inclusion in multiple contexts, if needed
Goodreads
be sure words display correctlydja
do I need both add-wishlist and remove-wishlist classes? do they differ?
better alignment on I am ungluing & badges
make sure backed/backing/wishlist is the order we want the badges to be in
test code with other campaign statuses -- random_campaigns needs to set a variety of statuses!
why is there a status in regluit.payment.models.Transaction? does it duplicate the status in regluit.core.models.Campaign?
there's no tab for seeing ALL my books, only the filters! huh.
{% endcomment %}
{% block topsection %}
<div id="locationhash">{{ activetab }}</div>
{% if supporter.library %}
<div class="launch_top pale">{{ supporter.library }} is a Library participating in Unglue.it. <a href="{% url join_library supporter.username %}">Click here</a> to use {{ supporter.library }}'s books.
</div>
{% endif %}
<div id="js-topsection">
<div class="js-main">
<div class="js-topnews">
@ -207,9 +195,9 @@ there's no tab for seeing ALL my books, only the filters! huh.
</div>
<div class="check-list" id="connectgr">
{% if user.profile.goodreads_user_id %}
<a href="{{goodreads_auth_url}}">Update your GoodReads connection</a> <br /> or disconnect GoodReads: {{ profile_form.clear_goodreads }}
<a href="{% url goodreads_auth %}">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
<a href="{% url goodreads_auth %}">Connect your GoodReads account</a> to Unglue.it
{% endif %}
</div>
<div class="check-list" id="connectlt">
@ -220,7 +208,7 @@ there's no tab for seeing ALL my books, only the filters! huh.
</form>
<div class="block block3">
<h3 class="title">Import your books</h3>
{% if goodreads_id %}
{% if request.user.profile.goodreads_user_id %}
<form id="load_shelf_form" method="post" action="#">
{% csrf_token %}
<div class="fieldWrapper">
@ -232,7 +220,7 @@ there's no tab for seeing ALL my books, only the filters! huh.
{% else %}
<div id="loadgr" onclick="highlightTarget('#connectgr'); return false;"><div>Connect your GoodReads account to import from GoodReads.</div></div>
{% endif %}
{% if librarything_id %}
{% if request.user.profile.librarything_id %}
<form id="librarything_load" method="post" action="#">
{% csrf_token %}
<div id="loadlt"><input type="submit" id="librarything_input" value="Add your LibraryThing library" /></div>

View File

@ -1,6 +1,8 @@
{% extends "base.html" %}
{% load comments %}
{% load humanize %}
{% load purchased %}
{% load lib_acqs %}
{% block title %}&#151;
{% if work.first_ebook %}
{{ work.title }} is a Free eBook
@ -11,7 +13,6 @@
{% block extra_css %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign2.css" />
{% endblock %}
{% block extra_js %}
@ -58,7 +59,9 @@
{% endif %}
{% endblock %}
{% block content %}
{% block content %}
{% purchased %}
{% lib_acqs %}
{% with work.last_campaign_status as status %}
{% with work.id as work_id %}
<div id="main-container">
@ -123,6 +126,15 @@
<span>{{ work.last_campaign.supporters_count }}</span> ungluers
{% endif %}
</div>
{% ifequal work.last_campaign.type 2 %}
<div class="campaign-status-info">
{% if work.lib_acqs.count == 1 %}
<span>1</span> copy in a library
{% else %}
<span>{{ work.lib_acqs.count }}</span> in libraries
{% endif %}
</div>
{% endifequal %}
<div class="campaign-status-info">
{% ifequal work.last_campaign.type 1 %}
<span>{{ work.last_campaign.countdown }}</span> to go
@ -458,13 +470,15 @@
<div class="btn_support"><form action="{% url pledge work_id %}" method="get"><input type="submit" value="Pledge" /></form></div>
{% endif %}
{% else %}
{% if purchased %}
{% if license_is_active %}
<div class="btn_support">
<a href="{% url download_purchased work_id %}" class="hijax"><span>Download</span></a>
</div>
{% else %}{% if borrowable %}
<div class="btn_support"><form action="{% url borrow work_id %}" method="get"><input type="submit" value="Borrow" /></form></div>
{% else %}
<div class="btn_support"><form action="{% url purchase work_id %}" method="get"><input type="submit" value="Purchase" /></form></div>
{% endif %}
<div class="btn_support"><form action="{% url purchase work_id %}" method="get"><input type="submit" value="{% if next_acq %}Reserve{% else %}Purchase{% endif %}" /></form></div>
{% endif %}{% endif %}
{% endif %}
{% else %}
{% if work.first_ebook %}
@ -600,23 +614,57 @@
</ul>
</div>
{% else %}
{% if purchased %}
<h3 class="jsmod-title">
<span class="on-wishlist">Purchased!</span>
</h3>
{% else %}
<a href="{% url purchase work_id %}"><h3 class="jsmod-title"><span>Purchase Options</span></h3></a>
{% if lib_licenses.available %}
<h3 class="jsmod-title"><span>Borrow</span></h3>
<div class="jsmod-content">
<ul class="support menu">
{% for lib_license in lib_licenses.all %}
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
<a href="???">Borrow!</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
<h3 class="jsmod-title"><span>Buy for Yourself</span></h3>
<div class="jsmod-content">
<ul class="support menu">
{% for offer in work.last_campaign.active_offers %}
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
<a href="{% url purchase work_id %}?offer_id={{offer.id}}">
<span class="menu-item-price">${{ offer.price|floatformat:0|intcomma }}</span>
<span class="menu-item-desc">{{ offer.get_license_display }}</span>
</a></li>
{% endfor %}
{% if purchased %}
<li class="first no_link">
<span class="menu-item-desc">Purchased!</span>
</li>
{% else %}
<li class="first">
<a href="{% url purchase work_id %}?offer_id={{work.last_campaign.individual_offer.id}}">
<span class="menu-item-price">${{ work.last_campaign.individual_offer.price|floatformat:0|intcomma }}</span>
<span class="menu-item-desc">{{ work.last_campaign.individual_offer.get_license_display }}</span>
</a>
</li>
{% endif %}
</ul>
</div>
{% if borrowed %}
<h3 class="jsmod-title">
<span class="on-wishlist">Borrowed!</span>
</h3>
{% else %}
<h3 class="jsmod-title"><span>Buy for a Library</span></h3>
<div class="jsmod-content">
<ul class="support menu">
<li class="first last">
{% if request.user.profile.libraries %}
<a href="{% url purchase work_id %}?offer_id={{work.last_campaign.library_offer.id}}">
<span class="menu-item-price">${{ work.last_campaign.library_offer.price|floatformat:0|intcomma }}</span>
<span class="menu-item-desc">{{ work.last_campaign.library_offer.get_license_display }}</span>
</a>
{% else %}
<a href="{% url library_list %}"><span class="menu-item-desc">
Join a Library to share and borrow unglue.it ebooks
</span></a>
{% endif %}
</li>
</ul>
</div>
{% endif %}
{% endifequal %}
</div>

View File

@ -0,0 +1,38 @@
from django import template
from regluit.utils.localdatetime import now
from regluit.parameters import *
register = template.Library()
@register.simple_tag(takes_context=True)
def bookpanel(context):
work = context['work']
library = context.get('library',None)
user = context['request'].user
campaign = context.get('last_campaign', None)
# compute a boolean that's true if bookpanel should show a "pledge" button...
# campaign is ACTIVE, type 1 - PLEDGE
# user has not pledged or user is anonymous
show_pledge = False
if campaign and campaign.type==PLEDGE:
if user.is_anonymous() or not user.id in context.get('supporters', []):
show_pledge = True
context['show_pledge'] = show_pledge
# compute a boolean that's true if bookpanel should show a "purchase" button...
# campaign is ACTIVE, type 2 - BUY2UNGLUE
# user has not purchased or user is anonymous
# user has not borrowed or user is anonymous
# work not available in users library
# not on the library page
show_purchase = False
if campaign and campaign.type==BUY2UNGLUE:
if user.is_anonymous() or not context.get('license_is_active', False):
if not context.get('borrowable', False):
if not library:
show_purchase = True
context['show_purchase'] = show_purchase
return ''

View File

@ -0,0 +1,27 @@
from regluit.utils.localdatetime import now
from django import template
register = template.Library()
@register.simple_tag(takes_context=True)
def lib_acqs(context):
work = context['work']
library = context.get('library',False)
if library:
lib_user = library.user
else:
user = context['request'].user
if user.is_anonymous():
return ''
else:
lib_user = (lib.user for lib in user.profile.libraries)
try:
user_license = work.get_user_license(lib_user)
except AttributeError:
user_license = None
if user_license:
context['lib_acqs'] = user_license.lib_acqs
context['next_acq'] = user_license.next_acq
else:
context['lib_acqs'] = None
return ''

View File

@ -7,8 +7,23 @@ register = template.Library()
def purchased(context):
work = context['work']
user = context['request'].user
try:
context['purchased'] = work.purchased_by(user)
except:
context['purchased'] = False
if user.is_anonymous():
return ''
try:
user_license = work.get_user_license(user)
context['borrowable'] = work.borrowable(user)
context['in_library'] = work.in_library(user)
except AttributeError:
user_license = None
context['borrowable'] = None
context['in_library'] = None
if user_license:
context['purchased'] = user_license.purchased
context['borrowed'] = user_license.borrowed
context['license_is_active'] = user_license.is_active
else:
context['purchased'] = None
context['borrowed'] = None
context['license_is_active'] = False
return ''

View File

@ -48,8 +48,8 @@ urlpatterns = patterns(
url(r"^next/$", "next", name="next"),
url(r"^supporter/(?P<supporter_username>[^/]+)/$", "supporter", {'template_name': 'supporter.html'}, name="supporter"),
url(r"^supporter/(?P<userlist>[^/]+)/marc/$", "marc", name="user_marc"),
url(r"^library/(?P<library_name>[^/]+)/$", "library", name="library"),
url(r"^accounts/manage/$", login_required(ManageAccount.as_view()), name="manage_account"),
url(r'^accounts/superlogin/$', 'superlogin', name='superlogin'),
url(r"^search/$", "search", name="search"),
url(r"^privacy/$", TemplateView.as_view(template_name="privacy.html"),
name="privacy"),
@ -87,6 +87,8 @@ urlpatterns = patterns(
url(r"^work/(?P<work_id>\d+)/lockss/$", "lockss", name="lockss"),
url(r"^lockss/(?P<year>\d+)/$", "lockss_manifest", name="lockss_manifest"),
url(r"^work/(?P<work_id>\d+)/download/$", "download", name="download"),
url(r"^work/(?P<work_id>\d+)/borrow/$", "borrow", name="borrow"),
url(r"^work/(?P<work_id>\d+)/reserve/$", login_required(PurchaseView.as_view(),login_url='/accounts/login/purchase/'), name="reserve"),
url(r"^work/(?P<work_id>\d+)/merge/$", login_required(MergeView.as_view()), name="merge"),
url(r"^work/(?P<work_id>\d+)/split/$", "split_work", name="split"),
url(r"^work/\d+/acks/images/(?P<file_name>[\w\.]*)$", "static_redirect_view",{'dir': 'images'}),

View File

@ -107,7 +107,6 @@ from regluit.frontend.forms import (
WorkForm,
OtherWorkForm,
MsgForm,
AuthForm,
PressForm,
KindleEmailForm,
MARCUngluifyForm,
@ -136,6 +135,8 @@ from regluit.payment.parameters import (
from regluit.utils.localdatetime import now, date_today
from regluit.booxtream.exceptions import BooXtreamError
from regluit.libraryauth.views import Authenticator
from regluit.libraryauth.models import Library
logger = logging.getLogger(__name__)
@ -296,18 +297,6 @@ def stub(request):
def acks(request, work):
return render(request,'front_matter.html', {'campaign': work.last_campaign()})
def superlogin(request, **kwargs):
extra_context = None
if request.method == 'POST' and request.user.is_anonymous():
username=request.POST.get("username", "")
try:
user=models.User.objects.get(username=username)
extra_context={"socials":user.profile.social_auths}
except:
pass
if request.GET.has_key("add"):
request.session["add_wishlist"]=request.GET["add"]
return login(request, extra_context=extra_context, authentication_form=AuthForm, **kwargs)
@login_required
def social_auth_reset_password(request):
@ -424,7 +413,6 @@ def work(request, work_id, action='display'):
'claimstatus': claimstatus,
'rights_holder_name': rights_holder_name,
'cover_width': cover_width_number,
'purchased': work.purchased_by(request.user),
})
def edition_uploads(request, edition_id):
@ -1104,6 +1092,7 @@ class PurchaseView(PledgeView):
'tid': self.transaction.id if self.transaction else None,
'cover_width': cover_width(self.work),
'offer_id':self.offer_id,
'user_license': self.work.get_user_license(self.request.user),
})
return context
@ -1135,7 +1124,7 @@ class PurchaseView(PledgeView):
def get_preapproval_amount(self):
self.offer_id = self.request.REQUEST.get('offer_id', None)
if not self.offer_id:
self.offer_id = self.work.last_campaign().active_offers()[0].id
self.offer_id = self.work.last_campaign().individual_offer.id
preapproval_amount = None
if self.offer_id != None:
try:
@ -1766,7 +1755,7 @@ def campaign_admin(request):
return render(request, "campaign_admin.html", context)
def supporter(request, supporter_username, template_name):
def supporter(request, supporter_username, template_name, extra_context={}):
supporter = get_object_or_404(User, username=supporter_username)
wishlist = supporter.wishlist
works = []
@ -1846,22 +1835,10 @@ def supporter(request, supporter_username, template_name):
else:
profile_form= ProfileForm(instance=profile_obj)
if request.user.profile.goodreads_user_id is not None:
goodreads_id = request.user.profile.goodreads_user_id
else:
goodreads_id = None
if request.user.profile.librarything_id is not None:
librarything_id = request.user.profile.librarything_id
else:
librarything_id = None
else:
profile_form = ''
goodreads_id = None
librarything_id = None
process_kindle_email(request)
context = {
"supporter": supporter,
"wishlist": wishlist,
@ -1875,14 +1852,27 @@ def supporter(request, supporter_username, template_name):
"wished": wished,
"profile_form": profile_form,
"ungluers": userlists.other_users(supporter, 5 ),
"goodreads_auth_url": reverse('goodreads_auth'),
"goodreads_id": goodreads_id,
"librarything_id": librarything_id,
"activetab": activetab
"activetab": activetab,
}
context.update(extra_context)
return render(request, template_name, context)
def library(request,library_name):
context={}
try:
# determine if the supporter is a library
authenticator = Authenticator(request,library_name)
context['authenticator'] = authenticator
context['library'] = library = authenticator.library
except Library.DoesNotExist:
raise Http404
context['works_active']= models.Work.objects.filter(acqs__user=library.user,acqs__license=LIBRARY).distinct()
context['activetab'] = "#2"
context['ungluers'] = userlists.library_users(library, 5 )
return supporter(request,library_name,template_name='libraryauth/library.html', extra_context=context)
def edit_user(request):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('superlogin'))
@ -2633,6 +2623,33 @@ def download(request, work_id):
})
return render(request, "download.html", context)
@login_required
def borrow(request, work_id):
work = safe_get_work(work_id)
library = request.GET.get('library', '')
try:
libuser = User.objects.get(username = library)
except User.DoesNotExist:
libuser = None
if libuser:
acqs= models.Acq.objects.filter(user = libuser, license = LIBRARY, refreshes__lt = now())
if not libuser or acqs.count()==0:
acq=None
for other_library in request.user.profile.libraries:
if other_library.user!=libuser:
acqs= models.Acq.objects.filter(user = other_library.user, license = LIBRARY, refreshes__lt = now())
if acqs.count()>0:
acq=acqs[0]
continue
else:
acq=acqs[0]
if acq:
borrowed = acq.borrow(request.user)
return download(request, work_id)
else:
# shouldn't happen
return work(request, work_id)
def download_ebook(request, ebook_id):
ebook = get_object_or_404(models.Ebook,id=ebook_id)
@ -2647,6 +2664,8 @@ def download_purchased(request, work_id):
def download_acq(request, nonce, format):
acq = get_object_or_404(models.Acq,nonce=nonce)
if acq.on_reserve:
acq.borrow()
if format == 'epub':
return HttpResponseRedirect( acq.get_epub_url() )
else:

0
libraryauth/__init__.py Normal file
View File

44
libraryauth/admin.py Normal file
View File

@ -0,0 +1,44 @@
from . import models
from selectable.forms import AutoCompleteSelectWidget,AutoCompleteSelectField
from selectable.base import ModelLookup
from selectable.registry import registry
from django import forms
from django.contrib.admin import ModelAdmin
from django.contrib.auth.models import User, Group
class UserLookup(ModelLookup):
model = User
search_fields = ('username__icontains',)
registry.register(UserLookup)
class LibraryAdminForm(forms.ModelForm):
user = AutoCompleteSelectField(
UserLookup,
widget=AutoCompleteSelectWidget(UserLookup),
required=True,
)
class Meta(object):
model = models.Library
widgets= {'group':forms.HiddenInput}
exclude = ('group', )
class LibraryAdmin(ModelAdmin):
list_display = ('user', )
form = LibraryAdminForm
search_fields = ['user__username']
class BlockAdmin(ModelAdmin):
list_display = ('library', 'lower', 'upper',)
search_fields = ('library__user__username', 'lower', 'upper',)
class CardPatternAdmin(ModelAdmin):
list_display = ('library', 'pattern', 'checksum',)
search_fields = ('library__user__username', )
class EmailPatternAdmin(ModelAdmin):
list_display = ('library', 'pattern', )
search_fields = ('library__user__username',)

100
libraryauth/backends.py Normal file
View File

@ -0,0 +1,100 @@
'''
to make a backend named <backend> you need to...
1. define a function <backend>_authenticate(request, library)
returns true if can request.user can be authenticated to the library, and attaches a credential property to the library object
returns fals if otherwise.
2. define a class <backend>_authenticator
with a process((self, authenticator, success_url, deny_url) method which is expected to return a response
3. make a libraryauth/<backend>_join.html template (authenticator will be in its context) to insert a link or form for a user to join the library
4. if you need to show the user a form, define a model form class <backend>_form with init method __init__(self, request, library, *args, **kwargs)
and model LibraryUser
5. add new auth choice to Library.backend choices and the admin as desired
'''
import logging
from django.db.models import Q
from django import forms
from django.http import HttpResponseRedirect
from django.shortcuts import render
from .models import Block, IP, LibraryUser
logger = logging.getLogger(__name__)
def ip_authenticate(request, library):
try:
ip = IP(request.META['REMOTE_ADDR'])
blocks = Block.objects.filter(Q(lower=ip) | Q(lower__lte=ip, upper__gte=ip))
for block in blocks:
if block.library==library:
logger.info('%s authenticated for %s from %s'%(request.user, library, ip))
library.credential=ip
return True
return False
except KeyError:
return False
class ip_authenticator():
def process(authenticator, success_url, deny_url):
return HttpResponseRedirect(deny_url)
def cardnum_authenticate(request, library):
return False
class cardnum_authenticator():
def process(self, authenticator, success_url, deny_url):
if authenticator.form and authenticator.request.method=='POST' and authenticator.form.is_valid():
library = authenticator.form.cleaned_data['library']
library.credential = authenticator.form.cleaned_data['credential']
logger.info('%s authenticated for %s from %s'%(authenticator.request.user, authenticator.library, authenticator.form.cleaned_data.get('number')))
library.add_user(authenticator.form.cleaned_data['user'])
return HttpResponseRedirect(success_url)
else:
return render(authenticator.request, 'libraryauth/library.html', {
'library':authenticator.library,
'authenticator':authenticator,
})
class cardnum_form(forms.ModelForm):
credential = forms.RegexField(
label="Enter Your Library Card Number",
max_length=20,
regex=r'^\d+$',
required = True,
help_text = "(digits only)",
error_messages = {'invalid': "digits only!",}
)
def __init__(self, request, library, *args, **kwargs):
if request.method=="POST":
data=request.POST
super(cardnum_form, self).__init__(data=data)
else:
initial={'user':request.user, 'library':library}
super(cardnum_form, self).__init__(initial=initial)
def clean(self):
library = self.cleaned_data.get('library', None)
credential = self.cleaned_data.get('credential', '')
for card_pattern in library.card_patterns.all():
if card_pattern.is_valid(credential):
return self.cleaned_data
raise forms.ValidationError("the library card number must be VALID.")
class Meta:
model = LibraryUser
widgets = { 'library': forms.HiddenInput, 'user': forms.HiddenInput }
def email_authenticate(request, library):
if request.user.is_anonymous():
return False
email = request.user.email
for email_pattern in library.email_patterns.all():
if email_pattern.is_valid(email):
logger.info('%s authenticated for %s from %s'%(request.user, library, email))
library.credential=email
return True
return False
class email_authenticator():
def process(authenticator, success_url, deny_url):
return HttpResponseRedirect(deny_url)

10
libraryauth/forms.py Normal file
View File

@ -0,0 +1,10 @@
from django.contrib.auth.forms import AuthenticationForm
class AuthForm(AuthenticationForm):
def __init__(self, request=None, *args, **kwargs):
if request and request.method == 'GET':
saved_un= request.COOKIES.get('un', None)
super(AuthForm, self).__init__(initial={"username":saved_un},*args, **kwargs)
else:
super(AuthForm, self).__init__(*args, **kwargs)

View File

@ -0,0 +1,131 @@
# -*- coding: 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 'Library'
db.create_table('libraryauth_library', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='library', unique=True, to=orm['auth.User'])),
('group', self.gf('django.db.models.fields.related.OneToOneField')(related_name='library', unique=True, null=True, to=orm['auth.Group'])),
('backend', self.gf('django.db.models.fields.CharField')(default='ip', max_length=10)),
))
db.send_create_signal('libraryauth', ['Library'])
# Adding model 'Block'
db.create_table('libraryauth_block', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('library', self.gf('django.db.models.fields.related.ForeignKey')(related_name='blocks', to=orm['libraryauth.Library'])),
('lower', self.gf('regluit.libraryauth.models.IPAddressModelField')(unique=True, db_index=True)),
('upper', self.gf('regluit.libraryauth.models.IPAddressModelField')(db_index=True, null=True, blank=True)),
))
db.send_create_signal('libraryauth', ['Block'])
# Adding model 'CardPattern'
db.create_table('libraryauth_cardpattern', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('library', self.gf('django.db.models.fields.related.ForeignKey')(related_name='card_patterns', to=orm['libraryauth.Library'])),
('pattern', self.gf('django.db.models.fields.CharField')(max_length=20)),
('checksum', self.gf('django.db.models.fields.BooleanField')(default=True)),
))
db.send_create_signal('libraryauth', ['CardPattern'])
# Adding model 'LibraryUser'
db.create_table('libraryauth_libraryuser', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('library', self.gf('django.db.models.fields.related.ForeignKey')(related_name='library_users', to=orm['libraryauth.Library'])),
('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='user_libraries', to=orm['auth.User'])),
('credential', self.gf('django.db.models.fields.CharField')(max_length=30, null=True)),
('date_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
))
db.send_create_signal('libraryauth', ['LibraryUser'])
def backwards(self, orm):
# Deleting model 'Library'
db.delete_table('libraryauth_library')
# Deleting model 'Block'
db.delete_table('libraryauth_block')
# Deleting model 'CardPattern'
db.delete_table('libraryauth_cardpattern')
# Deleting model 'LibraryUser'
db.delete_table('libraryauth_libraryuser')
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'})
},
'libraryauth.block': {
'Meta': {'ordering': "['lower']", 'object_name': 'Block'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blocks'", 'to': "orm['libraryauth.Library']"}),
'lower': ('regluit.libraryauth.models.IPAddressModelField', [], {'unique': 'True', 'db_index': 'True'}),
'upper': ('regluit.libraryauth.models.IPAddressModelField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
},
'libraryauth.cardpattern': {
'Meta': {'object_name': 'CardPattern'},
'checksum': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'card_patterns'", 'to': "orm['libraryauth.Library']"}),
'pattern': ('django.db.models.fields.CharField', [], {'max_length': '20'})
},
'libraryauth.library': {
'Meta': {'object_name': 'Library'},
'backend': ('django.db.models.fields.CharField', [], {'default': "'ip'", 'max_length': '10'}),
'group': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'library'", 'unique': 'True', 'null': 'True', 'to': "orm['auth.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'library'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'libraryauth.libraryuser': {
'Meta': {'object_name': 'LibraryUser'},
'credential': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'library_users'", 'to': "orm['libraryauth.Library']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_libraries'", 'to': "orm['auth.User']"})
}
}
complete_apps = ['libraryauth']

View File

@ -0,0 +1,99 @@
# -*- coding: 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 'EmailPattern'
db.create_table('libraryauth_emailpattern', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('library', self.gf('django.db.models.fields.related.ForeignKey')(related_name='email_patterns', to=orm['libraryauth.Library'])),
('pattern', self.gf('django.db.models.fields.CharField')(max_length=20)),
))
db.send_create_signal('libraryauth', ['EmailPattern'])
def backwards(self, orm):
# Deleting model 'EmailPattern'
db.delete_table('libraryauth_emailpattern')
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'})
},
'libraryauth.block': {
'Meta': {'ordering': "['lower']", 'object_name': 'Block'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blocks'", 'to': "orm['libraryauth.Library']"}),
'lower': ('regluit.libraryauth.models.IPAddressModelField', [], {'unique': 'True', 'db_index': 'True'}),
'upper': ('regluit.libraryauth.models.IPAddressModelField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
},
'libraryauth.cardpattern': {
'Meta': {'object_name': 'CardPattern'},
'checksum': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'card_patterns'", 'to': "orm['libraryauth.Library']"}),
'pattern': ('django.db.models.fields.CharField', [], {'max_length': '20'})
},
'libraryauth.emailpattern': {
'Meta': {'object_name': 'EmailPattern'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'email_patterns'", 'to': "orm['libraryauth.Library']"}),
'pattern': ('django.db.models.fields.CharField', [], {'max_length': '20'})
},
'libraryauth.library': {
'Meta': {'object_name': 'Library'},
'backend': ('django.db.models.fields.CharField', [], {'default': "'ip'", 'max_length': '10'}),
'group': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'library'", 'unique': 'True', 'null': 'True', 'to': "orm['auth.Group']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'library'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'libraryauth.libraryuser': {
'Meta': {'object_name': 'LibraryUser'},
'credential': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True'}),
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'library': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'library_users'", 'to': "orm['libraryauth.Library']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_libraries'", 'to': "orm['auth.User']"})
}
}
complete_apps = ['libraryauth']

View File

285
libraryauth/models.py Normal file
View File

@ -0,0 +1,285 @@
# IP address part of this of this copied from https://github.com/benliles/django-ipauth/blob/master/ipauth/models.py
import re
from django.contrib.auth.models import User, Group
from django.core.exceptions import ValidationError
from django.core import validators
from django.db import models
from django.db.models import Q
from django.db.models.signals import post_save
from django.forms import IPAddressField as BaseIPAddressField
from django.utils.translation import ugettext_lazy as _
class Library(models.Model):
'''
name and other things derive from the User
'''
user = models.OneToOneField(User, related_name='library')
group = models.OneToOneField(Group, related_name='library', null = True)
backend = models.CharField(max_length=10, choices=(
('ip','IP authentication'),
('cardnum', 'Library Card Number check'),
('email', 'e-mail pattern check'),
),default='ip')
credential = None
def __unicode__(self):
return self.user.username
def add_user(self, user):
user.groups.add(self.group)
(library_user, created) = LibraryUser.objects.get_or_create(library=self, user=user)
library_user.credential=self.credential
library_user.save()
def has_user(self, user):
return self.group in user.groups.all() or user == self.user
@property
def join_template(self):
return 'libraryauth/' + self.backend + '_join.html'
def add_group(sender, created, instance, **kwargs):
if created:
num=''
while created:
(group,created)=Group.objects.get_or_create(name=instance.user.username + num)
# make sure not using a group twice!
if created:
created = False
else:
num += '+'
try:
group.library
created = True
except Library.DoesNotExist:
pass
instance.group=group
instance.save()
post_save.connect(add_group, sender=Library)
def ip_to_long(value):
validators.validate_ipv4_address(value)
lower_validator = validators.MinValueValidator(0)
upper_validator = validators.MinValueValidator(255)
value = value.split('.')
output = 0
for i in range(0, 4):
validators.validate_integer(value[i])
lower_validator(value[i])
upper_validator(value[i])
output += int(value[i]) * (256**(3-i))
return output
def long_to_ip(value):
validators.validate_integer(value)
value = int(value)
validators.MinValueValidator(0)(value)
validators.MaxValueValidator(4294967295)(value)
return '%d.%d.%d.%d' % (value >> 24, value >> 16 & 255,
value >> 8 & 255, value & 255)
class IP(object):
def __init__(self, value):
self.int = value
def _set_int(self, value):
if isinstance(value, IP):
self._int = IP.int
try:
self._int = int(value)
except ValueError:
self._int = ip_to_long(value)
except (TypeError, ValidationError):
self._int = None
def _get_int(self):
return self._int
int = property(_get_int, _set_int)
def _get_str(self):
if self.int!=None:
return long_to_ip(self.int)
return ''
string = property(_get_str, _set_int)
def __eq__(self, other):
if not isinstance(other, IP):
try:
other = IP(other)
except:
return False
return self.int == other.int
def __cmp__(self, other):
if not isinstance(other, IP):
other = IP(other)
if self.int and other.int:
return self.int.__cmp__(other.int)
raise ValueError('Invalid arguments')
def __unicode__(self):
return self.string
def __str__(self):
return self.string
class IPAddressFormField(BaseIPAddressField):
default_validators = []
def prepare_value(self, value):
if isinstance(value, IP):
return value.string
try:
return IP(value).string
except:
pass
return value
def to_python(self, value):
if value==0:
return IP(0)
if value in validators.EMPTY_VALUES:
return None
try:
return IP(value)
except ValidationError:
raise ValidationError(self.default_error_messages['invalid'],
code='invalid')
class IPAddressModelField(models.IPAddressField):
__metaclass__ = models.SubfieldBase
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
models.Field.__init__(self, *args, **kwargs)
def get_internal_type(self):
return "PositiveIntegerField"
def get_prep_value(self, value):
if not value:
return value
if isinstance(value, IP):
return value.int
def to_python(self, value):
if isinstance(value, IP):
return value
try:
return IP(value)
except ValidationError:
return None
def formfield(self, **kwargs):
defaults = {'form_class': IPAddressFormField}
defaults.update(kwargs)
return super(models.IPAddressField, self).formfield(**defaults)
class Block(models.Model):
library = models.ForeignKey(Library, related_name='blocks')
lower = IPAddressModelField(db_index=True, unique=True)
upper = IPAddressModelField(db_index=True, blank=True, null=True)
def clean(self):
if self.upper and self.upper.int:
try:
if self.lower > self.upper:
raise ValidationError('Lower end of the Block must be less '
'than or equal to the upper end')
except ValueError, e:
pass
others = Block.objects.exclude(pk=self.pk)
query = Q(lower__lte=self.lower, upper__gte=self.lower) | \
Q(lower=self.lower)
if self.upper and self.upper.int:
textual = u'%s-%s' % (self.lower, self.upper)
query = query | Q(lower__range=(self.lower, self.upper)) | \
Q(lower__lte=self.upper, upper__gte=self.upper)
else:
textual = str(self.lower)
query = others.filter(query)
if query.exists():
values = query.distinct().values_list('library__user__username', flat=True)
raise ValidationError('%s overlaps a block in in use by: %s' % (textual,
', '.join(list(frozenset(values))[:5])))
def __unicode__(self):
if self.upper and self.upper.int:
return u'%s %s-%s' % (self.library, self.lower, self.upper)
return u'%s %s' % (self.library, self.lower)
class Meta:
ordering = ['lower',]
from south.modelsinspector import add_introspection_rules
escaped_package= __name__.replace('.', '\.')
add_introspection_rules([], ['^' + escaped_package + '\.IPAddressModelField'])
# from http://en.wikipedia.org/wiki/Luhn_algorithm#Implementation_of_standard_Mod_10
def luhn_checksum(card_number):
def digits_of(n):
return [int(d) for d in str(n)]
digits = digits_of(card_number)
odd_digits = digits[-1::-2]
even_digits = digits[-2::-2]
checksum = 0
checksum += sum(odd_digits)
for d in even_digits:
checksum += sum(digits_of(d*2))
return checksum % 10
class CardPattern(models.Model):
library = models.ForeignKey(Library, related_name='card_patterns')
# match pattern ^\d+#+$
pattern = models.CharField(max_length=20)
checksum = models.BooleanField(default=True)
def is_valid(self, card_number):
match_pattern='^' + self.pattern.replace('#','\d',20) + '$'
if re.match(match_pattern,card_number) is None:
return False
if self.checksum:
return luhn_checksum(card_number) == 0
else:
return True
class LibraryUser(models.Model):
library = models.ForeignKey(Library, related_name='library_users')
user = models.ForeignKey(User, related_name='user_libraries')
credential = models.CharField(max_length=30, null=True)
date_modified = models.DateTimeField(auto_now=True)
class EmailPattern(models.Model):
library = models.ForeignKey(Library, related_name='email_patterns')
# email endswith string
pattern = models.CharField(max_length=20)
def is_valid(self, email):
if email.lower().endswith(self.pattern.lower()):
return True
else:
return False

View File

@ -0,0 +1,14 @@
<div class="p_form">To use {{ library }}...{{authenticator.form.non_field_errors}}<br />
<form action="{% url join_library library %}#" method="POST" class="std_form " id="join_form">
{% csrf_token %}
{{ authenticator.form.credential.label_tag }}: {{ authenticator.form.credential }}
{% if authenticator.form.credential.errors %}
{{ authenticator.form.credential.errors }}
{% else %}
{{ authenticator.form.credential.help_text }}
{% endif %}<br />
<input type="submit" name="join" value="Make this my Library">
{{ authenticator.form.library }}
{{ authenticator.form.user }}
</form>
</div>

View File

@ -0,0 +1 @@
Denied authentication for {{ request.user }} at {{ request.META.REMOTE_ADDR }}

View File

@ -0,0 +1,6 @@
<br />
{% if authenticator.allowed %}
<a href="{% url join_library authenticator.library %}?next={% url join_library authenticator.library %}" class="fakeinput">Make this my Library</a>
{% else %}
Based on your account's email address, you can't join {{ authenticator.library }}. You can <a href="{% url email_change %}"> change your email address</a> if you need to.
{% endif %}

View File

@ -0,0 +1,6 @@
<br />
{% if authenticator.allowed %}
<a href="{% url join_library authenticator.library %}?next={% url join_library authenticator.library %}" class="fakeinput">Make this my Library</a>
{% else %}
You can't join {{ authenticator.library }} at your current location.
{% endif %}

View File

@ -0,0 +1 @@
{{library}}

View File

@ -0,0 +1,2 @@
{% extends "registration/from_pledge.html" %}
{% block login_pitch %}<h3>Before you can join {{library}} on unglue.it, please login or make an unglue.it account. </h3>{% endblock %}

15
libraryauth/urls.py Normal file
View File

@ -0,0 +1,15 @@
from django.conf.urls.defaults import *
from django.core.urlresolvers import reverse
from django.views.generic.simple import direct_to_template
from . import views, models
urlpatterns = patterns(
"",
url(r"^libraryauth/(?P<library>[^/]+)/join/$", views.join_library, name="join_library"),
url(r"^libraryauth/(?P<library>[^/]+)/deny/$", direct_to_template, {'template':'libraryauth/denied.html'}, name="bad_library"),
url(r"^libraryauth/list/$", direct_to_template, {
'template':'libraryauth/list.html',
'extra_context':{'libraries':models.Library.objects.order_by('user__username')}
}, name="library_list"),
url(r'^accounts/superlogin/$', views.superlogin, name='superlogin'),
)

69
libraryauth/views.py Normal file
View File

@ -0,0 +1,69 @@
import logging
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404
from django.contrib.auth.views import login
from django.http import HttpResponseRedirect
from . import backends
from .models import Library
from .forms import AuthForm
logger = logging.getLogger(__name__)
def join_library(request, library):
library=get_object_or_404(Library, user__username=library)
return Authenticator(request,library).process(
reverse('library',args=[str(library)]),
reverse('bad_library',args=[str(library)]),
)
def superlogin(request, extra_context=None, **kwargs):
if request.method == 'POST' and request.user.is_anonymous():
username=request.POST.get("username", "")
try:
user=models.User.objects.get(username=username)
extra_context={"socials":user.profile.social_auths}
except:
pass
if request.GET.has_key("add"):
request.session["add_wishlist"]=request.GET["add"]
return login(request, extra_context=extra_context, authentication_form=AuthForm, **kwargs)
class Authenticator:
request=None
library=None
def __init__(self, request, library, *args, **kwargs):
self.request = request
if isinstance(library , basestring):
self.library = Library.objects.get(user__username=library)
elif isinstance(library , Library):
self.library=library
else:
raise Exception
try:
form_class = getattr(backends, self.library.backend + '_form')
self.form = form_class(request, library, *args, **kwargs)
except AttributeError:
self.form = None
def process(self, success_url, deny_url):
logger.info('authenticator for %s at %s.'%(self.request.user, self.library))
if self.library.has_user(self.request.user):
return HttpResponseRedirect(success_url)
backend_test= getattr(backends, self.library.backend + '_authenticate')
if backend_test(self.request, self.library):
if self.request.user.is_authenticated():
self.library.add_user(self.request.user)
return HttpResponseRedirect(success_url)
else:
return superlogin(self.request, extra_context={'library':self.library}, template_name='libraryauth/library_login.html')
else:
backend_authenticator= getattr(backends, self.library.backend + '_authenticator')
return backend_authenticator().process(self, success_url, deny_url)
def allowed(self):
backend_test= getattr(backends, self.library.backend + '_authenticate')
return backend_test(self.request, self.library)

View File

@ -150,7 +150,7 @@ INSTALLED_APPS = (
'django.contrib.admin',
'regluit.booxtream',
'regluit.pyepub',
'regluit.libraryauth',
)
# A sample logging configuration. The only tangible logging

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
static/css/liblist.css Normal file
View File

@ -0,0 +1 @@
.header-text{height:36px;line-height:36px;display:block;text-decoration:none;font-weight:bold;letter-spacing:-0.05em}.panelborders{border-width:1px 0;border-style:solid none;border-color:#fff}.roundedspan{border:1px solid #d4d4d4;-moz-border-radius:7px;-webkit-border-radius:7px;border-radius:7px;padding:1px;color:#fff;margin:0 8px 0 0;display:inline-block}.roundedspan>span{padding:7px 7px;min-width:15px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;text-align:center;display:inline-block}.roundedspan>span .hovertext{display:none}.roundedspan>span:hover .hovertext{display:inline}.mediaborder{padding:5px;border:solid 5px #edf3f4}.actionbuttons{width:auto;height:36px;line-height:36px;background:#8dc63f;-moz-border-radius:32px;-webkit-border-radius:32px;border-radius:32px;color:white;cursor:pointer;font-size:13px;font-weight:bold;padding:0 15px;border:0;margin:5px 0}.errors{-moz-border-radius:16px;-webkit-border-radius:16px;border-radius:16px;border:solid #e35351 3px;clear:both;width:90%;height:auto;line-height:16px;padding:7px 0;font-weight:bold;font-size:13px;text-align:center}.errors li{list-style:none;border:0}.items{clear:both;padding:5px;margin:0 5px 8px 0;width:95%}.items.row1{background:#f6f9f9}.items.row2{background:#fff}.items div{float:left}.items div img{margin:0 5px}.items .image img{height:100px}.items:after{content:".";display:block;height:0;clear:both;visibility:hidden}.items .nonavatar{width:620px;padding-top:5px}.items .nonavatar span{padding-right:5px}.items .nonavatar div.libname{width:100%}.items .nonavatar div.libname a{font-size:15px}.items .nonavatar div.libstat{width:25%;display:block}.items .avatar{float:left;margin:0 auto;padding-top:5px}.joined{border:3px #b8dde0 solid;margin-top:3px;margin-bottom:5px;padding-left:2px}.joined em{font-color:#b8dde0;font-style:italic}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -364,7 +364,9 @@ ul.support li {
border-bottom:1px solid @blue-grey;
padding:10px 5px 10px 10px;
background:url("@{image-base}icons/pledgearrow.png") 98% center no-repeat;
&.no_link{
background:none;
}
&.last {
border-bottom: none;
}
@ -396,6 +398,11 @@ ul.support li {
color: #fff;
text-decoration: none;
}
&.no_link{
background:#fff;
color:@call-to-action
}
}
}

View File

@ -285,11 +285,6 @@ div.pledge-container {
font-weight: bold;
}
.std_form, .std_form input, .std_form select {
line-height: 30px;
font-size: 15px;
}
.call-to-action {
color: @call-to-action;
}

71
static/less/liblist.less Normal file
View File

@ -0,0 +1,71 @@
@import "variables.less";
.items {
clear: both;
padding: 5px;
margin: 0 5px 8px 0;
//min-height: 105px;
width: 95%;
&.row1 {
background: #f6f9f9;
}
&.row2 {
background: #fff;
}
div {
float: left;
img {
margin: 0 5px;
}
}
.image img {
height: 100px;
}
// so div will stretch to height of content
&:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.nonavatar {
width: 620px;
padding-top: 5px;
span {
padding-right: 5px;
}
div.libname{
width: 100%;
a{
font-size: @font-size-larger;
}
}
div.libstat {
width:25%;
display: block;
}
}
.avatar {
float: left;
margin: 0 auto;
padding-top: 5px;
}
}
.joined {
border: 3px #B8DDE0 solid;
margin-top: 3px;
margin-bottom: 5px;
padding-left: 2px;
em {
font-color:#B8DDE0;
font-style:italic;
}
}

View File

@ -4,7 +4,7 @@
.preview_campaign {
float: right;
margin-right: 10px;
margin-right: 40px;
}
input[name="launch"] {

View File

@ -5,7 +5,7 @@
width: auto;
}
input[type="submit"] {
input[type="submit"], a.fakeinput {
float: right;
font-size: @font-size-header;
margin: 10px 0 10px;
@ -294,4 +294,4 @@ span.level2.menu.answer {
float: left;
width: 48%;
padding: 1%;
}
}

View File

@ -261,14 +261,13 @@ ul.menu{
.p_form .errorlist {
.one-border-radius(16px);
border: none;
color:@call-to-action;
color:@alert;
clear: none;
width: 100%;
height: auto;
line-height: 16px;
padding: 0;
font-weight: normal;
font-size: 13px;
text-align: left;
display: inline;
li {
@ -980,4 +979,9 @@ li.checked {
.yes_js {
display: none;
}
}
.std_form, .std_form input, .std_form select {
line-height: 30px;
font-size: 15px;
}

View File

@ -87,6 +87,20 @@
float:left;
width:25%;
}
.user-block23 {
color:@medium-blue;
font-size: @font-size-larger;
line-height:normal;
float:left;
width:50%;
}
.user-block24 {
color:@medium-blue;
font-size: @font-size-larger;
line-height:normal;
float:left;
width:75%;
}
.user-block3,
.user-block4 {

View File

@ -4,7 +4,8 @@ from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from frontend.forms import ProfileForm
from frontend.views import superlogin, social_auth_reset_password
from frontend.views import social_auth_reset_password
from libraryauth.views import superlogin
from regluit.admin import admin_site
from regluit.core.sitemaps import WorkSitemap, PublisherSitemap
@ -40,6 +41,7 @@ urlpatterns = patterns('',
(r'^api/', include('regluit.api.urls')),
(r'', include('regluit.frontend.urls')),
(r'', include('regluit.payment.urls')),
(r'', include('regluit.libraryauth.urls')),
(r'^selectable/', include('selectable.urls')),
url(r'^admin/', include(admin_site.urls)),
(r'^comments/', include('django.contrib.comments.urls')),