Improve docs management and add invoices repo (#51)

* Create a repository app for documents, first step

* Step two (broken)

* Complete the implementation of document repository and add invoices

* Add support for invoices

* Update submodules
This commit is contained in:
2025-02-17 23:25:19 +01:00
committed by GitHub
parent 5088f19b33
commit e80dc604a7
23 changed files with 997 additions and 160 deletions

View File

@@ -8,10 +8,10 @@ from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
from ram.admin import publish, unpublish from ram.admin import publish, unpublish
from ram.utils import generate_csv from ram.utils import generate_csv
from portal.utils import get_site_conf from portal.utils import get_site_conf
from repository.models import BookDocument, CatalogDocument
from bookshelf.models import ( from bookshelf.models import (
BaseBookProperty, BaseBookProperty,
BaseBookImage, BaseBookImage,
BaseBookDocument,
Book, Book,
Author, Author,
Publisher, Publisher,
@@ -28,13 +28,6 @@ class BookImageInline(SortableInlineAdminMixin, admin.TabularInline):
verbose_name = "Image" verbose_name = "Image"
class BookDocInline(admin.TabularInline):
model = BaseBookDocument
min_num = 0
extra = 0
classes = ["collapse"]
class BookPropertyInline(admin.TabularInline): class BookPropertyInline(admin.TabularInline):
model = BaseBookProperty model = BaseBookProperty
min_num = 0 min_num = 0
@@ -44,6 +37,17 @@ class BookPropertyInline(admin.TabularInline):
verbose_name_plural = "Properties" verbose_name_plural = "Properties"
class BookDocInline(admin.TabularInline):
model = BookDocument
min_num = 0
extra = 0
classes = ["collapse"]
class CatalogDocInline(BookDocInline):
model = CatalogDocument
@admin.register(Book) @admin.register(Book)
class BookAdmin(SortableAdminBase, admin.ModelAdmin): class BookAdmin(SortableAdminBase, admin.ModelAdmin):
inlines = ( inlines = (
@@ -60,7 +64,7 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
"published", "published",
) )
autocomplete_fields = ("authors", "publisher", "shop") 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") search_fields = ("title", "publisher__name", "authors__last_name")
list_filter = ("publisher__name", "authors") list_filter = ("publisher__name", "authors")
@@ -89,6 +93,7 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
"shop", "shop",
"purchase_date", "purchase_date",
"price", "price",
"invoices",
) )
}, },
), ),
@@ -115,6 +120,17 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
) )
return form return form
@admin.display(description="Invoices")
def invoices(self, obj):
if obj.invoice.exists():
html = "<br>".join(
"<a href=\"{}\" target=\"_blank\">{}</a>".format(
i.file.url, i
) for i in obj.invoice.all())
else:
html = "-"
return format_html(html)
@admin.display(description="Publisher") @admin.display(description="Publisher")
def get_publisher(self, obj): def get_publisher(self, obj):
return obj.publisher.name return obj.publisher.name
@@ -200,7 +216,7 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
inlines = ( inlines = (
BookPropertyInline, BookPropertyInline,
BookImageInline, BookImageInline,
BookDocInline, CatalogDocInline,
) )
list_display = ( list_display = (
"__str__", "__str__",
@@ -210,7 +226,7 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
"published", "published",
) )
autocomplete_fields = ("manufacturer",) autocomplete_fields = ("manufacturer",)
readonly_fields = ("creation_time", "updated_time") readonly_fields = ("invoices", "creation_time", "updated_time")
search_fields = ("manufacturer__name", "years", "scales__scale") search_fields = ("manufacturer__name", "years", "scales__scale")
list_filter = ("manufacturer__name", "publication_year", "scales__scale") list_filter = ("manufacturer__name", "publication_year", "scales__scale")
@@ -236,8 +252,10 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
"Purchase data", "Purchase data",
{ {
"fields": ( "fields": (
"shop",
"purchase_date", "purchase_date",
"price", "price",
"invoices",
) )
}, },
), ),
@@ -264,6 +282,17 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
) )
return form return form
@admin.display(description="Invoices")
def invoices(self, obj):
if obj.invoice.exists():
html = "<br>".join(
"<a href=\"{}\" target=\"_blank\">{}</a>".format(
i.file.url, i
) for i in obj.invoice.all())
else:
html = "-"
return format_html(html)
def download_csv(modeladmin, request, queryset): def download_csv(modeladmin, request, queryset):
header = [ header = [
"Catalog", "Catalog",

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.1.4 on 2025-02-09 13:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0022_basebook_shop"),
("repository", "0001_initial"),
]
operations = [
migrations.DeleteModel(
name="BaseBookDocument",
),
]

View File

@@ -6,7 +6,7 @@ from django.urls import reverse
from django_countries.fields import CountryField from django_countries.fields import CountryField
from ram.utils import DeduplicatedStorage from ram.utils import DeduplicatedStorage
from ram.models import BaseModel, Image, Document, PropertyInstance from ram.models import BaseModel, Image, PropertyInstance
from metadata.models import Scale, Manufacturer, Shop, Tag from metadata.models import Scale, Manufacturer, Shop, Tag
@@ -89,21 +89,6 @@ class BaseBookImage(Image):
) )
class BaseBookDocument(Document):
book = models.ForeignKey(
BaseBook, on_delete=models.CASCADE, related_name="document"
)
class Meta:
verbose_name_plural = "Documents"
constraints = [
models.UniqueConstraint(
fields=["book", "file"],
name="unique_book_file"
)
]
class BaseBookProperty(PropertyInstance): class BaseBookProperty(PropertyInstance):
book = models.ForeignKey( book = models.ForeignKey(
BaseBook, BaseBook,

View File

@@ -2,18 +2,16 @@ from django.contrib import admin
from django.utils.html import format_html from django.utils.html import format_html
from adminsortable2.admin import SortableAdminMixin from adminsortable2.admin import SortableAdminMixin
from ram.admin import publish, unpublish from repository.models import DecoderDocument
from metadata.models import ( from metadata.models import (
Property, Property,
Decoder, Decoder,
DecoderDocument,
Scale, Scale,
Shop, Shop,
Manufacturer, Manufacturer,
Company, Company,
Tag, Tag,
RollingStockType, RollingStockType,
GenericDocument,
) )
@@ -88,51 +86,6 @@ class RollingStockTypeAdmin(SortableAdminMixin, admin.ModelAdmin):
search_fields = ("type", "category") search_fields = ("type", "category")
@admin.register(GenericDocument)
class GenericDocumentAdmin(admin.ModelAdmin):
readonly_fields = ("size", "creation_time", "updated_time")
list_display = (
"__str__",
"description",
"private",
"size",
"download",
)
search_fields = (
"description",
"file",
)
fieldsets = (
(
None,
{
"fields": (
"private",
"description",
"file",
"size",
"tags",
)
},
),
(
"Notes",
{"classes": ("collapse",), "fields": ("notes",)},
),
(
"Audit",
{
"classes": ("collapse",),
"fields": (
"creation_time",
"updated_time",
),
},
),
)
actions = [publish, unpublish]
@admin.register(Shop) @admin.register(Shop)
class ShopAdmin(admin.ModelAdmin): class ShopAdmin(admin.ModelAdmin):
list_display = ("name", "on_line", "active") list_display = ("name", "on_line", "active")

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.1.4 on 2025-02-09 13:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("metadata", "0023_shop"),
("repository", "0001_initial"),
]
operations = [
migrations.RemoveField(
model_name="genericdocument",
name="tags",
),
migrations.DeleteModel(
name="DecoderDocument",
),
migrations.DeleteModel(
name="GenericDocument",
),
]

View File

@@ -6,9 +6,6 @@ from django.dispatch.dispatcher import receiver
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django_countries.fields import CountryField from django_countries.fields import CountryField
from tinymce import models as tinymce
from ram.models import Document
from ram.utils import DeduplicatedStorage, get_image_preview, slugify from ram.utils import DeduplicatedStorage, get_image_preview, slugify
from ram.managers import PublicManager from ram.managers import PublicManager
@@ -132,20 +129,6 @@ class Decoder(models.Model):
image_thumbnail.short_description = "Preview" image_thumbnail.short_description = "Preview"
class DecoderDocument(Document):
decoder = models.ForeignKey(
Decoder, on_delete=models.CASCADE, related_name="document"
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["decoder", "file"],
name="unique_decoder_file"
)
]
def calculate_ratio(ratio): def calculate_ratio(ratio):
try: try:
num, den = ratio.split(":") num, den = ratio.split(":")
@@ -239,14 +222,6 @@ class Tag(models.Model):
) )
class GenericDocument(Document):
notes = tinymce.HTMLField(blank=True)
tags = models.ManyToManyField(Tag, blank=True)
class Meta:
verbose_name_plural = "Generic Documents"
class Shop(models.Model): class Shop(models.Model):
name = models.CharField(max_length=128, unique=True) name = models.CharField(max_length=128, unique=True)
country = CountryField(blank=True) country = CountryField(blank=True)

View File

@@ -1,4 +1,4 @@
from ram.utils import git_suffix from ram.utils import git_suffix
__version__ = "0.16.9" __version__ = "0.17.0"
__version__ += git_suffix(__file__) __version__ += git_suffix(__file__)

View File

@@ -27,11 +27,6 @@ class Document(models.Model):
description = models.CharField(max_length=128, blank=True) description = models.CharField(max_length=128, blank=True)
file = models.FileField( file = models.FileField(
upload_to="files/", 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) creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True) updated_time = models.DateTimeField(auto_now=True)
@@ -61,8 +56,17 @@ class Document(models.Model):
'<a href="{0}" target="_blank">Link</a>'.format(self.file.url) '<a href="{0}" target="_blank">Link</a>'.format(self.file.url)
) )
class PrivateDocument(Document):
private = models.BooleanField(
default=False,
help_text="Document will be visible only to logged users",
)
objects = PublicManager() objects = PublicManager()
class Meta:
abstract = True
class Image(models.Model): class Image(models.Model):
order = models.PositiveIntegerField(default=0, blank=False, null=False) order = models.PositiveIntegerField(default=0, blank=False, null=False)

View File

@@ -50,6 +50,7 @@ INSTALLED_APPS = [
"portal", "portal",
# "driver", # uncomment this to enable the "driver" API # "driver", # uncomment this to enable the "driver" API
"metadata", "metadata",
"repository",
"roster", "roster",
"consist", "consist",
"bookshelf", "bookshelf",

View File

248
ram/repository/admin.py Normal file
View File

@@ -0,0 +1,248 @@
from django.contrib import admin
from ram.admin import publish, unpublish
from repository.models import (
GenericDocument,
InvoiceDocument,
BookDocument,
CatalogDocument,
DecoderDocument,
RollingStockDocument
)
@admin.register(GenericDocument)
class GenericDocumentAdmin(admin.ModelAdmin):
readonly_fields = ("size", "creation_time", "updated_time")
list_display = (
"__str__",
"description",
"private",
"size",
"download",
)
search_fields = (
"description",
"file",
)
fieldsets = (
(
None,
{
"fields": (
"private",
"description",
"file",
"size",
"tags",
)
},
),
(
"Notes",
{"classes": ("collapse",), "fields": ("notes",)},
),
(
"Audit",
{
"classes": ("collapse",),
"fields": (
"creation_time",
"updated_time",
),
},
),
)
actions = [publish, unpublish]
@admin.register(InvoiceDocument)
class InvoiceDocumentAdmin(admin.ModelAdmin):
readonly_fields = ("size", "creation_time", "updated_time")
list_display = (
"__str__",
"description",
"date",
"shop",
"size",
"download",
)
search_fields = (
"rolling_stock__manufacturer__name",
"rolling_stock__item_number",
"book__title",
"catalog__manufacturer__name",
"shop__name",
"description",
"file",
)
autocomplete_fields = ("rolling_stock", "book", "catalog", "shop")
fieldsets = (
(
None,
{
"fields": (
"rolling_stock",
"book",
"catalog",
"description",
"date",
"shop",
"file",
"size",
)
},
),
(
"Notes",
{"classes": ("collapse",), "fields": ("notes",)},
),
(
"Audit",
{
"classes": ("collapse",),
"fields": (
"creation_time",
"updated_time",
),
},
),
)
@admin.register(BookDocument)
class BookDocumentAdmin(admin.ModelAdmin):
readonly_fields = ("size",)
list_display = (
"__str__",
"book",
"description",
"private",
"size",
"download",
)
search_fields = (
"book__title",
"description",
"file",
)
autocomplete_fields = ("book",)
fieldsets = (
(
None,
{
"fields": (
"private",
"book",
"description",
"file",
"size",
)
},
),
)
actions = [publish, unpublish]
@admin.register(CatalogDocument)
class CatalogDocumentAdmin(admin.ModelAdmin):
readonly_fields = ("size",)
list_display = (
"__str__",
"catalog",
"description",
"private",
"size",
"download",
)
search_fields = (
"catalog__title",
"description",
"file",
)
autocomplete_fields = ("catalog",)
fieldsets = (
(
None,
{
"fields": (
"private",
"catalog",
"description",
"file",
"size",
)
},
),
)
actions = [publish, unpublish]
@admin.register(DecoderDocument)
class DecoderDocumentAdmin(admin.ModelAdmin):
readonly_fields = ("size",)
list_display = (
"__str__",
"decoder",
"description",
"private",
"size",
"download",
)
search_fields = (
"decoder__name",
"decoder__manufacturer__name",
"description",
"file",
)
autocomplete_fields = ("decoder",)
fieldsets = (
(
None,
{
"fields": (
"private",
"decoder",
"description",
"file",
"size",
)
},
),
)
actions = [publish, unpublish]
@admin.register(RollingStockDocument)
class RollingStockDocumentAdmin(admin.ModelAdmin):
readonly_fields = ("size",)
list_display = (
"__str__",
"rolling_stock",
"description",
"private",
"size",
"download",
)
search_fields = (
"rolling_stock__rolling_class__identifier",
"rolling_stock__item_number",
"description",
"file",
)
autocomplete_fields = ("rolling_stock",)
fieldsets = (
(
None,
{
"fields": (
"private",
"rolling_stock",
"description",
"file",
"size",
)
},
),
)
actions = [publish, unpublish]

6
ram/repository/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class RepositoryConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "repository"

View File

@@ -0,0 +1,361 @@
# Generated by Django 5.1.4 on 2025-02-09 13:04
import django.db.models.deletion
import ram.utils
import tinymce.models
from django.db import migrations, models
def migrate_document(apps, schema_editor):
document = apps.get_model("metadata", "GenericDocument")
document_new = apps.get_model("repository", "GenericDocument")
for d in document.objects.all():
n = document_new.objects.create(
notes=d.notes,
description=d.description,
file=d.file,
private=d.private,
creation_time=d.creation_time,
updated_time=d.updated_time,
)
for t in d.tags.all():
n.tags.add(t)
def migrate_decoder(apps, schema_editor):
dcc_document = apps.get_model("metadata", "DecoderDocument")
dcc_document_new = apps.get_model("repository", "DecoderDocument")
for d in dcc_document.objects.all():
dcc_document_new.objects.create(
decoder=d.decoder,
description=d.description,
file=d.file,
private=d.private,
creation_time=d.creation_time,
updated_time=d.updated_time,
)
def migrate_rollingstock(apps, schema_editor):
rs_document = apps.get_model("roster", "RollingStockDocument")
rs_document_new = apps.get_model("repository", "RollingStockDocument")
for d in rs_document.objects.all():
rs_document_new.objects.create(
rolling_stock=d.rolling_stock,
description=d.description,
file=d.file,
private=d.private,
creation_time=d.creation_time,
updated_time=d.updated_time,
)
def migrate_book(apps, schema_editor):
book_document = apps.get_model("bookshelf", "BaseBookDocument")
book_document_new = apps.get_model("repository", "BaseBookDocument")
catalog_document_new = apps.get_model("repository", "CatalogDocument")
for d in book_document.objects.all():
if hasattr(d.book, "book"):
book_document_new.objects.create(
book=d.book.book,
description=d.description,
file=d.file,
private=d.private,
creation_time=d.creation_time,
updated_time=d.updated_time,
)
else:
catalog_document_new.objects.create(
catalog=d.book.catalog,
description=d.description,
file=d.file,
private=d.private,
creation_time=d.creation_time,
updated_time=d.updated_time,
)
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0022_basebook_shop"),
("metadata", "0023_shop"),
("roster", "0035_alter_rollingstock_shop"),
]
operations = [
migrations.CreateModel(
name="BaseBookDocument",
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/"
),
),
(
"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)),
(
"book",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="new_document",
to="bookshelf.basebook",
),
),
],
options={
"verbose_name_plural": "Documents",
"abstract": False,
},
),
migrations.CreateModel(
name="BookDocument",
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=False,
help_text="Document will be visible only to logged users",
),
),
(
"book",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="document",
to="bookshelf.book",
),
),
],
options={
"verbose_name_plural": "Book documents",
"constraints": [
models.UniqueConstraint(
fields=("book", "file"), name="unique_book_file"
)
],
},
),
migrations.CreateModel(
name="CatalogDocument",
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=False,
help_text="Document will be visible only to logged users",
),
),
(
"catalog",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="document",
to="bookshelf.catalog",
),
),
],
options={
"verbose_name_plural": "Catalog documents",
"constraints": [
models.UniqueConstraint(
fields=("catalog", "file"), name="unique_catalog_file"
)
],
},
),
migrations.CreateModel(
name="GenericDocument",
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/"
),
),
(
"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)),
("notes", tinymce.models.HTMLField(blank=True)),
(
"tags",
models.ManyToManyField(
blank=True, related_name="new_document", to="metadata.tag"
),
),
],
options={
"verbose_name_plural": "Generic Documents",
},
),
migrations.CreateModel(
name="RollingStockDocument",
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/"
),
),
(
"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)),
(
"rolling_stock",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="new_document",
to="roster.rollingstock",
),
),
],
options={
"verbose_name_plural": "Documents",
"abstract": False,
},
),
migrations.CreateModel(
name="DecoderDocument",
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/"
),
),
(
"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)),
(
"decoder",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="new_document",
to="metadata.decoder",
),
),
],
options={
"verbose_name_plural": "Documents",
"abstract": False,
},
),
migrations.RunPython(
migrate_document,
reverse_code=migrations.RunPython.noop
),
migrations.RunPython(
migrate_decoder,
reverse_code=migrations.RunPython.noop
),
migrations.RunPython(
migrate_rollingstock,
reverse_code=migrations.RunPython.noop
),
migrations.RunPython(
migrate_book,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -0,0 +1,157 @@
# Generated by Django 5.1.4 on 2025-02-09 23:10
import django.db.models.deletion
import tinymce.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0023_delete_basebookdocument"),
(
"metadata",
"0024_remove_genericdocument_tags_delete_decoderdocument_and_more",
),
("repository", "0001_initial"),
("roster", "0036_delete_rollingstockdocument"),
]
operations = [
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)),
("creation_time", models.DateTimeField(auto_now_add=True)),
("updated_time", models.DateTimeField(auto_now=True)),
("private", models.BooleanField(default=True, editable=False)),
("date", models.DateField()),
("file", models.FileField(upload_to="files/invoices/")),
("notes", tinymce.models.HTMLField(blank=True)),
],
options={
"abstract": False,
},
),
migrations.RemoveField(
model_name="basebookdocument",
name="book",
),
migrations.AlterModelOptions(
name="decoderdocument",
options={},
),
migrations.AlterModelOptions(
name="genericdocument",
options={"verbose_name_plural": "Generic documents"},
),
migrations.AlterModelOptions(
name="rollingstockdocument",
options={},
),
migrations.AlterField(
model_name="bookdocument",
name="file",
field=models.FileField(upload_to="files/"),
),
migrations.AlterField(
model_name="catalogdocument",
name="file",
field=models.FileField(upload_to="files/"),
),
migrations.AlterField(
model_name="decoderdocument",
name="decoder",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="document",
to="metadata.decoder",
),
),
migrations.AlterField(
model_name="decoderdocument",
name="file",
field=models.FileField(upload_to="files/"),
),
migrations.AlterField(
model_name="genericdocument",
name="file",
field=models.FileField(upload_to="files/"),
),
migrations.AlterField(
model_name="genericdocument",
name="tags",
field=models.ManyToManyField(
blank=True, related_name="document", to="metadata.tag"
),
),
migrations.AlterField(
model_name="rollingstockdocument",
name="file",
field=models.FileField(upload_to="files/"),
),
migrations.AlterField(
model_name="rollingstockdocument",
name="rolling_stock",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="document",
to="roster.rollingstock",
),
),
migrations.AddConstraint(
model_name="decoderdocument",
constraint=models.UniqueConstraint(
fields=("decoder", "file"), name="unique_decoder_file"
),
),
migrations.AddConstraint(
model_name="rollingstockdocument",
constraint=models.UniqueConstraint(
fields=("rolling_stock", "file"), name="unique_stock_file"
),
),
migrations.AddField(
model_name="invoicedocument",
name="book",
field=models.ManyToManyField(
blank=True, related_name="invoice", to="bookshelf.book"
),
),
migrations.AddField(
model_name="invoicedocument",
name="catalog",
field=models.ManyToManyField(
blank=True, related_name="invoice", to="bookshelf.catalog"
),
),
migrations.AddField(
model_name="invoicedocument",
name="rolling_stock",
field=models.ManyToManyField(
blank=True, related_name="invoice", to="roster.rollingstock"
),
),
migrations.AddField(
model_name="invoicedocument",
name="shop",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="metadata.shop",
),
),
migrations.DeleteModel(
name="BaseBookDocument",
),
]

View File

90
ram/repository/models.py Normal file
View File

@@ -0,0 +1,90 @@
from django.db import models
from django.core.exceptions import ValidationError
from tinymce import models as tinymce
from ram.models import PrivateDocument
from metadata.models import Decoder, Shop, Tag
from roster.models import RollingStock
from bookshelf.models import Book, Catalog
class GenericDocument(PrivateDocument):
notes = tinymce.HTMLField(blank=True)
tags = models.ManyToManyField(Tag, blank=True, related_name="document")
class Meta:
verbose_name_plural = "Generic documents"
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
)
date = models.DateField()
shop = models.ForeignKey(
Shop, on_delete=models.SET_NULL, null=True, blank=True
)
file = models.FileField(
upload_to="files/invoices/",
)
notes = tinymce.HTMLField(blank=True)
class DecoderDocument(PrivateDocument):
decoder = models.ForeignKey(
Decoder, on_delete=models.CASCADE, related_name="document"
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["decoder", "file"], name="unique_decoder_file"
)
]
class BookDocument(PrivateDocument):
book = models.ForeignKey(
Book, on_delete=models.CASCADE, related_name="document"
)
class Meta:
verbose_name_plural = "Book documents"
constraints = [
models.UniqueConstraint(
fields=["book", "file"], name="unique_book_file"
)
]
class CatalogDocument(PrivateDocument):
catalog = models.ForeignKey(
Catalog, on_delete=models.CASCADE, related_name="document"
)
class Meta:
verbose_name_plural = "Catalog documents"
constraints = [
models.UniqueConstraint(
fields=["catalog", "file"], name="unique_catalog_file"
)
]
class RollingStockDocument(PrivateDocument):
rolling_stock = models.ForeignKey(
RollingStock, on_delete=models.CASCADE, related_name="document"
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["rolling_stock", "file"], name="unique_stock_file"
)
]

3
ram/repository/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

3
ram/repository/views.py Normal file
View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -8,13 +8,13 @@ from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
from ram.admin import publish, unpublish from ram.admin import publish, unpublish
from ram.utils import generate_csv from ram.utils import generate_csv
from repository.models import RollingStockDocument
from portal.utils import get_site_conf from portal.utils import get_site_conf
from roster.models import ( from roster.models import (
RollingClass, RollingClass,
RollingClassProperty, RollingClassProperty,
RollingStock, RollingStock,
RollingStockImage, RollingStockImage,
RollingStockDocument,
RollingStockProperty, RollingStockProperty,
RollingStockJournal, RollingStockJournal,
) )
@@ -76,42 +76,8 @@ class RollingStockJournalInline(admin.TabularInline):
classes = ["collapse"] classes = ["collapse"]
@admin.register(RollingStockDocument)
class RollingStockDocumentAdmin(admin.ModelAdmin):
readonly_fields = ("size",)
list_display = (
"__str__",
"rolling_stock",
"description",
"private",
"size",
"download",
)
search_fields = (
"rolling_stock__rolling_class__identifier",
"rolling_stock__item_number",
"description",
"file",
)
autocomplete_fields = ("rolling_stock",)
fieldsets = (
(
None,
{
"fields": (
"private",
"rolling_stock",
"description",
"file",
"size",
)
},
),
)
@admin.register(RollingStockJournal) @admin.register(RollingStockJournal)
class RollingJournalDocumentAdmin(admin.ModelAdmin): class RollingJournalAdmin(admin.ModelAdmin):
list_display = ( list_display = (
"__str__", "__str__",
"date", "date",
@@ -152,7 +118,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
RollingStockJournalInline, RollingStockJournalInline,
) )
autocomplete_fields = ("rolling_class", "shop") autocomplete_fields = ("rolling_class", "shop")
readonly_fields = ("preview", "creation_time", "updated_time") readonly_fields = ("preview", "invoices", "creation_time", "updated_time")
list_display = ( list_display = (
"__str__", "__str__",
"address", "address",
@@ -223,6 +189,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
"shop", "shop",
"purchase_date", "purchase_date",
"price", "price",
"invoices",
) )
}, },
), ),
@@ -249,6 +216,17 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
) )
return form return form
@admin.display(description="Invoices")
def invoices(self, obj):
if obj.invoice.exists():
html = "<br>".join(
"<a href=\"{}\" target=\"_blank\">{}</a>".format(
i.file.url, i
) for i in obj.invoice.all())
else:
html = "-"
return format_html(html)
def download_csv(modeladmin, request, queryset): def download_csv(modeladmin, request, queryset):
header = [ header = [
"Name", "Name",

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.1.4 on 2025-02-09 13:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("roster", "0035_alter_rollingstock_shop"),
("repository", "0001_initial"),
]
operations = [
migrations.DeleteModel(
name="RollingStockDocument",
),
]

View File

@@ -8,7 +8,7 @@ from django.dispatch import receiver
from tinymce import models as tinymce from tinymce import models as tinymce
from ram.models import BaseModel, Document, Image, PropertyInstance from ram.models import BaseModel, Image, PropertyInstance
from ram.utils import DeduplicatedStorage, slugify from ram.utils import DeduplicatedStorage, slugify
from ram.managers import PublicManager from ram.managers import PublicManager
from metadata.models import ( from metadata.models import (
@@ -169,20 +169,6 @@ def pre_save_internal_fields(sender, instance, *args, **kwargs):
instance.item_number_slug = slugify(instance.item_number) instance.item_number_slug = slugify(instance.item_number)
class RollingStockDocument(Document):
rolling_stock = models.ForeignKey(
RollingStock, on_delete=models.CASCADE, related_name="document"
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["rolling_stock", "file"],
name="unique_stock_file"
)
]
def rolling_stock_image_upload(instance, filename): def rolling_stock_image_upload(instance, filename):
return os.path.join( return os.path.join(
"images", "images",