16 Commits

Author SHA1 Message Date
32b5522a1e Change how images and consists are sorted (#14) 2023-01-02 16:08:25 +01:00
89b666dab2 Update README.md 2022-12-30 09:28:08 +01:00
ffad964373 Add possibility to inject js in head (analytics) 2022-12-28 23:54:49 +01:00
538dc0bd80 Add page title in html 2022-12-28 23:36:46 +01:00
8bd2635c28 Change image sort, thumbnails first 2022-12-28 22:14:10 +01:00
feda1f6cb4 [auto update] sync submodules 2022-11-28 18:22:05 +01:00
2c851b2822 Add migrations 2022-11-27 01:11:43 +01:00
e5ba2cfaec Merge pull request #13 from daniviga/dedup
Reuse existing file if content is the same
2022-11-27 01:09:50 +01:00
091f426242 Bump version 2022-11-27 01:09:34 +01:00
f603fd3e2d Reuse existing file if content is the same 2022-11-27 01:07:38 +01:00
a3b2112e03 Fix search query 2022-11-24 16:38:07 +01:00
055b0bab59 Enable "Save as" in roster and consist 2022-11-01 12:30:38 +01:00
3aea2ae340 Merge pull request #12 from daniviga/move-dcc-interface
Move decoder interface def into rolling stock
2022-11-01 00:07:18 +01:00
242fe6814d Move decoder interface def into rolling stock 2022-11-01 00:06:30 +01:00
90ffadb2ab Hotfix templates/companies.html pagination 2022-10-22 22:55:31 +02:00
21bf09687a Improve a CSS for journal 2022-08-28 11:32:19 +02:00
37 changed files with 411 additions and 71 deletions

View File

@@ -21,7 +21,7 @@ it has been developed with a commitment of few minutes a day;
it lacks any kind of documentation, code review, architectural review,
security assesment, pentest, ISO certification, etc.
This project probably doesn't match you needs nor expectations. Be aware.
This project probably doesn't match your needs nor expectations. Be aware.
Your model train may also catch fire while using this software.

View File

@@ -21,6 +21,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
list_display = ("identifier", "company", "era")
list_filter = list_display
search_fields = list_display
save_as = True
fieldsets = (
(

View File

@@ -0,0 +1,24 @@
# Generated by Django 4.1.2 on 2022-11-27 00:10
from django.db import migrations, models
import ram.utils
class Migration(migrations.Migration):
dependencies = [
("consist", "0006_md_to_html"),
]
operations = [
migrations.AlterField(
model_name="consist",
name="image",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/",
),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.1.3 on 2023-01-02 15:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("consist", "0007_alter_consist_image"),
]
operations = [
migrations.AlterModelOptions(
name="consist",
options={"ordering": ["company", "-creation_time"]},
),
]

View File

@@ -4,6 +4,7 @@ from django.urls import reverse
from ckeditor_uploader.fields import RichTextUploadingField
from ram.utils import DeduplicatedStorage
from metadata.models import Company, Tag
from roster.models import RollingStock
@@ -17,7 +18,9 @@ class Consist(models.Model):
)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
era = models.CharField(max_length=32, blank=True)
image = models.ImageField(upload_to="images/", null=True, blank=True)
image = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True
)
notes = RichTextUploadingField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
@@ -29,7 +32,7 @@ class Consist(models.Model):
return reverse("consist", kwargs={"uuid": self.uuid})
class Meta:
ordering = ["creation_time"]
ordering = ["company", "-creation_time"]
class ConsistItem(models.Model):

View File

@@ -20,8 +20,8 @@ class PropertyAdmin(admin.ModelAdmin):
@admin.register(Decoder)
class DecoderAdmin(admin.ModelAdmin):
readonly_fields = ("image_thumbnail",)
list_display = ("__str__", "interface")
list_filter = ("manufacturer", "interface")
list_display = ("__str__", "sound")
list_filter = ("manufacturer", "sound")
search_fields = ("name", "manufacturer__name")

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.2 on 2022-10-31 23:01
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("metadata", "0007_rename_track_scale_tracks"),
("roster", "0013_rollingstock_decoder_interface"),
]
operations = [
migrations.RemoveField(
model_name="decoder",
name="interface",
),
]

View File

@@ -0,0 +1,44 @@
# Generated by Django 4.1.2 on 2022-11-27 00:10
from django.db import migrations, models
import ram.utils
class Migration(migrations.Migration):
dependencies = [
("metadata", "0008_remove_decoder_interface"),
]
operations = [
migrations.AlterField(
model_name="company",
name="logo",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/",
),
),
migrations.AlterField(
model_name="decoder",
name="image",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/",
),
),
migrations.AlterField(
model_name="manufacturer",
name="logo",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/",
),
),
]

View File

@@ -3,7 +3,7 @@ from django.conf import settings
from django.dispatch.dispatcher import receiver
from django_countries.fields import CountryField
from ram.utils import get_image_preview, slugify
from ram.utils import DeduplicatedStorage, get_image_preview, slugify
class Property(models.Model):
@@ -24,7 +24,9 @@ class Manufacturer(models.Model):
max_length=64, choices=settings.MANUFACTURER_TYPES
)
website = models.URLField(blank=True)
logo = models.ImageField(upload_to="images/", null=True, blank=True)
logo = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True
)
class Meta:
ordering = ["category", "name"]
@@ -43,7 +45,9 @@ class Company(models.Model):
extended_name = models.CharField(max_length=128, blank=True)
country = CountryField()
freelance = models.BooleanField(default=False)
logo = models.ImageField(upload_to="images/", null=True, blank=True)
logo = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True
)
class Meta:
verbose_name_plural = "Companies"
@@ -66,11 +70,10 @@ class Decoder(models.Model):
limit_choices_to={"category": "model"},
)
version = models.CharField(max_length=64, blank=True)
interface = models.PositiveSmallIntegerField(
choices=settings.DECODER_INTERFACES, null=True, blank=True
)
sound = models.BooleanField(default=False)
image = models.ImageField(upload_to="images/", null=True, blank=True)
image = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True
)
def __str__(self):
return "{0} - {1}".format(self.manufacturer, self.name)

View File

@@ -3,7 +3,34 @@ from solo.admin import SingletonModelAdmin
from portal.models import SiteConfiguration, Flatpage
admin.site.register(SiteConfiguration, SingletonModelAdmin)
@admin.register(SiteConfiguration)
class SiteConfigurationAdmin(SingletonModelAdmin):
fieldsets = (
(
None,
{
"fields": (
"site_name",
"site_author",
"about",
"items_per_page",
"items_ordering",
"footer",
"footer_extended",
)
},
),
(
"Advanced",
{
"classes": ("collapse",),
"fields": (
"show_version",
"extra_head",
),
},
),
)
@admin.register(Flatpage)

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.3 on 2022-12-28 22:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("portal", "0013_remove_flatpage_draft_flatpage_published"),
]
operations = [
migrations.AddField(
model_name="siteconfiguration",
name="extra_head",
field=models.TextField(blank=True),
),
]

View File

@@ -35,6 +35,7 @@ class SiteConfiguration(SingletonModel):
footer = RichTextField(blank=True)
footer_extended = RichTextField(blank=True)
show_version = models.BooleanField(default=True)
extra_head = models.TextField(blank=True)
class Meta:
verbose_name = "Site Configuration"

View File

@@ -23,6 +23,15 @@ a.badge, a.badge:hover {
padding: .5rem;
}
#nav-journal ul, #nav-journal ol {
margin: 0;
padding-left: 1rem;
}
#nav-journal p {
margin: 0;
}
#footer > p {
display: inline;
}

View File

@@ -12,7 +12,7 @@
<meta name="description" content="{{ site_conf.about}}">
<meta name="author" content="{{ site_conf.site_author }}">
<meta name="generator" content="Django Framework">
<title>{{ site_conf.site_name }}</title>
<title>{% block title %}{{ title }}{% endblock %} - {{ site_conf.site_name }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-nightshade.min.css" rel="stylesheet">
<link href="{% static "css/main.css" %}" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
@@ -34,6 +34,10 @@
html.dark .d-light-inline { display: none !important; }
html.dark .d-dark-inline { display: inline !important; }
</style>
{% block extra_head %}
{{ site_conf.extra_head | safe }}
{% endblock %}
</head>
<body>
<header>
@@ -81,7 +85,9 @@
<section class="py-4 text-center container">
<div class="row">
<div class="mx-auto">
{% block header %}{% endblock %}
<h1 class="fw-light">{{ title }}</h1>
{% block header %}
{% endblock %}
</div>
</div>
</section>
@@ -94,7 +100,7 @@
<div class="col">
<div class="card shadow-sm">
{% for i in r.image.all %}
{% if i.is_thumbnail %}<a href="{{r.get_absolute_url}}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %}
{% if forloop.first %}<a href="{{r.get_absolute_url}}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %}
{% endfor %}
<div class="card-body">
<p class="card-text" style="position: relative;">

View File

@@ -1,8 +1,5 @@
{% extends "base.html" %}
{% block header %}
<h1 class="fw-light">Companies</h1>
{% endblock %}
{% block cards %}
{% for c in company %}
<div class="col">
@@ -59,7 +56,7 @@
<ul class="pagination justify-content-center mt-4 mb-0">
{% if company.has_previous %}
<li class="page-item">
<a class="page-link" href="{% url 'company_pagination' page=company.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a>
<a class="page-link" href="{% url 'companies_pagination' page=company.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a>
</li>
{% else %}
<li class="page-item disabled">
@@ -75,13 +72,13 @@
{% if i == company.paginator.ELLIPSIS %}
<li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="{% url 'company_pagination' page=i %}#rolling-stock">{{ i }}</a></li>
<li class="page-item"><a class="page-link" href="{% url 'companies_pagination' page=i %}#rolling-stock">{{ i }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% if company.has_next %}
<li class="page-item">
<a class="page-link" href="{% url 'company_pagination' page=company.next_page_number %}#rolling-stock" tabindex="-1">Next</a>
<a class="page-link" href="{% url 'companies_pagination' page=company.next_page_number %}#rolling-stock" tabindex="-1">Next</a>
</li>
{% else %}
<li class="page-item disabled">

View File

@@ -1,7 +1,6 @@
{% extends "base.html" %}
{% block header %}
<h1 class="fw-light">{{ consist }}</h1>
{% if consist.tags.all %}
<p><small>Tags:</small>
{% for t in consist.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
@@ -16,7 +15,7 @@
<div class="col">
<div class="card shadow-sm">
{% for i in r.rolling_stock.image.all %}
{% if i.is_thumbnail %}<a href="{{r.rolling_stock.get_absolute_url}}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %}
{% if forloop.first %}<a href="{{r.rolling_stock.get_absolute_url}}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %}
{% endfor %}
<div class="card-body">
<p class="card-text" style="position: relative;">

View File

@@ -1,8 +1,5 @@
{% extends "base.html" %}
{% block header %}
<h1 class="fw-light">Consists</h1>
{% endblock %}
{% block cards %}
{% for c in consist %}
<div class="col">
@@ -13,7 +10,7 @@
{% else %}
{% with c.consist_item.first.rolling_stock as r %}
{% for i in r.image.all %}
{% if i.is_thumbnail %}<img src="{{ i.image.url }}" alt="Card image cap">{% endif %}
{% if forloop.first %}<img src="{{ i.image.url }}" alt="Card image cap">{% endif %}
{% endfor %}
{% endwith %}
{% endif %}

View File

@@ -1,7 +1,6 @@
{% extends 'base.html' %}
{% block header %}
<h1 class="fw-light">{{ flatpage.name }}</h1>
<small class="text-muted">Updated {{ flatpage.updated_time | date:"M d, Y H:i" }}</small>
{% endblock %}
{% block extra_content %}

View File

@@ -1,7 +1,6 @@
{% extends "base.html" %}
{% block header %}
{% if site_conf.about %}<h1 class="fw-light">About</h1>{% endif %}
<p class="lead text-muted">{{ site_conf.about | safe }}</p>
{% endblock %}

View File

@@ -1,7 +1,6 @@
{% extends 'base.html' %}
{% block header %}
<h1 class="fw-light">{{ rolling_stock }}</h1>
{% if rolling_stock.tags.all %}
<p><small>Tags:</small>
{% for t in rolling_stock.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
@@ -102,7 +101,7 @@
</tr>
</tbody>
</table>
{% if rolling_stock.decoder %}
{% if rolling_stock.decoder or rolling_stock.decoder_interface %}
<table class="table table-striped">
<thead>
<tr>
@@ -111,13 +110,19 @@
</thead>
<tbody>
<tr>
<th width="35%" scope="row">Decoder</th>
<th width="35%" scope="row">Interface</th>
<td>{{ rolling_stock.get_decoder_interface_display }}</td>
</tr>
{% if rolling_stock.decoder %}
<tr>
<th scope="row">Decoder</th>
<td>{{ rolling_stock.decoder }}</td>
</tr>
<tr>
<th scope="row">Address</th>
<td>{{ rolling_stock.address }}</td>
</tr>
{% endif %}
</tbody>
</table>
{% endif %}
@@ -230,6 +235,10 @@
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Interface</th>
<td>{{ rolling_stock.get_decoder_interface_display }}</td>
</tr>
<tr>
<th width="35%" scope="row">Address</th>
<td>{{ rolling_stock.address }}</td>
@@ -246,10 +255,6 @@
<th scope="row">Version</th>
<td>{{ rolling_stock.decoder.version }}</td>
</tr>
<tr>
<th scope="row">Interface</th>
<td>{{ rolling_stock.decoder.get_interface_display }}</td>
</tr>
<tr>
<th scope="row">Sound</th>
<td>{{ rolling_stock.decoder.sound | yesno:"Yes,No" }}</td>

View File

@@ -1,8 +1,5 @@
{% extends "base.html" %}
{% block header %}
<h1 class="fw-light">Scales</h1>
{% endblock %}
{% block cards %}
{% for s in scale %}
<div class="col">

View File

@@ -1,7 +1,6 @@
{% extends "base.html" %}
{% block header %}
<h1 class="fw-light">{{ filter | default_if_none:"Search" | title }}: {{ search }}</h1>
<p class="lead text-muted">Results found: {{ matches }}</p>
{% endblock %}
{% block pagination %}

View File

@@ -46,7 +46,11 @@ class GetHome(View):
return render(
request,
"home.html",
{"rolling_stock": rolling_stock, "page_range": page_range},
{
"title": "Home",
"rolling_stock": rolling_stock,
"page_range": page_range,
},
)
@@ -83,8 +87,10 @@ class GetHomeFiltered(View):
query = Q(tags__slug__iexact=search)
else:
raise Http404
rolling_stock = RollingStock.objects.filter(query).order_by(
*order_by_fields()
rolling_stock = (
RollingStock.objects.filter(query)
.distinct()
.order_by(*order_by_fields())
)
matches = len(rolling_stock)
@@ -105,6 +111,7 @@ class GetHomeFiltered(View):
request,
"search.html",
{
"title": "{0}: {1}".format(_filter.capitalize(), search),
"search": search,
"filter": _filter,
"matches": matches,
@@ -125,6 +132,7 @@ class GetHomeFiltered(View):
request,
"search.html",
{
"title": "{0}: {1}".format(_filter.capitalize(), search),
"search": search,
"filter": _filter,
"matches": matches,
@@ -164,6 +172,7 @@ class GetRollingStock(View):
request,
"page.html",
{
"title": rolling_stock,
"rolling_stock": rolling_stock,
"class_properties": class_properties,
"rolling_stock_properties": rolling_stock_properties,
@@ -186,7 +195,11 @@ class Consists(View):
return render(
request,
"consists.html",
{"consist": consist, "page_range": page_range},
{
"title": "Consists",
"consist": consist,
"page_range": page_range,
},
)
@@ -209,6 +222,7 @@ class GetConsist(View):
request,
"consist.html",
{
"title": consist,
"consist": consist,
"rolling_stock": rolling_stock,
"page_range": page_range,
@@ -230,7 +244,11 @@ class Companies(View):
return render(
request,
"companies.html",
{"company": company, "page_range": page_range},
{
"title": "Companies",
"company": company,
"page_range": page_range,
},
)
@@ -248,7 +266,7 @@ class Scales(View):
return render(
request,
"scales.html",
{"scale": scale, "page_range": page_range},
{"title": "Scales", "scale": scale, "page_range": page_range},
)
@@ -264,5 +282,5 @@ class GetFlatpage(View):
return render(
request,
"flatpage.html",
{"flatpage": flatpage},
{"title": flatpage.name, "flatpage": flatpage},
)

View File

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

View File

@@ -1,8 +1,29 @@
import os
import hashlib
import subprocess
from django.utils.html import format_html
from django.utils.text import slugify as django_slugify
from django.core.files.storage import FileSystemStorage
class DeduplicatedStorage(FileSystemStorage):
"""
A derived FileSystemStorage class that compares already existing files
(with the same name) with new uploaded ones and stores new file only if
sha256 hash on is content is different
"""
def save(self, name, content, max_length=None):
if super().exists(name):
new = hashlib.sha256(content.file.getbuffer()).hexdigest()
with open(super().path(name), "rb") as file:
file_binary = file.read()
old = hashlib.sha256(file_binary).hexdigest()
if old == new:
return name
return super().save(name, content, max_length)
def git_suffix(fname):

View File

@@ -1,4 +1,6 @@
from django.contrib import admin
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
from roster.models import (
RollingClass,
RollingClassProperty,
@@ -35,7 +37,7 @@ class RollingStockDocInline(admin.TabularInline):
classes = ["collapse"]
class RollingStockImageInline(admin.TabularInline):
class RollingStockImageInline(SortableInlineAdminMixin, admin.TabularInline):
model = RollingStockImage
min_num = 0
extra = 0
@@ -93,7 +95,7 @@ class RollingJournalDocumentAdmin(admin.ModelAdmin):
@admin.register(RollingStock)
class RollingStockAdmin(admin.ModelAdmin):
class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
inlines = (
RollingStockPropertyInline,
RollingStockImageInline,
@@ -125,6 +127,7 @@ class RollingStockAdmin(admin.ModelAdmin):
"address",
"sku",
)
save_as = True
fieldsets = (
(
@@ -148,6 +151,7 @@ class RollingStockAdmin(admin.ModelAdmin):
"DCC",
{
"fields": (
"decoder_interface",
"decoder",
"address",
)

View File

@@ -0,0 +1,42 @@
# Generated by Django 4.1 on 2022-10-31 22:27
from django.db import migrations, models
def meta_to_roster(apps, schema_editor):
model = apps.get_model("roster", "RollingStock")
for row in model.objects.all():
if row.decoder:
decoder_interface = row.decoder.interface
row.__dict__["decoder_interface"] = decoder_interface
row.save(update_fields=["decoder_interface"])
class Migration(migrations.Migration):
dependencies = [
("roster", "0012_rollingstockjournal"),
]
operations = [
migrations.AddField(
model_name="rollingstock",
name="decoder_interface",
field=models.PositiveSmallIntegerField(
blank=True,
choices=[
(1, "NEM651"),
(2, "NEM652"),
(3, "PluX"),
(4, "21MTC"),
(5, "Next18/Next18S"),
],
null=True,
),
),
migrations.RunPython(
meta_to_roster,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 4.1.2 on 2022-11-27 00:10
from django.db import migrations, models
import ram.utils
class Migration(migrations.Migration):
dependencies = [
("roster", "0013_rollingstock_decoder_interface"),
]
operations = [
migrations.AlterField(
model_name="rollingstockdocument",
name="file",
field=models.FileField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage(),
upload_to="files/",
),
),
migrations.AlterField(
model_name="rollingstockimage",
name="image",
field=models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/",
),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.1.3 on 2022-12-28 21:07
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("roster", "0014_alter_rollingstockdocument_file_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="rollingstockimage",
options={"ordering": ["-is_thumbnail"]},
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.1.3 on 2023-01-02 12:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("roster", "0015_alter_rollingstockimage_options"),
]
operations = [
migrations.AlterModelOptions(
name="rollingstockimage",
options={"ordering": ["order"]},
),
migrations.AddField(
model_name="rollingstockimage",
name="order",
field=models.PositiveIntegerField(default=0),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.1.3 on 2023-01-02 15:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("roster", "0016_alter_rollingstockimage_options_and_more"),
]
operations = [
migrations.RemoveField(
model_name="rollingstockimage",
name="is_thumbnail",
),
]

View File

@@ -3,12 +3,13 @@ import re
from uuid import uuid4
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 get_image_preview
from ram.utils import DeduplicatedStorage, get_image_preview
from metadata.models import (
Property,
Scale,
@@ -19,11 +20,6 @@ from metadata.models import (
RollingStockType,
)
# class OverwriteMixin(FileSystemStorage):
# def get_available_name(self, name, max_length):
# self.delete(name)
# return name
class RollingClass(models.Model):
identifier = models.CharField(max_length=128, unique=False)
@@ -87,6 +83,9 @@ class RollingStock(models.Model):
)
scale = models.ForeignKey(Scale, on_delete=models.CASCADE)
sku = models.CharField(max_length=32, blank=True)
decoder_interface = models.PositiveSmallIntegerField(
choices=settings.DECODER_INTERFACES, null=True, blank=True
)
decoder = models.ForeignKey(
Decoder, on_delete=models.CASCADE, null=True, blank=True
)
@@ -133,7 +132,12 @@ class RollingStockDocument(models.Model):
RollingStock, on_delete=models.CASCADE, related_name="document"
)
description = models.CharField(max_length=128, blank=True)
file = models.FileField(upload_to="files/", null=True, blank=True)
file = models.FileField(
upload_to="files/",
storage=DeduplicatedStorage(),
null=True,
blank=True,
)
class Meta(object):
unique_together = ("rolling_stock", "file")
@@ -151,11 +155,13 @@ class RollingStockDocument(models.Model):
class RollingStockImage(models.Model):
order = models.PositiveIntegerField(default=0, blank=False, null=False)
rolling_stock = models.ForeignKey(
RollingStock, on_delete=models.CASCADE, related_name="image"
)
image = models.ImageField(upload_to="images/", null=True, blank=True)
is_thumbnail = models.BooleanField()
image = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True
)
def image_thumbnail(self):
return get_image_preview(self.image.url)
@@ -165,12 +171,8 @@ class RollingStockImage(models.Model):
def __str__(self):
return "{0}".format(os.path.basename(self.image.name))
def save(self, **kwargs):
if self.is_thumbnail:
RollingStockImage.objects.filter(
rolling_stock=self.rolling_stock
).update(is_thumbnail=False)
super().save(**kwargs)
class Meta:
ordering = ["order"]
class RollingStockProperty(models.Model):