summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorluxagraf <sng@luxagraf.net>2015-10-01 22:10:21 -0400
committerluxagraf <sng@luxagraf.net>2015-10-01 22:10:21 -0400
commite0de7a6c08bbbd31fae2e7b796457a982cb3e895 (patch)
tree998de97c36aef12098f5545d3dcfd690e600b23b
parent93ac5f4acb8d1249461425c97d46bbf69915112d (diff)
did a lot of work on the books section
-rw-r--r--app/books/kindleparser.py14
-rw-r--r--app/books/models.py19
-rw-r--r--app/books/retriever.py60
-rw-r--r--app/books/urls.py9
-rw-r--r--app/books/views.py32
-rw-r--r--app/builder/base.py70
-rw-r--r--app/builder/views.py5
-rw-r--r--config/base_urls.py1
-rw-r--r--design/sass/_books.scss57
-rw-r--r--design/sass/_queries.scss4
-rw-r--r--design/templates/admin/buttons.html5
-rw-r--r--design/templates/archives/books.html55
-rw-r--r--design/templates/details/book.html47
13 files changed, 344 insertions, 34 deletions
diff --git a/app/books/kindleparser.py b/app/books/kindleparser.py
index aaa742f..44a4686 100644
--- a/app/books/kindleparser.py
+++ b/app/books/kindleparser.py
@@ -22,11 +22,11 @@ def parse_kindle_clippings(path):
except KeyError:
body_markdown = ""
try:
- location = int(clip['locationRange'])
+ location = clip['locationRange']
except:
location = 0
try:
- page = int(clip['pageRange'])
+ page = int(clip['pageRange'][0])
except:
page = 0
try:
@@ -44,13 +44,21 @@ def parse_kindle_clippings(path):
)
try:
#see if we already this highlight
- row = BookHighlight.objects.get(
+ bh = BookHighlight.objects.get(
book__title=clip['title'],
date_added=clip_date
)
+ #print(bh.book.title)
+ print(location)
+ #bh.location = location
+ #bh.page = page
+ #bh.save()
#if we don't create a new book highlight
except ObjectDoesNotExist:
book = Book.objects.get(title=clip['title'])
+ print(book.title)
+ print(page)
+ print(location)
bh, created = BookHighlight.objects.get_or_create(
book=book,
page=page,
diff --git a/app/books/models.py b/app/books/models.py
index cfa8379..14c7160 100644
--- a/app/books/models.py
+++ b/app/books/models.py
@@ -4,6 +4,7 @@ from django.conf import settings
from django.template.defaultfilters import slugify
from taggit.managers import TaggableManager
+import markdown
class Book(models.Model):
title = models.CharField(max_length=200)
@@ -13,8 +14,13 @@ class Book(models.Model):
read_date = models.DateTimeField()
isbn = models.CharField(max_length=100, blank=True, null=True)
body_html = models.TextField(null=True, blank=True)
+ read_in = models.TextField(null=True, blank=True)
url = models.CharField(max_length=200, blank=True, null=True)
tags = TaggableManager()
+ pages = models.CharField(max_length=5, blank=True, null=True)
+ publish_date = models.CharField(max_length=40, blank=True, null=True)
+ publish_place = models.CharField(max_length=100, blank=True, null=True)
+ openlib_url = models.CharField(max_length=400, blank=True, null=True)
RATINGS = (
('1', "1 Star"),
('2', "2 Stars"),
@@ -22,7 +28,7 @@ class Book(models.Model):
('4', "4 Stars"),
('5', "5 Stars"),
)
- rating = models.CharField(max_length=1, choices=RATINGS, null=True)
+ rating = models.CharField(max_length=1, choices=RATINGS, null=True, blank=True)
enable_comments = models.BooleanField(default=False)
image = models.FileField(upload_to='book-covers/', null=True, blank=True)
@@ -32,8 +38,11 @@ class Book(models.Model):
def __str__(self):
return self.title
+ def get_absolute_url(self):
+ return "/books/%s" % (self.slug)
+
def get_image_url(self):
- return '/media/%s' % (self.image)
+ return '%sbook-covers/%s.jpg' % (settings.IMAGES_URL, self.slug)
def admin_thumbnail(self):
return force_text('<a href=""><img src="%s" width="100" style="width:100px"></a>' % (self.get_image_url()))
@@ -44,11 +53,12 @@ class Book(models.Model):
self.slug = slugify(self.title[:50])
super(Book, self).save()
+#class Book
class BookHighlight(models.Model):
book = models.ForeignKey(Book)
page = models.PositiveSmallIntegerField(null=True)
- location = models.PositiveSmallIntegerField(null=True)
+ location = models.CharField(max_length=200, blank=True, null=True)
date_added = models.DateTimeField()
body_markdown = models.TextField()
@@ -58,6 +68,9 @@ class BookHighlight(models.Model):
def __str__(self):
return "%s%s" % (self.book.title, self.id)
+ def body_html(self):
+ return markdown.markdown(self.body_markdown, extensions=['extra'], safe_mode=False)
+
class BookNote(BookHighlight):
pass
diff --git a/app/books/retriever.py b/app/books/retriever.py
new file mode 100644
index 0000000..e98f46f
--- /dev/null
+++ b/app/books/retriever.py
@@ -0,0 +1,60 @@
+import requests
+url = "https://www.googleapis.com/books/v1/volumes?q=isbn:%s&key=AIzaSyCCYn5v58R08msS06pRfOzTHANUuG2tSHI" % (book.isbn)
+
+def get_book_isbn(book):
+ print(book.title)
+ if book.author_name != "Instapaper":
+ url = "https://www.googleapis.com/books/v1/volumes?q=%s+inauthor:%s&key=AIzaSyCCYn5v58R08msS06pRfOzTHANUuG2tSHI" % (book.title, book.author_name)
+ r = requests.get(url, timeout=3.001)
+ data = r.json()
+ isbn10 = ""
+ isbn13 = ""
+ for item in data["items"]:
+ for isbn in item["volumeInfo"]["industryIdentifiers"]:
+ print(isbn['identifier'])
+ if isbn['type'] == "ISBN_13":
+ isbn13 = isbn['identifier']
+ elif isbn['type'] == "ISBN_10":
+ isbn10 = isbn['identifier']
+ print(isbn10)
+ return isbn10
+ break
+
+import json
+def get_book_data(book):
+ if book.author_name != "Instapaper":
+ url = "https://openlibrary.org/api/books?bibkeys=ISBN:%s&format=json&jscmd=data" % book.isbn
+ r = requests.get(url, timeout=9.001)
+ j = json.loads(r.text)
+ obj = "ISBN:%s" % book.isbn
+ try:
+ pages = j[obj]['number_of_pages']
+ except:
+ pages = ''
+ try:
+ publish_date = j[obj]['publish_date']
+ except:
+ publish_date = ''
+ try:
+ publish_places = j[obj]['publish_places'][0]['name']
+ except:
+ publish_places = ''
+ try:
+ openlib_url = j[obj]['url']
+ except:
+ openlib_url = ''
+ print("pages " + str(pages))
+ print("date " + publish_date)
+ print("place " + publish_places)
+ print("openlib " + openlib_url)
+ book.pages = pages
+ book.publish_date = publish_date
+ book.publish_place = publish_places
+ book.openlib_url = openlib_url
+ book.save()
+
+def fetch_image(book):
+ path = '%s/%s/%s.jpg' %(settings.IMAGES_ROOT, 'book-covers', book.slug)
+ r = requests.get(book.image, timeout=6.001)
+ im = io.StringIO(r.content) # constructs a StringIO holding the image
+ im.save(path)
diff --git a/app/books/urls.py b/app/books/urls.py
new file mode 100644
index 0000000..003d6d6
--- /dev/null
+++ b/app/books/urls.py
@@ -0,0 +1,9 @@
+from django.conf.urls import *
+from django.views.generic.base import RedirectView
+
+urlpatterns = patterns('',
+ #url(r'(?P<slug>[-\w]+)/(?P<page>\d+)/$', 'blog.views.entry_list_by_area'),
+ url(r'(?P<page>\d+)/$', 'books.views.book_list'),
+ url(r'(?P<slug>[-\w]+)/$', 'books.views.book_detail'),
+ url(r'^$', RedirectView.as_view(url="/books/1/")),
+)
diff --git a/app/books/views.py b/app/books/views.py
new file mode 100644
index 0000000..7809f17
--- /dev/null
+++ b/app/books/views.py
@@ -0,0 +1,32 @@
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template import RequestContext
+from django.http import Http404
+from django.conf import settings
+# from django.views.generic import ListView
+
+from books.models import Book
+from locations.models import Region, Country
+from photos.models import Photo
+
+
+
+def book_detail(request, slug):
+ obj = get_object_or_404(Book, slug__exact=slug)
+ return render_to_response('details/book.html', {'object': obj}, context_instance=RequestContext(request))
+
+def book_list(request, page):
+ request.page_url = '/book/%d/'
+ request.page = int(page)
+ try:
+ is_build = request.POST['builder']
+ extra_context={
+ 'page':page,
+ 'MEDIA_URL': settings.BAKED_MEDIA_URL
+ }
+ except:
+ extra_context={'page':page}
+ context = {
+ 'object_list': Book.objects.order_by('-read_date').select_related(),
+ 'page': page
+ }
+ return render_to_response("archives/books.html", context, context_instance=RequestContext(request))
diff --git a/app/builder/base.py b/app/builder/base.py
index 7da7df3..62cfad0 100644
--- a/app/builder/base.py
+++ b/app/builder/base.py
@@ -4,7 +4,7 @@ from decimal import Decimal
from django.test.client import Client
from django.template.loader import render_to_string
from django.template import Context
-from django.db.models import get_model
+from django.apps import apps
from django.conf import settings
#from pages.models import PageGenerator
@@ -72,7 +72,7 @@ class BuildWriting(Build):
self.writing_month_archives()
def get_model_querset(self):
- model = get_model('blog', 'entry')
+ model = apps.get_model('blog', 'entry')
qs = model.objects.filter(status__exact=1)
return qs
@@ -95,8 +95,8 @@ class BuildWriting(Build):
self.build_archive_pages(qs, 'jrnl/')
def build_region_archive_pages(self):
- model = get_model('locations', 'Region')
- blog = get_model('blog', 'entry')
+ model = apps.get_model('locations', 'Region')
+ blog = apps.get_model('blog', 'entry')
regions = model.objects.all()
for c in regions:
qs = blog.objects.filter(status__exact=1, location__state__country__lux_region=c.id).order_by('-pub_date')
@@ -104,8 +104,8 @@ class BuildWriting(Build):
self.build_archive_pages(qs, path)
def build_country_archive_pages(self):
- model = get_model('locations', 'Country')
- blog = get_model('blog', 'entry')
+ model = apps.get_model('locations', 'Country')
+ blog = apps.get_model('blog', 'entry')
countries = model.objects.filter(visited=True)
for c in countries:
qs = blog.objects.filter(status__exact=1, location__state__country=c).order_by('-pub_date')
@@ -113,7 +113,7 @@ class BuildWriting(Build):
self.build_archive_pages(qs, path)
def writing_year_archives(self):
- entry = get_model('blog', 'entry')
+ entry = apps.get_model('blog', 'entry')
years = entry.objects.dates('pub_date', 'year')
for year in years:
year = year.strftime('%Y')
@@ -124,7 +124,7 @@ class BuildWriting(Build):
self.write_file(fpath, t)
def writing_month_archives(self):
- entry = get_model('blog', 'entry')
+ entry = apps.get_model('blog', 'entry')
months = entry.objects.dates('pub_date', 'month')
for m in months:
year = m.strftime('%Y')
@@ -139,8 +139,8 @@ class BuildWriting(Build):
self.write_file(fpath, t)
def build_homepage(self):
- obj = get_model('blog', 'homepagecurrator').objects.get(pk=1)
- recent = get_model('blog', 'entry').objects.filter(status__exact=1)[:4]
+ obj = apps.get_model('blog', 'homepagecurrator').objects.get(pk=1)
+ recent = apps.get_model('blog', 'entry').objects.filter(status__exact=1)[:4]
template = obj.template_name
c = Context({'homepage': obj, 'recent': recent, 'MEDIA_URL': settings.BAKED_MEDIA_URL, 'IMAGES_URL': settings.BAKED_IMAGES_URL})
t = render_to_string(template, c).encode('utf-8')
@@ -158,11 +158,11 @@ class BuildPhotos(Build):
self.build_js()
def build_photo_archive_pages(self):
- qs = get_model('photos', 'PhotoGallery').objects.all()
+ qs = apps.get_model('photos', 'PhotoGallery').objects.all()
self.build_archive_pages(qs, 'photos/', 18)
def build_detail_pages(self):
- qs = get_model('photos', 'PhotoGallery').objects.all()
+ qs = apps.get_model('photos', 'PhotoGallery').objects.all()
for photo in qs:
c = Context({'object': photo, 'MEDIA_URL':
settings.BAKED_MEDIA_URL, 'IMAGES_URL': settings.BAKED_IMAGES_URL})
@@ -188,14 +188,14 @@ class BuildProjects(Build):
def get_projects(self):
all_proj = []
- projects = get_model('projects', 'Project').objects.filter(status__exact=1).order_by('-pub_date')
+ projects = apps.get_model('projects', 'Project').objects.filter(status__exact=1).order_by('-pub_date')
for proj in projects:
row = {'slug': proj.slug, 'name': proj.model_name}
all_proj.append(row)
return all_proj
def build_project_archive(self):
- qs = get_model('projects', 'Project').objects.filter(status__exact=1).order_by('-pub_date')
+ qs = apps.get_model('projects', 'Project').objects.filter(status__exact=1).order_by('-pub_date')
c = Context({'object_list': qs, 'MEDIA_URL': settings.BAKED_MEDIA_URL,
'IMAGES_URL': settings.BAKED_IMAGES_URL})
t = render_to_string('archives/projects.html', c).encode('utf-8')
@@ -204,7 +204,7 @@ class BuildProjects(Build):
def build_project_details(self):
projects = self.get_projects()
for proj in projects:
- model = get_model('projects', proj['name'])
+ model = apps.get_model('projects', proj['name'])
if proj['name'] == 'NationalParks':
qs = model.objects.filter(visited__exact=True).order_by("-date_visited_begin")
else:
@@ -224,7 +224,7 @@ class BuildProjects(Build):
functions it is.
"""
def build_gifs(self):
- qs = get_model('projects', 'AnimatedGif').objects.all()
+ qs = apps.get_model('projects', 'AnimatedGif').objects.all()
for gif in qs:
c = Context({
'object': gif,
@@ -236,7 +236,7 @@ class BuildProjects(Build):
self.write_file(path, t)
def build_project_data(self):
- model = get_model('projects', 'NationalParks')
+ model = apps.get_model('projects', 'NationalParks')
for park in model.objects.filter(visited__exact=True):
path = 'projects/data/natparks/'
json = park.mpoly.json
@@ -260,7 +260,7 @@ class BuildSitemap(Build):
class BuildWritingFeed(Build):
def build(self):
- qs = get_model('blog', 'entry').objects.filter(status__exact=1).order_by('-pub_date')[:20]
+ qs = apps.get_model('blog', 'entry').objects.filter(status__exact=1).order_by('-pub_date')[:20]
c = Context({'object_list': qs, 'SITE_URL': settings.SITE_URL})
t = render_to_string('feed.xml', c).encode('utf-8')
fpath = '%s' % ('rss/',)
@@ -268,7 +268,7 @@ class BuildWritingFeed(Build):
class BuildPages(Build):
def build(self):
- model = get_model('pages', 'page')
+ model = apps.get_model('pages', 'page')
pages = model.objects.all()
for page in pages:
c = Context({'object':page,'SITE_URL':settings.SITE_URL, 'MEDIA_URL':settings.BAKED_MEDIA_URL})
@@ -280,10 +280,10 @@ class BuildPages(Build):
class BuildMap(Build):
def build(self):
- qs = get_model('blog', 'entry').objects.filter(status__exact=1)
- cl = get_model('locations', 'Country').objects.filter(visited=True).exclude(name='default')
- rl = get_model('locations', 'Region').objects.all()
- rtl = get_model('locations', 'Route').objects.all()
+ qs = apps.get_model('blog', 'entry').objects.filter(status__exact=1)
+ cl = apps.get_model('locations', 'Country').objects.filter(visited=True).exclude(name='default')
+ rl = apps.get_model('locations', 'Region').objects.all()
+ rtl = apps.get_model('locations', 'Route').objects.all()
c = Context({
'object_list': qs,
'country_list': cl,
@@ -311,7 +311,7 @@ class BuildMap(Build):
# Back up entries to markdown text files which are then stored in dropbox and git
class EntryBak(Build):
def get_model_querset(self):
- model = get_model('blog', 'entry')
+ model = apps.get_model('blog', 'entry')
qs = model.objects.filter(status__exact=1)
return qs
@@ -327,3 +327,25 @@ class EntryBak(Build):
path = "%szbak/posts/%s_%s.txt" %(settings.PROJ_ROOT, (obj.pub_date.strftime("%Y-%m-%d").lower()), obj.slug)
t = render_to_string('details/entry-bak.txt', c).encode('utf-8')
self.write_file(path, t)
+
+
+
+class BuildBooks(Build):
+ def build(self):
+ self.build_detail_pages()
+ self.build_book_archive_pages()
+
+
+ def build_book_archive_pages(self):
+ qs = apps.get_model('books', 'Book').objects.all().order_by('-read_date').select_related()
+ self.build_archive_pages(qs, 'books/', 18)
+
+
+ def build_detail_pages(self):
+ qs = apps.get_model('books', 'Book').objects.all().order_by('-read_date').select_related()
+ for book in qs:
+ c = Context({'object': book,})
+ t = render_to_string('details/book.html', c).encode('utf-8')
+ path = 'books/'
+ slug = '%s' % (book.slug)
+ self.write_file(path, t, 'html', slug)
diff --git a/app/builder/views.py b/app/builder/views.py
index 9e2bd08..0d69c60 100644
--- a/app/builder/views.py
+++ b/app/builder/views.py
@@ -1,7 +1,7 @@
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.conf import settings
-from builder.base import BuildWriting, BuildWritingFeed, BuildMap, BuildPhotos, BuildProjects, BuildSitemap, BuildPages
+from builder.base import BuildWriting, BuildWritingFeed, BuildMap, BuildPhotos, BuildProjects, BuildSitemap, BuildPages, BuildBooks
#from pages.models import PageGenerator
options = {
@@ -40,6 +40,9 @@ def do_build(request):
elif section == 'homepage':
context = {'message': 'Writing index to Disk'}
BuildWriting().build_homepage()
+ elif section == 'buildbooks':
+ context = {'message': 'Writing Book Pages to Disk'}
+ BuildBooks().build()
elif section == 'scrapeflickr':
from photos.retriever import SyncFlickr
f = SyncFlickr()
diff --git a/config/base_urls.py b/config/base_urls.py
index 4585d21..5388c93 100644
--- a/config/base_urls.py
+++ b/config/base_urls.py
@@ -54,6 +54,7 @@ urlpatterns += patterns('',
(r'^locations/', include('locations.urls')),
(r'^expenses/', include('expenses.urls')),
(r'^photos/', include('photos.urls')),
+ (r'^books/', include('books.urls')),
(r'^field-notes/', include('notes.urls')),
(r'^photo/', include('photos.detail_urls')),
(r'^birds/', include('birds.urls')),
diff --git a/design/sass/_books.scss b/design/sass/_books.scss
new file mode 100644
index 0000000..3ed8e2e
--- /dev/null
+++ b/design/sass/_books.scss
@@ -0,0 +1,57 @@
+.book {
+ margin-top: 2em;
+ @include breakpoint(gamma) {
+ margin-top: 3em;
+ }
+}
+.book:after {
+ @include constrain_narrow;
+ color: white;
+ @include faded_line_after;
+ margin-top: 2em;
+ @include breakpoint(gamma) {
+ margin-top: 3em;
+ }
+}
+.book-metadata {
+ @extend %clearfix;
+ text-align: left;
+ dd {
+ display: inline;
+ margin: 0;
+ }
+ dd:after{
+ display: block;
+ content: '';
+ }
+ dt{
+ display: inline-block;
+ }
+ dt:after {
+ content: ":";
+ }
+}
+.book-title {
+ @include constrain_narrow;
+ line-height: 1.3;
+}
+.book-cover {
+}
+.thoughts, .highlights, .meta-cover {
+ @include constrain_narrow;
+ text-align: left;
+ p {
+ max-width: 100%;
+ }
+}
+.thoughts h5, .highlights h4, .meta-cover h5 {
+ font-weight: 500;
+ letter-spacing: 1px;
+ margin: 3em 0 .5em 0;
+ @include generic_sans;
+ @include smcaps;
+ @include fontsize(14);
+}
+.highlights .foot {
+ @include fontsize(14);
+}
diff --git a/design/sass/_queries.scss b/design/sass/_queries.scss
index cbfc3c3..1d7f3ef 100644
--- a/design/sass/_queries.scss
+++ b/design/sass/_queries.scss
@@ -1,5 +1,6 @@
$breakpoint-alpha: 38em;
$breakpoint-beta: 49em;
+$breakpoint-book-beta: 620px;
$breakpoint-gamma: 56em;
$breakpoint-delta: 73.125em;
$breakpoint-epsilon: 79.625em;
@@ -11,6 +12,9 @@ $breakpoint-epsilon: 79.625em;
@else if $point == "beta" {
@media screen and (min-width: $breakpoint-beta) { @content; }
}
+ @else if $point == "book-beta" {
+ @media screen and (min-width: $breakpoint-book-beta) { @content; }
+ }
@else if $point == "gamma" {
@media screen and (min-width: $breakpoint-gamma) { @content; }
}
diff --git a/design/templates/admin/buttons.html b/design/templates/admin/buttons.html
index 5046695..9e36e03 100644
--- a/design/templates/admin/buttons.html
+++ b/design/templates/admin/buttons.html
@@ -32,7 +32,7 @@
<div class="module" id="recent-actions-module">
<div class="grp-module" id="grp-recent-actions-module">
- <h2>Publish Site</h2>
+ <h2>Publish Site</h2>, BuildBooks
{% if message %}
<ul class="messagelist">
<li style="font-weight: bold; color: red;">{{message}}...</li>
@@ -47,10 +47,9 @@
<li class="item"><a href="/admin/build/build?id=photo_galleries">Build Photo Galleries</a></li>
<li class="item"><a href="/admin/build/build?id=projects">Build Project Pages</a></li>
<li class="item"><a href="/admin/build/build?id=map">Build Map</a></li>
+ <li class="item"><a href="/admin/build/build?id=buildbooks">Build Books</a></li>
<li class="item"><a href="/admin/build/build?id=pages">Build All Pages</a></li>
- <li class="item"><a href="/admin/build/build?id=feed">Build RSS Feed</a></li>
<li class="item"><a href="/admin/build/build?id=sitemap">Build Sitemap</a></li>
- <li class="item"><a href="/admin/build/build?id=all">Build Entire Site</a></li>
<li class="item"><a href="/admin/build/build?id=scrapeflickr">Scrape Flickr</a></li>
</ul>
</div>
diff --git a/design/templates/archives/books.html b/design/templates/archives/books.html
new file mode 100644
index 0000000..f462a3a
--- /dev/null
+++ b/design/templates/archives/books.html
@@ -0,0 +1,55 @@
+{% extends 'base.html' %}
+{% load typogrify_tags %}
+{% load html5_datetime %}
+{% load pagination_tags %}
+{% block pagetitle %} Books | luxagraf {% endblock %}
+{% block metadescription %}Books and thoughts on them. {% endblock %}
+{%block bodyid%}class="books" id="books-archive"{%endblock%}
+
+{% block primary %}
+ <ul class="bl" id="breadcrumbs" itemscope itemtype="http://data-vocabulary.org/Breadcrumb">
+ <li><a href="/" title="luxagraf homepage" itemprop="url"><span itemprop="title">Home</span></a> &rarr; </li>
+ <li>Books</li>
+ </ul>
+ <main role="main">
+ <h1>Books</h1>
+ <p>I wear glasses because as a child I would stay up late, covers pulled over my head, reading by the dim light of a dying flashlight. At least that's what an eye doctor told me when I was younger. Probably a load of crap, but I still love reading and I still often do it by poor light far later in the night than is reasonable.</p>
+ <p>A few years ago, I realized I was forgetting the things I'd read. Forgetting the things they had made me think of, things I'd learned, bits I wanted to remember. So I started taking notes while reading, usually with a pen and paper, but sometimes just photographing a page and using OCR to save it to a text file. I wanted to remember, to recall.</p>
+ <p>And of course since I have all this stuff in text files I thought might as well put it online. Thanks to some APIs out there it isn't hard to get all the info you need about a book. And well, here you have it, books I've read and things I've thought about them.</p>
+ {% autopaginate object_list 24 %}{% for object in object_list %}
+ <article itemscope itemtype="http://schema.org/Book">
+ {% if object.image %}<img itemprop="image" src="{{object.get_image_url}}" alt="cover art: red horse, city in background"/>{%endif%}
+ <h2 itemprop="name"><a href="{{object.get_absolute_url}}">{{object.title|safe|amp|smartypants}}</a></h2>
+ <h4 itemprop="author">{{object.author_name}}</h4>
+ <span itemprop="isbn">{{object.isbn}}</span>
+ <div itemprop="review" itemscope itemtype="http://schema.org/Review">
+ {% if object.rating %}<span itemprop="reviewRating">{{object.rating}}</span>stars{%endif%}
+ <span class="hide" itemprop="author">Scott Gilbertson</span>
+ <meta itemprop="datePublished" content="{{object.read_date|date:'F Y'}}"><span>Read in: {{object.read_date|date:"F Y"}}</span>
+ <div itemprop="reviewBody">{{object.body_html|safe|amp|smartypants|urlizetrunc:45 }}</div>
+ </article>
+{% endfor %}
+ </main>
+{% endblock %}
+
+
+ title = models.CharField(max_length=200)
+ author_name = models.CharField(max_length=200)
+ slug = models.CharField(max_length=50)
+ year_pub = models.CharField(max_length=4, blank=True, null=True)
+ read_date = models.DateTimeField()
+ isbn = models.CharField(max_length=100, blank=True, null=True)
+ body_html = models.TextField(null=True, blank=True)
+ url = models.CharField(max_length=200, blank=True, null=True)
+ tags = TaggableManager()
+ RATINGS = (
+ ('1', "1 Star"),
+ ('2', "2 Stars"),
+ ('3', "3 Stars"),
+ ('4', "4 Stars"),
+ ('5', "5 Stars"),
+ )
+ rating = models.CharField(max_length=1, choices=RATINGS, null=True)
+ enable_comments = models.BooleanField(default=False)
+ image = models.FileField(upload_to='book-covers/', null=True, blank=True)
+
diff --git a/design/templates/details/book.html b/design/templates/details/book.html
new file mode 100644
index 0000000..46ac10b
--- /dev/null
+++ b/design/templates/details/book.html
@@ -0,0 +1,47 @@
+{% extends 'base.html' %}
+{% load typogrify_tags %}
+{%block bodyid%}id="book-page"{%endblock%}
+{% block primary %}<ul class="bl" id="breadcrumbs" itemscope itemtype="http://data-vocabulary.org/Breadcrumb">
+ <li><a href="/" title="luxagraf homepage" itemprop="url"><span itemprop="title">Home</span></a> &rarr; </li>
+ <li>Readings</li>
+ </ul>
+ <main role="main" itemprop="mainEntity" itemscope itemtype="http://schema.org/Book">
+ <h1 class="book-title" itemprop="name">{{object.title|smartypants|widont|safe}}</h1>
+ <div class="meta-cover">
+ <img src="{{object.get_image_url}}" alt="{{object.title}} cover" class="book-cover" />
+ <h5>Meta</h5>
+ <dl class="book-metadata">
+ <dt>Author</dt>
+ <dd>{{object.author_name}}<dd>
+ {% if object.publish_place %}<dt>Published</dt>
+ <dd>{{object.publish_place}}{%endif%}{%if object.publish_date%}, {{object.publish_date}}</dd>{%endif%}
+ {% if object.pages %}<dt>Pages</dt>
+ <dd>{{object.pages}}</dd>{%endif%}
+ {% if object.isbn %}<dt>ISBN</dt>
+ <dd>{{object.isbn}}</dd>{%endif%}
+ {% if object.read_in %}<dt>Read</dt>
+ <dd>{{object.read_in}}</dd>{%endif%}
+ </dl>
+ </div>{%if object.body_html%}
+ <div class="thoughts" itemprop="review" itemscope itemtype="http://schema.org/Review">
+ <h5>Notes</h5>
+ <span class="hide" itemprop="reviewRating">{{object.rating}}</span>
+ <meta itemprop="author" value="Scott Gilbertson" />
+ <meta itemprop="datePublished" content="{{object.read_date|date:"c"}}">
+ <div itemprop="reviewBody">{{object.body_html|safe|smartypants|widont}}</div>
+
+ </div>{%endif%}
+ {% if object.bookhighlight_set.all %}
+ <div class="highlights">
+ <h4>Highlights:</h4>
+ {% for object in object.bookhighlight_set.all reversed %}
+ <article class="h-entry hentry post--article book" itemscope itemType="http://schema.org/Article">
+ {{object.body_html|safe|amp|smartypants}}
+ <p class="foot">page: {{object.page}} <small>kindle location: {{object.location|cut:"["|cut:"]"}}</small></p>
+ </article>
+ {% endfor %}
+ </div>
+ {%endif%}
+</main>
+{% endblock %}
+