From 41b955245a5a422b5850f9adffb5eda727b9eab7 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Fri, 28 Oct 2011 10:54:06 -0700 Subject: [PATCH 1/9] Some basic code for zotero, Librarything, amazon wishilst --- amazon_wishlist.py | 29 ++++++++++++ my_librarything_books.py | 47 ++++++++++++++++++ zotero_books.py | 100 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 amazon_wishlist.py create mode 100644 my_librarything_books.py create mode 100644 zotero_books.py diff --git a/amazon_wishlist.py b/amazon_wishlist.py new file mode 100644 index 00000000..9580704b --- /dev/null +++ b/amazon_wishlist.py @@ -0,0 +1,29 @@ +# scrape my amazon wishlist + +import requests +import lxml.html +from lxml.cssselect import CSSSelector + +def net_text(e): + children = e.getchildren() + if len(children) > 0 : + return "".join(map(net_text,children)) + else: + return e.text if e.text is not None else '' + +wishlist_id = '1U5EXVPVS3WP5' +url = "http://www.amazon.com/wishlist/%s/ref=cm_wl_act_print_o?_encoding=UTF8&layout=standard-print&disableNav=1&visitor-view=1&items-per-page=500&page=1" % (wishlist_id) +r = requests.get(url) +html = lxml.html.fromstring(r.content.decode("UTF-8")) + +sel = CSSSelector('#itemsTable tr') +elems = sel(html) + +# just realized no isbn in print version....need to do more work + +for (i, tr) in enumerate(elems): + print i, + for td in tr.findall('td'): + print net_text(td), + print + diff --git a/my_librarything_books.py b/my_librarything_books.py new file mode 100644 index 00000000..af0d02ee --- /dev/null +++ b/my_librarything_books.py @@ -0,0 +1,47 @@ +import mechanize +import csv +import HTMLParser +import argparse + +h = HTMLParser.HTMLParser() + +# parse options + +parser = argparse.ArgumentParser(description='Download and parse LibraryThing booklist.') +parser.add_argument('user', help='LibraryThing username') +parser.add_argument('password', help='LibraryThing password') + +args = parser.parse_args() + +USERNAME = args.user +PW = args.password + +LT_url = "https://www.librarything.com" +LT_csv_file_url = "http://www.librarything.com/export-csv" + +def retrieve_book_list(user,password): + br = mechanize.Browser() + br.open(LT_url) + # select 2 form + br.select_form(nr=1) + br["formusername"] = user + br["formpassword"] = password + br.submit() + + # get CSV file + response = br.open(LT_csv_file_url) + return response + +def parse_csv(f): + reader = csv.DictReader(f) + for (i,row) in enumerate(reader): + print i, h.unescape(row["'TITLE'"]), h.unescape(row["'AUTHOR (first, last)'"]), row["'ISBNs'"], row["'COMMENT'"], row["'TAGS'"], row["'COLLECTIONS'"], h.unescape(row["'REVIEWS'"]) + +if __name__ == '__main__': + dynamic = True + if dynamic: + f = retrieve_book_list(USERNAME, PW) + else: + fname = "/Users/raymondyee/Downloads/LibraryThing_export.csv" + f = open(fname,"rb") + parse_csv(f) \ No newline at end of file diff --git a/zotero_books.py b/zotero_books.py new file mode 100644 index 00000000..f347d095 --- /dev/null +++ b/zotero_books.py @@ -0,0 +1,100 @@ +from zoteroconf import user_id, user_key + +from itertools import islice +from functools import partial + +from pyzotero.zotero import Zotero + +class Zotero2(Zotero): + def __init__(self, user_id = None, user_key = None): + self.__params = {} + super(Zotero2,self).__init__(user_id,user_key) + def set_parameters(self, **kwargs): + self.__params.update(kwargs) + def _to_iterator(self, f, *args, **kwargs): + current_start = self.__params.get("start", 0) + params = self.__params.copy() + + params["start"] = current_start + more_items = True + + while more_items: + self.add_parameters(**params) + items = f(*args,**kwargs) + for item in items: + yield item + params["start"] += len(items) + if len(items) == 0: + more_items = False + def __getattribute__(self, name): + if name in ['items','item','top','children','tag_items', 'group_items','group_top', + 'group_item','group_item_children', 'group_items_tag', + 'group_collection_items', 'group_collection_item', + 'group_collection_top','collection_items','get_subset', + 'collections','collections_sub','group_collections', + 'group_collection', + 'groups','tags','item_tags','group_tags','group_item_tags']: + #f = super(Zotero2,self).items + f = getattr(super(Zotero2,self),name) + #return self._to_iterator(f) + return partial(self._to_iterator,f) + else: + return super(Zotero2,self).__getattribute__(name) + #def items(self): + # f = super(Zotero2,self).items + # return self._to_iterator(f) + #def items0(self): + # current_start = self._params.get("start", 1) + # params = self._params + # + # params["start"] = current_start + # more_items = True + # + # while more_items: + # self.add_parameters(**params) + # items = super(Zotero2,self).items() + # for item in items: + # yield item + # params["start"] += len(items) + # if len(items) == 0: + # more_items = False + +class MyZotero(Zotero2): + def __init__(self, user_id=user_id, user_key=user_key): + super(MyZotero,self).__init__(user_id,user_key) + def items_in_unglue_it_collection(self): + return self.collection_items('3RKQ23IP') + def compare_keys(self,max,pagesize1,pagesize2): + set1 = self.item_keys(max,pagesize1) + set2 = self.item_keys(max,pagesize2) + + print "length: ", len(set1), len(set2) + print set1 ^ set2 + def item_keys(self, max, page_size): + self.set_parameters(limit=page_size) + items = self.items() + item_set = set() + for (i, item) in enumerate(islice(items,max)): + item_set.add((item["group_id"], item["key"], item["title"])) + print i, (item["group_id"], item["key"], item["title"]) + return item_set + def get_all_items(self): + print len(self.item_keys(5000,99)) + def get_all_books(self,max): + self.set_parameters(sort="type") + items = self.items() + book_set = set() + for (i, item) in enumerate(islice(items,max)): + if item.get("itemType") == 'book': + print i, (item["group_id"], item["key"], item["title"], item.get("itemType"), item.get("ISBN", None)) + book_set.add((item["group_id"], item["key"], item["title"])) + print len(book_set) + return book_set + + +zot = MyZotero() +#zot.compare_keys(24,7,3) +to_unglue = list(zot.items_in_unglue_it_collection()) +print len(to_unglue), [item["title"] for item in to_unglue] +zot.get_all_books(50) +#print zot.get_all_items() \ No newline at end of file From ed4ea516c2aa93e1c63cfbca8cc0475840675633 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Sat, 29 Oct 2011 15:40:00 -0700 Subject: [PATCH 2/9] Storing goodreads info in user.profile --- ...ile__add_field_userprofile_goodreads_id.py | 149 +++++++++++++++ ...o__add_field_goodreadsprofile_user_link.py | 137 ++++++++++++++ ...dreads_id__add_field_goodreadsprofile_u.py | 143 +++++++++++++++ ..._field_userprofile_goodreads_user_id__a.py | 171 ++++++++++++++++++ core/models.py | 12 +- frontend/templates/goodreads_display.html | 4 +- frontend/templates/supporter.html | 2 +- frontend/urls.py | 3 +- frontend/views.py | 68 ++++--- 9 files changed, 642 insertions(+), 47 deletions(-) create mode 100644 core/migrations/0003_auto__add_goodreadsprofile__add_field_userprofile_goodreads_id.py create mode 100644 core/migrations/0004_auto__add_field_goodreadsprofile_user_link.py create mode 100644 core/migrations/0005_auto__del_field_userprofile_goodreads_id__add_field_goodreadsprofile_u.py create mode 100644 core/migrations/0006_auto__del_goodreadsprofile__add_field_userprofile_goodreads_user_id__a.py diff --git a/core/migrations/0003_auto__add_goodreadsprofile__add_field_userprofile_goodreads_id.py b/core/migrations/0003_auto__add_goodreadsprofile__add_field_userprofile_goodreads_id.py new file mode 100644 index 00000000..70495780 --- /dev/null +++ b/core/migrations/0003_auto__add_goodreadsprofile__add_field_userprofile_goodreads_id.py @@ -0,0 +1,149 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'GoodreadsProfile' + db.create_table('core_goodreadsprofile', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user_id', self.gf('django.db.models.fields.CharField')(max_length=32)), + ('user_name', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)), + ('auth_token', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('auth_secret', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + )) + db.send_create_signal('core', ['GoodreadsProfile']) + + # Adding field 'UserProfile.goodreads_id' + db.add_column('core_userprofile', 'goodreads_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['core.GoodreadsProfile'], unique=True, null=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting model 'GoodreadsProfile' + db.delete_table('core_goodreadsprofile') + + # Deleting field 'UserProfile.goodreads_id' + db.delete_column('core_userprofile', 'goodreads_id_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'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.author': { + 'Meta': {'object_name': 'Author'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500'}) + }, + 'core.campaign': { + 'Meta': {'object_name': 'Campaign'}, + 'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deadline': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}), + 'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'suspended': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'suspended_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}), + 'withdrawn': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'withdrawn_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"}) + }, + 'core.edition': { + 'Meta': {'object_name': 'Edition'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}), + 'googlebooks_id': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'isbn_10': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), + 'isbn_13': ('django.db.models.fields.CharField', [], {'max_length': '13', 'null': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True'}), + 'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"}) + }, + 'core.goodreadsprofile': { + 'Meta': {'object_name': 'GoodreadsProfile'}, + 'auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'core.subject': { + 'Meta': {'object_name': 'Subject'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", '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.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'goodreads_id': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.GoodreadsProfile']", 'unique': 'True', 'null': 'True'}), + 'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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.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', 'to': "orm['core.Work']"}) + }, + 'core.work': { + 'Meta': {'object_name': 'Work'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'openlibrary_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}) + } + } + + complete_apps = ['core'] diff --git a/core/migrations/0004_auto__add_field_goodreadsprofile_user_link.py b/core/migrations/0004_auto__add_field_goodreadsprofile_user_link.py new file mode 100644 index 00000000..7e53ed50 --- /dev/null +++ b/core/migrations/0004_auto__add_field_goodreadsprofile_user_link.py @@ -0,0 +1,137 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'GoodreadsProfile.user_link' + db.add_column('core_goodreadsprofile', 'user_link', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'GoodreadsProfile.user_link' + db.delete_column('core_goodreadsprofile', 'user_link') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.author': { + 'Meta': {'object_name': 'Author'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500'}) + }, + 'core.campaign': { + 'Meta': {'object_name': 'Campaign'}, + 'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deadline': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}), + 'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'suspended': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'suspended_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}), + 'withdrawn': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'withdrawn_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"}) + }, + 'core.edition': { + 'Meta': {'object_name': 'Edition'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}), + 'googlebooks_id': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'isbn_10': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), + 'isbn_13': ('django.db.models.fields.CharField', [], {'max_length': '13', 'null': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True'}), + 'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"}) + }, + 'core.goodreadsprofile': { + 'Meta': {'object_name': 'GoodreadsProfile'}, + 'auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'user_link': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'core.subject': { + 'Meta': {'object_name': 'Subject'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", '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.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'goodreads_id': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.GoodreadsProfile']", 'unique': 'True', 'null': 'True'}), + 'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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.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', 'to': "orm['core.Work']"}) + }, + 'core.work': { + 'Meta': {'object_name': 'Work'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'openlibrary_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}) + } + } + + complete_apps = ['core'] diff --git a/core/migrations/0005_auto__del_field_userprofile_goodreads_id__add_field_goodreadsprofile_u.py b/core/migrations/0005_auto__del_field_userprofile_goodreads_id__add_field_goodreadsprofile_u.py new file mode 100644 index 00000000..2eff674f --- /dev/null +++ b/core/migrations/0005_auto__del_field_userprofile_goodreads_id__add_field_goodreadsprofile_u.py @@ -0,0 +1,143 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting field 'UserProfile.goodreads_id' + db.delete_column('core_userprofile', 'goodreads_id_id') + + # Adding field 'GoodreadsProfile.user_profile' + db.add_column('core_goodreadsprofile', 'user_profile', self.gf('django.db.models.fields.related.OneToOneField')(related_name='goodreads_id', unique=True, null=True, to=orm['core.UserProfile']), keep_default=False) + + + def backwards(self, orm): + + # Adding field 'UserProfile.goodreads_id' + db.add_column('core_userprofile', 'goodreads_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['core.GoodreadsProfile'], unique=True, null=True), keep_default=False) + + # Deleting field 'GoodreadsProfile.user_profile' + db.delete_column('core_goodreadsprofile', 'user_profile_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'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.author': { + 'Meta': {'object_name': 'Author'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500'}) + }, + 'core.campaign': { + 'Meta': {'object_name': 'Campaign'}, + 'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deadline': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}), + 'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'suspended': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'suspended_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}), + 'withdrawn': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'withdrawn_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"}) + }, + 'core.edition': { + 'Meta': {'object_name': 'Edition'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}), + 'googlebooks_id': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'isbn_10': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), + 'isbn_13': ('django.db.models.fields.CharField', [], {'max_length': '13', 'null': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True'}), + 'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"}) + }, + 'core.goodreadsprofile': { + 'Meta': {'object_name': 'GoodreadsProfile'}, + 'auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'user_link': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}), + 'user_profile': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'goodreads_id'", 'unique': 'True', 'null': 'True', 'to': "orm['core.UserProfile']"}) + }, + 'core.subject': { + 'Meta': {'object_name': 'Subject'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", '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.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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.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', 'to': "orm['core.Work']"}) + }, + 'core.work': { + 'Meta': {'object_name': 'Work'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'openlibrary_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}) + } + } + + complete_apps = ['core'] diff --git a/core/migrations/0006_auto__del_goodreadsprofile__add_field_userprofile_goodreads_user_id__a.py b/core/migrations/0006_auto__del_goodreadsprofile__add_field_userprofile_goodreads_user_id__a.py new file mode 100644 index 00000000..cfa65112 --- /dev/null +++ b/core/migrations/0006_auto__del_goodreadsprofile__add_field_userprofile_goodreads_user_id__a.py @@ -0,0 +1,171 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting model 'GoodreadsProfile' + db.delete_table('core_goodreadsprofile') + + # Adding field 'UserProfile.goodreads_user_id' + db.add_column('core_userprofile', 'goodreads_user_id', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True), keep_default=False) + + # Adding field 'UserProfile.goodreads_user_name' + db.add_column('core_userprofile', 'goodreads_user_name', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True), keep_default=False) + + # Adding field 'UserProfile.goodreads_auth_token' + db.add_column('core_userprofile', 'goodreads_auth_token', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) + + # Adding field 'UserProfile.goodreads_auth_secret' + db.add_column('core_userprofile', 'goodreads_auth_secret', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) + + # Adding field 'UserProfile.goodreads_user_link' + db.add_column('core_userprofile', 'goodreads_user_link', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True), keep_default=False) + + + def backwards(self, orm): + + # Adding model 'GoodreadsProfile' + db.create_table('core_goodreadsprofile', ( + ('user_id', self.gf('django.db.models.fields.CharField')(max_length=32)), + ('user_link', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True)), + ('user_profile', self.gf('django.db.models.fields.related.OneToOneField')(related_name='goodreads_id', unique=True, null=True, to=orm['core.UserProfile'])), + ('auth_secret', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('user_name', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)), + ('auth_token', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + )) + db.send_create_signal('core', ['GoodreadsProfile']) + + # Deleting field 'UserProfile.goodreads_user_id' + db.delete_column('core_userprofile', 'goodreads_user_id') + + # Deleting field 'UserProfile.goodreads_user_name' + db.delete_column('core_userprofile', 'goodreads_user_name') + + # Deleting field 'UserProfile.goodreads_auth_token' + db.delete_column('core_userprofile', 'goodreads_auth_token') + + # Deleting field 'UserProfile.goodreads_auth_secret' + db.delete_column('core_userprofile', 'goodreads_auth_secret') + + # Deleting field 'UserProfile.goodreads_user_link' + db.delete_column('core_userprofile', 'goodreads_user_link') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.author': { + 'Meta': {'object_name': 'Author'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500'}) + }, + 'core.campaign': { + 'Meta': {'object_name': 'Campaign'}, + 'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'deadline': ('django.db.models.fields.DateTimeField', [], {}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}), + 'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'suspended': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'suspended_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}), + 'withdrawn': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'withdrawn_reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"}) + }, + 'core.edition': { + 'Meta': {'object_name': 'Edition'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}), + 'googlebooks_id': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'isbn_10': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True'}), + 'isbn_13': ('django.db.models.fields.CharField', [], {'max_length': '13', 'null': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True'}), + 'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), + 'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"}) + }, + 'core.subject': { + 'Meta': {'object_name': 'Subject'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", '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.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + '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'}), + '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.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', 'to': "orm['core.Work']"}) + }, + 'core.work': { + 'Meta': {'object_name': 'Work'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'openlibrary_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}) + } + } + + complete_apps = ['core'] diff --git a/core/models.py b/core/models.py index 3c7f7d62..dffe843d 100755 --- a/core/models.py +++ b/core/models.py @@ -185,18 +185,18 @@ class Wishlist(models.Model): user = models.OneToOneField(User, related_name='wishlist') works = models.ManyToManyField('Work', related_name='wishlists') - class UserProfile(models.Model): user = models.OneToOneField(User, related_name='profile') tagline = models.CharField(max_length=140, blank=True) home_url = models.URLField(blank=True) twitter_id = models.CharField(max_length=15, blank=True) + + goodreads_user_id = models.CharField(max_length=32, null=True, blank=True) + goodreads_user_name = models.CharField(max_length=200, null=True, blank=True) + goodreads_auth_token = models.TextField(null=True, blank=True) + goodreads_auth_secret = models.TextField(null=True, blank=True) + goodreads_user_link = models.CharField(max_length=200, null=True, blank=True) -#class GoodreadsProfile(models.Model): -# user_id = models.CharField(max_length=32) -# username = models.CharField(max_length=200, blank=True) -# auth_token = models.TextField(null=True, blank=True) -# auth_secret = models.TextField(null=True, blank=True) from regluit.core import signals from regluit.payment.manager import PaymentManager diff --git a/frontend/templates/goodreads_display.html b/frontend/templates/goodreads_display.html index 18399778..f1fc5267 100644 --- a/frontend/templates/goodreads_display.html +++ b/frontend/templates/goodreads_display.html @@ -13,8 +13,8 @@

Goodreads

- {% if goodreads_userid %} -

Welcome, Goodreads user id# {{goodreads_userid}}. Your GR username is {{goodreads_username}}. Want to see your Goodreads profile?

+ {% if user.profile.goodreads_user_id %} +

Welcome, Goodreads user id# {{user.profile.goodreads_user_id}}. Your GR username is {{user.profile.goodreads_user_name}}. Want to see your Goodreads profile?

{% else %}

We don't currently know your Goodreads user information.

Enable us to link to your Goodreads account.

diff --git a/frontend/templates/supporter.html b/frontend/templates/supporter.html index e7b9dc4f..92457823 100644 --- a/frontend/templates/supporter.html +++ b/frontend/templates/supporter.html @@ -156,7 +156,7 @@ how do I integrate the your wishlist thing with the tabs thing?
-

Other Stuff

+

Other Stuff (to use?)

diff --git a/frontend/urls.py b/frontend/urls.py index 610d21d1..0702a20a 100644 --- a/frontend/urls.py +++ b/frontend/urls.py @@ -2,6 +2,7 @@ from django.conf.urls.defaults import * from django.views.generic.simple import direct_to_template from django.views.generic.base import TemplateView from django.views.generic import ListView, DetailView +from django.contrib.auth.decorators import login_required from regluit.core.models import Campaign from regluit.frontend.views import CampaignFormView, GoodreadsDisplayView @@ -20,7 +21,7 @@ urlpatterns = patterns( url(r"^campaigns/$", ListView.as_view( model=Campaign,template_name="campaign_list.html", context_object_name="campaign_list")), url(r"^campaigns/(?P\d+)/$",CampaignFormView.as_view(), name="campaign_by_id"), - url(r"^goodreads/$", GoodreadsDisplayView.as_view(), name="goodreads_display"), + url(r"^goodreads/$", login_required(GoodreadsDisplayView.as_view()), name="goodreads_display"), url(r"^goodreads/auth_cb/$", "goodreads_cb", name="goodreads_cb"), url(r"^goodreads/flush/$","goodreads_flush_session", name="goodreads_flush_session") ) diff --git a/frontend/views.py b/frontend/views.py index cbef8b43..c46f2c26 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -226,46 +226,32 @@ class GoodreadsDisplayView(TemplateView): template_name = "goodreads_display.html" def get_context_data(self, **kwargs): context = super(GoodreadsDisplayView, self).get_context_data(**kwargs) - - goodreads_attribs = ["goodreads_userid", "goodreads_username", "goodreads_isauthorized", "goodreads_userlink"] - # for this round, I will use the Session to hold the Goodreads profile info session = self.request.session - - # load up context with this info even though the template can directly access the session. - # down the road, we won't store the goodreads user info in the session. - - for attrib in goodreads_attribs: - context[attrib] = session.get(attrib, None) - - # a Goodreads user shouldn't have to authorize us to get certain functionality - # if we don't know the Goodreads identity of user, offer to authenticate user to Goodreads and grab identity gr_client = GoodreadsClient(key=settings.GOODREADS_API_KEY, secret=settings.GOODREADS_API_SECRET) + + user = self.request.user - if session.get("goodreads_isauthorized") is None: + if user.profile.goodreads_user_id is None: # calculate the Goodreads authorization URL (context["goodreads_auth_url"], request_token) = gr_client.begin_authorization(self.request.build_absolute_uri(reverse('goodreads_cb'))) - + logger.info("goodreads_auth_url: %s" %(context["goodreads_auth_url"])) # store request token in session so that we can redeem it for auth_token if authorization works session['goodreads_request_token'] = request_token['oauth_token'] session['goodreads_request_secret'] = request_token['oauth_token_secret'] - - # if we have a userid, grab info about the book shelves and books - if session.get("goodreads_userid") is not None: - context["shelves_info"] = gr_client.shelves_list(user_id=session["goodreads_userid"]) - context["reviews"] = list(islice(gr_client.review_list(user_id=session["goodreads_userid"],per_page=50),50)) - - # also, now grab the books on the user's shelves + else: + context["shelves_info"] = gr_client.shelves_list(user_id=user.profile.goodreads_user_id) + context["reviews"] = list(islice(gr_client.review_list(user_id=user.profile.goodreads_user_id, per_page=50),50)) return context - + +@login_required def goodreads_cb(request): - # handle callback from Goodreads - # how to check that Goodreads is saying it's a go? + """handle callback from Goodreads""" + session = request.session authorized_flag = request.GET['authorize'] # is it '1'? request_oauth_token = request.GET['oauth_token'] - # compare what Goodreads sends back in terms of a request_token vs what is stored in session if authorized_flag == '1': request_token = {'oauth_token': session.get('goodreads_request_token'), 'oauth_token_secret': session.get('goodreads_request_secret')} @@ -273,26 +259,34 @@ def goodreads_cb(request): access_token = gr_client.complete_authorization(request_token) - # store the access token in the session - session["goodreads_access_token"] = access_token["oauth_token"] - session["goodreads_access_secret"] = access_token["oauth_token_secret"] - session["goodreads_isauthorized"] = True - - # delete old request tokens in the session - del session['goodreads_request_token'] - del session['goodreads_request_secret'] + # store the access token in the user profile + profile = request.user.profile + profile.goodreads_auth_token = access_token["oauth_token"] + profile.goodreads_auth_secret = access_token["oauth_token_secret"] # let's get the userid, username user = gr_client.auth_user() - session["goodreads_userid"] = user["userid"] - session["goodreads_username"] = user["name"] - session["goodreads_userlink"] = user["link"] + + profile.goodreads_user_id = user["userid"] + profile.goodreads_user_name = user["name"] + profile.goodreads_user_link = user["link"] + + profile.save() # is this needed? # redirect to the Goodreads display page -- should observe some next later return HttpResponseRedirect(reverse('goodreads_display')) def goodreads_flush_session(request): + user = request.user + if user.is_authenticated(): + profile = user.profile + profile.goodreads_user_id = None + profile.goodreads_user_name = None + profile.goodreads_user_link = None + profile.goodreads_auth_token = None + profile.goodreads_auth_secret = None + profile.save() request.session.flush() - return HttpResponse("Your session has been flushed. Go back to goodreads display" + return HttpResponse("Your session and goodreads identity have been flushed. Go back to goodreads display" % (reverse('goodreads_display'))) From f7ea58a602949e68ce1dc83935a9f54cbf9a7252 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Mon, 31 Oct 2011 17:26:05 -0700 Subject: [PATCH 3/9] Integrated Goodreads shelf loading to wishlist into UI --- core/goodreads.py | 36 ++++++++++++- .../commands/goodreads_load_books.py | 16 ++---- core/tasks.py | 7 ++- frontend/forms.py | 5 ++ frontend/templates/goodreads_display.html | 46 ++++++++++++++++- frontend/urls.py | 4 +- frontend/views.py | 50 +++++++++++++++---- 7 files changed, 137 insertions(+), 27 deletions(-) diff --git a/core/goodreads.py b/core/goodreads.py index db93e8ce..fcc98dc5 100644 --- a/core/goodreads.py +++ b/core/goodreads.py @@ -6,6 +6,10 @@ import oauth2 as oauth from urlparse import urlparse, urlunparse, urljoin from urllib import urlencode import httplib +from itertools import islice +from regluit.core import bookloader +import regluit.core + # import parse_qsl from cgi if it doesn't exist in urlparse try: from urlparse import parse_qsl @@ -148,10 +152,10 @@ class GoodreadsClient(object): while (more_pages): + logger.info('request to review_list: %s %s', request_url, params) r = request(method,request_url,params=params) if r.status_code != httplib.OK: - #logger.info('headers, content: %s | %s ' % (r.headers,r.content)) raise GoodreadsException('Error in review_list: %s %s ' % (r.headers, r.content)) else: #logger.info('headers, content: %s | %s ' % (r.headers,r.content)) @@ -197,9 +201,37 @@ class GoodreadsClient(object): d = dict( [ (k,int(shelves.attrib[k])) for k in shelves.attrib ] ) d["user_shelves"] = [{'name':shelf.find('name').text, 'book_count':int(shelf.find('book_count').text), - 'description':shelf.find('description').text if shelf.find('description').attrib['nil'] != 'true' else None } \ + 'description':shelf.find('description').text if shelf.find('description').attrib['nil'] != 'true' else None, + 'exclusive_flag':shelf.find('exclusive_flag').text} \ for shelf in shelves.findall('user_shelf')] + + d["total_book_count"] = sum([shelf['book_count'] if shelf['exclusive_flag'] == 'true' else 0 for shelf in d["user_shelves"]]) return d +def load_goodreads_shelf_into_wishlist(user, shelf_name='all', goodreads_user_id=None, max_books=None): + """ + Load a specified Goodreads shelf (by default: all the books from the Goodreads account associated with user) + """ + gc = GoodreadsClient(key=settings.GOODREADS_API_KEY, secret=settings.GOODREADS_API_SECRET) + + if goodreads_user_id is None: + if user.profile.goodreads_user_id is not None: + goodreads_user_id = user.profile.goodreads_user_id + else: + raise Exception("No Goodreads user_id is associated with user.") + + for (i, review) in enumerate(islice(gc.review_list(goodreads_user_id,shelf=shelf_name),max_books)): + isbn = review["book"]["isbn10"] if review["book"]["isbn10"] is not None else review["book"]["isbn13"] + logger.info("%d %s %s %s ", i, review["book"]["title"], isbn, review["book"]["small_image_url"]) + try: + edition = bookloader.add_by_isbn(isbn) + # let's not trigger too much traffic to Google books for now + # regluit.core.tasks.add_related.delay(isbn) + user.wishlist.works.add(edition.work) + logger.info("Work with isbn %s added to wishlist.", isbn) + except Exception, e: + logger.info ("error adding ISBN %s: %s", isbn, e) + + diff --git a/core/management/commands/goodreads_load_books.py b/core/management/commands/goodreads_load_books.py index ec91283b..e0c5c5ec 100644 --- a/core/management/commands/goodreads_load_books.py +++ b/core/management/commands/goodreads_load_books.py @@ -3,7 +3,9 @@ from regluit.core.goodreads import GoodreadsClient from regluit.core import tasks, bookloader from django.conf import settings from django.contrib.auth.models import User -from itertools import islice + +#from regluit.core.goodreads import load_shelf_into_wishlist +from regluit.core import tasks class Command(BaseCommand): help = "list books on given user bookshelf" @@ -13,15 +15,5 @@ class Command(BaseCommand): user = User.objects.get(username=user_name) max_books = int(max_books) - gc = GoodreadsClient(key=settings.GOODREADS_API_KEY, secret=settings.GOODREADS_API_SECRET) - for (i, review) in enumerate(islice(gc.review_list(goodreads_user_id,shelf=shelf_name),max_books)): - isbn = review["book"]["isbn10"] if review["book"]["isbn10"] is not None else review["book"]["isbn13"] - print i, review["book"]["title"], isbn, review["book"]["small_image_url"] - try: - edition = bookloader.add_by_isbn(isbn) - # add related editions asynchronously - tasks.add_related.delay(isbn) - user.wishlist.works.add(edition.work) - except Exception, e: - print "error adding ISBN %s: %s" % (isbn, e) \ No newline at end of file + tasks.load_goodreads_shelf_into_wishlist.delay(user, shelf_name, goodreads_user_id, max_books) \ No newline at end of file diff --git a/core/tasks.py b/core/tasks.py index 1eb2c59d..ffdbb577 100644 --- a/core/tasks.py +++ b/core/tasks.py @@ -1,6 +1,6 @@ from celery.decorators import task - from regluit.core import bookloader +from regluit.core import goodreads @task def add_related(isbn): @@ -9,3 +9,8 @@ def add_related(isbn): @task def add_by_isbn(isbn): return bookloader.add_by_isbn(isbn) + +@task +def load_goodreads_shelf_into_wishlist(user, shelf_name='all', goodreads_user_id=None, max_books=None): + return goodreads.load_goodreads_shelf_into_wishlist(user,shelf_name,goodreads_user_id,max_books) + diff --git a/frontend/forms.py b/frontend/forms.py index d220141f..86d321b5 100644 --- a/frontend/forms.py +++ b/frontend/forms.py @@ -53,3 +53,8 @@ class CampaignPledgeForm(forms.Form): raise forms.ValidationError("Only one of pledge_amount and pre_approval can be non-zero.") return cleaned_data + +class GoodreadsShelfLoadingForm(forms.Form): + goodreads_shelf_name = forms.CharField(widget=forms.Select(choices=( + ('all','all'), + ))) diff --git a/frontend/templates/goodreads_display.html b/frontend/templates/goodreads_display.html index f1fc5267..8b0339e5 100644 --- a/frontend/templates/goodreads_display.html +++ b/frontend/templates/goodreads_display.html @@ -1,6 +1,37 @@ {% extends "base.html" %} + {% block extra_head %} - + {% endblock %} {% block title %}Goodreads{% endblock %} @@ -13,8 +44,17 @@

Goodreads

+

Click here for a test!

+
{% if user.profile.goodreads_user_id %}

Welcome, Goodreads user id# {{user.profile.goodreads_user_id}}. Your GR username is {{user.profile.goodreads_user_name}}. Want to see your Goodreads profile?

+
+ {{gr_shelf_load_form.as_p}} + +
+
+ +
{% else %}

We don't currently know your Goodreads user information.

Enable us to link to your Goodreads account.

@@ -38,7 +78,9 @@ {% endfor %} {% endif %} -

Clear the association with your Goodreads account by flushing your session.

+
+

+

Revoke access for Unglue.it at Goodreads user apps panel.

diff --git a/frontend/urls.py b/frontend/urls.py index 0702a20a..995b0141 100644 --- a/frontend/urls.py +++ b/frontend/urls.py @@ -23,5 +23,7 @@ urlpatterns = patterns( url(r"^campaigns/(?P\d+)/$",CampaignFormView.as_view(), name="campaign_by_id"), url(r"^goodreads/$", login_required(GoodreadsDisplayView.as_view()), name="goodreads_display"), url(r"^goodreads/auth_cb/$", "goodreads_cb", name="goodreads_cb"), - url(r"^goodreads/flush/$","goodreads_flush_session", name="goodreads_flush_session") + url(r"^goodreads/flush/$","goodreads_flush_assoc", name="goodreads_flush_assoc"), + url(r"^goodreads/load_shelf/$","goodreads_load_shelf", name="goodreads_load_shelf"), + url(r"^goodreads/clear_wishlist/$","clear_wishlist", name="clear_wishlist"), ) diff --git a/frontend/views.py b/frontend/views.py index c46f2c26..bc41f25b 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -5,6 +5,7 @@ from django.conf import settings from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.core.exceptions import ObjectDoesNotExist +from django.forms import Select from django.http import HttpResponseRedirect from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt @@ -21,8 +22,7 @@ from regluit.core import tasks from regluit.core import models, bookloader from regluit.core.search import gluejar_search from regluit.core.goodreads import GoodreadsClient -from regluit.frontend.forms import UserData, ProfileForm -from regluit.frontend.forms import CampaignPledgeForm +from regluit.frontend.forms import UserData, ProfileForm, CampaignPledgeForm, GoodreadsShelfLoadingForm from regluit.payment.manager import PaymentManager from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN @@ -239,8 +239,15 @@ class GoodreadsDisplayView(TemplateView): session['goodreads_request_token'] = request_token['oauth_token'] session['goodreads_request_secret'] = request_token['oauth_token_secret'] else: - context["shelves_info"] = gr_client.shelves_list(user_id=user.profile.goodreads_user_id) - context["reviews"] = list(islice(gr_client.review_list(user_id=user.profile.goodreads_user_id, per_page=50),50)) + gr_shelves = gr_client.shelves_list(user_id=user.profile.goodreads_user_id) + context["shelves_info"] = gr_shelves + gr_shelf_load_form = GoodreadsShelfLoadingForm() + # load the shelves into the form + choices = [('all','all (%d)' % (gr_shelves["total_book_count"]))] + [(s["name"],"%s (%d)" % (s["name"],s["book_count"])) for s in gr_shelves["user_shelves"]] + gr_shelf_load_form.fields['goodreads_shelf_name'].widget = Select(choices=tuple(choices)) + + context["gr_shelf_load_form"] = gr_shelf_load_form + #context["reviews"] = list(islice(gr_client.review_list(user_id=user.profile.goodreads_user_id, per_page=50),50)) return context @@ -275,8 +282,11 @@ def goodreads_cb(request): # redirect to the Goodreads display page -- should observe some next later return HttpResponseRedirect(reverse('goodreads_display')) - -def goodreads_flush_session(request): + +@require_POST +@login_required +@csrf_exempt +def goodreads_flush_assoc(request): user = request.user if user.is_authenticated(): profile = user.profile @@ -286,7 +296,29 @@ def goodreads_flush_session(request): profile.goodreads_auth_token = None profile.goodreads_auth_secret = None profile.save() - request.session.flush() - return HttpResponse("Your session and goodreads identity have been flushed. Go back to goodreads display" - % (reverse('goodreads_display'))) + return HttpResponseRedirect(reverse('goodreads_display')) +@require_POST +@login_required +@csrf_exempt +def goodreads_load_shelf(request): + """ + a view to allow user load goodreads shelf into her wishlist + """ + # Should be moved to the API + goodreads_shelf_name = request.POST.get('goodreads_shelf_name', 'all') + user = request.user + try: + logger.info('Adding task to load shelf %s to user %s', goodreads_shelf_name, user) + tasks.load_goodreads_shelf_into_wishlist.delay(user, goodreads_shelf_name) + return HttpResponse("Shelf loading placed on task queue.") + except Exception,e: + return HttpResponse("Error in loading shelf: %s " % (e)) + logger.info("Error in loading shelf: %s ", e) + +@require_POST +@login_required +@csrf_exempt +def clear_wishlist(request): + request.user.wishlist.clear() + \ No newline at end of file From 6f1ebb3247f758d16c37a5a20bbbad8508c6f50c Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Tue, 1 Nov 2011 09:50:05 -0700 Subject: [PATCH 4/9] Embedding api_key and username to get a functioning API call into goodreads_display.html --- frontend/templates/goodreads_display.html | 27 +++++++++-------------- frontend/views.py | 4 ++++ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/frontend/templates/goodreads_display.html b/frontend/templates/goodreads_display.html index 8b0339e5..ff1685b3 100644 --- a/frontend/templates/goodreads_display.html +++ b/frontend/templates/goodreads_display.html @@ -17,8 +17,8 @@ $.ajax({url:'/api/v1/wishlist/1/', data: {format:'json', - api_key:'71728c0a4f78fba45da3acb6833bf2f5fec4fbed', - username: 'RaymondYee'}, + api_key:'{{api_key}}', + username: '{{user.username}}'}, success: function(json) { $('#gr_status').html('

Number of books on your wishlist at ' + d.toUTCString() + @@ -47,7 +47,14 @@

Click here for a test!

{% if user.profile.goodreads_user_id %} -

Welcome, Goodreads user id# {{user.profile.goodreads_user_id}}. Your GR username is {{user.profile.goodreads_user_name}}. Want to see your Goodreads profile?

+

Welcome, Goodreads user id# {{user.profile.goodreads_user_id}}. Your GR username is {{user.profile.goodreads_user_name}} and here is your Goodreads profile.

+
+

+
+

You can revoke access for Unglue.it at Goodreads user apps panel.

+ +
+
{{gr_shelf_load_form.as_p}} @@ -59,16 +66,6 @@

We don't currently know your Goodreads user information.

Enable us to link to your Goodreads account.

{% endif %} - {% if shelves_info %} -

You have {{shelves_info.total}} shelves.

- {% if shelves_info.user_shelves %} -
    - {% for shelf in shelves_info.user_shelves %} -
  • {{shelf.name}}: {{shelf.book_count}} book(s)
  • - {% endfor %} -
- {% endif %} - {% endif %} {% if reviews %}

Here are some of your books that you've put on your Goodreads shelves:

@@ -78,10 +75,6 @@ {% endfor %} {% endif %} - -

-
-

Revoke access for Unglue.it at Goodreads user apps panel.

diff --git a/frontend/views.py b/frontend/views.py index bc41f25b..ebf7da6f 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -27,6 +27,7 @@ from regluit.payment.manager import PaymentManager from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN from regluit.core import goodreads +from tastypie.models import ApiKey logger = logging.getLogger(__name__) @@ -230,6 +231,9 @@ class GoodreadsDisplayView(TemplateView): gr_client = GoodreadsClient(key=settings.GOODREADS_API_KEY, secret=settings.GOODREADS_API_SECRET) user = self.request.user + if user.is_authenticated(): + api_key = ApiKey.objects.filter(user=user)[0].key + context['api_key'] = api_key if user.profile.goodreads_user_id is None: # calculate the Goodreads authorization URL From 90cfdb900726edc391f92a332d6c92eca5f966fa Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Tue, 1 Nov 2011 10:41:39 -0700 Subject: [PATCH 5/9] Added ability to clear wishlist --- frontend/templates/goodreads_display.html | 14 ++++++++------ frontend/views.py | 9 +++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/frontend/templates/goodreads_display.html b/frontend/templates/goodreads_display.html index ff1685b3..eb848733 100644 --- a/frontend/templates/goodreads_display.html +++ b/frontend/templates/goodreads_display.html @@ -21,14 +21,15 @@ username: '{{user.username}}'}, success: function(json) { - $('#gr_status').html('

Number of books on your wishlist at ' + d.toUTCString() + - " " + json["works"].length + '

'); + $('#gr_status').html('

Number of books on your wishlist: ' + + json["works"].length + " (" + d.toUTCString() + ')

'); }, error: function(jqXHR, textStatus, errorThrown) { alert('error') } }) }) + }); @@ -44,7 +45,10 @@

Goodreads

-

Click here for a test!

+

Click here to see how many books on your wishlist.

+
+ +
{% if user.profile.goodreads_user_id %}

Welcome, Goodreads user id# {{user.profile.goodreads_user_id}}. Your GR username is {{user.profile.goodreads_user_name}} and here is your Goodreads profile.

@@ -59,9 +63,7 @@ {{gr_shelf_load_form.as_p}} -
- -
+ {% else %}

We don't currently know your Goodreads user information.

Enable us to link to your Goodreads account.

diff --git a/frontend/views.py b/frontend/views.py index ebf7da6f..1bd70e02 100755 --- a/frontend/views.py +++ b/frontend/views.py @@ -322,7 +322,12 @@ def goodreads_load_shelf(request): @require_POST @login_required -@csrf_exempt +@csrf_exempt def clear_wishlist(request): - request.user.wishlist.clear() + try: + request.user.wishlist.works.clear() + return HttpResponse('wishlist cleared') + except Exception, e: + return HttpResponse("Error in clearing wishlist: %s " % (e)) + logger.info("Error in clearing wishlist: %s ", e) \ No newline at end of file From d6fc0b623069baceb35d5e710c684bdd8fb0f0e4 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Tue, 1 Nov 2011 14:09:04 -0700 Subject: [PATCH 6/9] Refining display of Goodreads import --- frontend/templates/goodreads_display.html | 52 ++++++++++++++--------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/frontend/templates/goodreads_display.html b/frontend/templates/goodreads_display.html index eb848733..a0384911 100644 --- a/frontend/templates/goodreads_display.html +++ b/frontend/templates/goodreads_display.html @@ -7,13 +7,7 @@ $('#test_click').click(function() { var d = new Date(); - - // $('

hello

').appendTo('#gr_status'); - - - // alert('hello'); - // call http://127.0.0.1:8082/api/v1/wishlist/1/?format=json&api_key=71728c0a4f78fba45da3acb6833bf2f5fec4fbed&username=RaymondYee and - // retrieve the works on the wishlist + // d.toUTCString() $.ajax({url:'/api/v1/wishlist/1/', data: {format:'json', @@ -21,8 +15,7 @@ username: '{{user.username}}'}, success: function(json) { - $('#gr_status').html('

Number of books on your wishlist: ' + - json["works"].length + " (" + d.toUTCString() + ')

'); + $('#number_of_books_on_wishlist').html(json["works"].length); }, error: function(jqXHR, textStatus, errorThrown) { alert('error') @@ -30,6 +23,20 @@ }) }) + // post to form_loc and alert with response + post_and_alert = function(form_loc){ + + return function() { + $.post(form_loc, {}, function(data) { + alert(data); + }); + + return false; + } + }; + + $('#clear_wishlist_form').submit(post_and_alert('{% url clear_wishlist %}')); + }); @@ -45,29 +52,34 @@

Goodreads

-

Click here to see how many books on your wishlist.

-
- -
-
+ {% if user.profile.goodreads_user_id %}

Welcome, Goodreads user id# {{user.profile.goodreads_user_id}}. Your GR username is {{user.profile.goodreads_user_name}} and here is your Goodreads profile.

-
-

+ +

-

You can revoke access for Unglue.it at Goodreads user apps panel.

+

You can also revoke access for Unglue.it at Goodreads user apps panel.

-
+
{{gr_shelf_load_form.as_p}} - +
{% else %}

We don't currently know your Goodreads user information.

-

Enable us to link to your Goodreads account.

+

Enable Unglue.it to link to your Goodreads account

{% endif %} + +
+ +

Click here to update the number of books on your wishlist:

+

Number of books on your wishlist: {{user.wishlist.books.all|length}}

+
+ +
+ {% if reviews %}

Here are some of your books that you've put on your Goodreads shelves:

From 6ddebadf87620e4096c3c7620779933d309bebf0 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Tue, 1 Nov 2011 15:23:54 -0700 Subject: [PATCH 7/9] Enough on goodreads for now. --- frontend/templates/goodreads_display.html | 58 +++++++++++++++-------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/frontend/templates/goodreads_display.html b/frontend/templates/goodreads_display.html index a0384911..ed28840d 100644 --- a/frontend/templates/goodreads_display.html +++ b/frontend/templates/goodreads_display.html @@ -5,37 +5,53 @@ jQuery(document).ready(function($) { - $('#test_click').click(function() { - var d = new Date(); - // d.toUTCString() + // post to form_loc and alert with response + var post_and_alert = function (form_loc){ + + return function(bubble,params) { + $.post(form_loc, params, function (data) { + alert(data); + }); + + return bubble; + } + }; + + + var update_number_of_books = function (){ $.ajax({url:'/api/v1/wishlist/1/', data: {format:'json', api_key:'{{api_key}}', username: '{{user.username}}'}, success: function(json) { - $('#number_of_books_on_wishlist').html(json["works"].length); + var d = new Date(); + $('#num_books_update_time').html(d.toUTCString()); }, error: function(jqXHR, textStatus, errorThrown) { alert('error') } - }) - }) + }) + + } + + // add a current date time to the display of current number of books on wishlist + var d = new Date() + $('#num_books_update_time').html(d.toUTCString()); + + $('#test_click').click(update_number_of_books); - // post to form_loc and alert with response - post_and_alert = function(form_loc){ - - return function() { - $.post(form_loc, {}, function(data) { - alert(data); - }); - - return false; - } - }; - - $('#clear_wishlist_form').submit(post_and_alert('{% url clear_wishlist %}')); + $('#clear_wishlist_form').submit(function (){ + post_and_alert('{% url clear_wishlist %}')(true,{}); + update_number_of_books(); + return false; + }); + + $('#load_shelf_form').submit(function(){ + post_and_alert('{% url goodreads_load_shelf%}')(false,$('#load_shelf_form').serialize()); + return false; + }); }); @@ -62,7 +78,7 @@
-
+ {{gr_shelf_load_form.as_p}}
@@ -75,7 +91,7 @@

Click here to update the number of books on your wishlist:

-

Number of books on your wishlist: {{user.wishlist.books.all|length}}

+

Number of books on your wishlist: {{user.wishlist.books.all|length}} (as of )

From 672b1b3b200f90c0064c3420fb488f2c326e71c7 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Tue, 1 Nov 2011 17:42:39 -0700 Subject: [PATCH 8/9] Implemented LibraryThing import as a command and separated out LT functionality into a separate module --- core/librarything.py | 67 +++++++++++++++++++ .../commands/librarything_load_books.py | 16 +++++ core/tasks.py | 6 +- my_librarything_books.py | 47 ------------- requirements.pip | 1 + 5 files changed, 89 insertions(+), 48 deletions(-) create mode 100644 core/librarything.py create mode 100644 core/management/commands/librarything_load_books.py delete mode 100644 my_librarything_books.py diff --git a/core/librarything.py b/core/librarything.py new file mode 100644 index 00000000..437b801c --- /dev/null +++ b/core/librarything.py @@ -0,0 +1,67 @@ +import mechanize +import csv +import HTMLParser +import logging +import re + +logger = logging.getLogger(__name__) + + +class LibraryThing(object): + """ + This class retrieves and parses the CSV representation of a LibraryThing user's library. + """ + url = "https://www.librarything.com" + csv_file_url = "http://www.librarything.com/export-csv" + + def __init__(self, username=None, password=None): + self.username = username + self.password = password + self.csv_handle = None + def retrieve_csv(self): + br = mechanize.Browser() + br.open(LibraryThing.url) + # select form#2 + br.select_form(nr=1) + br["formusername"] = self.username + br["formpassword"] = self.password + br.submit() + self.csv_handle = br.open(LibraryThing.csv_file_url) + return self.csv_handle + def parse_csv(self): + h = HTMLParser.HTMLParser() + reader = csv.DictReader(self.csv_handle) + # There are more fields to be parsed out. Note that there is a second author column to handle + for (i,row) in enumerate(reader): + # ISBNs are written like '[123456789x]' in the CSV, suggesting possibility of a list + m = re.match(r'^\[(.*)\]$', row["'ISBNs'"]) + if m: + isbn = m.group(1).split() + else: + isbn = [] + yield {'title':h.unescape(row["'TITLE'"]), 'author':h.unescape(row["'AUTHOR (first, last)'"]), + 'isbn':isbn, 'comment':row["'COMMENT'"], + 'tags':row["'TAGS'"], 'collections':row["'COLLECTIONS'"], + 'reviews':h.unescape(row["'REVIEWS'"])} + +def load_librarything_into_wishlist(user, lt_username, lt_password, max_books=None): + """ + Load a specified Goodreads shelf (by default: all the books from the Goodreads account associated with user) + """ + + from regluit.core import bookloader + from itertools import islice + + lt = LibraryThing(lt_username,lt_password) + lt.retrieve_csv() + for (i,book) in enumerate(islice(lt.parse_csv(),max_books)): + isbn = book["isbn"][0] # grab the first one + logger.info("%d %s %s", i, book["title"], isbn) + try: + edition = bookloader.add_by_isbn(isbn) + # let's not trigger too much traffic to Google books for now + # regluit.core.tasks.add_related.delay(isbn) + user.wishlist.works.add(edition.work) + logger.info("Work with isbn %s added to wishlist.", isbn) + except Exception, e: + logger.info ("error adding ISBN %s: %s", isbn, e) \ No newline at end of file diff --git a/core/management/commands/librarything_load_books.py b/core/management/commands/librarything_load_books.py new file mode 100644 index 00000000..3a7eb22a --- /dev/null +++ b/core/management/commands/librarything_load_books.py @@ -0,0 +1,16 @@ +from django.core.management.base import BaseCommand +from regluit.core import librarything +from regluit.core import tasks +from django.conf import settings +from django.contrib.auth.models import User + +class Command(BaseCommand): + help = "load Librarything books into wishlist" + args = "" + + def handle(self, user_name, lt_username, lt_password, max_books, **options): + + user = User.objects.get(username=user_name) + max_books = int(max_books) + + tasks.load_librarything_into_wishlist.delay(user, lt_username, lt_password, max_books) \ No newline at end of file diff --git a/core/tasks.py b/core/tasks.py index ffdbb577..f9cabfd6 100644 --- a/core/tasks.py +++ b/core/tasks.py @@ -1,6 +1,6 @@ from celery.decorators import task from regluit.core import bookloader -from regluit.core import goodreads +from regluit.core import goodreads, librarything @task def add_related(isbn): @@ -14,3 +14,7 @@ def add_by_isbn(isbn): def load_goodreads_shelf_into_wishlist(user, shelf_name='all', goodreads_user_id=None, max_books=None): return goodreads.load_goodreads_shelf_into_wishlist(user,shelf_name,goodreads_user_id,max_books) +@task +def load_librarything_into_wishlist(user, lt_username, lt_password, max_books=None): + return librarything.load_librarything_into_wishlist(user, lt_username, lt_password, max_books) + diff --git a/my_librarything_books.py b/my_librarything_books.py deleted file mode 100644 index af0d02ee..00000000 --- a/my_librarything_books.py +++ /dev/null @@ -1,47 +0,0 @@ -import mechanize -import csv -import HTMLParser -import argparse - -h = HTMLParser.HTMLParser() - -# parse options - -parser = argparse.ArgumentParser(description='Download and parse LibraryThing booklist.') -parser.add_argument('user', help='LibraryThing username') -parser.add_argument('password', help='LibraryThing password') - -args = parser.parse_args() - -USERNAME = args.user -PW = args.password - -LT_url = "https://www.librarything.com" -LT_csv_file_url = "http://www.librarything.com/export-csv" - -def retrieve_book_list(user,password): - br = mechanize.Browser() - br.open(LT_url) - # select 2 form - br.select_form(nr=1) - br["formusername"] = user - br["formpassword"] = password - br.submit() - - # get CSV file - response = br.open(LT_csv_file_url) - return response - -def parse_csv(f): - reader = csv.DictReader(f) - for (i,row) in enumerate(reader): - print i, h.unescape(row["'TITLE'"]), h.unescape(row["'AUTHOR (first, last)'"]), row["'ISBNs'"], row["'COMMENT'"], row["'TAGS'"], row["'COLLECTIONS'"], h.unescape(row["'REVIEWS'"]) - -if __name__ == '__main__': - dynamic = True - if dynamic: - f = retrieve_book_list(USERNAME, PW) - else: - fname = "/Users/raymondyee/Downloads/LibraryThing_export.csv" - f = open(fname,"rb") - parse_csv(f) \ No newline at end of file diff --git a/requirements.pip b/requirements.pip index 6acd64db..cb7d762d 100644 --- a/requirements.pip +++ b/requirements.pip @@ -13,3 +13,4 @@ django-kombu django-celery redis oauth2 +mechanize From ee097ffd9757da0aec99f5960cf6145909a04966 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Wed, 2 Nov 2011 16:42:36 -0700 Subject: [PATCH 9/9] Added changes to zotero proof of concept code --- requirements.pip | 1 + zotero_books.py | 35 +++++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/requirements.pip b/requirements.pip index cb7d762d..2811c196 100644 --- a/requirements.pip +++ b/requirements.pip @@ -14,3 +14,4 @@ django-celery redis oauth2 mechanize +pyzotero diff --git a/zotero_books.py b/zotero_books.py index f347d095..5a86a469 100644 --- a/zotero_books.py +++ b/zotero_books.py @@ -4,6 +4,8 @@ from itertools import islice from functools import partial from pyzotero.zotero import Zotero +import logging +logger = logging.getLogger(__name__) class Zotero2(Zotero): def __init__(self, user_id = None, user_key = None): @@ -80,21 +82,38 @@ class MyZotero(Zotero2): return item_set def get_all_items(self): print len(self.item_keys(5000,99)) - def get_all_books(self,max): + def get_books(self,max=10000): self.set_parameters(sort="type") items = self.items() - book_set = set() for (i, item) in enumerate(islice(items,max)): if item.get("itemType") == 'book': - print i, (item["group_id"], item["key"], item["title"], item.get("itemType"), item.get("ISBN", None)) - book_set.add((item["group_id"], item["key"], item["title"])) - print len(book_set) - return book_set + yield {'group_id':item["group_id"], 'key':item["key"], 'title':item["title"], + 'itemType':item.get("itemType"), 'isbn':item.get("ISBN", None)} + def upload_to_unglue_it(self,unglueit_user_name, max): + from regluit.core import bookloader + from django.contrib.auth.models import User + + user = User.objects.get(username=unglueit_user_name) + books = self.get_books(max=max) + for book in books: + try: + isbn = book['isbn'] + if isbn: + edition = bookloader.add_by_isbn(isbn) + # let's not trigger too much traffic to Google books for now + # regluit.core.tasks.add_related.delay(isbn) + user.wishlist.works.add(edition.work) + logger.info("Work with isbn %s added to wishlist.", isbn) + except Exception, e: + logger.info ("error adding ISBN %s: %s", isbn, e) + + - zot = MyZotero() #zot.compare_keys(24,7,3) to_unglue = list(zot.items_in_unglue_it_collection()) print len(to_unglue), [item["title"] for item in to_unglue] -zot.get_all_books(50) +for b in zot.get_books(50): + print b +zot.upload_to_unglue_it('RaymondYee',5000) #print zot.get_all_items() \ No newline at end of file