From d3aefba1652fe7d6b5ef56a992c9b5031916cb5a Mon Sep 17 00:00:00 2001 From: sanj Date: Tue, 2 Mar 2010 22:47:02 +0530 Subject: [PATCH] added files --- index/README.txt | 22 ++ index/__init__.py | 0 index/books/__init__.py | 0 index/books/admin.py | 80 +++++ index/books/fields.py | 58 ++++ index/books/isbnlookup.py | 226 +++++++++++++ index/books/isbnlookup2.py | 418 +++++++++++++++++++++++++ index/books/isbnlookup2.py.THIS | 380 ++++++++++++++++++++++ index/books/isbnvalidate.py | 166 ++++++++++ index/books/models.py | 228 ++++++++++++++ index/books/myfields.py | 15 + index/books/thumbs.py | 164 ++++++++++ index/books/views.py | 183 +++++++++++ index/catalogues/__init__.py | 0 index/catalogues/admin.py | 10 + index/catalogues/models.py | 24 ++ index/catalogues/views.py | 8 + index/dropandcreatedb.py | 20 ++ index/harddrives/__init__.py | 0 index/harddrives/admin.py | 13 + index/harddrives/importFromTxtFiles.py | 53 ++++ index/harddrives/models.py | 20 ++ index/harddrives/views.py | 1 + index/hardware/__init__.py | 0 index/hardware/admin.py | 12 + index/hardware/models.py | 50 +++ index/hardware/views.py | 8 + index/manage.py | 11 + index/media/css/base.css | 57 ++++ index/media/css/form.css | 151 +++++++++ index/media/images/loading.gif | Bin 0 -> 847 bytes index/media/images/plus.gif | Bin 0 -> 119 bytes index/media/js/bookform.js | 146 +++++++++ index/media/js/formset.js | 192 ++++++++++++ index/media/js/jquery.js | 19 ++ index/settings.py | 92 ++++++ index/templates/addbook.html | 171 ++++++++++ index/templates/base.html | 17 + index/templates/index.html | 20 ++ index/templates/login.html | 30 ++ index/templates/logout.html | 6 + index/templates/site_base.html | 46 +++ index/urls.py | 31 ++ 43 files changed, 3148 insertions(+) create mode 100755 index/README.txt create mode 100755 index/__init__.py create mode 100755 index/books/__init__.py create mode 100755 index/books/admin.py create mode 100755 index/books/fields.py create mode 100755 index/books/isbnlookup.py create mode 100755 index/books/isbnlookup2.py create mode 100644 index/books/isbnlookup2.py.THIS create mode 100755 index/books/isbnvalidate.py create mode 100755 index/books/models.py create mode 100755 index/books/myfields.py create mode 100755 index/books/thumbs.py create mode 100755 index/books/views.py create mode 100755 index/catalogues/__init__.py create mode 100755 index/catalogues/admin.py create mode 100755 index/catalogues/models.py create mode 100755 index/catalogues/views.py create mode 100755 index/dropandcreatedb.py create mode 100644 index/harddrives/__init__.py create mode 100755 index/harddrives/admin.py create mode 100644 index/harddrives/importFromTxtFiles.py create mode 100644 index/harddrives/models.py create mode 100644 index/harddrives/views.py create mode 100755 index/hardware/__init__.py create mode 100755 index/hardware/admin.py create mode 100644 index/hardware/models.py create mode 100755 index/hardware/views.py create mode 100755 index/manage.py create mode 100755 index/media/css/base.css create mode 100755 index/media/css/form.css create mode 100755 index/media/images/loading.gif create mode 100755 index/media/images/plus.gif create mode 100755 index/media/js/bookform.js create mode 100644 index/media/js/formset.js create mode 100755 index/media/js/jquery.js create mode 100644 index/settings.py create mode 100644 index/templates/addbook.html create mode 100755 index/templates/base.html create mode 100755 index/templates/index.html create mode 100755 index/templates/login.html create mode 100755 index/templates/logout.html create mode 100755 index/templates/site_base.html create mode 100755 index/urls.py diff --git a/index/README.txt b/index/README.txt new file mode 100755 index 0000000..a0151b5 --- /dev/null +++ b/index/README.txt @@ -0,0 +1,22 @@ +Settings assumes mysql db name of 'idx' - change if your db is something else + +Requires django-tagging from svn: +svn checkout http://django-tagging.googlecode.com/svn/trunk/ tagging-trunk + +Requires python-dateutil +Requires elementtree: sudo easy_install elementtree +Requires simplejson: sudo easy_install simplejson + +Setup DB: +mysql -u root +mysql> create database idx; +mysql> quit; + +Create tables: +python manage.py syncdb (or python dropandcreatedb.py) + +Run Server: +python manage.py runserver + +Open Browser: +http://localhost:8000 diff --git a/index/__init__.py b/index/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/index/books/__init__.py b/index/books/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/index/books/admin.py b/index/books/admin.py new file mode 100755 index 0000000..1919b40 --- /dev/null +++ b/index/books/admin.py @@ -0,0 +1,80 @@ +from books.models import * +from django.contrib import admin +from django.contrib.auth.models import User + +def showCategories(obj): + cats = obj.categories.iterator() + s = '' + for c in cats: + s += c.name + " " + return s + +def labelID(obj): + s = '' + if obj.box: + try: + loc = Location.objects.get(pk=obj.box.id) + s += loc.code + "/" + except: + s += "" + s += obj.box.box_no + "/" +# if obj.box.location: +# s += obj.box.location.code + "/" + s += str(obj.id) + return s + +class BookImageInline(admin.TabularInline): + model = Image + extra = 3 + +class BookAdmin(admin.ModelAdmin): + ordering = ('title',) + search_fields = ['title', 'description'] + inlines = [BookImageInline] + save_on_top = True; + +class HardwareAdmin(admin.ModelAdmin): + ordering = ('-id',) + list_display = ('title', labelID, 'box', 'category', 'tags',) + list_filter = ('category', 'box', 'tags',) + list_editable = ('box', 'tags',) + search_fields = ['title', 'description',] + save_as = True + save_on_top = True + radio_fields = {'category': admin.HORIZONTAL} + +class HardwareInline(admin.StackedInline): + model = Hardware + extra = 5 + save_on_top = True; + +class BoxAdmin(admin.ModelAdmin): + ordering = ('-id',) + search_fields = ['box_no', 'description'] + list_display = ('box_no', 'location', 'tags',) + inlines = [HardwareInline] + list_filter = ('location',) + list_editable = ('location', 'tags',) + save_on_top = True; + +class CatalogueAdmin(admin.ModelAdmin): + ordering = ('title',) + search_fields = ['title', 'artist'] + save_on_top = True; + +class Link(admin.ModelAdmin): + pass + +class HardwareCategoryAdmin(admin.ModelAdmin): + pass + +admin.site.register(Hardware, HardwareAdmin) +admin.site.register(Box, BoxAdmin) +admin.site.register(HardwareCategory, HardwareCategoryAdmin) +#admin.site.register(Link) +admin.site.register(HardwareLink) +admin.site.register(Catalogue, CatalogueAdmin) +admin.site.register(Book, BookAdmin) +admin.site.register(Owner) +admin.site.register(Location) + diff --git a/index/books/fields.py b/index/books/fields.py new file mode 100755 index 0000000..e152fb7 --- /dev/null +++ b/index/books/fields.py @@ -0,0 +1,58 @@ +from django.db import models +from django import forms +from django.utils.text import capfirst + +class MultiSelectFormField(forms.MultipleChoiceField): + widget = forms.CheckboxSelectMultiple + + def __init__(self, *args, **kwargs): + self.max_choices = kwargs.pop('max_choices', 0) + super(MultiSelectFormField, self).__init__(*args, **kwargs) + + def clean(self, value): + if not value and self.required: + raise forms.ValidationError(self.error_messages['required']) + if value and self.max_choices and len(value) > self.max_choices: + raise forms.ValidationError('You must select a maximum of %s choice%s.' + % (apnumber(self.max_choices), pluralize(self.max_choices))) + return value + +class MultiSelectField(models.Field): + __metaclass__ = models.SubfieldBase + + def get_internal_type(self): + return "CharField" + + def get_choices_default(self): + return self.get_choices(include_blank=False) + + def _get_FIELD_display(self, field): + value = getattr(self, field.attname) + choicedict = dict(field.choices) + + def formfield(self, **kwargs): + # don't call super, as that overrides default widget if it has choices + defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), + 'help_text': self.help_text, 'choices':self.choices} + if self.has_default(): + defaults['initial'] = self.get_default() + defaults.update(kwargs) + return MultiSelectFormField(**defaults) + + def get_db_prep_value(self, value): + if isinstance(value, basestring): + return value + elif isinstance(value, list): + return ",".join(value) + + def to_python(self, value): + if isinstance(value, list): + return value + return value.split(",") + + def contribute_to_class(self, cls, name): + super(MultiSelectField, self).contribute_to_class(cls, name) + if self.choices: + func = lambda self, fieldname = name, choicedict = dict(self.choices):",".join([choicedict.get(value,value) for value in getattr(self,fieldname)]) + setattr(cls, 'get_%s_display' % self.name, func) + diff --git a/index/books/isbnlookup.py b/index/books/isbnlookup.py new file mode 100755 index 0000000..83e0fbd --- /dev/null +++ b/index/books/isbnlookup.py @@ -0,0 +1,226 @@ +#!/usr/bin/python + +import sys +import types +import time +import string +import fileinput +import re +import urllib +import isbnvalidate +from dateutil.parser import parse as parseDate import elementtree.ElementTree as ET +import feedparser +import simplejson + + +def myDate(datestr): + dateObj = parseDate(datestr) + month = dateObj.month + yr = dateObj.year + d = { + 'year': yr, + 'month': month + } + return d + +def getHtmlFromUrl(url): + try: + u = urllib.urlopen(url) + t = u.read() + u.close + except: + return False + return t + +def getimagesrc(isbn): + if (len(isbn) == 13): + newisbn = isbnvalidate.convertISBN13toISBN10(isbn) + isbn = newisbn.strip() + + inf = urllib.urlopen('http://www.amazon.com/gp/product/images/' + isbn) + allhtml = inf.read() + inf.close() + imghtml = re.search('
', allhtml) + + imgsrc1 = imghtml.group(1) + return imgsrc1 + + + +#TODO: Try avoiding wrapping entire search in 'try'. +def ChaptersCa(isbn): + url = 'http://www.chapters.ca/books/details/default.asp?ISBN=' + isbn + html = getHtmlFromUrl(url) + if not html: + return False + if (string.find(html, 'chapters.indigo.ca: Error:') >= 0): + return False + titlehtml = re.search('

(.*?)

', html) + authorhtml = re.search('', html) + dimensionshtml = re.search('(.*?)

', html) + publisherhtml = re.search('(.*?)

', html) + publishedhtml = re.search('(.*?)

', html) + #print (titlehtml, authorhtml, dimensionshtml, publisherhtml, publishedhtml) + try: + authorlinks = authorhtml.group(1).strip() + authors = re.findall('
(.*?)', authorlinks) # returns list of authors + + for a in authors: + if re.search('See more ', a): + tmp = a + authors.remove(a) + d = {} + + d['source'] = "ChaptersCa" + d['pages'] = dimensionshtml.group(1).split(",")[0].strip() + d['dimensions'] = dimensionshtml.group(1).split(",")[1].strip() + # dimensions = dimensionshtml.group(1).strip() + d['publisher'] = publisherhtml.group(1).strip() + d['published'] = myDate(publishedhtml.group(1).strip()) + d['title'] = titlehtml.group(1).strip() + d['coverimg'] = getimagesrc(isbn) + d['url'] = url + d['authors'] = authors + except: + return False + return d + +#TODO: Regex failing. Test. +def Amazon(isbn): + url = 'http://www.amazon.com/gp/search/ref=sr_adv_b/?field-isbn=' + isbn + html = getHtmlFromUrl(url) + if not html: + return False + if (string.find(html, 'Your search did not match any products.') >= 0): + return False + titlehtml = re.search('(.*?)', html) + paperbackhtml = re.search('
  • Paperback:(.*?)
  • ', html) + weighthtml = re.search('
  • Shipping Weight:(.*?)\(.*?\)
  • ', html) + publisherhtml = re.search('
  • Publisher:(.*?) \((.*?)\)
  • ', html) + authors = re.findall('(.*?) \(Author\)', html) # returns list of authors + try: + d = {} + d['source'] = 'Amazon' + d['pages'] = paperbackhtml.group(1).strip() + d['dimensions'] = weighthtml.group(1).strip() + d['publisher'] = publisherhtml.group(1).strip() + d['published'] = myDate(publisherhtml.group(2).strip()) + d['title'] = titlehtml.group(1).strip() + d['coverimg'] = getimagesrc(isbn) + d['url'] = url + except: + return False +# sourceurl = 'http://www.amazon.com/gp/search/ref=sr_adv_b/?field-isbn=' + isbn + return d + +#TODO: Dont return False if a single key does not exist +def ISBNdb(isbn): + ISBNdb_KEY = 'HLRQD9WN' +# http://isbndb.com/api/books.xml?access_key=HLRQD9WN&index1=isbn&value1=1400082463 + url = 'http://isbndb.com/api/books.xml?access_key=' + ISBNdb_KEY + '&index1=isbn&value1=' + isbn + try: + xml = getHtmlFromUrl(url) + tree = ET.XML(xml) + except: + return False + results = tree.find("BookList").get("total_results") + if (results < 1): + return False + d = {} + d['source'] = 'ISBNdb' + try: + book = tree.getchildren()[0].getchildren()[0] + d['title'] = book.findtext("Title") + titleLong = book.findtext("TitleLong").strip() + if (titleLong != ''): + d['title'] = titleLong + d['publisher'] = book.findtext("PublisherText") + d['summary'] = book.findtext("Summary") + d['notes'] = book.findtext("Notes") + authorsTxt = book.findtext("AuthorsText") + d['authors'] = authorsTxt.split(",") + except: + return False + return d + +#TODO: Fetch weblinks. +#TODO: Clean-up to not go to exception when can't find a single attribute +#TODO: Go through XML and fetch more relevant fields. +#TODO: Make URL more specific to look for ISBN codes. [IMP!!] +def GoogleBooks(isbn): + url = 'http://books.google.com/books/feeds/volumes?q=' + isbn + try: + feed = feedparser.parse(url) + book = feed.entries[0] + except: + return False + try: + d = {} + d['source'] = 'GoogleBooks' + d['title'] = book.title + # d['summary'] = book.description + d['pages'] = book.dc_format + d['author'] = [book.author] + d['publisher'] = book.publisher + except: + return False + return d + +#TODO: Don't return book! Return dict according to our fields. +#TODO: Fetch Chapters in a meaningful way +#TODO: Add relevant fields - first_sentence, etc. +def OpenLibrary(isbn): + if (len(isbn) == 10): + url1 = 'http://openlibrary.org/api/things?query={%22type%22%3A+%22\%2Ftype\%2Fedition%22%2C+%22isbn_10%22%3A+%22' + isbn + '%22}&prettyprint=true&text=true' + elif (len(isbn) == 13): + url1 = 'http://openlibrary.org/api/things?query={%22type%22%3A+%22\%2Ftype\%2Fedition%22%2C+%22isbn_13%22%3A+%22' + isbn + '%22}&prettyprint=true&text=true' + else: + return False + json = getHtmlFromUrl(url1) + print json + if not json: + return False + j = simplejson.loads(json) + if (j['status'] != 'ok' or len(j['result']) == 0): + return False + olId = j['result'][0] + url2 = "http://openlibrary.org/api/get?key=" + olId + json2 = getHtmlFromUrl(url2) + book = simplejson.loads(json2) + return book + +#TODO: write regex parser for LookUpByIsbn to return esp. Reviews +def LookUpByIsbn(isbn): + url = "http://www.lookupbyisbn.com/itemDetail.aspx?type=Books&id=" + isbn + return False + + +def searchAll(isbn, **kwargs): + sources = [OpenLibrary, GoogleBooks, ISBNdb, Amazon, ChaptersCa] + return getAggregate(sources, isbn) + +#TODO: Handle specific cases of 'author' and 'weblink' fields. Also, other lists, like Chapters. +def getAggregate(sources, isbn, **kwargs): + if not isbnvalidate.isValidISBN(isbn): + return {'errors': "Invalid ISBN"} +# if kwargs['isbnformat'] == 10 and len(isbn) == 13: +# isbn = isbnvalidate.convertISBN13toISBN10(isbn) +# if kwargs['isbnformat'] == 13 and len(isbn) == 10: +# isbn = isbnvalidate.convertISBN10toI3(isbn) + + results = [] + for source in sources: + rDict = source(isbn) + if (rDict): + results.append(rDict) + dFinal = {} + for result in results: + for key in r.keys(): + if (key not in dFinal.keys()): + dFinal[key] = result[key] + return dFinal + + + + + diff --git a/index/books/isbnlookup2.py b/index/books/isbnlookup2.py new file mode 100755 index 0000000..7436ce4 --- /dev/null +++ b/index/books/isbnlookup2.py @@ -0,0 +1,418 @@ +#!/usr/bin/python +# Usage: searchAll(isbn) with isbn passed as a string. +# Returns values defined in returnDict + +import sys +import types +import time +import string +import fileinput +import re +import urllib +import isbnvalidate +from dateutil.parser import parse as parseDate import elementtree.ElementTree as ET +import feedparser +import simplejson + +# ISBNlist = [ '9780596001322', '9781590593912', '9780596003135', '0596102062', '0131103628', '1905789041', '1400082463' ] + +def searchAll(isbn, **kwargs): + sources = [OpenLibrary, GoogleBooks, ISBNdb, Amazon, ChaptersCa] +# sources = [Amazon] + return getAggregate(sources, isbn) + +def getAggregate(sources, isbn, **kwargs): + if not isbnvalidate.isValidISBN(isbn): + return {'errors': "Invalid ISBN"} + returnDict = { + 'title': u'', + 'titleLong': u'', + 'authors': [], + 'weblinks': [], + 'published': {}, + 'publisher': u'', + 'isbn': u'', + 'first_sentence': u'', + 'chapters': [], + 'netReviews': [], + 'summaries': [], + 'pages': 0, + 'dimensions': u'', + 'weight': 0, + 'relatedBooks': [], + 'tags': [], + 'sources': [], + 'imageUrls': [], + 'coverImage': u'' + } +# if kwargs['isbnformat'] == 10 and len(isbn) == 13: +# isbn = isbnvalidate.convertISBN13toISBN10(isbn) +# if kwargs['isbnformat'] == 13 and len(isbn) == 10: +# isbn = isbnvalidate.convertISBN10toI3(isbn) + results = [] + for source in sources: + rDict = source(isbn) + if (rDict): + results.append(rDict) + lists = [] +# lists = ['authors', 'weblinks', 'chapters', 'netReviews', 'summaries', 'relatedBooks', 'source'] + for result in results: + for key in result.keys(): + if returnDict.has_key(key): + if isinstance(result[key], list): + if key not in lists: + lists.append(key) + for r in result[key]: + returnDict[key].append(r) + else: + returnDict[key] = result[key] + for l in lists: + if len(returnDict[l]) > 1: + returnDict[l] = unique(returnDict[l]) + returnDict['isbn'] = isbn + returnDict['coverImage'] = getCoverImageUrl(isbn) + + return returnDict + + +def ChaptersCa(isbn): + url = 'http://www.chapters.ca/books/details/default.asp?ISBN=' + isbn + html = getHtmlFromUrl(url) + if not html: + return False + if (string.find(html, 'chapters.indigo.ca: Error:') >= 0): + return False + titlehtml = re.search('

    (.*?)

    ', html) + authorhtml = re.search('', html) + dimensionshtml = re.search('(.*?)

    ', html) + publisherhtml = re.search('(.*?)

    ', html) + publishedhtml = re.search('(.*?)

    ', html) + + #print (titlehtml, authorhtml, dimensionshtml, publisherhtml, publishedhtml) + try: + authorlinks = authorhtml.group(1).strip() + authors = re.findall('
    (.*?)', authorlinks) # returns list of authors + i = 0 + for a in authors: + if re.search('See more ', a): + authors.remove(a) + else: + authors[i] = a.strip() + if (authors[i] == ''): + authors.remove(a) + i += 1 + except: + authors = [] + + d = {} + d['sources'] = [{'name': 'ChaptersCa', 'url': url}] + + try: + d['pages'] = dimensionshtml.group(1).split(",")[0].strip() + d['dimensions'] = dimensionshtml.group(1).split(",")[1].strip() + except: + pass +# dimensions = dimensionshtml.group(1).strip() + try: + d['publisher'] = publisherhtml.group(1).strip() + except: + pass + + try: + d['published'] = myDate(publishedhtml.group(1).strip()) + except: + pass + + try: + d['title'] = titlehtml.group(1).strip() + except: + pass + + try: + d['imageUrls'] = [getimagesrc(isbn)] + except: + pass + + d['authors'] = authors + return d + +def Amazon(isbn): + url = 'http://www.amazon.com/gp/search/ref=sr_adv_b/?field-isbn=' + isbn + html = getHtmlFromUrl(url) + if not html: + return False + if (string.find(html, 'Your search did not match any products.') >= 0): + return False + titlehtml = re.search('(.*?)', html) + paperbackhtml = re.search('
  • Paperback:(.*?)
  • ', html) + weighthtml = re.search('
  • Shipping Weight:(.*?)\(.*?\)
  • ', html) + publisherhtml = re.search('
  • Publisher:(.*?) \((.*?)\)
  • ', html) + authors = re.findall('(.*?) \(Author\)', html) # returns list of authors + d = {} + d['sources'] = [{'name': 'Amazon', 'url': url}] + try: + d['pages'] = paperbackhtml.group(1).strip() + except: + pass + try: + d['dimensions'] = weighthtml.group(1).strip() + except: + pass + try: + d['publisher'] = publisherhtml.group(1).strip() + except: + pass + try: + d['published'] = myDate(publisherhtml.group(2).strip()) + except: + pass + try: + d['title'] = titlehtml.group(1).strip() + except: + pass +# sourceurl = 'http://www.amazon.com/gp/search/ref=sr_adv_b/?field-isbn=' + isbn + + d['authors'] = authors + + return d + + +def ISBNdb(isbn): + ISBNdb_KEY = 'HLRQD9WN' +# http://isbndb.com/api/books.xml?access_key=HLRQD9WN&index1=isbn&value1=1400082463 + url = 'http://isbndb.com/api/books.xml?access_key=' + ISBNdb_KEY + '&index1=isbn&value1=' + isbn + try: + xml = getHtmlFromUrl(url) + tree = ET.XML(xml) + except: + return False + results = tree.find("BookList").get("total_results") + if (results < 1): + return False + d = {} + d['sources'] = [{'name': 'ISBNdb', 'url': url}] + try: + book = tree.getchildren()[0].getchildren()[0] + except: + return False + + try: + d['titleLong'] = book.findtext("TitleLong").strip() + except: + pass + + try: + d['title'] = book.findtext("Title") + except: + pass + + try: + d['publisher'] = book.findtext("PublisherText") + except: + pass + + try: + d['summaries'] = [book.findtext("Summary")] + except: + pass + + try: + d['notes'] = book.findtext("Notes") + except: + pass + + try: + authors = book.findtext("AuthorsText").split(",") + i = 0 + for a in authors: + if a.strip() == '': + authors.remove(a) + except: + pass + + return d + +#TODO: Make URL more specific to look for ISBN codes. [IMP!!] - Alden +def GoogleBooks(isbn): + url = 'http://books.google.com/books/feeds/volumes?q=' + isbn + try: + feed = feedparser.parse(url) + book = feed.entries[0] + except: + return False + d = {} + d['sources'] = [{'name': 'GoogleBooks', 'url': url}] + try: + d['title'] = book.title + except: + pass + try: + d['summaries'] = [book.dc_description] + except: + pass + try: + d['pages'] = book.dc_format + except: + pass + try: + d['author'] = [book.dc_creator] + except: + pass + try: + d['publisher'] = book.publisher + except: + pass + + try: + d['published'] = myDate(book.dc_date) + except: + pass + + try: + d['weblinks'] = [] + for link in book['links']: + l = {'url': link['href'], 'description': link['rel'] } + d['weblinks'].append(l) + except: + pass + + return d + +def OpenLibrary(isbn): + # 'http://openlibrary.org/api/things?query={%22type%22%3A+%22\%2Ftype\%2Fedition%22%2C+%22isbn_13%22%3A+%221400082463%22}&prettyprint=true&text=true' + if (len(isbn) == 10): + url1 = 'http://openlibrary.org/api/things?query={%22type%22%3A+%22\%2Ftype\%2Fedition%22%2C+%22isbn_10%22%3A+%22' + isbn + '%22}&prettyprint=true&text=true' + elif (len(isbn) == 13): + url1 = 'http://openlibrary.org/api/things?query={%22type%22%3A+%22\%2Ftype\%2Fedition%22%2C+%22isbn_13%22%3A+%22' + isbn + '%22}&prettyprint=true&text=true' + else: + return False + json = getHtmlFromUrl(url1) +# print json + if not json: + return False + j = simplejson.loads(json) + if (j['status'] != 'ok' or len(j['result']) == 0): + return False + olId = j['result'][0] + url2 = "http://openlibrary.org/api/get?key=" + olId + + try: + json2 = getHtmlFromUrl(url2) + book = simplejson.loads(json2) + r = book['result'] + except: + return False + + d = {} + d['sources'] = [{'name': "OpenLibrary", 'url': url2}] + + try: + d['first_sentence'] = r['first_sentence']['value'] + except: + pass + + try: + d['pages'] = r['number_of_pages'] + except: + pass + + try: + d['dimensions'] = r['physical_dimensions'] + except: + pass + + try: + d['published'] = myDate(r['publish_date']) + except: + pass + + chapters = [] + try: + for chapter in r['table_of_contents']: + c = {} + c['title'] = chapter['title'] + c['synopsis'] = '' + c['link'] = '' + chapters.append(c) + d['chapters'] = chapters + except: + pass + + try: + d['weight'] = r['weight'] + except: + pass + + tags = [] + try: + for tag in r['subjects']: + tags.append(tag) + d['tags'] = tags + except: + pass + + try: + d['titleLong'] = r['subtitle'] + except: + pass + + return d + +#TODO: write regex parser for LookUpByIsbn to return esp. Reviews. - Alden (low priority) +def LookUpByIsbn(isbn): + url = "http://www.lookupbyisbn.com/itemDetail.aspx?type=Books&id=" + isbn + return False + +#TODO: Write regex parser for CB-India computer books. -Alden (low priority) +def CBIndia(isbn): + return False + +#TODO: This seems like a fairly large collection of Indian books +def FirstAndSecond(isbn): + url = "http://www.firstandsecond.com/store/books/info/search.asp?styp=isb&isb=" + isbn + return False + + +def myDate(datestr): + dateObj = parseDate(datestr) + month = dateObj.month + yr = dateObj.year + day = dateObj.day + d = { + 'year': yr, + 'month': month, + 'day': day + } + return d + + +def getHtmlFromUrl(url): + try: + u = urllib.urlopen(url) + t = u.read() + u.close + except: + return False + return t + +def getCoverImageUrl(isbn): + if (len(isbn) == 13): + newisbn = isbnvalidate.convertISBN13toISBN10(isbn) + isbn = newisbn.strip() + try: + inf = urllib.urlopen('http://www.amazon.com/gp/product/images/' + isbn) + allhtml = inf.read() + inf.close() + imghtml = re.search('
    ', allhtml) + imgsrc1 = imghtml.group(1) + except: +# print "Unable to retrieve book cover image" + return '' + return imgsrc1 + +def unique(seq): + checked = [] + for e in seq: + if e not in checked: + checked.append(e) + return checked diff --git a/index/books/isbnlookup2.py.THIS b/index/books/isbnlookup2.py.THIS new file mode 100644 index 0000000..2005895 --- /dev/null +++ b/index/books/isbnlookup2.py.THIS @@ -0,0 +1,380 @@ +#!/usr/bin/python +# (c) CAMP - http://camputer.org +# Licensed GPLv3 + +import sys +import types +import time +import string +import fileinput +import re +import urllib +import isbnvalidate +from dateutil.parser import parse as parseDate import elementtree.ElementTree as ET +import feedparser +import simplejson + +#ISBNlist = [ '9780596001322', '9781590593912', '9780596003135', '0596102062', '0131103628', '1905789041', '1400082463' ] + +returnDict = { + 'title': u'', + 'titleLong': u'', + 'authors': [], + 'weblinks': [], + 'published': {}, + 'publisher': u'', + 'isbn': u'', + 'first_sentence': u'', + 'chapters': [], + 'netReviews': [], + 'summaries': [], + 'pages': 0, + 'dimensions': u'', + 'weight': 0, + 'relatedBooks': [], + 'tags': [], + 'source': [], + 'imageUrls': [] + } + +def searchAll(isbn, **kwargs): + sources = [OpenLibrary, ISBNdb, Amazon, ChaptersCa] + return getAggregate(sources, isbn) + +#TODO: Author field sometimes has a space. +def getAggregate(sources, isbn, **kwargs): + if not isbnvalidate.isValidISBN(isbn): + return {'errors': "Invalid ISBN"} +# if kwargs['isbnformat'] == 10 and len(isbn) == 13: +# isbn = isbnvalidate.convertISBN13toISBN10(isbn) +# if kwargs['isbnformat'] == 13 and len(isbn) == 10: +# isbn = isbnvalidate.convertISBN10toI3(isbn) + results = [] + for source in sources: + rDict = source(isbn) + if (rDict): + results.append(rDict) + lists = [] +# lists = ['authors', 'weblinks', 'chapters', 'netReviews', 'summaries', 'relatedBooks', 'source'] + for result in results: + for key in result.keys(): + if returnDict.has_key(key): + if isinstance(result[key], list): + if key not in lists: + lists.append(key) + for r in result[key]: + if r: + returnDict[key].append(r) + else: + returnDict[key] = result[key] + for l in lists: + if len(returnDict[l]) > 1: + returnDict[l] = unique(returnDict[l]) + returnDict['isbn'] = isbn + return returnDict + + +def ChaptersCa(isbn): + url = 'http://www.chapters.ca/books/details/default.asp?ISBN=' + isbn + html = getHtmlFromUrl(url) + if not html: + return False + if (string.find(html, 'chapters.indigo.ca: Error:') >= 0): + return False + titlehtml = re.search('

    (.*?)

    ', html) + authorhtml = re.search('', html) + dimensionshtml = re.search('(.*?)

    ', html) + publisherhtml = re.search('(.*?)

    ', html) + publishedhtml = re.search('(.*?)

    ', html) + + #print (titlehtml, authorhtml, dimensionshtml, publisherhtml, publishedhtml) + try: + authorlinks = authorhtml.group(1).strip() + authors = re.findall('
    (.*?)', authorlinks) # returns list of authors + for a in authors: + if re.search('See more ', a): + authors.remove(a) + except: + authors = [] + + d = {} + d['source'] = [{'name': 'ChaptersCa', 'url': url}] + + try: + d['pages'] = dimensionshtml.group(1).split(",")[0].strip() + d['dimensions'] = dimensionshtml.group(1).split(",")[1].strip() + except: + pass +# dimensions = dimensionshtml.group(1).strip() + try: + d['publisher'] = publisherhtml.group(1).strip() + except: + pass + + try: + d['published'] = myDate(publishedhtml.group(1).strip()) + except: + pass + + try: + d['title'] = titlehtml.group(1).strip() + except: + pass + + try: + d['coverimg'] = getimagesrc(isbn) + except: + pass + + d['authors'] = authors + return d + +#TODO: Regex failing. Test. - Alden +def Amazon(isbn): + url = 'http://www.amazon.com/gp/search/ref=sr_adv_b/?field-isbn=' + isbn + html = getHtmlFromUrl(url) + if not html: + return False + if (string.find(html, 'Your search did not match any products.') >= 0): + return False + titlehtml = re.search('(.*?)', html) + paperbackhtml = re.search('
  • Paperback:(.*?)
  • ', html) + weighthtml = re.search('
  • Shipping Weight:(.*?)\(.*?\)
  • ', html) + publisherhtml = re.search('
  • Publisher:(.*?) \((.*?)\)
  • ', html) + authors = re.findall('(.*?) \(Author\)', html) # returns list of authors + d = {} + d['source'] = [{'name': 'Amazon', 'url': url}] + try: + d['pages'] = paperbackhtml.group(1).strip() + except: + pass + try: + d['dimensions'] = weighthtml.group(1).strip() + except: + pass + try: + d['publisher'] = publisherhtml.group(1).strip() + except: + pass + try: + d['published'] = myDate(publisherhtml.group(2).strip()) + except: + pass + try: + d['title'] = titlehtml.group(1).strip() + except: + pass + try: + d['coverimg'] = getimagesrc(isbn) + except: + pass +# sourceurl = 'http://www.amazon.com/gp/search/ref=sr_adv_b/?field-isbn=' + isbn + return d + + +def ISBNdb(isbn): + ISBNdb_KEY = 'HLRQD9WN' +# http://isbndb.com/api/books.xml?access_key=HLRQD9WN&index1=isbn&value1=1400082463 + url = 'http://isbndb.com/api/books.xml?access_key=' + ISBNdb_KEY + '&index1=isbn&value1=' + isbn + try: + xml = getHtmlFromUrl(url) + tree = ET.XML(xml) + except: + return False + results = tree.find("BookList").get("total_results") + if (results < 1): + return False + d = {} + d['source'] = [{'name': 'ISBNdb', 'url': url}] + try: + book = tree.getchildren()[0].getchildren()[0] + except: + return False + + try: + d['titleLong'] = book.findtext("TitleLong").strip() + except: + pass + + try: + d['title'] = book.findtext("Title") + except: + pass + + try: + d['publisher'] = book.findtext("PublisherText") + except: + pass + + try: + d['summaries'] = [book.findtext("Summary")] + except: + pass + + try: + d['notes'] = book.findtext("Notes") + except: + pass + + try: + authorsTxt = book.findtext("AuthorsText") + d['authors'] = authorsTxt.split(",") + except: + pass + + return d + +#TODO: Fetch weblinks. - Sanj +#TODO: Go through XML and fetch more relevant fields. - Sanj +#TODO: Make URL more specific to look for ISBN codes. [IMP!!] - Alden +def GoogleBooks(isbn): + url = 'http://books.google.com/books/feeds/volumes?q=' + isbn + try: + feed = feedparser.parse(url) + book = feed.entries[0] + except: + return False + d = {} + d['source'] = [{'name': 'GoogleBooks', 'url': url}] + try: + d['title'] = book.title + except: + pass + # d['summary'] = book.description + try: + d['pages'] = book.dc_format + except: + pass + try: + d['author'] = [book.author] + except: + pass + try: + d['publisher'] = book.publisher + except: + pass + return d + +def OpenLibrary(isbn): + # 'http://openlibrary.org/api/things?query={%22type%22%3A+%22\%2Ftype\%2Fedition%22%2C+%22isbn_13%22%3A+%221400082463%22}&prettyprint=true&text=true' + if (len(isbn) == 10): + url1 = 'http://openlibrary.org/api/things?query={%22type%22%3A+%22\%2Ftype\%2Fedition%22%2C+%22isbn_10%22%3A+%22' + isbn + '%22}&prettyprint=true&text=true' + elif (len(isbn) == 13): + url1 = 'http://openlibrary.org/api/things?query={%22type%22%3A+%22\%2Ftype\%2Fedition%22%2C+%22isbn_13%22%3A+%22' + isbn + '%22}&prettyprint=true&text=true' + else: + return False + json = getHtmlFromUrl(url1) + print json + if not json: + return False + j = simplejson.loads(json) + if (j['status'] != 'ok' or len(j['result']) == 0): + return False + olId = j['result'][0] + url2 = "http://openlibrary.org/api/get?key=" + olId + try: + json2 = getHtmlFromUrl(url2) + book = simplejson.loads(json2) + r = book['result'] + except: + return False + d = {} + d['source'] = [{'name': "OpenLibrary", 'url': url2}] + try: + d['first_sentence'] = r['first_sentence']['value'] + except: + pass + + try: + d['pages'] = r['number_of_pages'] + except: + pass + + try: + d['dimensions'] = r['physical_dimensions'] + except: + pass + + try: + d['published'] = myDate(r['publish_date']) + except: + pass + + chapters = [] + try: + for chapter in r['table_of_contents']: + chapters.append(chapter['title']) + d['chapters'] = chapters + except: + pass + + try: + d['weight'] = r['weight'] + except: + pass + + tags = [] + try: + for tag in r['subjects']: + tags.append(tag) + d['tags'] = tags + except: + pass + + try: + d['titleLong'] = r['subtitle'] + except: + pass + + return d + +#TODO: write regex parser for LookUpByIsbn to return esp. Reviews. - Alden (low priority) +def LookUpByIsbn(isbn): + url = "http://www.lookupbyisbn.com/itemDetail.aspx?type=Books&id=" + isbn + return False + +#TODO: Write regex parser for CB-India computer books. -Alden (low priority) +def CBIndia(isbn): + return False + + +def myDate(datestr): + dateObj = parseDate(datestr) + month = dateObj.month + yr = dateObj.year + day = dateObj.day + d = { + 'year': yr, + 'month': month, + 'day': day + } + return d + +def getHtmlFromUrl(url): + try: + u = urllib.urlopen(url) + t = u.read() + u.close + except: + return False + return t + +def getimagesrc(isbn): + if (len(isbn) == 13): + newisbn = isbnvalidate.convertISBN13toISBN10(isbn) + isbn = newisbn.strip() + + inf = urllib.urlopen('http://www.amazon.com/gp/product/images/' + isbn) + allhtml = inf.read() + inf.close() + imghtml = re.search('
    ', allhtml) + + imgsrc1 = imghtml.group(1) + return imgsrc1 + +def unique(seq): + checked = [] + for e in seq: + if e not in checked: + checked.append(e) + return checked diff --git a/index/books/isbnvalidate.py b/index/books/isbnvalidate.py new file mode 100755 index 0000000..7ed2940 --- /dev/null +++ b/index/books/isbnvalidate.py @@ -0,0 +1,166 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Kirbi ISBN handling functions +""" + +class InvalidISBN(ValueError): + """This is not a valid ISBN-10 or ISBN-13""" + +def filterDigits(input): + """ Strip the input of all non-digits, but retain last X if present. """ + input = input.strip() + digits = [c for c in input if c.isdigit()] + # an X may appear as check digit in ISBN-10 + if input[-1].upper() == 'X': + digits.append('X') + return ''.join(digits) + +def checksumISBN10(digits): + """ Return ISBN-10 check digit as a string of len 1 (may be 0-9 or X) + References: + http://www.isbn-international.org/en/userman/chapter4.html#usm4_4 + http://www.bisg.org/isbn-13/conversions.html + """ + sum = 0 + for i, weight in enumerate(range(10,1,-1)): + sum += int(digits[i]) * weight + check = 11 - sum % 11 + if check == 10: return 'X' + elif check == 11: return '0' + return str(check) + +def checksumEAN(digits): + """ Return EAN check digit as a string (may be 0-9) + Every ISBN-13 is a valid EAN + Reference: + http://www.bisg.org/isbn-13/conversions.html + """ + sum = 0 + for i, d in enumerate(digits[:12]): + weight = i%2*2+1 + sum += int(d) * weight + check = 10 - sum % 10 + if check == 10: return '0' + return str(check) + +def validatedEAN(digits): + if not digits: + return None + if len(digits) != 13: + digits = filterDigits(digits) + if len(digits) != 13: + return None + if digits[-1] == checksumEAN(digits): + return digits + +def validatedISBN10(digits): + if not digits: + return None + if len(digits) != 10: + digits = filterDigits(digits) + if len(digits) != 10: + return None + if digits[-1] == checksumISBN10(digits): + return digits + +def validatedISBN13(digits): + if not digits: + return None + if digits.strip()[:3] not in ['978','979']: + return None + return validatedEAN(digits) + +def isValidISBN10(digits): + return validatedISBN10(digits) is not None + +def isValidISBN13(digits): + return validatedISBN13(digits) is not None + +def isValidEAN(digits): + return validatedEAN(digits) is not None + +def isValidISBN(digits): + return isValidISBN10(digits) or isValidISBN13(digits) + +def convertISBN10toISBN13(digits): + if len(digits) > 10: + digits = filterDigits(digits) + if len(digits) != 10: + raise InvalidISBN, '%s is not a valid ISBN-10' + else: + digits = '978' + digits[:-1] + return digits + checksumEAN(digits) + +def convertISBN13toISBN10(digits): + if len(digits) > 13: + digits = filterDigits(digits) + if len(digits) != 13: + raise InvalidISBN, '%s is not a valid ISBN-13' + if digits.startswith('978'): + digits = digits[3:-1] + return digits + checksumISBN10(digits) + elif digits.startswith('979'): + raise InvalidISBN, '%s is a valid ISBN-13 but has no ISBN-10 equivalent' + else: + raise InvalidISBN, '%s is not a valid ISBN-13 (wrong prefix)' + +def toISBN13(digits): + digits = filterDigits(digits) + if isValidISBN13(digits): return digits + else: + return convertISBN10toISBN13(digits) + +# Note: ISBN group identifiers related to languages +# http://www.isbn-international.org/en/identifiers/allidentifiers.html +# http://www.loc.gov/standards/iso639-2/php/code_list.php +lang_groups = { + 'en':(0,1),'fr':(2,),'de':(3,),'jp':(4,), 'ru':(5,), + 'es':(84, # Spain + 950, 987, # Argentina + 956, # Chile + 958, # Colombia + 959, # Cuba + 968, 970, # Mexico + 980, # Venezuela + 9942, 9978, # Ecuador + 9945, 99934, # Dominican Republic + 9962, # Panama + 9968, # Costa Rica (and 9977) + 9972, # Peru + 9974, # Uruguay + 99922, 99939, # Guatemala + 99923, # El Salvador + 99924, # Nicaragua + 99925, 99953, # Paraguay + 99926, # Honduras + ), + 'pt':(85, # Brazil + 972, 989, # Portugal + ), + } + +group_lang = {} + +for lang, groups in lang_groups.iteritems(): + for group in groups: + group_lang[str(group)] = lang + +def convertISBN13toLang(isbn13): + assert len(isbn13)==13 + registration_group_field = isbn13[3:8] + for i in range(1,6): + possible_group = registration_group_field[:i] + if possible_group in group_lang: + return group_lang[possible_group] + return None diff --git a/index/books/models.py b/index/books/models.py new file mode 100755 index 0000000..18040af --- /dev/null +++ b/index/books/models.py @@ -0,0 +1,228 @@ +from django.db import models +from thumbs import ImageWithThumbsField +from django.contrib.auth.models import User, Group +from tagging.fields import TagField +from tagging.models import Tag +from myfields import ISBNField +from books.fields import MultiSelectField, MultiSelectFormField + +class Book(models.Model): + title = models.CharField(max_length=255) + isbn = models.CharField(max_length=20, help_text='ISBN Code - digits only, no dashes, etc.', unique=True) + pages = models.CharField(max_length=100, blank=True) + weight = models.CharField(max_length=100, blank=True) + dimensions = models.CharField(max_length=120, blank=True, help_text='no. of pages and dimensions of book') + published = models.DateField(blank=True) + first_sentence = models.TextField(blank=True) + publisher = models.CharField(max_length=255, blank=True) + description = models.TextField(blank=True) + owner = models.ForeignKey('Owner', blank=True) + location = models.ForeignKey('Location', blank=True) + tags = TagField("Tags", blank=True, help_text="Enter as many tags as you like, separated by commas") + autotags = TagField("AutoTags", blank=True, help_text="Will be auto filled by lookup script") + authors = models.ManyToManyField('Author') + mainimg = ImageWithThumbsField(upload_to='images/uploads', blank=True, sizes=((125,125),(200,200)), height_field="image_height", width_field='image_width') + added_by = models.ForeignKey(User, editable=False) + date_added = models.DateTimeField(auto_now=True, editable=False) + + def __unicode__(self): + return self.title + +class ChapterEntry(models.Model): + title = models.TextField() + synopsis = models.TextField(blank=True) + link = models.URLField(max_length=255, blank=True) + book = models.ForeignKey(Book) + + def __unicode__(self): + return self.title + +class Weblink(models.Model): + url = models.URLField() + description = models.CharField(max_length=255, blank=True) + book = models.ForeignKey(Book) + + def __unicode__(self): + return self.url + +class Author(models.Model): + name = models.CharField(max_length=255) + bio = models.TextField(blank=True) + def __unicode__(self): + return self.name + +class WebReview(models.Model): + txt = models.TextField(blank=True) + book = models.ForeignKey(Book) + + def __unicode__(self): + return self.txt + +class Summary(models.Model): + txt = models.TextField(blank=True) + book = models.ForeignKey(Book) + + def __unicode__(self): + return self.txt + +class RelatedBook(models.Model): + url = models.URLField(blank=True) + title = models.CharField(max_length=255) + isbn = models.CharField(max_length=16, blank=True) + book = models.ForeignKey(Book) + + def __unicode__(self): + return self.title + +class ImageUrl(models.Model): + typ = models.CharField(max_length=255) + url = models.URLField(blank=True) + book = models.ForeignKey(Book) + + def __unicode__(self): + return self.url + +class Source(models.Model): + name = models.CharField(max_length=255) + url = models.URLField() + book = models.ForeignKey(Book) + + def __unicode__(self): + return self.name + +class Image(models.Model): + image = ImageWithThumbsField(upload_to='images/uploads', sizes=((125,125),(200,200)), height_field='height', width_field='width') + book = models.ForeignKey(Book) + typ = models.CharField(max_length=255) + + def __unicode__(self): + return self.image + +class Owner(models.Model): + name = models.CharField(max_length=100) + + def __unicode__(self): + return self.name + +class Location(models.Model): + name = models.CharField(max_length=255) + code = models.CharField(max_length=10, unique=True) + + def __unicode__(self): + return "%s (%s)" % (self.name, self.code) + + +##################################### CATALOGUES ###################################################### + + +class Catalogue(models.Model): + title = models.CharField(max_length=255) + artist = models.CharField(max_length=255, blank=True) + exhibition = models.CharField(max_length=255, blank=True) + published_date = models.DateField(blank=True, verbose_name="Published on") + condition = models.TextField(blank=True) + description = models.TextField(blank=True) + owner = models.ForeignKey('Owner', blank=True) + location = models.ForeignKey(Location, blank=True) + tags = TagField("Tags", blank=True, help_text="Enter as many tags as you like, separated by commas") + added_by = models.ForeignKey(User, editable=False) + date_added = models.DateTimeField(auto_now=True, editable=False) + def __unicode__(self): + return self.name + + class Meta: + verbose_name_plural = "Catalogues" + + +##################################### HARDWARE ###################################################### + +HARDWARE_CATEGORY_CHOICES = ( + ('artworks', 'Artworks'), + ('control', 'Control'), + ('cables', 'Cables'), + ('connectors', 'Connectors'), + ('computer', 'Computer'), + ('storage', 'Storage Devices'), + ('av', 'Audio / Video Equipment'), + ('electronics', 'Electronics'), + ('electrical', 'Electrical & Lighting'), + ('mechanical', 'Mechanical Parts'), + ('media', 'Media'), + ('tools', 'Tools'), + ('supplies', 'Supplies'), + ('other', 'Other'), +) + +class Box(models.Model): + box_no = models.CharField(max_length=255, blank=True, unique=True) + description = models.TextField(blank=True) + owner = models.ForeignKey('Owner', blank=True, null=True) + location = models.ForeignKey(Location, blank=True, null=True) + tags = TagField("Tags", blank=True, help_text="Enter as many tags as you like, separated by commas") + + def __unicode__(self): + s = '' + if self.location: + s += self.location.code + "/" + s += self.box_no + return s + + class Meta: + verbose_name_plural = "Boxes" + +class Hardware(models.Model): + title = models.CharField(max_length=255, verbose_name="Title / Name") + category = models.ForeignKey("HardwareCategory", blank=True, null=True) +# category = MultiSelectField(max_length=355, choices=HARDWARE_CATEGORY_CHOICES, verbose_name="Category") + model_number = models.CharField(max_length=255, blank=True) + serial_number = models.CharField(max_length=255, blank=True) + description = models.TextField(blank=True) + quantity = models.IntegerField(blank=True, null=True, default=1, help_text="This will NOT create separate entries for the item. Use 'Save as New' to duplicate items") +# location = models.ForeignKey(Location, blank=True) + tags = TagField("Tags", blank=True, help_text="Enter as many tags as you like, separated by commas") +# added_by = models.ForeignKey(User, editable=False) + date_added = models.DateTimeField(auto_now=True, editable=False) + links = models.ManyToManyField('HardwareLink', blank=True, null=True) + box = models.ForeignKey('Box', blank=True, null=True) + published = models.BooleanField(default=False) + image = models.ImageField(upload_to='hw_images/', blank=True, null=True) + + def __unicode__(self): + return self.labelID() + + def labelID(self): + s = '' + if self.box: + try: + loc = Location.objects.get(pk=self.box.id) + s += loc.code + "/" + except: + s += "" + s += self.box.box_no + "/" + # if obj.box.location: + # s += obj.box.location.code + "/" + s += str(self.id) + return s + + class Meta: + verbose_name_plural = "Hardware" + +class HardwareCategory(models.Model): + name = models.CharField(max_length=255) + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = "Hardware Category" + verbose_name_plural = "Hardware Categories" + +class HardwareLink(models.Model): + name = models.CharField(max_length=255) + url = models.URLField() + + def __unicode__(self): + return self.name + + + diff --git a/index/books/myfields.py b/index/books/myfields.py new file mode 100755 index 0000000..3456dc8 --- /dev/null +++ b/index/books/myfields.py @@ -0,0 +1,15 @@ +from django.db import models +from isbnvalidate import isValidISBN +from django import forms + +class ISBNField(models.Field): + + def __init__(self, *args, **kwargs): + kwargs['max_length'] = 13 + super(ISBNField, self).__init__(*args, **kwargs) + + def clean(self, value): + if not isValidISBN(value): + raise forms.ValidationError("not a valid ISBN") + else: + return value diff --git a/index/books/thumbs.py b/index/books/thumbs.py new file mode 100755 index 0000000..ec7b88a --- /dev/null +++ b/index/books/thumbs.py @@ -0,0 +1,164 @@ +# -*- encoding: utf-8 -*- +""" +django-thumbs by Antonio Melé +http://django.es +""" +from django.db.models import ImageField +from django.db.models.fields.files import ImageFieldFile +from PIL import Image +from django.core.files.base import ContentFile +import cStringIO + +def generate_thumb(img, thumb_size, format): + """ + Generates a thumbnail image and returns a ContentFile object with the thumbnail + + Parameters: + =========== + img File object + + thumb_size desired thumbnail size, ie: (200,120) + + format format of the original image ('jpeg','gif','png',...) + (this format will be used for the generated thumbnail, too) + """ + + img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details + image = Image.open(img) + + # Convert to RGB if necessary + if image.mode not in ('L', 'RGB'): + image = image.convert('RGB') + + # get size + thumb_w, thumb_h = thumb_size + # If you want to generate a square thumbnail + if thumb_w == thumb_h: + # quad + xsize, ysize = image.size + # get minimum size + minsize = min(xsize,ysize) + # largest square possible in the image + xnewsize = (xsize-minsize)/2 + ynewsize = (ysize-minsize)/2 + # crop it + image2 = image.crop((xnewsize, ynewsize, xsize-xnewsize, ysize-ynewsize)) + # load is necessary after crop + image2.load() + # thumbnail of the cropped image (with ANTIALIAS to make it look better) + image2.thumbnail(thumb_size, Image.ANTIALIAS) + else: + # not quad + image2 = image + image2.thumbnail(thumb_size, Image.ANTIALIAS) + + io = cStringIO.StringIO() + # PNG and GIF are the same, JPG is JPEG + if format.upper()=='JPG': + format = 'JPEG' + + image2.save(io, format) + return ContentFile(io.getvalue()) + +class ImageWithThumbsFieldFile(ImageFieldFile): + """ + See ImageWithThumbsField for usage example + """ + def __init__(self, *args, **kwargs): + super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs) + self.sizes = self.field.sizes + + if self.sizes: + def get_size(self, size): + if not self: + return '' + else: + split = self.url.rsplit('.',1) + thumb_url = '%s.%sx%s.%s' % (split[0],w,h,split[1]) + return thumb_url + + for size in self.sizes: + (w,h) = size + setattr(self, 'url_%sx%s' % (w,h), get_size(self, size)) + + def save(self, name, content, save=True): + super(ImageWithThumbsFieldFile, self).save(name, content, save) + + if self.sizes: + for size in self.sizes: + (w,h) = size + split = self._name.rsplit('.',1) + thumb_name = '%s.%sx%s.%s' % (split[0],w,h,split[1]) + + # you can use another thumbnailing function if you like + thumb_content = generate_thumb(content, size, split[1]) + + thumb_name_ = self.storage.save(thumb_name, thumb_content) + + if not thumb_name == thumb_name_: + raise ValueError('There is already a file named %s' % thumb_name) + + def delete(self, save=True): + name=self.name + super(ImageWithThumbsFieldFile, self).delete(save) + if self.sizes: + for size in self.sizes: + (w,h) = size + split = name.rsplit('.',1) + thumb_name = '%s.%sx%s.%s' % (split[0],w,h,split[1]) + try: + self.storage.delete(thumb_name) + except: + pass + +class ImageWithThumbsField(ImageField): + attr_class = ImageWithThumbsFieldFile + """ + Usage example: + ============== + photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),) + + To retrieve image URL, exactly the same way as with ImageField: + my_object.photo.url + To retrieve thumbnails URL's just add the size to it: + my_object.photo.url_125x125 + my_object.photo.url_300x200 + + Note: The 'sizes' attribute is not required. If you don't provide it, + ImageWithThumbsField will act as a normal ImageField + + How it works: + ============= + For each size in the 'sizes' atribute of the field it generates a + thumbnail with that size and stores it following this format: + + available_filename.[width]x[height].extension + + Where 'available_filename' is the available filename returned by the storage + backend for saving the original file. + + Following the usage example above: For storing a file called "photo.jpg" it saves: + photo.jpg (original file) + photo.125x125.jpg (first thumbnail) + photo.300x200.jpg (second thumbnail) + + With the default storage backend if photo.jpg already exists it will use these filenames: + photo_.jpg + photo_.125x125.jpg + photo_.300x200.jpg + + Note: django-thumbs assumes that if filename "any_filename.jpg" is available + filenames with this format "any_filename.[widht]x[height].jpg" will be available, too. + + To do: + ====== + Add method to regenerate thubmnails + + """ + def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, sizes=None, **kwargs): + self.verbose_name=verbose_name + self.name=name + self.width_field=width_field + self.height_field=height_field + self.sizes = sizes + super(ImageField, self).__init__(**kwargs) diff --git a/index/books/views.py b/index/books/views.py new file mode 100755 index 0000000..ea4338e --- /dev/null +++ b/index/books/views.py @@ -0,0 +1,183 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.contrib.auth.decorators import login_required +from django.shortcuts import render_to_response +# from django.contrib.auth import authenticate, login, logout +from books.models import * +from django.utils import simplejson +from django import forms +from django.forms.models import inlineformset_factory +from isbnlookup2 import searchAll +#from django.contrib.auth.messages import * +import datetime +import sys +from django.template import RequestContext +from dateutil.parser import parse as parseDate + +class BookForm(forms.ModelForm): + title = forms.CharField(widget = forms.TextInput(attrs = {'class': 'autoisbn'})) + authors = forms.CharField(widget = forms.TextInput(attrs = {'class': 'autoisbn'}), required=False) + dimensions = forms.CharField(widget = forms.TextInput(attrs = {'class': 'autoisbn'}), required=False) + publisher = forms.CharField(widget = forms.TextInput(attrs = {'class': 'autoisbn'}), required=False) + published = forms.CharField(widget = forms.TextInput(attrs = {'class': 'autoisbn'}), required=False) + pages = forms.CharField(widget = forms.TextInput(attrs = {'class': 'autoisbn'}), required=False) + weight = forms.CharField(widget = forms.TextInput(attrs = {'class': 'autoisbn'}), required=False) + first_sentence = forms.CharField(widget = forms.TextInput(attrs = {'class': 'autoisbn'}), required=False) + tags = forms.CharField(widget = forms.TextInput(attrs = {'size': '50'}), required=False) + autotags = forms.CharField(widget = forms.TextInput(attrs = {'size': '50', 'class' : 'autoisbn'}), required=False) + + class Meta: + model = Book + +def dateformat(datestr): + return datetime.date(*time.strptime(datestr, "%B %d, %Y")[0:3]).isoformat() + +def index(request): + books = Book.objects.all() + + return render_to_response("index.html", {'books': books}) + +# return render_to_response("index.html", context_instance=RequestContext(request)) + +@login_required +def add(request): + linksInline = inlineformset_factory(Book, Weblink) + reviewsInline = inlineformset_factory(Book, WebReview) + summariesInline = inlineformset_factory(Book, Summary) + relatedBookInline = inlineformset_factory(Book, RelatedBook) + imageUrlInline = inlineformset_factory(Book, ImageUrl) + chapterInline = inlineformset_factory(Book, ChapterEntry) + sourcesInline = inlineformset_factory(Book, Source) +# + if (request.method == 'POST'): + bookform = BookForm(request.POST) +# bookform.published = dateformat(bookform.published) + linksFormset = linksInline(request.POST, prefix='weblinks') + reviewsFormset = reviewsInline(request.POST, prefix='webreviews') + summariesFormset = summariesInline(request.POST, prefix='summaries') + relatedBookFormset = relatedBookInline(request.POST, prefix='relatedbooks') + imageUrlFormset = imageUrlInline(request.POST, prefix='imageurls') + chapterFormset = chapterInline(request.POST, prefix='chapters') + sourcesFormset = sourcesInline(request.POST, prefix='sources') + if bookform.is_valid(): + newbook = bookform.save(commit=False) + newbook.added_by = request.user + request.session['owner'] = newbook.owner.id + request.session['location'] = newbook.location.id + newbook.published = parseDate(bookform.cleaned_data['published']) + newbook.save() + astring = bookform.cleaned_data['authors'] + alist = astring.split(';') + authors = [] + for a in alist: + if (a.strip() != ''): + au = Author(name = a) + au.save() + authors.append(au) + newbook.authors = authors +# weblinks = linksFormset.save() +# reviews = reviewsFormset.save() +# summaries = summariesFormset.save() +# relatedBooks = relatedBookFormset.save() +# imageUrls = imageUrlFormset.save() +# chapters = chapterFormset.save() +# newbook.weblinks = weblinks + newbook.save() + + for f in linksFormset.forms: + if f.is_valid(): + f.cleaned_data['book'] = newbook + if f.cleaned_data.has_key('url'): + f.save() + + for f in summariesFormset.forms: + if f.is_valid(): + f.cleaned_data['book'] = newbook + if f.cleaned_data.has_key('txt'): + f.save() + + for f in relatedBookFormset.forms: + if f.is_valid(): + f.cleaned_data['book'] = newbook + if f.cleaned_data.has_key('url'): + f.save() + + for f in imageUrlFormset.forms: + if f.is_valid(): + f.cleaned_data['book'] = newbook + if f.cleaned_data.has_key('url'): + f.save() + + for f in chapterFormset.forms: + if f.is_valid(): + f.cleaned_data['book'] = newbook + if f.cleaned_data.has_key('link'): + f.save() + + for f in sourcesFormset.forms: + if f.is_valid(): + f.cleaned_data['book'] = newbook + if f.cleaned_data.has_key('name'): + f.save() + +# if f.cleaned_data['url'] != '': +# f.save() +# weblink = form.save(commit=False) +# weblink.book = newbook +# if form.is_valid(): +# weblink = Weblink() +# weblink['url'] = form.cleaned_data['url'] +# weblink['description'] = form.cleaned_data['description'] +# weblink['book'] = newbook +# weblink.save() +# weblink.book = thisBook +# weblink.save() + request.user.message_set.create(message="Your book was added successfully.") + return HttpResponseRedirect("/add") + else: + linksFormset = linksInline(prefix='weblinks') + reviewsFormset = reviewsInline(prefix='webreviews') + summariesFormset = summariesInline(prefix='summaries') + relatedBookFormset = relatedBookInline(prefix='relatedbooks') + imageUrlFormset = imageUrlInline(prefix='imageurls') + chapterFormset = chapterInline(prefix='chapters') + sourcesFormset = sourcesInline(prefix='sources') + bookform = BookForm() +# formsets = [linksFormset, chapterFormset] + formsets = [linksFormset, reviewsFormset, summariesFormset, relatedBookFormset, imageUrlFormset, chapterFormset, sourcesFormset] + defaultsD = {} + if request.session.has_key('location'): + defaultsD['location'] = request.session['location'] + if request.session.has_key('owner'): + defaultsD['owner'] = request.session['owner'] +# print repr(defaultsD) + return render_to_response("addbook.html", {'form': bookform, 'formsets': formsets, 'defaults': defaultsD}, context_instance=RequestContext(request)) + +def isbnLookup(request): + isbn = request.GET.get('isbn') + isbnDict = searchAll(isbn) + if (request.is_ajax()): + return HttpResponse(simplejson.dumps(isbnDict), mimetype='application/javascript') + else: + +#Ideally we would not have an if and else statement doing the same thing ;-) + return HttpResponse(simplejson.dumps(isbnDict), mimetype='application/javascript') + +def addLocation(request): + if request.is_ajax(): + locationName = request.GET['name'] + l = Location(name = locationName) + l.save() + return HttpResponse(l.id) + else: + return HttpResponse(str(o.id)) + +def addOwner(request): + if request.is_ajax(): + ownerName = request.GET['name'] + o = Owner(name = ownerName) + o.save() + return HttpResponse(str(o.id)) + else: + return HttpResponse("you are not ajax.") + + diff --git a/index/catalogues/__init__.py b/index/catalogues/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/index/catalogues/admin.py b/index/catalogues/admin.py new file mode 100755 index 0000000..844558d --- /dev/null +++ b/index/catalogues/admin.py @@ -0,0 +1,10 @@ +from catalogues.models import * +from django.contrib import admin +from django.contrib.auth.models import User + +class CatalogueAdmin(admin.ModelAdmin): + ordering = ('title',) + search_fields = ['title', 'artist'] + +admin.site.register(Catalogue, CatalogueAdmin) + diff --git a/index/catalogues/models.py b/index/catalogues/models.py new file mode 100755 index 0000000..47593d8 --- /dev/null +++ b/index/catalogues/models.py @@ -0,0 +1,24 @@ +from django.db import models +from django.contrib.auth.models import User +from books.models import Owner, Location +from tagging.fields import TagField +from tagging.models import Tag + +class Catalogue(models.Model): + title = models.CharField(max_length=255) + artist = models.CharField(max_length=255, blank=True) + exhibition = models.CharField(max_length=255, blank=True) + published_date = models.DateField(blank=True, verbose_name="Published on") + condition = models.TextField(blank=True) + description = models.TextField(blank=True) + owner = models.ForeignKey(Owner, blank=True) + location = models.ForeignKey(Location, blank=True) + tags = TagField("Tags", blank=True, help_text="Enter as many tags as you like, separated by commas") + added_by = models.ForeignKey(User, editable=False) + date_added = models.DateTimeField(auto_now=True, editable=False) + def __unicode__(self): + return self.name + + class Meta: + verbose_name_plural = "Catalogues" + diff --git a/index/catalogues/views.py b/index/catalogues/views.py new file mode 100755 index 0000000..d14ee9c --- /dev/null +++ b/index/catalogues/views.py @@ -0,0 +1,8 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from catalogue.models import * +from django import forms + +def add(request): + return render_to_response("addcatalogue.html") #, {'form': bookform, 'formset': formset}) + diff --git a/index/dropandcreatedb.py b/index/dropandcreatedb.py new file mode 100755 index 0000000..94127e4 --- /dev/null +++ b/index/dropandcreatedb.py @@ -0,0 +1,20 @@ +import sys +import os +from settings import DATABASE_NAME + +try: + import settings # Assumed to be in the same directory. +except ImportError: + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +filename = "/tmp/tmp.txt" +tmpfile = open(filename, "w") +tmpfile.write("DROP DATABASE %s;\nCREATE DATABASE %s;\n" % (DATABASE_NAME, DATABASE_NAME) ) +tmpfile.close() + +os.system("mysql -u root < " + filename) +os.system("python manage.py syncdb --noinput") +os.system("python manage.py createsuperuser --username admin --email user@user.com") + + diff --git a/index/harddrives/__init__.py b/index/harddrives/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/index/harddrives/admin.py b/index/harddrives/admin.py new file mode 100755 index 0000000..c26c288 --- /dev/null +++ b/index/harddrives/admin.py @@ -0,0 +1,13 @@ +from harddrives.models import * +from django.contrib import admin + +class FileAdmin(admin.ModelAdmin): + search_fields = ('fullPath',) + list_display = ('filename', 'fullPath', 'hd',) + list_filter = ['hd'] + +class HardDriveAdmin(admin.ModelAdmin): + pass + +admin.site.register(HardDrive, HardDriveAdmin) +admin.site.register(File, FileAdmin) diff --git a/index/harddrives/importFromTxtFiles.py b/index/harddrives/importFromTxtFiles.py new file mode 100644 index 0000000..b70b30c --- /dev/null +++ b/index/harddrives/importFromTxtFiles.py @@ -0,0 +1,53 @@ +import os +from os.path import join +from models import HardDrive, File + +ERROR_LOG = open("errors.txt", "a") + +def loopThroughDir(path): + dir = os.listdir(path) + for f in dir: + if f.endswith('txt'): + addHardDrive(f, path) + ERROR_LOG.close() + +def addHardDrive(filename, path): + filePath = u'' + filePath = join(path, filename) + hdName = filename.replace(".txt", "") + file = open(filePath) + if hardDriveExists(hdName): + return False + else: + hd = HardDrive(name = hdName) + hd.save() + print hdName + for f in file: + addFile(f, hd) + +def addFile(filePath, hd): + filename = u'' + filename = getFileNameFromPath(filePath) + newfile = File(filename = filename, fullPath = filePath, hd = hd) + try: + newfile.save() + except: + err = hd.name + ":\n" + err += filePath + "\n\n" + ERROR_LOG.write(err) + print "added " + filename + +def hardDriveExists(hdName): + return False + +def getFileNameFromPath(filePath): + return os.path.basename(filePath) + +if __name__ == '__main__': + import sys + if len(sys.argv) < 2: + print "usage: %s " % sys.argv[0] + sys.exit() + else: + path = sys.argv[1] + loopThroughDir(path) diff --git a/index/harddrives/models.py b/index/harddrives/models.py new file mode 100644 index 0000000..5c66dff --- /dev/null +++ b/index/harddrives/models.py @@ -0,0 +1,20 @@ +from django.db import models + +class HardDrive(models.Model): + name = models.CharField(max_length = 255, unique = True) + def __unicode__(self): + return self.name +# volumeLabel = models.CharField(max_length = 255) + + +class File(models.Model): + filename = models.CharField(max_length = 512) + fullPath = models.TextField() + hd = models.ForeignKey(HardDrive) + + def __unicode__(self): + return self.fullPath + + + +# Create your models here. diff --git a/index/harddrives/views.py b/index/harddrives/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/index/harddrives/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/index/hardware/__init__.py b/index/hardware/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/index/hardware/admin.py b/index/hardware/admin.py new file mode 100755 index 0000000..68e9466 --- /dev/null +++ b/index/hardware/admin.py @@ -0,0 +1,12 @@ +from hardware.models import * +from django.contrib import admin +from django.contrib.auth.models import User + +class HardwareAdmin(admin.ModelAdmin): + ordering = ('title',) + list_display = ('title', 'category', 'box',) + list_filter = ('category', 'box',) + search_fields = ['title', 'description'] + +admin.site.register(Hardware, HardwareAdmin) +admin.site.register(Link) diff --git a/index/hardware/models.py b/index/hardware/models.py new file mode 100644 index 0000000..3b31028 --- /dev/null +++ b/index/hardware/models.py @@ -0,0 +1,50 @@ +from django.db import models +from django.contrib.auth.models import User +from books.models import Owner, Location +from tagging.fields import TagField +from tagging.models import Tag +from books.fields import MultiSelectField, MultiSelectFormField + +CATEGORY_CHOICES = ( + ('artworks', 'Artworks'), + ('control', 'Control'), + ('cables', 'Cables'), + ('connectors', 'Connectors'), + ('computer', 'Computer'), + ('storage', 'Storage Devices'), + ('av', 'Audio / Video Equipment'), + ('electronics', 'Electronics'), + ('electrical', 'Electrical & Lighting'), + ('mechanical', 'Mechanical Parts'), + ('media', 'Media'), + ('tools', 'Tools'), + ('supplies', 'Supplies'), + ('other', 'Other'), +) + +class Hardware(models.Model): + title = models.CharField(max_length=255) + model_number = models.CharField(max_length=255, blank=True) + serial_number = models.CharField(max_length=255, blank=True) + description = models.TextField(blank=True) + owner = models.ForeignKey(Owner, blank=True) + location = models.ForeignKey(Location, blank=True) + tags = TagField("Tags", blank=True, help_text="Enter as many tags as you like, separated by commas") + added_by = models.ForeignKey(User, editable=False) + date_added = models.DateTimeField(auto_now=True, editable=False) + category = MultiSelectField(max_length=355, choices=CATEGORY_CHOICES, verbose_name="Category") + + + def __unicode__(self): + return self.name + + class Meta: + verbose_name_plural = "Hardware" + +class Link(models.Model): + name = models.CharField(max_length=255) + url = models.URLField() + + def __unicode__(self): + return self.name + diff --git a/index/hardware/views.py b/index/hardware/views.py new file mode 100755 index 0000000..3a869b0 --- /dev/null +++ b/index/hardware/views.py @@ -0,0 +1,8 @@ +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from hardware.models import * +from django import forms + +#def add(request): + #return render_to_response("addhardware.html") #, {'form': bookform, 'formset': formset}) + diff --git a/index/manage.py b/index/manage.py new file mode 100755 index 0000000..bcdd55e --- /dev/null +++ b/index/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/index/media/css/base.css b/index/media/css/base.css new file mode 100755 index 0000000..557ace7 --- /dev/null +++ b/index/media/css/base.css @@ -0,0 +1,57 @@ +body { + background: #000; + overflow-x: hidden; + } + +#header { + position: relative; + width: 100%; + background: #333; + color: #333; + height: 40px; + margin-bottom: 4px; + padding: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + } + +#buttons { + position: absolute; + bottom: 10px; + right: 10px; + } + +#pageTitle { + width: 100%; + color: #fff; + font-weight: bold; + font-size: 18px; + padding-top: 8px; + } + +.button { + padding: 2px; + padding-top: 6px; + float: left; + border: 2px solid #000; + background: #000; + color: #00f; + height: 20px; + margin-left: 5px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + width: 100px; + font-size: 14px; + text-align: center; + cursor: pointer; + } + +.button a { + color: #f33; + text-decoration: none; + } + +.button a:hover { + color: yellow; + } + diff --git a/index/media/css/form.css b/index/media/css/form.css new file mode 100755 index 0000000..2fa3894 --- /dev/null +++ b/index/media/css/form.css @@ -0,0 +1,151 @@ +#isbnlookup { + color: blue; + cursor: pointer; + } + +#loading { + display: none; + } + +#userFields { + float: left; + width: 50%; + } + +#userFields input { + width: 80%; + } + +#userFields input, textarea, select { + margin-left: 10px; + } + + + +#lookupIsbn { + width: 8em; + background: #db1010; + color: #fff; + font-weight: bold; + border: 1px solid #f39b0f; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + cursor: pointer; + } + +#lookupIsbn:hover { + background-color: #f39b0f; + + } + +#userFields textarea { + width: 50%; + } +#autoFields { + float: left; + width: 50%; + } + +#autoFields input { + width: 90%; + } + +#formErrors { + margin-left: auto; + margin-right: auto; + text-align: center; + color: #f00; + font-weight: bold; + font-size: 12px; + background: yellow; + width: 40%; + } + +.loadingimage { + display: none; + } + +.button_loadingimage { + display: none; + text-align: right; + } + +.formset { + clear: both; + } + +.formset_title { + cursor: pointer; + margin-bottom: 3px; + margin-left: 40%; + font-weight: bold; + border: 1px solid #f00; + text-align: center; + -moz-border-radius: 10px; + width: 10em; + color: #fff; + } + +.formset_no_results { + font-size: 12px; + color: #f00; + font-weight: bold; + } + +.formset_forms { + display: none; + border: 1px dotted #f00; + } + +.formset_form { + border: 1px dotted #f00; + } + +#formsets { + padding: 5px; + } + +.addButton { + font-style: bold; + font-size: 14px; + cursor: pointer; + color: #00f; + } + +label { + color: #f19f9f; + font-weight: bold 70%; + position: relative; + left: 15px; + } + + + +#submitBtn { + width: 40%; + margin-left: auto; + margin-right: auto; + } + +#submitButton { + width: 20px; + background-color: #1d0213; + color: #fff; + font-weight: bold; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + border: 1px solid #f39b0f; + cursor: pointer; + } + +#submitButton:hover { + color: yellow; + } + +input, textarea { + background: #ccc; + } + +#isbn input { + width: 13em; + } diff --git a/index/media/images/loading.gif b/index/media/images/loading.gif new file mode 100755 index 0000000000000000000000000000000000000000..e192ca895cd00d6b752ec84619b787188f30ee41 GIT binary patch literal 847 zcmZ?wbhEHb6krfw_`<;O|Nnmm28JI$eppyow6wIav9YPCsa?BvZN-WeVq#)tWo2n; zX-}R!nK5I=v17+PJUqg}!hq_D2a5l>{aizWogD*Qjr0td8G$+#|4BI)r6!i7rYMwW zmSiX-W+hhSNI#p{aB=u^kJ9BqzM)+D@@g7D>_ZH z6>Nk>K2^#dec$hd&5{fSg)a9?JsDb3M<1+M;h^GLd*HyqYe$(ldZsj_W{3#!96X@l zAjsu&py5MupnEfu)0U^(0!(Kp*sL-QO$pql{X%Kq;`Av7E5z0lI$B?E`RzKdsAZ)9=nHHN!5+~JF4SY+VADb}iE(C2i8t1nx?>)BhL zPS>_TA<--6ZN7=uLx=rfr*28JR#UU9l!(BR!@3s} qR&*pBVEQRw*vTQWVY)*bLIMx~ literal 0 HcmV?d00001 diff --git a/index/media/images/plus.gif b/index/media/images/plus.gif new file mode 100755 index 0000000000000000000000000000000000000000..ee70e1adba52480cc6aedbee650000c5d55b0088 GIT binary patch literal 119 zcmZ?wbhEHb(s)E@aY^3 F)&O8RB1ZrK literal 0 HcmV?d00001 diff --git a/index/media/js/bookform.js b/index/media/js/bookform.js new file mode 100755 index 0000000..94881b1 --- /dev/null +++ b/index/media/js/bookform.js @@ -0,0 +1,146 @@ +$(document).ready(function() { + $('#lookupIsbn').click(isbnLookup); + +//To handle colour changing on focus / blur of form elems: + $('input, textarea').focus(function() { + $(this).css("background", "#fff"); + }).blur(function() { + $(this).css("background", "#ccc"); + }); + +//To add Owner / Location fields dynamically. + $('.addButton').click(function() { + var fieldName = $(this).attr("data_field"); + var value = prompt("Please type the name of the new " + fieldName + "you would like to add:"); + if (value != null && $.trim(value) != '') { + var url = "/add" + fieldName + "/"; + $.get(url, { + 'name': value + }, function(response) { + addedId = response; + var html = ''; + html += '"; + selectId = "#id_" + fieldName; + $(selectId).append(html); + }); + } + }); + }) + +//TODO: Make this a function that actually validates. +function isValidIsbn(isbn) { + if ($.trim(isbn) != '') { + return true; + } else { + return false; + } + } + +//Function fired on start loading ISBN Lookup +function doLoading() { + $('.autoisbn').css("background", "#f00").attr("disabled", "true"); + $('#lookupIsbn').fadeOut(); + $('#loading').fadeIn(); + $('.loadingimage').fadeIn(); + $('.button_loadingimage').fadeIn(); + } + +//Function fired when ISBN Lookup is loaded +function stopLoading() { + $('.autoisbn').removeAttr("disabled").css("background", "#fff"); + $('#loading').hide(); + $('.loadingimage').fadeOut(); + $('.button_loadingimage').fadeOut(); + $('#lookupIsbn').fadeIn(); + } + +//Fetch ISBN Lookup data and populate relevant fields. +function isbnLookup() { + isbn = $('#id_isbn').val(); + if (isValidIsbn(isbn)) { + doLoading(); + $.getJSON("/isbnlookup/", { + 'isbn': isbn + }, function(data) { + if (data.errors && data.errors != '') { + $('#formErrors').html(data.errors); + stopLoading(); + } else { +// alert(data.title); + $('#id_title').val(data.title); + $('#id_dimensions').val(data.dimensions); + var publishedStr = getStrFromDate(data.published); + $('#id_published').val(publishedStr); + $('#id_publisher').val(data.publisher); + if (data.weight != 0) { + $('#id_weight').val(data.weight); + } + if (data.pages != 0) { + $('#id_pages').val(data.pages); + } + if (data.tags.length > 0) { + var tString = ''; + for (t in data.tags) { + tString += data.tags[t] + ", "; + } + $('#id_autotags').val(tString); + } + $('#id_first_sentence').val(data.first_sentence); +//TODO: Formsets should be referencable by prefixes. + formsets[0].populate(data.weblinks); + formsets[1].populate(data.netReviews); + formsets[2].populate(data.summaries); + formsets[3].populate(data.relatedBooks); + formsets[4].populate(data.imageUrls); + formsets[5].populate(data.chapters); + formsets[6].populate(data.sources); + var authorString = ''; + for (author in data.authors) { + authorString += data.authors[author] + "; "; + } +// alert(authorString); + $('#id_authors').val(authorString); + stopLoading(); + } + }); + } + } + +//Isbn Lookup passes date as a dict - convert to string. +function getStrFromDate(d) { + var str = ''; + if (d.year) { + str += d.year + if (d.month) { + str += '-' + d.month + if (d.day) { + str += '-' + d.day + } + } + } + return str; + } + +//################################################# +//BEGIN Formset functions +//################################################# + +var prefixes = ['weblinks', 'webreviews', 'summaries', 'relatedbooks', 'imageurls', 'chapters', 'sources'] + +//TODO: more elegant way to calculate length of buttons +$(document).ready(function() { + formsets = []; + for (var p in prefixes) { + formsets[p] = new Formset(prefixes[p]); + } + + $('.formset_title').hover(function() { + $(this).css('border-color', '#fff'); + $(this).css('color', '#f00'); + }, function() { + $(this).css('border-color', '#f00'); + $(this).css('color', '#fff'); + }); + }); + + diff --git a/index/media/js/formset.js b/index/media/js/formset.js new file mode 100644 index 0000000..baac184 --- /dev/null +++ b/index/media/js/formset.js @@ -0,0 +1,192 @@ +/* +*@class Formset class +*@property {String} prefix same prefix as defined in django view while instantiating formset. +*@property {String} prefixStr prefix string to +*@property {String} jqPrefix just a helpful prefix with '#_id' prepended to use in jQuery selector strings +*@property {jQuery Object} totalFormsElem the Input field of the TOTAL_FORMS field for this formset. (Django: management_form) +*@property {jQuery Object} cloneObj a jQuery element that is a clone of a single form in the formset. +*@property {jQuery Object} titleElem The div that contains the title of the formset + +*/ + +/* +*@constructor +*@param {String} Prefix for this formset (same as used in django during instantiation) +*/ +var Formset = function(prefix) { + var that = this; + this.prefix = prefix; + this.jqPrefix = '#id_' + prefix; + this.prefixStr = 'id_' + this.prefix + '-'; + this.totalFormsElem = $(that.jqPrefix + '-TOTAL_FORMS'); + this.cloneObj = function() { + var that = this; + return $('#' + that.prefix + ' > .formset_forms').children('.formset_form:first').clone() + } + this.titleElem = $('#' + that.prefix + ' > .formset_title'); + this.formsetElem = $('#' + that.prefix + ' > .formset_forms'); + this.titleElem.click(function() { + that.formsetElem.toggle('slow', function() { + thisTop = that.titleElem.offset().top; + $(document).scrollTop(thisTop); + }); + }); + } + +/* +*Toggles display of formset (except for title element), also scrolls the page to the top of the title element. +* TODO: fix the scroll being weird or get rid of it. +*/ +Formset.prototype.toggleDisplay = function() { + var that = this; + that.formsetElem.toggle('slow', function() { + thisTop = that.titleElem.offset().top; + $(document).scrollTop(thisTop); + }); + } + +/* +* Get total no of forms in formset. +* @returns {Int} Total no of forms according to formset metadata. +*/ +Formset.prototype.getTotal = function() { + return parseInt(this.totalFormsElem.val()); + } + +/* +* Set total no of forms in formset. +*@param {Int} No of forms to set formset metadata to. +*/ +Formset.prototype.setTotal = function(newNo) { + this.totalFormsElem.val(newNo); + return true; + } + +/* +* Increment formset metadata total by one +*/ +Formset.prototype.increment = function() { + var currTotal = this.getTotal(); + var newTotal = currTotal + 1; + this.setTotal(newTotal); + return true; + } + + +Formset.prototype.getFieldName = function(idStr) { +// var thisId = jqObj.attr('id'); +// var thisPrefixStr = 'id_' + this.prefix + '-'; + var that = this; + var prefixLength = this.prefixStr.length + 2; + var thisField = idStr.substring(prefixLength); + console.log(that.prefixStr); + return thisField; + } + +/* +*@returns {Array} returns array of strings representing field names present in this formset. +*/ +Formset.prototype.getFields = function() { + var that = this; + var fields = []; + that.cloneObj().find('input').each(function() { + if ($(this).attr('type') != 'hidden') { + var thisField = that.getFieldName($(this).attr('id')) + if (thisField != 'DELETE') { + fields.push(thisField); + } + } + }); + that.cloneObj().find('textarea').each(function() { + if ($(this).attr('type') != 'hidden') { + var thisField = that.getFieldName($(this).attr('id')) + if (thisField != 'DELETE') { + fields.push(thisField); + } + } + }); + return fields; + } + +/* +* Add a form to the formset. Use this method from the outside. +* @param {Int} No of forms to add (Optional), defaults to 1. +*/ +Formset.prototype.add = function(no) { + if (!no) { + var no = 1; + } + var that = this; + for (var i=0; i < no; i++) { + var e = this.cloneObj(); + //TODO: Figure out how to deal with changing 'for' attribute on labels correctly. + /* + e.find('label').each(function() { + var newId = that.incrementId($(this).attr('for')); + // $(this).attr('id', newId); + $(this).attr('for', newId); + }); + */ + e.find('input').each(function() { + $(this).attr('id', that.incrementId($(this).attr('id'))); + $(this).attr('name', that.incrementName($(this).attr('id'))); + }); + e.find('textarea').each(function() { + $(this).attr('id', that.incrementId($(this).attr('id'))); + $(this).attr('name', that.incrementName($(this).attr('id'))); + }); + this.increment(); + this.formsetElem.append(e); + } + return true; + } + +/* +*@param {String} id of element to increment +*@returns {String} incremented id +*/ +Formset.prototype.incrementId = function(idStr) { + var newTotal = this.getTotal() + 1; + var newId = this.prefixStr + newTotal.toString() + '-' + this.getFieldName(idStr); + return newId; + } + +/* +*@param {String} name of element to increment +*@returns {String} incremented name +*/ +Formset.prototype.incrementName = function(idStr) { + var newTotal = this.getTotal() + 1; + var newId = this.prefix + '-' + newTotal.toString() + '-' + this.getFieldName(idStr); + return newId; + } + +/* +* Populates a formset with an array of JSON data +*@param {JSON Array} like so - [{'field1': 'some value', 'field2': 'some more value'}, {'field1': 'foo', 'field2': 'bar'}] +*@returns true once done +*/ + +Formset.prototype.populate = function(data) { + var that = this; + var noOfEntries = data.length; + if (data.length > 0) { +// that.titleElem.html(that.titleElem.html() + " " + noOfEntries.toString()); + that.titleElem.children('.formset_no_results').html(noOfEntries.toString()); + var totalFormsToShow = noOfEntries + 3; + var newFormsToAdd = totalFormsToShow - this.getTotal(); + this.add(newFormsToAdd); + var fields = this.getFields(); + for (var f in fields) { + var fieldName = fields[f]; + for (var d in data) { + if (data[d]) { + var val = data[d][fieldName]; + $(that.jqPrefix + '-' + d.toString() + '-' + fieldName).val(val); + } + } + } + } + return true; + } + diff --git a/index/media/js/jquery.js b/index/media/js/jquery.js new file mode 100755 index 0000000..b1ae21d --- /dev/null +++ b/index/media/js/jquery.js @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
    ","
    "]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

    ";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
    ";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
    ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
    ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); \ No newline at end of file diff --git a/index/settings.py b/index/settings.py new file mode 100644 index 0000000..8047c6e --- /dev/null +++ b/index/settings.py @@ -0,0 +1,92 @@ +# Django settings for index project. +import os +from os.path import join + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +PROJECT_PATH = os.path.dirname(__file__) + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASE_ENGINE = 'mysql' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. +DATABASE_NAME = 'idx' # Or path to database file if using sqlite3. +DATABASE_USER = 'root' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = join(PROJECT_PATH, 'media') + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '/media/' + +LOGIN_URL = '/login/' +LOGIN_REDIRECT_URL = '/' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/admin/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = ')o1)c68=b9k5z@af=_@s31)pcp58yxo@5@$b5!^_vlijibziq1' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +) + +ROOT_URLCONF = 'urls' + +TEMPLATE_DIRS = ( + join(PROJECT_PATH, 'templates'), + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'books', + 'harddrives', + 'tagging', + 'south', +) diff --git a/index/templates/addbook.html b/index/templates/addbook.html new file mode 100644 index 0000000..e257a19 --- /dev/null +++ b/index/templates/addbook.html @@ -0,0 +1,171 @@ +{% extends 'site_base.html' %} + +{% block extra_head %} + + + + + + +{% endblock %} + +{% block content %} + +
    + +
    +
    Loading...
    + {{ form.errors }} + +
    +{% if messages %} +
      + {% for message in messages %} +
    • {{ message }}
    • + {% endfor %} +
    +{% endif %} +
    + + +
    + + + + + +
    + +
    + +
    {{ form.isbn}}   + +
    + +

    +
    + {{ form.tags }} +

    + +

    +
    + {{ form.description }} +

    + +

    +
    + {{ form.owner }} +

    + +

    +
    + {{ form.location }} +

    + +

    + +

    +
    + +
    +

    +
    + {{ form.title}} + +

    + +

    +
    + {{ form.authors}} + +

    + +

    +
    + {{ form.publisher }} + +

    + +

    +
    + {{ form.published }} + +

    + +

    + + {{ form.autotags }} + +

    + +

    +
    + {{ form.pages }} + +

    + +

    +
    + {{ form.weight }} + +

    + +

    +
    + {{ form.dimensions }} + +

    + +

    +
    + {{ form.first_sentence }} + +

    + +
    +
    + {% for formset in formsets %} +
    +
    + {{formset.prefix}} + +
    +
    + {{ formset.management_form }} +
    +
    + {% for form in formset.forms %} +
    + {{ form.as_p }} +
    + {% endfor %} +
    +
    + {% endfor %} + +
    + + + +
    + +{% endblock %} diff --git a/index/templates/base.html b/index/templates/base.html new file mode 100755 index 0000000..2a3df25 --- /dev/null +++ b/index/templates/base.html @@ -0,0 +1,17 @@ + + + + +{% block head %} + +{% endblock %} + + + +{% block body %} + +{% endblock %} + + + + diff --git a/index/templates/index.html b/index/templates/index.html new file mode 100755 index 0000000..7112b97 --- /dev/null +++ b/index/templates/index.html @@ -0,0 +1,20 @@ +{% extends 'site_base.html' %} +{% block title %} Home {% endblock %} +{% block extra_head %} + + +{% endblock %} + + +{% block content %} + + +{% endblock %} + + diff --git a/index/templates/login.html b/index/templates/login.html new file mode 100755 index 0000000..08f3989 --- /dev/null +++ b/index/templates/login.html @@ -0,0 +1,30 @@ +{% extends 'site_base.html' %} + +{% block title %} + Login +{% endblock %} + +{% block content %} + +{% if form.errors %} +

    Your username and password didn't match. Please try again.

    +{% endif %} + +
    + + + + + + + + + +
    {{ form.username.label_tag }}{{ form.username }}
    {{ form.password.label_tag }}{{ form.password }}
    + + + +
    + +{% endblock %} + diff --git a/index/templates/logout.html b/index/templates/logout.html new file mode 100755 index 0000000..209c841 --- /dev/null +++ b/index/templates/logout.html @@ -0,0 +1,6 @@ +{% extends 'site_base.html' %} +{% block title %} Logout {% endblock %} +{% block content %} + {{ title }} +{% endblock %} + diff --git a/index/templates/site_base.html b/index/templates/site_base.html new file mode 100755 index 0000000..b5ccef6 --- /dev/null +++ b/index/templates/site_base.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} +{% block head %} + +{% block title %} {% endblock %} + {% block extra_head %} + + {% endblock %} + +{% endblock %} + +{% block body %} + + + + {% block content %} + + {% endblock %} + +{% endblock %} diff --git a/index/urls.py b/index/urls.py new file mode 100755 index 0000000..63150a1 --- /dev/null +++ b/index/urls.py @@ -0,0 +1,31 @@ +from django.conf.urls.defaults import * +from settings import PROJECT_PATH +from os.path import join + +# Uncomment the next two lines to enable the admin: +from django.contrib import admin +admin.autodiscover() + +urlpatterns = patterns('', + # Example: + # (r'^index/', include('index.foo.urls')), + (r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}), + (r'^logout/$', 'django.contrib.auth.views.logout', {'template_name': 'logout.html'}), + (r'^$', 'books.views.index'), +# (r'^addhardware/(.*)', 'hardware.views.addhardware'), + (r'^add/$', 'books.views.add'), + (r'^addlocation/$', 'books.views.addLocation'), + (r'addowner/$', 'books.views.addOwner'), + (r'^(\w+)/add/$', 'books.views.add'), + (r'^isbnlookup/$', 'books.views.isbnLookup'), + (r'^media/(?P.*)$', 'django.views.static.serve', + {'document_root': join(PROJECT_PATH, 'media')}), + + # Uncomment the admin/doc line below and add 'django.contrib.admindocs' + # to INSTALLED_APPS to enable admin documentation: + # (r'^admin/doc/', include('django.contrib.admindocs.urls')), +# (r'^admin/', include('django.contrib.admin.urls')), + # Uncomment the next line to enable the admin: + (r'^admin/(.*)', admin.site.root), + +)