From 3f905877e7ca9c581f835e52fe15af9fcdfede4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniele=20Vigan=C3=B2?= Date: Mon, 2 Oct 2023 22:19:04 +0200 Subject: [PATCH] Extend the bookshelf implementation --- ram/bookshelf/admin.py | 19 +++++--- ram/bookshelf/migrations/0003_bookimage.py | 51 ++++++++++++++++++++++ ram/bookshelf/models.py | 29 ++++++------ ram/bookshelf/serializers.py | 26 +++++++++++ ram/bookshelf/urls.py | 7 +++ ram/bookshelf/views.py | 19 +++++++- ram/ram/models.py | 36 ++++++++++++++- ram/ram/urls.py | 1 + ram/roster/models.py | 43 +++--------------- 9 files changed, 172 insertions(+), 59 deletions(-) create mode 100644 ram/bookshelf/migrations/0003_bookimage.py create mode 100644 ram/bookshelf/serializers.py create mode 100644 ram/bookshelf/urls.py diff --git a/ram/bookshelf/admin.py b/ram/bookshelf/admin.py index 71deb73..34027e0 100644 --- a/ram/bookshelf/admin.py +++ b/ram/bookshelf/admin.py @@ -1,6 +1,15 @@ from django.contrib import admin +from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin -from bookshelf.models import BookProperty, Book, Author, Publisher +from bookshelf.models import BookProperty, BookImage, Book, Author, Publisher + + +class BookImageInline(SortableInlineAdminMixin, admin.TabularInline): + model = BookImage + min_num = 0 + extra = 0 + readonly_fields = ("image_thumbnail",) + classes = ["collapse"] class BookPropertyInline(admin.TabularInline): @@ -10,8 +19,8 @@ class BookPropertyInline(admin.TabularInline): @admin.register(Book) -class BookAdmin(admin.ModelAdmin): - inlines = (BookPropertyInline,) +class BookAdmin(SortableAdminBase, admin.ModelAdmin): + inlines = (BookImageInline, BookPropertyInline,) list_display = ( "title", "get_authors", @@ -19,8 +28,8 @@ class BookAdmin(admin.ModelAdmin): "publication_year", "numbers_of_pages" ) - search_fields = ("title",) - list_filter = ("publisher__name",) + search_fields = ("title", "publisher__name", "authors__last_name") + list_filter = ("publisher__name", "authors") @admin.display(description="Publisher") def get_publisher(self, obj): diff --git a/ram/bookshelf/migrations/0003_bookimage.py b/ram/bookshelf/migrations/0003_bookimage.py new file mode 100644 index 0000000..b272b79 --- /dev/null +++ b/ram/bookshelf/migrations/0003_bookimage.py @@ -0,0 +1,51 @@ +# Generated by Django 4.2.5 on 2023-10-02 10:36 + +from django.db import migrations, models +import django.db.models.deletion +import ram.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookshelf", "0002_book_language_book_numbers_of_pages_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="BookImage", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("order", models.PositiveIntegerField(default=0)), + ( + "image", + models.ImageField( + blank=True, + null=True, + storage=ram.utils.DeduplicatedStorage, + upload_to="images/books/", + ), + ), + ( + "book", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="image", + to="bookshelf.book", + ), + ), + ], + options={ + "ordering": ["order"], + "abstract": False, + }, + ), + ] diff --git a/ram/bookshelf/models.py b/ram/bookshelf/models.py index d151fdf..c170be8 100644 --- a/ram/bookshelf/models.py +++ b/ram/bookshelf/models.py @@ -5,10 +5,9 @@ from django_countries.fields import CountryField from ckeditor_uploader.fields import RichTextUploadingField -from metadata.models import ( - Property, - Tag, -) +from metadata.models import Tag +from ram.utils import DeduplicatedStorage +from ram.models import Image, PropertyInstance class Publisher(models.Model): @@ -62,7 +61,19 @@ class Book(models.Model): # return reverse("rolling_stock", kwargs={"uuid": self.uuid}) -class BookProperty(models.Model): +class BookImage(Image): + book = models.ForeignKey( + Book, on_delete=models.CASCADE, related_name="image" + ) + image = models.ImageField( + upload_to="images/books/", # FIXME, find a better way to replace this + storage=DeduplicatedStorage, + null=True, + blank=True + ) + + +class BookProperty(PropertyInstance): book = models.ForeignKey( Book, on_delete=models.CASCADE, @@ -70,11 +81,3 @@ class BookProperty(models.Model): blank=False, related_name="property", ) - property = models.ForeignKey(Property, on_delete=models.CASCADE) - value = models.CharField(max_length=256) - - def __str__(self): - return self.property.name - - class Meta: - verbose_name_plural = "Properties" diff --git a/ram/bookshelf/serializers.py b/ram/bookshelf/serializers.py new file mode 100644 index 0000000..d331a9c --- /dev/null +++ b/ram/bookshelf/serializers.py @@ -0,0 +1,26 @@ +from rest_framework import serializers +from bookshelf.models import Book, Author, Publisher +from metadata.serializers import TagSerializer + + +class AuthorSerializer(serializers.ModelSerializer): + class Meta: + model = Author + fields = "__all__" + + +class PublisherSerializer(serializers.ModelSerializer): + class Meta: + model = Publisher + fields = "__all__" + + +class BookSerializer(serializers.ModelSerializer): + authors = AuthorSerializer(many=True) + publisher = PublisherSerializer() + tags = TagSerializer(many=True) + + class Meta: + model = Book + fields = "__all__" + read_only_fields = ("creation_time", "updated_time") diff --git a/ram/bookshelf/urls.py b/ram/bookshelf/urls.py new file mode 100644 index 0000000..c9479af --- /dev/null +++ b/ram/bookshelf/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from bookshelf.views import BookList, BookGet + +urlpatterns = [ + path("book/list", BookList.as_view()), + path("book/get/", BookGet.as_view()), +] diff --git a/ram/bookshelf/views.py b/ram/bookshelf/views.py index 91ea44a..57268b7 100644 --- a/ram/bookshelf/views.py +++ b/ram/bookshelf/views.py @@ -1,3 +1,18 @@ -from django.shortcuts import render +from rest_framework.generics import ListAPIView, RetrieveAPIView +from rest_framework.schemas.openapi import AutoSchema -# Create your views here. +from bookshelf.models import Book +from bookshelf.serializers import BookSerializer + + +class BookList(ListAPIView): + queryset = Book.objects.all() + serializer_class = BookSerializer + + +class BookGet(RetrieveAPIView): + queryset = Book.objects.all() + serializer_class = BookSerializer + lookup_field = "uuid" + + schema = AutoSchema(operation_id_base="retrieveBookByUUID") diff --git a/ram/ram/models.py b/ram/ram/models.py index b370728..59749ad 100644 --- a/ram/ram/models.py +++ b/ram/ram/models.py @@ -3,7 +3,7 @@ import os from django.db import models from django.utils.safestring import mark_safe -from ram.utils import DeduplicatedStorage +from ram.utils import DeduplicatedStorage, get_image_preview class Document(models.Model): @@ -28,3 +28,37 @@ class Document(models.Model): return mark_safe( 'Link'.format(self.file.url) ) + + +class Image(models.Model): + order = models.PositiveIntegerField(default=0, blank=False, null=False) + image = models.ImageField( + upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True + ) + + def image_thumbnail(self): + return get_image_preview(self.image.url) + + image_thumbnail.short_description = "Preview" + + def __str__(self): + return "{0}".format(os.path.basename(self.image.name)) + + class Meta: + abstract = True + ordering = ["order"] + + +class PropertyInstance(models.Model): + property = models.ForeignKey( + "metadata.Property", # To avoid circular dependencies + on_delete=models.CASCADE + ) + value = models.CharField(max_length=256) + + def __str__(self): + return self.property.name + + class Meta: + abstract = True + verbose_name_plural = "Properties" diff --git a/ram/ram/urls.py b/ram/ram/urls.py index 12114b6..7760faf 100644 --- a/ram/ram/urls.py +++ b/ram/ram/urls.py @@ -28,6 +28,7 @@ urlpatterns = [ path("api/v1/consist/", include("consist.urls")), path("api/v1/roster/", include("roster.urls")), path("api/v1/dcc/", include("driver.urls")), + path("api/v1/bookshelf/", include("bookshelf.urls")), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG: diff --git a/ram/roster/models.py b/ram/roster/models.py index e2d336e..1573c4c 100644 --- a/ram/roster/models.py +++ b/ram/roster/models.py @@ -1,4 +1,3 @@ -import os import re from uuid import uuid4 from django.db import models @@ -8,10 +7,9 @@ from django.dispatch import receiver from ckeditor_uploader.fields import RichTextUploadingField -from ram.utils import DeduplicatedStorage, get_image_preview -from ram.models import Document +from ram.models import Document, Image, PropertyInstance +from ram.utils import get_image_preview from metadata.models import ( - Property, Scale, Manufacturer, Decoder, @@ -43,7 +41,7 @@ class RollingClass(models.Model): return "{0} {1}".format(self.company, self.identifier) -class RollingClassProperty(models.Model): +class RollingClassProperty(PropertyInstance): rolling_class = models.ForeignKey( RollingClass, on_delete=models.CASCADE, @@ -52,14 +50,6 @@ class RollingClassProperty(models.Model): related_name="property", verbose_name="Class", ) - property = models.ForeignKey(Property, on_delete=models.CASCADE) - value = models.CharField(max_length=256) - - def __str__(self): - return self.property.name - - class Meta: - verbose_name_plural = "Properties" class RollingStock(models.Model): @@ -136,28 +126,13 @@ class RollingStockDocument(Document): unique_together = ("rolling_stock", "file") -class RollingStockImage(models.Model): - order = models.PositiveIntegerField(default=0, blank=False, null=False) +class RollingStockImage(Image): rolling_stock = models.ForeignKey( RollingStock, on_delete=models.CASCADE, related_name="image" ) - image = models.ImageField( - upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True - ) - - def image_thumbnail(self): - return get_image_preview(self.image.url) - - image_thumbnail.short_description = "Preview" - - def __str__(self): - return "{0}".format(os.path.basename(self.image.name)) - - class Meta: - ordering = ["order"] -class RollingStockProperty(models.Model): +class RollingStockProperty(PropertyInstance): rolling_stock = models.ForeignKey( RollingStock, on_delete=models.CASCADE, @@ -165,14 +140,6 @@ class RollingStockProperty(models.Model): null=False, blank=False, ) - property = models.ForeignKey(Property, on_delete=models.CASCADE) - value = models.CharField(max_length=256) - - def __str__(self): - return self.property.name - - class Meta: - verbose_name_plural = "Properties" class RollingStockJournal(models.Model):