diff --git a/ram/bookshelf/admin.py b/ram/bookshelf/admin.py index ae9392c..9d0bcd1 100644 --- a/ram/bookshelf/admin.py +++ b/ram/bookshelf/admin.py @@ -60,53 +60,57 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin): "published", ) autocomplete_fields = ("authors", "publisher", "shop") - readonly_fields = ("creation_time", "updated_time") + readonly_fields = ("invoices", "creation_time", "updated_time") search_fields = ("title", "publisher__name", "authors__last_name") list_filter = ("publisher__name", "authors") - fieldsets = ( - ( - None, - { - "fields": ( - "published", - "title", - "authors", - "publisher", - "ISBN", - "language", - "number_of_pages", - "publication_year", - "description", - "tags", - ) - }, - ), - ( - "Purchase data", - { - "fields": ( - "shop", - "purchase_date", - "price", - ) - }, - ), - ( - "Notes", - {"classes": ("collapse",), "fields": ("notes",)}, - ), - ( - "Audit", - { - "classes": ("collapse",), - "fields": ( - "creation_time", - "updated_time", - ), - }, - ), - ) + def get_fieldsets(self, request, obj=None): + fieldsets = ( + ( + None, + { + "fields": ( + "published", + "title", + "authors", + "publisher", + "ISBN", + "language", + "number_of_pages", + "publication_year", + "description", + "tags", + ) + }, + ), + ( + "Purchase data", + { + "fields": ( + "shop", + "purchase_date", + "price", + ) + }, + ), + ( + "Notes", + {"classes": ("collapse",), "fields": ("notes",)}, + ), + ( + "Audit", + { + "classes": ("collapse",), + "fields": ( + "creation_time", + "updated_time", + ), + }, + ), + ) + if obj and obj.invoice.count() > 0: + fieldsets[1][1]["fields"] += ("invoices",) + return fieldsets def get_form(self, request, obj=None, **kwargs): form = super().get_form(request, obj, **kwargs) @@ -115,6 +119,14 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin): ) return form + @admin.display(description="Invoices") + def invoices(self, obj): + html = "
".join( + "{}".format( + i.file.url, i + ) for i in obj.invoice.all()) + return format_html(html) + @admin.display(description="Publisher") def get_publisher(self, obj): return obj.publisher.name @@ -214,48 +226,52 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin): search_fields = ("manufacturer__name", "years", "scales__scale") list_filter = ("manufacturer__name", "publication_year", "scales__scale") - fieldsets = ( - ( - None, - { - "fields": ( - "published", - "manufacturer", - "years", - "scales", - "ISBN", - "language", - "number_of_pages", - "publication_year", - "description", - "tags", - ) - }, - ), - ( - "Purchase data", - { - "fields": ( - "purchase_date", - "price", - ) - }, - ), - ( - "Notes", - {"classes": ("collapse",), "fields": ("notes",)}, - ), - ( - "Audit", - { - "classes": ("collapse",), - "fields": ( - "creation_time", - "updated_time", - ), - }, - ), - ) + def get_fieldsets(self, request, obj=None): + fieldsets = ( + ( + None, + { + "fields": ( + "published", + "manufacturer", + "years", + "scales", + "ISBN", + "language", + "number_of_pages", + "publication_year", + "description", + "tags", + ) + }, + ), + ( + "Purchase data", + { + "fields": ( + "purchase_date", + "price", + ) + }, + ), + ( + "Notes", + {"classes": ("collapse",), "fields": ("notes",)}, + ), + ( + "Audit", + { + "classes": ("collapse",), + "fields": ( + "creation_time", + "updated_time", + ), + }, + ), + ) + if obj and obj.invoice.count() > 0: + fieldsets[1][1]["fields"] += ("invoices",) + return fieldsets def get_form(self, request, obj=None, **kwargs): form = super().get_form(request, obj, **kwargs) diff --git a/ram/bookshelf/migrations/0023_delete_basebookdocument.py b/ram/bookshelf/migrations/0023_delete_basebookdocument.py index bd7cdc3..cd3123e 100644 --- a/ram/bookshelf/migrations/0023_delete_basebookdocument.py +++ b/ram/bookshelf/migrations/0023_delete_basebookdocument.py @@ -7,6 +7,7 @@ class Migration(migrations.Migration): dependencies = [ ("bookshelf", "0022_basebook_shop"), + ("repository", "0001_initial"), ] operations = [ diff --git a/ram/metadata/migrations/0024_remove_genericdocument_tags_delete_decoderdocument_and_more.py b/ram/metadata/migrations/0024_remove_genericdocument_tags_delete_decoderdocument_and_more.py index 39a21f0..dc7ea56 100644 --- a/ram/metadata/migrations/0024_remove_genericdocument_tags_delete_decoderdocument_and_more.py +++ b/ram/metadata/migrations/0024_remove_genericdocument_tags_delete_decoderdocument_and_more.py @@ -7,6 +7,7 @@ class Migration(migrations.Migration): dependencies = [ ("metadata", "0023_shop"), + ("repository", "0001_initial"), ] operations = [ diff --git a/ram/ram/models.py b/ram/ram/models.py index 9153bf6..f389de3 100644 --- a/ram/ram/models.py +++ b/ram/ram/models.py @@ -29,10 +29,6 @@ class Document(models.Model): upload_to="files/", storage=DeduplicatedStorage(), ) - private = models.BooleanField( - default=False, - help_text="Document will be visible only to logged users", - ) creation_time = models.DateTimeField(auto_now_add=True) updated_time = models.DateTimeField(auto_now=True) @@ -61,8 +57,17 @@ class Document(models.Model): 'Link'.format(self.file.url) ) + +class PrivateDocument(Document): + private = models.BooleanField( + default=False, + help_text="Document will be visible only to logged users", + ) objects = PublicManager() + class Meta: + abstract = True + class Image(models.Model): order = models.PositiveIntegerField(default=0, blank=False, null=False) diff --git a/ram/repository/admin.py b/ram/repository/admin.py index e8284a3..c65bb54 100644 --- a/ram/repository/admin.py +++ b/ram/repository/admin.py @@ -3,7 +3,8 @@ from django.contrib import admin from ram.admin import publish, unpublish from repository.models import ( GenericDocument, - BaseBookDocument, + InvoiceDocument, + # BaseBookDocument, DecoderDocument, RollingStockDocument ) @@ -54,6 +55,51 @@ class GenericDocumentAdmin(admin.ModelAdmin): actions = [publish, unpublish] +@admin.register(InvoiceDocument) +class InvoiceDocumentAdmin(admin.ModelAdmin): + readonly_fields = ("size", "creation_time", "updated_time") + list_display = ( + "__str__", + "description", + "size", + "download", + ) + search_fields = ( + "description", + "file", + ) + autocomplete_fields = ("rolling_stock", "book", "catalog") + fieldsets = ( + ( + None, + { + "fields": ( + "description", + "rolling_stock", + "book", + "catalog", + "file", + "size", + ) + }, + ), + ( + "Notes", + {"classes": ("collapse",), "fields": ("notes",)}, + ), + ( + "Audit", + { + "classes": ("collapse",), + "fields": ( + "creation_time", + "updated_time", + ), + }, + ), + ) + + # @admin.register(BaseBookDocument) # class BookDocumentAdmin(admin.ModelAdmin): # readonly_fields = ("size",) diff --git a/ram/repository/migrations/0001_initial.py b/ram/repository/migrations/0001_initial.py index 4ac6917..0aff19b 100644 --- a/ram/repository/migrations/0001_initial.py +++ b/ram/repository/migrations/0001_initial.py @@ -66,8 +66,6 @@ def migrate_book(apps, schema_editor): class Migration(migrations.Migration): - initial = True - dependencies = [ ("bookshelf", "0022_basebook_shop"), ("metadata", "0023_shop"), diff --git a/ram/repository/migrations/0003_alter_basebookdocument_options_and_more.py b/ram/repository/migrations/0003_alter_basebookdocument_options_and_more.py new file mode 100644 index 0000000..bd32b29 --- /dev/null +++ b/ram/repository/migrations/0003_alter_basebookdocument_options_and_more.py @@ -0,0 +1,66 @@ +# Generated by Django 5.1.4 on 2025-02-09 15:16 + +import ram.utils +import tinymce.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookshelf", "0023_delete_basebookdocument"), + ("repository", "0002_alter_decoderdocument_options_and_more"), + ("roster", "0036_delete_rollingstockdocument"), + ] + + operations = [ + migrations.AlterModelOptions( + name="basebookdocument", + options={"verbose_name_plural": "Bookshelf Documents"}, + ), + migrations.AlterModelOptions( + name="genericdocument", + options={"verbose_name_plural": "Generic documents"}, + ), + migrations.CreateModel( + name="InvoiceDocument", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("description", models.CharField(blank=True, max_length=128)), + ( + "file", + models.FileField( + storage=ram.utils.DeduplicatedStorage(), upload_to="files/" + ), + ), + ("creation_time", models.DateTimeField(auto_now_add=True)), + ("updated_time", models.DateTimeField(auto_now=True)), + ("private", models.BooleanField(default=True, editable=False)), + ("notes", tinymce.models.HTMLField(blank=True)), + ( + "book", + models.ManyToManyField( + blank=True, related_name="invoice", to="bookshelf.basebook" + ), + ), + ( + "rolling_stock", + models.ManyToManyField( + blank=True, related_name="invoice", to="roster.rollingstock" + ), + ), + ], + options={ + "verbose_name": "Invoice", + "verbose_name_plural": "Invoices", + }, + ), + ] diff --git a/ram/repository/migrations/0004_invoicedocument_catalog_alter_invoicedocument_book.py b/ram/repository/migrations/0004_invoicedocument_catalog_alter_invoicedocument_book.py new file mode 100644 index 0000000..74e83a1 --- /dev/null +++ b/ram/repository/migrations/0004_invoicedocument_catalog_alter_invoicedocument_book.py @@ -0,0 +1,28 @@ +# Generated by Django 5.1.4 on 2025-02-09 17:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookshelf", "0023_delete_basebookdocument"), + ("repository", "0003_alter_basebookdocument_options_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="invoicedocument", + name="catalog", + field=models.ManyToManyField( + blank=True, related_name="invoice", to="bookshelf.catalog" + ), + ), + migrations.AlterField( + model_name="invoicedocument", + name="book", + field=models.ManyToManyField( + blank=True, related_name="invoice", to="bookshelf.book" + ), + ), + ] diff --git a/ram/repository/models.py b/ram/repository/models.py index 0c31e7e..b753e7d 100644 --- a/ram/repository/models.py +++ b/ram/repository/models.py @@ -1,30 +1,43 @@ from django.db import models -from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes import fields from tinymce import models as tinymce -from ram.models import Document +from ram.models import PrivateDocument from metadata.models import Decoder, Tag from roster.models import RollingStock -from bookshelf.models import BaseBook +from bookshelf.models import Book, Catalog, BaseBook -class GenericDocument(Document): +class GenericDocument(PrivateDocument): notes = tinymce.HTMLField(blank=True) tags = models.ManyToManyField(Tag, blank=True, related_name="document") class Meta: - verbose_name_plural = "Generic Documents" + verbose_name_plural = "Generic documents" -class InvoiceDocument(Document): - content_type = models.ForeignKey(ContentType) - object_id = models.PositiveIntegerField() - content_object = fields.GenericForeignKey("content_type", "object_id") +class InvoiceDocument(PrivateDocument): + private = models.BooleanField(default=True, editable=False) + rolling_stock = models.ManyToManyField( + RollingStock, related_name="invoice", + blank=True + ) + book = models.ManyToManyField( + Book, related_name="invoice", + blank=True + ) + catalog = models.ManyToManyField( + Catalog, related_name="invoice", + blank=True + ) + notes = tinymce.HTMLField(blank=True) + + class Meta: + verbose_name = "Invoice" + verbose_name_plural = "Invoices" -class DecoderDocument(Document): +class DecoderDocument(PrivateDocument): decoder = models.ForeignKey( Decoder, on_delete=models.CASCADE, related_name="document" ) @@ -38,13 +51,13 @@ class DecoderDocument(Document): ] -class BaseBookDocument(Document): +class BaseBookDocument(PrivateDocument): book = models.ForeignKey( BaseBook, on_delete=models.CASCADE, related_name="document" ) class Meta: - verbose_name_plural = "Documents" + verbose_name_plural = "Bookshelf Documents" constraints = [ models.UniqueConstraint( fields=["book", "file"], @@ -53,7 +66,7 @@ class BaseBookDocument(Document): ] -class RollingStockDocument(Document): +class RollingStockDocument(PrivateDocument): rolling_stock = models.ForeignKey( RollingStock, on_delete=models.CASCADE, related_name="document" ) diff --git a/ram/roster/admin.py b/ram/roster/admin.py index fc665fb..8f94842 100644 --- a/ram/roster/admin.py +++ b/ram/roster/admin.py @@ -8,8 +8,8 @@ from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin from ram.admin import publish, unpublish from ram.utils import generate_csv -from portal.utils import get_site_conf from repository.models import RollingStockDocument +from portal.utils import get_site_conf from roster.models import ( RollingClass, RollingClassProperty, @@ -118,7 +118,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin): RollingStockJournalInline, ) autocomplete_fields = ("rolling_class", "shop") - readonly_fields = ("preview", "creation_time", "updated_time") + readonly_fields = ("preview", "invoices", "creation_time", "updated_time") list_display = ( "__str__", "address", @@ -152,61 +152,65 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin): ' {}'.format(obj.country.flag, obj.country) ) - fieldsets = ( - ( - None, - { - "fields": ( - "preview", - "published", - "rolling_class", - "road_number", - "scale", - "manufacturer", - "item_number", - "set", - "era", - "description", - "production_year", - "tags", - ) - }, - ), - ( - "DCC", - { - "fields": ( - "decoder_interface", - "decoder", - "address", - ) - }, - ), - ( - "Purchase data", - { - "fields": ( - "shop", - "purchase_date", - "price", - ) - }, - ), - ( - "Notes", - {"classes": ("collapse",), "fields": ("notes",)}, - ), - ( - "Audit", - { - "classes": ("collapse",), - "fields": ( - "creation_time", - "updated_time", - ), - }, - ), - ) + def get_fieldsets(self, request, obj=None): + fieldsets = ( + ( + None, + { + "fields": ( + "preview", + "published", + "rolling_class", + "road_number", + "scale", + "manufacturer", + "item_number", + "set", + "era", + "description", + "production_year", + "tags", + ) + }, + ), + ( + "DCC", + { + "fields": ( + "decoder_interface", + "decoder", + "address", + ) + }, + ), + ( + "Purchase data", + { + "fields": ( + "shop", + "purchase_date", + "price", + ) + }, + ), + ( + "Notes", + {"classes": ("collapse",), "fields": ("notes",)}, + ), + ( + "Audit", + { + "classes": ("collapse",), + "fields": ( + "creation_time", + "updated_time", + ), + }, + ), + ) + if obj and obj.invoice.count() > 0: + fieldsets[2][1]["fields"] += ("invoices",) + return fieldsets def get_form(self, request, obj=None, **kwargs): form = super().get_form(request, obj, **kwargs) @@ -215,6 +219,14 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin): ) return form + @admin.display(description="Invoices") + def invoices(self, obj): + html = "
".join( + "{}".format( + i.file.url, i + ) for i in obj.invoice.all()) + return format_html(html) + def download_csv(modeladmin, request, queryset): header = [ "Name", diff --git a/ram/roster/migrations/0036_delete_rollingstockdocument.py b/ram/roster/migrations/0036_delete_rollingstockdocument.py index 01fa429..3234a3e 100644 --- a/ram/roster/migrations/0036_delete_rollingstockdocument.py +++ b/ram/roster/migrations/0036_delete_rollingstockdocument.py @@ -7,6 +7,7 @@ class Migration(migrations.Migration): dependencies = [ ("roster", "0035_alter_rollingstock_shop"), + ("repository", "0001_initial"), ] operations = [ diff --git a/ram/roster/models.py b/ram/roster/models.py index 99ce68d..72fc261 100644 --- a/ram/roster/models.py +++ b/ram/roster/models.py @@ -5,14 +5,12 @@ from django.db import models from django.urls import reverse from django.conf import settings from django.dispatch import receiver -from django.contrib.contenttypes import fields from tinymce import models as tinymce from ram.models import BaseModel, Image, PropertyInstance from ram.utils import DeduplicatedStorage, slugify from ram.managers import PublicManager -from repository.models import InvoiceDocument from metadata.models import ( Scale, Manufacturer, @@ -115,9 +113,6 @@ class RollingStock(BaseModel): null=True, blank=True, ) - invoice = fields.GenericRelation( - app.get_model("repository", "InvoiceDocument"), - InvoiceDocument) tags = models.ManyToManyField( Tag, related_name="rolling_stock", blank=True )