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
)