diff --git a/ram/bookshelf/admin.py b/ram/bookshelf/admin.py
index abae703..b5efe57 100644
--- a/ram/bookshelf/admin.py
+++ b/ram/bookshelf/admin.py
@@ -2,7 +2,7 @@ import html
from django.conf import settings
from django.contrib import admin
-from django.utils.html import strip_tags
+from django.utils.html import format_html, strip_tags
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
from ram.admin import publish, unpublish
@@ -93,12 +93,7 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
),
(
"Notes",
- {
- "classes": ("collapse",),
- "fields": (
- "notes",
- )
- },
+ {"classes": ("collapse",), "fields": ("notes",)},
),
(
"Audit",
@@ -150,23 +145,25 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
"{}:{}".format(property.property.name, property.value)
for property in obj.property.all()
)
- data.append([
- obj.title,
- obj.authors_list.replace(",", settings.CSV_SEPARATOR_ALT),
- obj.publisher.name,
- obj.ISBN,
- dict(settings.LANGUAGES)[obj.language],
- obj.number_of_pages,
- obj.publication_year,
- html.unescape(strip_tags(obj.description)),
- settings.CSV_SEPARATOR_ALT.join(
- t.name for t in obj.tags.all()
- ),
- obj.purchase_date,
- obj.price,
- html.unescape(strip_tags(obj.notes)),
- properties,
- ])
+ data.append(
+ [
+ obj.title,
+ obj.authors_list.replace(",", settings.CSV_SEPARATOR_ALT),
+ obj.publisher.name,
+ obj.ISBN,
+ dict(settings.LANGUAGES)[obj.language],
+ obj.number_of_pages,
+ obj.publication_year,
+ html.unescape(strip_tags(obj.description)),
+ settings.CSV_SEPARATOR_ALT.join(
+ t.name for t in obj.tags.all()
+ ),
+ obj.purchase_date,
+ obj.price,
+ html.unescape(strip_tags(obj.notes)),
+ properties,
+ ]
+ )
return generate_csv(header, data, "bookshelf_books.csv")
@@ -185,9 +182,15 @@ class AuthorAdmin(admin.ModelAdmin):
@admin.register(Publisher)
class PublisherAdmin(admin.ModelAdmin):
- list_display = ("name", "country")
+ list_display = ("name", "country_flag")
search_fields = ("name",)
+ @admin.display(description="Country")
+ def country_flag(self, obj):
+ return format_html(
+ '
{}'.format(obj.country.flag, obj.country.name)
+ )
+
@admin.register(Catalog)
class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
@@ -237,12 +240,7 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
),
(
"Notes",
- {
- "classes": ("collapse",),
- "fields": (
- "notes",
- )
- },
+ {"classes": ("collapse",), "fields": ("notes",)},
),
(
"Audit",
@@ -287,24 +285,26 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
"{}:{}".format(property.property.name, property.value)
for property in obj.property.all()
)
- data.append([
- obj.__str__(),
- obj.manufacturer.name,
- obj.years,
- obj.get_scales(),
- obj.ISBN,
- dict(settings.LANGUAGES)[obj.language],
- obj.number_of_pages,
- obj.publication_year,
- html.unescape(strip_tags(obj.description)),
- settings.CSV_SEPARATOR_ALT.join(
- t.name for t in obj.tags.all()
- ),
- obj.purchase_date,
- obj.price,
- html.unescape(strip_tags(obj.notes)),
- properties,
- ])
+ data.append(
+ [
+ obj.__str__(),
+ obj.manufacturer.name,
+ obj.years,
+ obj.get_scales(),
+ obj.ISBN,
+ dict(settings.LANGUAGES)[obj.language],
+ obj.number_of_pages,
+ obj.publication_year,
+ html.unescape(strip_tags(obj.description)),
+ settings.CSV_SEPARATOR_ALT.join(
+ t.name for t in obj.tags.all()
+ ),
+ obj.purchase_date,
+ obj.price,
+ html.unescape(strip_tags(obj.notes)),
+ properties,
+ ]
+ )
return generate_csv(header, data, "bookshelf_catalogs.csv")
diff --git a/ram/bookshelf/migrations/0021_basebookdocument_creation_time_and_more.py b/ram/bookshelf/migrations/0021_basebookdocument_creation_time_and_more.py
new file mode 100644
index 0000000..bf228b5
--- /dev/null
+++ b/ram/bookshelf/migrations/0021_basebookdocument_creation_time_and_more.py
@@ -0,0 +1,34 @@
+# Generated by Django 5.1.4 on 2025-01-18 11:20
+
+import django.utils.timezone
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("bookshelf", "0020_alter_basebookdocument_unique_together_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="basebookdocument",
+ name="creation_time",
+ field=models.DateTimeField(
+ auto_now_add=True, default=django.utils.timezone.now
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="basebookdocument",
+ name="updated_time",
+ field=models.DateTimeField(auto_now=True),
+ ),
+ migrations.AlterField(
+ model_name="basebookdocument",
+ name="private",
+ field=models.BooleanField(
+ default=False, help_text="Document will be visible only to logged users"
+ ),
+ ),
+ ]
diff --git a/ram/consist/admin.py b/ram/consist/admin.py
index 3551770..58cb60b 100644
--- a/ram/consist/admin.py
+++ b/ram/consist/admin.py
@@ -1,4 +1,5 @@
from django.contrib import admin
+from django.utils.html import format_html
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
from ram.admin import publish, unpublish
@@ -28,10 +29,16 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
"updated_time",
)
list_filter = ("company", "era", "published")
- list_display = ("__str__",) + list_filter
+ list_display = ("__str__",) + list_filter + ("country_flag",)
search_fields = ("identifier",) + list_filter
save_as = True
+ @admin.display(description="Country")
+ def country_flag(self, obj):
+ return format_html(
+ '
{}'.format(obj.country.flag, obj.country)
+ )
+
fieldsets = (
(
None,
diff --git a/ram/consist/models.py b/ram/consist/models.py
index b97b375..a6ce884 100644
--- a/ram/consist/models.py
+++ b/ram/consist/models.py
@@ -39,6 +39,10 @@ class Consist(BaseModel):
def get_absolute_url(self):
return reverse("consist", kwargs={"uuid": self.uuid})
+ @property
+ def country(self):
+ return self.company.country
+
def clean(self):
if self.consist_item.filter(rolling_stock__published=False).exists():
raise ValidationError(
diff --git a/ram/metadata/admin.py b/ram/metadata/admin.py
index 81ec1a8..5cdfec0 100644
--- a/ram/metadata/admin.py
+++ b/ram/metadata/admin.py
@@ -1,4 +1,5 @@
from django.contrib import admin
+from django.utils.html import format_html
from adminsortable2.admin import SortableAdminMixin
from ram.admin import publish, unpublish
@@ -47,18 +48,30 @@ class ScaleAdmin(admin.ModelAdmin):
@admin.register(Company)
class CompanyAdmin(admin.ModelAdmin):
readonly_fields = ("logo_thumbnail",)
- list_display = ("name", "country")
- list_filter = list_display
+ list_display = ("name", "country_flag")
+ list_filter = ("name", "country")
search_fields = ("name",)
+ @admin.display(description="Country")
+ def country_flag(self, obj):
+ return format_html(
+ '
{}'.format(obj.country.flag, obj.country.name)
+ )
+
@admin.register(Manufacturer)
class ManufacturerAdmin(admin.ModelAdmin):
readonly_fields = ("logo_thumbnail",)
- list_display = ("name", "category")
+ list_display = ("name", "category", "country_flag")
list_filter = ("category",)
search_fields = ("name",)
+ @admin.display(description="Country")
+ def country_flag(self, obj):
+ return format_html(
+ '
{}'.format(obj.country.flag, obj.country.name)
+ )
+
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
@@ -76,7 +89,7 @@ class RollingStockTypeAdmin(SortableAdminMixin, admin.ModelAdmin):
@admin.register(GenericDocument)
class GenericDocumentAdmin(admin.ModelAdmin):
- readonly_fields = ("size",)
+ readonly_fields = ("size", "creation_time", "updated_time")
list_display = (
"__str__",
"description",
@@ -88,4 +101,32 @@ class GenericDocumentAdmin(admin.ModelAdmin):
"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]
diff --git a/ram/metadata/migrations/0022_decoderdocument_creation_time_and_more.py b/ram/metadata/migrations/0022_decoderdocument_creation_time_and_more.py
new file mode 100644
index 0000000..3032c5a
--- /dev/null
+++ b/ram/metadata/migrations/0022_decoderdocument_creation_time_and_more.py
@@ -0,0 +1,66 @@
+# Generated by Django 5.1.4 on 2025-01-18 11:20
+
+import django.utils.timezone
+import django_countries.fields
+import tinymce.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("metadata", "0021_genericdocument"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="decoderdocument",
+ name="creation_time",
+ field=models.DateTimeField(
+ auto_now_add=True, default=django.utils.timezone.now
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="decoderdocument",
+ name="updated_time",
+ field=models.DateTimeField(auto_now=True),
+ ),
+ migrations.AddField(
+ model_name="genericdocument",
+ name="creation_time",
+ field=models.DateTimeField(
+ auto_now_add=True, default=django.utils.timezone.now
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="genericdocument",
+ name="notes",
+ field=tinymce.models.HTMLField(blank=True),
+ ),
+ migrations.AddField(
+ model_name="genericdocument",
+ name="updated_time",
+ field=models.DateTimeField(auto_now=True),
+ ),
+ migrations.AddField(
+ model_name="manufacturer",
+ name="country",
+ field=django_countries.fields.CountryField(blank=True, max_length=2),
+ ),
+ migrations.AlterField(
+ model_name="decoderdocument",
+ name="private",
+ field=models.BooleanField(
+ default=False, help_text="Document will be visible only to logged users"
+ ),
+ ),
+ migrations.AlterField(
+ model_name="genericdocument",
+ name="private",
+ field=models.BooleanField(
+ default=False, help_text="Document will be visible only to logged users"
+ ),
+ ),
+ ]
diff --git a/ram/metadata/models.py b/ram/metadata/models.py
index 23fe45f..0a5e880 100644
--- a/ram/metadata/models.py
+++ b/ram/metadata/models.py
@@ -6,6 +6,8 @@ from django.dispatch.dispatcher import receiver
from django.core.exceptions import ValidationError
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.managers import PublicManager
@@ -34,6 +36,7 @@ class Manufacturer(models.Model):
category = models.CharField(
max_length=64, choices=settings.MANUFACTURER_TYPES
)
+ country = CountryField(blank=True)
website = models.URLField(blank=True)
logo = models.ImageField(
upload_to=os.path.join("images", "manufacturers"),
@@ -237,6 +240,7 @@ class Tag(models.Model):
class GenericDocument(Document):
+ notes = tinymce.HTMLField(blank=True)
tags = models.ManyToManyField(Tag, blank=True)
class Meta:
diff --git a/ram/portal/templates/cards/company.html b/ram/portal/templates/cards/company.html
index 2ef9306..04307b8 100644
--- a/ram/portal/templates/cards/company.html
+++ b/ram/portal/templates/cards/company.html
@@ -7,7 +7,10 @@
- Company |
+ Company |
+ {% if d.item.freelance %}
+ Freelance |
+ {% endif %}
@@ -27,7 +30,7 @@
Country |
- {{ d.item.country.name }}
+ | {{ d.item.country.name }} |
{% if d.item.freelance %}
diff --git a/ram/portal/templates/cards/consist.html b/ram/portal/templates/cards/consist.html
index 4caffa2..85bc81e 100644
--- a/ram/portal/templates/cards/consist.html
+++ b/ram/portal/templates/cards/consist.html
@@ -24,7 +24,10 @@
- Consist |
+ Consist |
+ {% if d.item.company.freelance %}
+ Freelance |
+ {% endif %}
diff --git a/ram/portal/templates/cards/roster.html b/ram/portal/templates/cards/roster.html
index c2ebc8c..3936cdb 100644
--- a/ram/portal/templates/cards/roster.html
+++ b/ram/portal/templates/cards/roster.html
@@ -22,7 +22,10 @@
- Rolling stock |
+ Rolling stock |
+ {% if d.item.rolling_class.company.freelance %}
+ Freelance |
+ {% endif %}
diff --git a/ram/portal/templates/consist.html b/ram/portal/templates/consist.html
index 3b7da7d..33d48c4 100644
--- a/ram/portal/templates/consist.html
+++ b/ram/portal/templates/consist.html
@@ -79,7 +79,10 @@
- Data |
+ Consist |
+ {% if consist.company.freelance %}
+ Freelance |
+ {% endif %}
diff --git a/ram/portal/templates/rollingstock.html b/ram/portal/templates/rollingstock.html
index 30271f8..f3d0b39 100644
--- a/ram/portal/templates/rollingstock.html
+++ b/ram/portal/templates/rollingstock.html
@@ -73,7 +73,10 @@
- Rolling stock |
+ Rolling stock |
+ {% if company.freelance %}
+ Freelance |
+ {% endif %}
@@ -84,7 +87,13 @@
Company |
- {{ company }} {{ company.extended_name_pp }}
+ {{ company }} {{ company.extended_name_pp }}
+ |
+
+
+ Country |
+
+ {{ company.country.name }}
|
@@ -281,7 +290,10 @@
- Company data |
+ Company data |
+ {% if company.freelance %}
+ Freelance |
+ {% endif %}
@@ -293,18 +305,16 @@
{% endif %}
Name |
- {{ company.name }} {{ company.extended_name_pp }} |
+
+ {{ company.name }} {{ company.extended_name_pp }}
+ |
Country |
- {{ company.country.name }}
+ |
+ {{ company.country.name }}
+ |
- {% if company.freelance %}
-
- Notes |
- A freelance company |
-
- {% endif %}
diff --git a/ram/ram/__init__.py b/ram/ram/__init__.py
index a995164..90c1856 100644
--- a/ram/ram/__init__.py
+++ b/ram/ram/__init__.py
@@ -1,4 +1,4 @@
from ram.utils import git_suffix
-__version__ = "0.16.0"
+__version__ = "0.16.1"
__version__ += git_suffix(__file__)
diff --git a/ram/ram/models.py b/ram/ram/models.py
index 63d7c65..ee5e36b 100644
--- a/ram/ram/models.py
+++ b/ram/ram/models.py
@@ -28,7 +28,12 @@ class Document(models.Model):
upload_to="files/",
storage=DeduplicatedStorage(),
)
- private = models.BooleanField(default=False)
+ 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)
class Meta:
abstract = True
diff --git a/ram/roster/admin.py b/ram/roster/admin.py
index 388852a..026c56e 100644
--- a/ram/roster/admin.py
+++ b/ram/roster/admin.py
@@ -2,7 +2,7 @@ import html
from django.conf import settings
from django.contrib import admin
-from django.utils.html import strip_tags
+from django.utils.html import format_html, strip_tags
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
@@ -31,7 +31,7 @@ class RollingClassPropertyInline(admin.TabularInline):
class RollingClass(admin.ModelAdmin):
inlines = (RollingClassPropertyInline,)
autocomplete_fields = ("manufacturer",)
- list_display = ("__str__", "type", "company")
+ list_display = ("__str__", "type", "company", "country_flag")
list_filter = ("company", "type__category", "type")
search_fields = (
"identifier",
@@ -40,6 +40,12 @@ class RollingClass(admin.ModelAdmin):
)
save_as = True
+ @admin.display(description="Country")
+ def country_flag(self, obj):
+ return format_html(
+ '
{}'.format(obj.country.flag, obj.country)
+ )
+
class RollingStockDocInline(admin.TabularInline):
model = RollingStockDocument
@@ -87,6 +93,21 @@ class RollingStockDocumentAdmin(admin.ModelAdmin):
"description",
"file",
)
+ autocomplete_fields = ("rolling_stock",)
+ fieldsets = (
+ (
+ None,
+ {
+ "fields": (
+ "private",
+ "rolling_stock",
+ "description",
+ "file",
+ "size",
+ )
+ },
+ ),
+ )
@admin.register(RollingStockJournal)
@@ -94,19 +115,32 @@ class RollingJournalDocumentAdmin(admin.ModelAdmin):
list_display = (
"__str__",
"date",
- "rolling_stock",
"private",
)
list_filter = (
"date",
"private",
)
+ autocomplete_fields = ("rolling_stock",)
search_fields = (
"rolling_stock__rolling_class__identifier",
"rolling_stock__road_number",
"rolling_stock__item_number",
"log",
)
+ fieldsets = (
+ (
+ None,
+ {
+ "fields": (
+ "private",
+ "rolling_stock",
+ "log",
+ "date",
+ )
+ },
+ ),
+ )
@admin.register(RollingStock)
@@ -126,7 +160,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
"scale",
"item_number",
"company",
- "country",
+ "country_flag",
"published",
)
list_filter = (
@@ -146,6 +180,12 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
)
save_as = True
+ @admin.display(description="Country")
+ def country_flag(self, obj):
+ return format_html(
+ '
{}'.format(obj.country.flag, obj.country)
+ )
+
fieldsets = (
(
None,
@@ -187,12 +227,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
),
(
"Notes",
- {
- "classes": ("collapse",),
- "fields": (
- "notes",
- )
- },
+ {"classes": ("collapse",), "fields": ("notes",)},
),
(
"Audit",
@@ -241,29 +276,31 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
"{}:{}".format(property.property.name, property.value)
for property in obj.property.all()
)
- data.append([
- obj.__str__(),
- obj.rolling_class.company.name,
- obj.rolling_class.identifier,
- obj.road_number,
- obj.manufacturer.name,
- obj.scale.scale,
- obj.item_number,
- obj.set,
- obj.era,
- html.unescape(strip_tags(obj.description)),
- obj.production_year,
- html.unescape(strip_tags(obj.notes)),
- settings.CSV_SEPARATOR_ALT.join(
- t.name for t in obj.tags.all()
- ),
- obj.decoder_interface,
- obj.decoder,
- obj.address,
- obj.purchase_date,
- obj.price,
- properties,
- ])
+ data.append(
+ [
+ obj.__str__(),
+ obj.rolling_class.company.name,
+ obj.rolling_class.identifier,
+ obj.road_number,
+ obj.manufacturer.name,
+ obj.scale.scale,
+ obj.item_number,
+ obj.set,
+ obj.era,
+ html.unescape(strip_tags(obj.description)),
+ obj.production_year,
+ html.unescape(strip_tags(obj.notes)),
+ settings.CSV_SEPARATOR_ALT.join(
+ t.name for t in obj.tags.all()
+ ),
+ obj.decoder_interface,
+ obj.decoder,
+ obj.address,
+ obj.purchase_date,
+ obj.price,
+ properties,
+ ]
+ )
return generate_csv(header, data, "rolling_stock.csv")
diff --git a/ram/roster/migrations/0032_rollingstockdocument_creation_time_and_more.py b/ram/roster/migrations/0032_rollingstockdocument_creation_time_and_more.py
new file mode 100644
index 0000000..b3f69bf
--- /dev/null
+++ b/ram/roster/migrations/0032_rollingstockdocument_creation_time_and_more.py
@@ -0,0 +1,34 @@
+# Generated by Django 5.1.4 on 2025-01-18 11:20
+
+import django.utils.timezone
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("roster", "0031_alter_rollingstockdocument_unique_together_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="rollingstockdocument",
+ name="creation_time",
+ field=models.DateTimeField(
+ auto_now_add=True, default=django.utils.timezone.now
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="rollingstockdocument",
+ name="updated_time",
+ field=models.DateTimeField(auto_now=True),
+ ),
+ migrations.AlterField(
+ model_name="rollingstockdocument",
+ name="private",
+ field=models.BooleanField(
+ default=False, help_text="Document will be visible only to logged users"
+ ),
+ ),
+ ]
diff --git a/ram/roster/models.py b/ram/roster/models.py
index 3d5217d..637495c 100644
--- a/ram/roster/models.py
+++ b/ram/roster/models.py
@@ -42,6 +42,10 @@ class RollingClass(models.Model):
def __str__(self):
return "{0} {1}".format(self.company, self.identifier)
+ @property
+ def country(self):
+ return self.company.country
+
class RollingClassProperty(PropertyInstance):
rolling_class = models.ForeignKey(
@@ -127,7 +131,7 @@ class RollingStock(BaseModel):
@property
def country(self):
- return str(self.rolling_class.company.country)
+ return self.rolling_class.company.country
@property
def company(self):