summaryrefslogtreecommitdiff
path: root/app/books
diff options
context:
space:
mode:
authorluxagraf <sng@luxagraf.net>2021-08-14 09:39:08 -0400
committerluxagraf <sng@luxagraf.net>2021-08-14 09:39:08 -0400
commit9327e01c60b114286f5a142552aae60843029a48 (patch)
tree7f353f4299d50013c3b90f7b749fc6ab211b32d4 /app/books
parent0feb366b10cf422f12d18fa753e5d876120f4195 (diff)
initial commit
Diffstat (limited to 'app/books')
-rw-r--r--app/books/__init__.py0
-rw-r--r--app/books/admin.py11
-rw-r--r--app/books/build.py35
-rw-r--r--app/books/migrations/0001_initial.py38
-rw-r--r--app/books/migrations/0002_book_other_link.py18
-rw-r--r--app/books/migrations/__init__.py0
-rw-r--r--app/books/models.py93
-rw-r--r--app/books/templates/books/book_detail.html62
-rw-r--r--app/books/templates/books/book_list.html53
-rw-r--r--app/books/urls.py24
-rw-r--r--app/books/views.py16
11 files changed, 350 insertions, 0 deletions
diff --git a/app/books/__init__.py b/app/books/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/books/__init__.py
diff --git a/app/books/admin.py b/app/books/admin.py
new file mode 100644
index 0000000..8366510
--- /dev/null
+++ b/app/books/admin.py
@@ -0,0 +1,11 @@
+from django.contrib import admin
+from .models import Book
+
+
+@admin.register(Book)
+class BookAdmin(admin.ModelAdmin):
+ list_display = ('title', 'admin_thumbnail', 'isbn', 'author_last_name')
+ search_fields = ['title', 'body_markdown']
+
+ class Media:
+ js = ('next-prev-links.js',)
diff --git a/app/books/build.py b/app/books/build.py
new file mode 100644
index 0000000..486cebc
--- /dev/null
+++ b/app/books/build.py
@@ -0,0 +1,35 @@
+import os
+from django.urls import reverse
+from builder.base import BuildNew
+
+
+class BuildBooks(BuildNew):
+
+ def build(self):
+ self.build_detail_view()
+ self.build_list_view(
+ base_path=reverse("books:list"),
+ paginate_by=24
+ )
+ print("building books")
+
+ def get_model_queryset(self):
+ return self.model.objects.all()
+
+ def build_detail_view(self):
+ '''
+ write out all the expenses for each trip
+ '''
+ for obj in self.get_model_queryset():
+ url = obj.get_absolute_url()
+ path, slug = os.path.split(url)
+ path = '%s/' % path
+ # write html
+ response = self.client.get(url)
+ print(path, slug)
+ self.write_file(path, response.content, filename=slug)
+
+
+def builder():
+ j = BuildBooks("books", "book")
+ j.build()
diff --git a/app/books/migrations/0001_initial.py b/app/books/migrations/0001_initial.py
new file mode 100644
index 0000000..6257e40
--- /dev/null
+++ b/app/books/migrations/0001_initial.py
@@ -0,0 +1,38 @@
+# Generated by Django 3.2.6 on 2021-08-14 12:50
+
+import books.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Book',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('sort_order', models.IntegerField(blank=True, db_index=True)),
+ ('title', models.CharField(max_length=200)),
+ ('author_last_name', models.CharField(max_length=200)),
+ ('author_first_name', models.CharField(max_length=200)),
+ ('slug', models.CharField(max_length=50)),
+ ('isbn', models.CharField(blank=True, max_length=100, null=True)),
+ ('body_markdown', models.TextField(blank=True)),
+ ('body_html', models.TextField(blank=True, null=True)),
+ ('pages', models.CharField(blank=True, max_length=5, null=True)),
+ ('amazon_link', models.CharField(blank=True, max_length=400, null=True)),
+ ('bookshop_link', models.CharField(blank=True, max_length=400, null=True)),
+ ('thriftbooks_link', models.CharField(blank=True, max_length=400, null=True)),
+ ('image', models.FileField(blank=True, null=True, upload_to=books.models.get_upload_path)),
+ ],
+ options={
+ 'ordering': ['sort_order'],
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/app/books/migrations/0002_book_other_link.py b/app/books/migrations/0002_book_other_link.py
new file mode 100644
index 0000000..bf64351
--- /dev/null
+++ b/app/books/migrations/0002_book_other_link.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.6 on 2021-08-14 13:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('books', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='book',
+ name='other_link',
+ field=models.CharField(blank=True, max_length=400, null=True),
+ ),
+ ]
diff --git a/app/books/migrations/__init__.py b/app/books/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/app/books/migrations/__init__.py
diff --git a/app/books/models.py b/app/books/models.py
new file mode 100644
index 0000000..c8eb041
--- /dev/null
+++ b/app/books/models.py
@@ -0,0 +1,93 @@
+import os
+from PIL import Image
+from django.db import models
+from django.db.models.signals import post_save
+from django.contrib.sitemaps import Sitemap
+from django.dispatch import receiver
+from django.urls import reverse
+from django.apps import apps
+from django.utils.html import format_html
+from django.conf import settings
+
+# allows for manually ordering items:
+from orderable.models import Orderable
+
+from media.utils import resize_image
+from utils.util import markdown_to_html
+
+
+def get_upload_path(self, filename):
+ return "images/book-covers/%s" % (filename)
+
+
+class Book(Orderable):
+ title = models.CharField(max_length=200)
+ author_last_name = models.CharField(max_length=200)
+ author_first_name = models.CharField(max_length=200)
+ slug = models.CharField(max_length=50)
+ isbn = models.CharField(max_length=100, blank=True, null=True)
+ body_markdown = models.TextField(blank=True)
+ body_html = models.TextField(null=True, blank=True)
+ pages = models.CharField(max_length=5, blank=True, null=True)
+ amazon_link = models.CharField(max_length=400, blank=True, null=True)
+ bookshop_link = models.CharField(max_length=400, blank=True, null=True)
+ thriftbooks_link = models.CharField(max_length=400, blank=True, null=True)
+ other_link = models.CharField(max_length=400, blank=True, null=True)
+ image = models.FileField(upload_to=get_upload_path, null=True, blank=True)
+
+ def __str__(self):
+ return self.title
+
+ def get_absolute_url(self):
+ return reverse("books:detail", kwargs={"slug": self.slug})
+
+ def get_image_url(self):
+ return '%sbook-covers/%s' % (settings.IMAGES_URL, self.image.name.split('/')[-1])
+
+ def get_small_image_url(self):
+ filename, file_extension = os.path.splitext(self.image.path)
+ return "%s/book-covers/%s_small%s" % (settings.IMAGES_URL, filename.split('/')[-1], file_extension)
+
+ @property
+ def get_previous_admin_url(self):
+ n = self.get_previous_by_read_date()
+ return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[n.id])
+
+ @property
+ def get_next_admin_url(self):
+ model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name)
+ try:
+ return reverse('admin:%s_%s_change' % (self._meta.app_label, self._meta.model_name), args=[self.get_next_by_read_date().pk])
+ except model.DoesNotExist:
+ return ''
+
+ def admin_thumbnail(self):
+ return format_html('<img src="%s" width="100" style="width:100px" />' % (self.get_image_url()))
+ admin_thumbnail.short_description = 'Thumbnail'
+
+ def save(self, *args, **kwargs):
+ if self.body_markdown:
+ self.body_html = markdown_to_html(self.body_markdown)
+ super(Book, self).save()
+
+
+@receiver(post_save, sender=Book)
+def post_save_events(sender, update_fields, created, instance, **kwargs):
+ if instance.image:
+ base_path = "%s/%s/" % (settings.MEDIA_ROOT, "/".join(str(i) for i in instance.image.name.split('/')[:-1]))
+ filename, file_extension = os.path.splitext(instance.image.path)
+ img = Image.open(instance.image.path)
+ resize_image(img, None, 160, 78, base_path, "%s_tn%s" % (filename.split('/')[-1], file_extension))
+ resize_image(img, None, 650, 78, base_path, "%s_small%s" % (filename.split('/')[-1], file_extension))
+
+
+class BookSitemap(Sitemap):
+ changefreq = "never"
+ priority = 0.7
+ protocol = "https"
+
+ def items(self):
+ return Book.objects.filter(is_public=True).exclude(body_markdown='')
+
+ def lastmod(self, obj):
+ return obj.read_date
diff --git a/app/books/templates/books/book_detail.html b/app/books/templates/books/book_detail.html
new file mode 100644
index 0000000..98b2c60
--- /dev/null
+++ b/app/books/templates/books/book_detail.html
@@ -0,0 +1,62 @@
+{% extends 'base.html' %}
+{% load typogrify_tags %}
+{%block bodyid%}class="detail"{%endblock%}
+{% block breadcrumbs %}{% include "lib/breadcrumbs.html" with breadcrumbs=breadcrumbs %}{% endblock %}
+ {% block primary %}<main itemprop="mainEntity" itemscope itemtype="http://schema.org/Book">
+ <header class="tight">
+ <h1 class="post-title book-title" itemprop="name">{{object.title|smartypants|widont|safe}}</h1>
+ <h2 class="post-subtitle" itemprop="author" itemscope itemtype="http://schema.org/Person">
+ <meta itemprop="name" content="{{object.author_name}}"/>by {{object.author_name}}</h2>
+
+
+ </header>
+ <div class="book-cover-wrapper">
+ <img src="{{object.get_image_url}}" alt="{{object.title}} cover" />
+ </div>
+ <div class="meta-cover">
+ <dl class="book-metadata">
+
+ {% if object.rating %}<dt>Rating</dt><dd class="book-stars">
+ {% for i in object.ratings_range %}{% if i <= object.get_rating%}&#9733;{%else%}&#9734;{%endif%}{%endfor%}</span></dd>{%endif%}
+ {% if object.read_in %}<dt>Read</dt>
+ <dd>{{object.read_in}}</dd>{%endif%}
+ {% if object.pages %}<dt>Pages</dt>
+ <dd itemprop="numberOfPages">{{object.pages}}</dd>{%endif%}
+ {% if object.publish_date %}<dt>Published</dt>
+ <dd>{%if object.publish_place%}{{object.publish_place}}, {%endif%}{{object.publish_date}}</dd>{%endif%}
+ {% if object.isbn %}<dt>ISBN</dt>
+ <dd>{{object.isbn}}</dd>{%endif%}
+ </dl>
+ <div class="buy-btn-wrapper">
+ {% if object.isbn %}<a class="buy-btn" href="http://worldcat.org/isbn/{{object.isbn}}" title="find {{object.title}} in your local library">Borrow</a>{%endif%}
+ {% if object.afflink %}<a class="buy-btn" href="{{object.afflink}}" title="buy {{object.title}} at Amazon">Buy</a>{%endif%}
+ </div>
+ </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" content="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 %}
+ <div class="h-entry hentry post--article book">
+ <a class="highlink" id="h{{forloop.counter}}-page-{{object.page}}" href="#h{{forloop.counter}}-page-{{object.page}}">&para;</a>
+ <div class="book-highlight">
+ {{object.body_html|safe|amp|smartypants}}
+ </div>
+ <p class="foot">&ndash; Page: {{object.page}} {% if object.location %}(Kindle location: {{object.location|cut:"["|cut:"]"}}){%endif%}</p>
+ </div>
+ {% endfor %}
+ </div>
+ {%endif%}
+ </main>
+{% endblock %}
+{% block disclaimer %}
+<div class="highlights">
+ <p class="dis"><em>When you buy a book using the link above, I may earn a small affiliate commission.</em></p>
+</div>
+{% endblock %}
diff --git a/app/books/templates/books/book_list.html b/app/books/templates/books/book_list.html
new file mode 100644
index 0000000..96d4d1b
--- /dev/null
+++ b/app/books/templates/books/book_list.html
@@ -0,0 +1,53 @@
+{% extends 'base.html' %}
+{% load typogrify_tags %}
+{% load html5_datetime %}
+{% load pagination_tags %}
+{% block pagetitle %} Books | luxagraf {% endblock %}
+{% block metadescription %}Books I've read and thoughts on them. {% endblock %}
+{%block bodyid%}class="archive" id="books-archive"{%endblock%}
+{% block breadcrumbs %}{% include "lib/breadcrumbs.html" with breadcrumbs=breadcrumbs %}{% endblock %}
+{% block primary %}<main>{% autopaginate object_list 24 %}
+ <h1 class="hide">Books</h1>
+ <div class="intro">
+ <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>I've always taken notes while reading, usually with a pen and paper, but sometimes as highlights in the Kindle. And of course since I have all this stuff in text files I thought might as well put it online. So here you have it, books I've read and things I've thought about them.</p>
+ </div>
+ <nav class="pagination">{% paginate %}
+ </nav>
+ <div class="archive-grid book-grid"> {% for object in object_list %}
+ <article class="archive-card" itemscope itemtype="http://schema.org/Book">
+ {% if object.image %}<div class="img-wrapper"><a href="{{object.get_absolute_url}}"><img itemprop="image" src="{{object.get_image_url}}" alt="cover art for {{object.title}} by {{object.author_name}}"/></a></div>{%endif%}
+ <h2 class="post-title" itemprop="name"><a href="{{object.get_absolute_url}}">{{object.title|amp|smartypants|widont|safe}}</a></h2>
+ <h4 class="author" itemprop="author">{{object.author_name}}</h4>
+ <div class="post-summary" itemprop="review" itemscope itemtype="http://schema.org/Review">
+ {% if object.rating %}<span class="book-stars" itemprop="reviewRating">{% for i in object.ratings_range %}{% if i <= object.get_rating%}&#9733;{%else%}&#9734;{%endif%}{%endfor%}</span>{%endif%}
+ <span class="hide" itemprop="author">Scott Gilbertson</span>
+ <meta itemprop="datePublished" content="{{object.read_date|date:'F Y'}}"><span class="read-in">Read in: {{object.read_date|date:"F Y"}}</span></div>
+ </article>
+ {% endfor %}</div>
+ <nav aria-label="page navigation" class="pagination">{% paginate %}
+ </nav>
+ </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/app/books/urls.py b/app/books/urls.py
new file mode 100644
index 0000000..d7ffd33
--- /dev/null
+++ b/app/books/urls.py
@@ -0,0 +1,24 @@
+from django.urls import path, re_path
+
+from . import views
+
+app_name = "books"
+
+urlpatterns = [
+ re_path(
+ r'^(?P<page>\d+)/$',
+ views.BookListView.as_view(),
+ name="list"
+ ),
+ path(
+ r'',
+ views.BookListView.as_view(),
+ {'page': 1},
+ name="list"
+ ),
+ path(
+ r'<str:slug>',
+ views.BookDetailView.as_view(),
+ name='detail',
+ ),
+]
diff --git a/app/books/views.py b/app/books/views.py
new file mode 100644
index 0000000..bf09996
--- /dev/null
+++ b/app/books/views.py
@@ -0,0 +1,16 @@
+from django.views.generic.detail import DetailView
+from utils.views import PaginatedListView, LuxDetailView
+
+from .models import Book
+
+
+class BookListView(PaginatedListView):
+ model = Book
+
+ def get_queryset(self):
+ return Book.objects.filter(is_public=True).order_by('-read_date').select_related()
+
+
+class BookDetailView(LuxDetailView):
+ model = Book
+ slug_field = "slug"