diff --git a/ram/metadata/admin.py b/ram/metadata/admin.py
index 7ac1786..1ba1b6c 100644
--- a/ram/metadata/admin.py
+++ b/ram/metadata/admin.py
@@ -4,6 +4,7 @@ from adminsortable2.admin import SortableAdminMixin
from metadata.models import (
Property,
Decoder,
+ DecoderDocument,
Scale,
Manufacturer,
Company,
@@ -17,8 +18,16 @@ class PropertyAdmin(admin.ModelAdmin):
search_fields = ("name",)
+class DecoderDocInline(admin.TabularInline):
+ model = DecoderDocument
+ min_num = 0
+ extra = 0
+ classes = ["collapse"]
+
+
@admin.register(Decoder)
class DecoderAdmin(admin.ModelAdmin):
+ inlines = (DecoderDocInline,)
readonly_fields = ("image_thumbnail",)
list_display = ("__str__", "sound")
list_filter = ("manufacturer", "sound")
diff --git a/ram/metadata/migrations/0012_alter_decoder_manufacturer_decoderdocument.py b/ram/metadata/migrations/0012_alter_decoder_manufacturer_decoderdocument.py
new file mode 100644
index 0000000..a29b821
--- /dev/null
+++ b/ram/metadata/migrations/0012_alter_decoder_manufacturer_decoderdocument.py
@@ -0,0 +1,59 @@
+# Generated by Django 4.2 on 2023-09-30 21:54
+
+from django.db import migrations, models
+import django.db.models.deletion
+import ram.utils
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("metadata", "0011_company_slug_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="decoder",
+ name="manufacturer",
+ field=models.ForeignKey(
+ limit_choices_to={"category": "accessory"},
+ on_delete=django.db.models.deletion.CASCADE,
+ to="metadata.manufacturer",
+ ),
+ ),
+ 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(
+ blank=True,
+ null=True,
+ storage=ram.utils.DeduplicatedStorage(),
+ upload_to="files/",
+ ),
+ ),
+ (
+ "decoder",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="document",
+ to="metadata.decoder",
+ ),
+ ),
+ ],
+ options={
+ "unique_together": {("decoder", "file")},
+ },
+ ),
+ ]
diff --git a/ram/metadata/models.py b/ram/metadata/models.py
index ef79f07..acccf61 100644
--- a/ram/metadata/models.py
+++ b/ram/metadata/models.py
@@ -1,11 +1,10 @@
-from urllib.parse import quote
-
from django.db import models
from django.urls import reverse
from django.conf import settings
from django.dispatch.dispatcher import receiver
from django_countries.fields import CountryField
+from ram.models import Document
from ram.utils import DeduplicatedStorage, get_image_preview, slugify
@@ -88,7 +87,7 @@ class Decoder(models.Model):
manufacturer = models.ForeignKey(
Manufacturer,
on_delete=models.CASCADE,
- limit_choices_to={"category": "model"},
+ limit_choices_to={"category": "accessory"},
)
version = models.CharField(max_length=64, blank=True)
sound = models.BooleanField(default=False)
@@ -105,6 +104,15 @@ class Decoder(models.Model):
image_thumbnail.short_description = "Preview"
+class DecoderDocument(Document):
+ decoder = models.ForeignKey(
+ Decoder, on_delete=models.CASCADE, related_name="document"
+ )
+
+ class Meta:
+ unique_together = ("decoder", "file")
+
+
class Scale(models.Model):
scale = models.CharField(max_length=32, unique=True)
slug = models.CharField(max_length=32, unique=True, editable=False)
@@ -167,7 +175,6 @@ class Tag(models.Model):
)
-
@receiver(models.signals.pre_save, sender=Manufacturer)
@receiver(models.signals.pre_save, sender=Company)
@receiver(models.signals.pre_save, sender=Scale)
diff --git a/ram/portal/static/css/main.css b/ram/portal/static/css/main.css
index aad3c5c..df6847d 100644
--- a/ram/portal/static/css/main.css
+++ b/ram/portal/static/css/main.css
@@ -36,7 +36,3 @@ a.badge, a.badge:hover {
#footer > p {
display: inline;
}
-
-th.th-35 {
- width: 35%;
-}
diff --git a/ram/portal/templates/cards.html b/ram/portal/templates/cards.html
index eab6169..895a533 100644
--- a/ram/portal/templates/cards.html
+++ b/ram/portal/templates/cards.html
@@ -32,7 +32,7 @@
- Type |
+ Type |
{{ d.rolling_class.type }} |
@@ -54,7 +54,7 @@
{{ d.era }} |
- Manufacturer |
+ Manufacturer |
{%if d.manufacturer %}
{{ d.manufacturer }}{% if d.manufacturer.website %} {% endif %}
{% endif %} |
@@ -78,7 +78,7 @@
- Decoder |
+ Decoder |
{{ d.decoder }} |
diff --git a/ram/portal/templates/companies.html b/ram/portal/templates/companies.html
index 448c5b0..964c44a 100644
--- a/ram/portal/templates/companies.html
+++ b/ram/portal/templates/companies.html
@@ -17,25 +17,25 @@
{% if d.logo %}
- Logo |
+ Logo |
 |
{% endif %}
- Name |
+ Name |
{{ d.extended_name }} |
- Abbreviation |
+ Abbreviation |
{{ d.name }} |
- Country |
+ Country |
{{ d.country.name }}
|
{% if d.freelance %}
- Notes |
+ Notes |
A freelance company |
{% endif %}
diff --git a/ram/portal/templates/consist.html b/ram/portal/templates/consist.html
index 9fefaca..1ab8e67 100644
--- a/ram/portal/templates/consist.html
+++ b/ram/portal/templates/consist.html
@@ -82,7 +82,7 @@
- Company |
+ Company |
{{ consist.company }} |
diff --git a/ram/portal/templates/consists.html b/ram/portal/templates/consists.html
index d4125ce..d9f84b4 100644
--- a/ram/portal/templates/consists.html
+++ b/ram/portal/templates/consists.html
@@ -36,12 +36,12 @@
{% if d.address %}
- Address |
+ Address |
{{ d.address }} |
{% endif %}
- Company |
+ Company |
{{ d.company }} |
diff --git a/ram/portal/templates/manufacturers.html b/ram/portal/templates/manufacturers.html
index 9a0e580..723a1e2 100644
--- a/ram/portal/templates/manufacturers.html
+++ b/ram/portal/templates/manufacturers.html
@@ -17,18 +17,18 @@
{% if d.logo %}
- Logo |
+ Logo |
 |
{% endif %}
{% if d.website %}
- Website |
+ Website |
|
{% endif %}
- Category |
+ Category |
{{ d.category | title }} |
diff --git a/ram/portal/templates/rollingstock.html b/ram/portal/templates/rollingstock.html
index 58ab8fd..396f8b8 100644
--- a/ram/portal/templates/rollingstock.html
+++ b/ram/portal/templates/rollingstock.html
@@ -64,7 +64,7 @@
- Type |
+ Type |
{{ rolling_stock.rolling_class.type }} |
@@ -95,7 +95,7 @@
- Manufacturer |
+ Manufacturer |
{%if rolling_stock.manufacturer %}
{{ rolling_stock.manufacturer }}{% if rolling_stock.manufacturer.website %} {% endif %}
{% endif %} |
@@ -119,7 +119,7 @@
- Interface |
+ Interface |
{{ rolling_stock.get_decoder_interface_display }} |
{% if rolling_stock.decoder %}
@@ -145,7 +145,7 @@
- Manufacturer |
+ Manufacturer |
{%if rolling_stock.manufacturer %}
{{ rolling_stock.manufacturer }}{% if rolling_stock.manufacturer.website %} {% endif %}
{% endif %} |
@@ -182,7 +182,7 @@
{% for p in rolling_stock_properties %}
- {{ p.property }} |
+ {{ p.property }} |
{{ p.value }} |
{% endfor %}
@@ -199,7 +199,7 @@
- Class |
+ Class |
{{ rolling_stock.rolling_class.identifier }} |
@@ -234,7 +234,7 @@
{% for p in class_properties %}
- {{ p.property }} |
+ {{ p.property }} |
{{ p.value }} |
{% endfor %}
@@ -255,7 +255,7 @@
{{ rolling_stock.get_decoder_interface_display }} |
- Address |
+ Address |
{{ rolling_stock.address }} |
@@ -301,13 +301,31 @@
{% for d in rolling_stock.document.all %}
- {{ d.description }} |
+ {{ d.description }} |
{{ d.filename }} |
{{ d.file.size | filesizeformat }} |
{% endfor %}
+ {% if rolling_stock.decoder.document.count > 0 %}
+
+
+
+ Decoder documents |
+
+
+
+ {% for d in rolling_stock.decoder.document.all %}
+
+ {{ d.description }} |
+ {{ d.filename }} |
+ {{ d.file.size | filesizeformat }} |
+
+ {% endfor %}
+
+
+ {% endif %}
@@ -319,7 +337,7 @@
{% for j in rolling_stock_journal %}
- {{ j.date }} |
+ {{ j.date }} |
{{ j.log | safe }} |
{% endfor %}
diff --git a/ram/portal/templates/scales.html b/ram/portal/templates/scales.html
index 37a65e2..d32f792 100644
--- a/ram/portal/templates/scales.html
+++ b/ram/portal/templates/scales.html
@@ -14,19 +14,19 @@
- Name |
+ Name |
{{ d.scale }} |
- Ratio |
+ Ratio |
{{ d.ratio }} |
- Gauge |
+ Gauge |
{{ d.gauge }} |
- Tracks |
+ Tracks |
{{ d.tracks }} |
diff --git a/ram/portal/templates/types.html b/ram/portal/templates/types.html
index 22e0fad..83f7d36 100644
--- a/ram/portal/templates/types.html
+++ b/ram/portal/templates/types.html
@@ -14,11 +14,11 @@
- Type |
+ Type |
{{ d.type }} |
- Category |
+ Category |
{{ d.category | title}} |
diff --git a/ram/ram/__init__.py b/ram/ram/__init__.py
index da274de..830ef68 100644
--- a/ram/ram/__init__.py
+++ b/ram/ram/__init__.py
@@ -1,4 +1,4 @@
from ram.utils import git_suffix
-__version__ = "0.3.3"
+__version__ = "0.4.0"
__version__ += git_suffix(__file__)
diff --git a/ram/ram/models.py b/ram/ram/models.py
new file mode 100644
index 0000000..b370728
--- /dev/null
+++ b/ram/ram/models.py
@@ -0,0 +1,30 @@
+import os
+
+from django.db import models
+from django.utils.safestring import mark_safe
+
+from ram.utils import DeduplicatedStorage
+
+
+class Document(models.Model):
+ description = models.CharField(max_length=128, blank=True)
+ file = models.FileField(
+ upload_to="files/",
+ storage=DeduplicatedStorage(),
+ null=True,
+ blank=True,
+ )
+
+ class Meta:
+ abstract = True
+
+ def __str__(self):
+ return "{0}".format(os.path.basename(self.file.name))
+
+ def filename(self):
+ return self.__str__()
+
+ def download(self):
+ return mark_safe(
+ 'Link'.format(self.file.url)
+ )
diff --git a/ram/ram/utils.py b/ram/ram/utils.py
index 208787d..ba13204 100644
--- a/ram/ram/utils.py
+++ b/ram/ram/utils.py
@@ -16,7 +16,7 @@ class DeduplicatedStorage(FileSystemStorage):
def save(self, name, content, max_length=None):
if super().exists(name):
- new = hashlib.sha256(content.file.getbuffer()).hexdigest()
+ new = hashlib.sha256(content.read()).hexdigest()
with open(super().path(name), "rb") as file:
file_binary = file.read()
old = hashlib.sha256(file_binary).hexdigest()
diff --git a/ram/roster/models.py b/ram/roster/models.py
index 0e3d2c7..8b99b9c 100644
--- a/ram/roster/models.py
+++ b/ram/roster/models.py
@@ -5,11 +5,11 @@ from django.db import models
from django.urls import reverse
from django.conf import settings
from django.dispatch import receiver
-from django.utils.safestring import mark_safe
from ckeditor_uploader.fields import RichTextUploadingField
from ram.utils import DeduplicatedStorage, get_image_preview
+from ram.models import Document
from metadata.models import (
Property,
Scale,
@@ -127,32 +127,14 @@ def pre_save_running_number(sender, instance, *args, **kwargs):
pass
-class RollingStockDocument(models.Model):
+class RollingStockDocument(Document):
rolling_stock = models.ForeignKey(
RollingStock, on_delete=models.CASCADE, related_name="document"
)
- description = models.CharField(max_length=128, blank=True)
- file = models.FileField(
- upload_to="files/",
- storage=DeduplicatedStorage(),
- null=True,
- blank=True,
- )
class Meta(object):
unique_together = ("rolling_stock", "file")
- def __str__(self):
- return "{0}".format(os.path.basename(self.file.name))
-
- def filename(self):
- return self.__str__()
-
- def download(self):
- return mark_safe(
- 'Link'.format(self.file.url)
- )
-
class RollingStockImage(models.Model):
order = models.PositiveIntegerField(default=0, blank=False, null=False)