From 9327e01c60b114286f5a142552aae60843029a48 Mon Sep 17 00:00:00 2001 From: luxagraf Date: Sat, 14 Aug 2021 09:39:08 -0400 Subject: initial commit --- app/books/__init__.py | 0 app/books/admin.py | 11 ++++ app/books/build.py | 35 +++++++++++ app/books/migrations/0001_initial.py | 38 ++++++++++++ app/books/migrations/0002_book_other_link.py | 18 ++++++ app/books/migrations/__init__.py | 0 app/books/models.py | 93 ++++++++++++++++++++++++++++ app/books/templates/books/book_detail.html | 62 +++++++++++++++++++ app/books/templates/books/book_list.html | 53 ++++++++++++++++ app/books/urls.py | 24 +++++++ app/books/views.py | 16 +++++ 11 files changed, 350 insertions(+) create mode 100644 app/books/__init__.py create mode 100644 app/books/admin.py create mode 100644 app/books/build.py create mode 100644 app/books/migrations/0001_initial.py create mode 100644 app/books/migrations/0002_book_other_link.py create mode 100644 app/books/migrations/__init__.py create mode 100644 app/books/models.py create mode 100644 app/books/templates/books/book_detail.html create mode 100644 app/books/templates/books/book_list.html create mode 100644 app/books/urls.py create mode 100644 app/books/views.py (limited to 'app/books') diff --git a/app/books/__init__.py b/app/books/__init__.py new file mode 100644 index 0000000..e69de29 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 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('' % (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 %}
+
+

{{object.title|smartypants|widont|safe}}

+ + + +
+
+ {{object.title}} cover +
+
+ +
+ {% if object.isbn %}Borrow{%endif%} + {% if object.afflink %}Buy{%endif%} +
+
{%if object.body_html%} +
+
Notes
+ {{object.rating}} + + +
{{object.body_html|safe|smartypants|widont}}
+
{%endif%} + {% if object.bookhighlight_set.all %} +
+

Highlights:

+ {% for object in object.bookhighlight_set.all %} +
+ +
+ {{object.body_html|safe|amp|smartypants}} +
+

– Page: {{object.page}} {% if object.location %}(Kindle location: {{object.location|cut:"["|cut:"]"}}){%endif%}

+
+ {% endfor %} +
+ {%endif%} +
+{% endblock %} +{% block disclaimer %} +
+

When you buy a book using the link above, I may earn a small affiliate commission.

+
+{% 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 %}
{% autopaginate object_list 24 %} +

Books

+
+

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.

+

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.

+
+ +
{% for object in object_list %} +
+ {% if object.image %}
cover art for {{object.title}} by {{object.author_name}}
{%endif%} +

{{object.title|amp|smartypants|widont|safe}}

+ +
+ {% if object.rating %}{% for i in object.ratings_range %}{% if i <= object.get_rating%}★{%else%}☆{%endif%}{%endfor%}{%endif%} + + Read in: {{object.read_date|date:"F Y"}}
+
+ {% endfor %}
+ +
+{% 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\d+)/$', + views.BookListView.as_view(), + name="list" + ), + path( + r'', + views.BookListView.as_view(), + {'page': 1}, + name="list" + ), + path( + r'', + 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" -- cgit v1.2.3-70-g09d2