23 Commits

Author SHA1 Message Date
e023edbeeb Add support for dark mode 2022-07-22 22:39:02 +02:00
c9c8976c60 UX improvements 2022-07-21 23:01:34 +02:00
5765472704 Fix to scale abbr 2022-07-21 22:11:17 +02:00
4fb9d1903f Reduce elided_page_range 2022-07-20 21:51:18 +02:00
63379c9673 Expose tracks 2022-07-18 23:45:13 +02:00
be6a685f55 Gauge vs track 2022-07-18 23:41:47 +02:00
ad33731913 Fix filtered pagination 2022-07-18 22:48:04 +02:00
503a214a4d Minor fixes 2022-07-18 17:07:01 +02:00
1528d1ba56 Make card title a stretched link 2022-07-17 20:46:00 +02:00
5b04abb262 Add countries and scales pages 2022-07-17 12:25:09 +02:00
9fa70ae656 Add filtering by scale 2022-07-16 21:24:36 +02:00
49b7aac807 Run black 2022-07-16 21:00:17 +02:00
24af738ad4 Fix page range 2022-07-16 20:57:29 +02:00
8136a180ab Try another fix for ellipsis 2022-07-16 19:56:08 +02:00
908790c3e0 Try a fix for ellipsis 2022-07-16 19:46:33 +02:00
44cdb8b09f Refactor paginator and add ellipsis 2022-07-16 18:57:46 +02:00
7d3f29e734 Use int sort for road numbers 2022-07-16 17:48:04 +02:00
65b615ae63 Validate search 2022-07-15 22:26:37 +02:00
66a85b4ed7 Add tag based filtering 2022-07-15 21:31:40 +02:00
70c12c69b2 Improve road number sorting and enforce company on consists 2022-07-15 18:10:18 +02:00
e55f953c8a Add sorting, enforce foreign keys 2022-07-15 17:28:44 +02:00
273225f919 Default if none in template 2022-07-13 21:43:50 +02:00
1296c4e663 Default if none in template 2022-07-13 21:42:33 +02:00
31 changed files with 722 additions and 161 deletions

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.0.6 on 2022-07-15 16:08
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('metadata', '0004_alter_rollingstocktype_options_and_more'),
('consist', '0003_consist_image'),
]
operations = [
migrations.AlterField(
model_name='consist',
name='company',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='metadata.company'),
),
]

View File

@@ -13,9 +13,7 @@ class Consist(models.Model):
consist_address = models.SmallIntegerField( consist_address = models.SmallIntegerField(
default=None, null=True, blank=True default=None, null=True, blank=True
) )
company = models.ForeignKey( company = models.ForeignKey(Company, on_delete=models.CASCADE)
Company, on_delete=models.CASCADE, null=True, blank=True
)
era = models.CharField(max_length=32, blank=True) era = models.CharField(max_length=32, blank=True)
image = models.ImageField(upload_to="images/", null=True, blank=True) image = models.ImageField(upload_to="images/", null=True, blank=True)
notes = models.TextField(blank=True) notes = models.TextField(blank=True)
@@ -23,7 +21,7 @@ class Consist(models.Model):
updated_time = models.DateTimeField(auto_now=True) updated_time = models.DateTimeField(auto_now=True)
def __str__(self): def __str__(self):
return "{0}".format(self.identifier) return "{0} {1}".format(self.company, self.identifier)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("consist", kwargs={"uuid": self.uuid}) return reverse("consist", kwargs={"uuid": self.uuid})

View File

@@ -9,11 +9,7 @@ class DriverConfigurationAdmin(SingletonModelAdmin):
fieldsets = ( fieldsets = (
( (
"General configuration", "General configuration",
{ {"fields": ("enabled",)},
"fields": (
"enabled",
)
},
), ),
( (
"Remote DCC-EX configuration", "Remote DCC-EX configuration",

View File

@@ -1,4 +1,6 @@
from django.contrib import admin from django.contrib import admin
from adminsortable2.admin import SortableAdminMixin
from metadata.models import ( from metadata.models import (
Property, Property,
Decoder, Decoder,
@@ -20,13 +22,13 @@ class DecoderAdmin(admin.ModelAdmin):
readonly_fields = ("image_thumbnail",) readonly_fields = ("image_thumbnail",)
list_display = ("__str__", "interface") list_display = ("__str__", "interface")
list_filter = ("manufacturer", "interface") list_filter = ("manufacturer", "interface")
search_fields = ("__str__",) search_fields = ("name", "manufacturer__name")
@admin.register(Scale) @admin.register(Scale)
class ScaleAdmin(admin.ModelAdmin): class ScaleAdmin(admin.ModelAdmin):
list_display = ("scale", "ratio", "gauge") list_display = ("scale", "ratio", "gauge", "tracks")
list_filter = ("ratio", "gauge") list_filter = ("ratio", "gauge", "tracks")
search_fields = list_display search_fields = list_display
@@ -54,7 +56,7 @@ class TagAdmin(admin.ModelAdmin):
@admin.register(RollingStockType) @admin.register(RollingStockType)
class RollingStockTypeAdmin(admin.ModelAdmin): class RollingStockTypeAdmin(SortableAdminMixin, admin.ModelAdmin):
list_display = ("__str__",) list_display = ("__str__",)
list_filter = ("type", "category") list_filter = ("type", "category")
search_fields = list_display search_fields = ("type", "category")

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.0.6 on 2022-07-14 14:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('metadata', '0003_property_private'),
]
operations = [
migrations.AlterModelOptions(
name='rollingstocktype',
options={'ordering': ['order']},
),
migrations.AddField(
model_name='rollingstocktype',
name='order',
field=models.PositiveSmallIntegerField(default=0),
preserve_default=False,
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.6 on 2022-07-18 21:40
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('metadata', '0004_alter_rollingstocktype_options_and_more'),
]
operations = [
migrations.RenameField(
model_name='scale',
old_name='gauge',
new_name='track',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.6 on 2022-07-18 21:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('metadata', '0005_rename_gauge_scale_track'),
]
operations = [
migrations.AddField(
model_name='scale',
name='gauge',
field=models.CharField(blank=True, max_length=16),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.6 on 2022-07-18 21:44
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('metadata', '0006_scale_gauge'),
]
operations = [
migrations.RenameField(
model_name='scale',
old_name='track',
new_name='tracks',
),
]

View File

@@ -63,7 +63,7 @@ class Decoder(models.Model):
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
Manufacturer, Manufacturer,
on_delete=models.CASCADE, on_delete=models.CASCADE,
limit_choices_to={"category": "model"} limit_choices_to={"category": "model"},
) )
version = models.CharField(max_length=64, blank=True) version = models.CharField(max_length=64, blank=True)
interface = models.PositiveSmallIntegerField( interface = models.PositiveSmallIntegerField(
@@ -85,6 +85,7 @@ class Scale(models.Model):
scale = models.CharField(max_length=32, unique=True) scale = models.CharField(max_length=32, unique=True)
ratio = models.CharField(max_length=16, blank=True) ratio = models.CharField(max_length=16, blank=True)
gauge = models.CharField(max_length=16, blank=True) gauge = models.CharField(max_length=16, blank=True)
tracks = models.CharField(max_length=16, blank=True)
class Meta: class Meta:
ordering = ["scale"] ordering = ["scale"]
@@ -108,12 +109,14 @@ def tag_pre_save(sender, instance, **kwargs):
class RollingStockType(models.Model): class RollingStockType(models.Model):
type = models.CharField(max_length=64) type = models.CharField(max_length=64)
order = models.PositiveSmallIntegerField()
category = models.CharField( category = models.CharField(
max_length=64, choices=settings.ROLLING_STOCK_TYPES max_length=64, choices=settings.ROLLING_STOCK_TYPES
) )
class Meta(object): class Meta(object):
unique_together = ("category", "type") unique_together = ("category", "type")
ordering = ["order"]
def __str__(self): def __str__(self):
return "{0} {1}".format(self.type, self.category) return "{0} {1}".format(self.type, self.category)

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.0.6 on 2022-07-15 12:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('portal', '0006_alter_siteconfiguration_site_name'),
]
operations = [
migrations.AddField(
model_name='siteconfiguration',
name='items_ordering',
field=models.CharField(choices=[('type', 'By rolling stock type'), ('company', 'By company name'), ('identifier', 'By rolling stock class')], default='type', max_length=10),
),
]

View File

@@ -16,6 +16,15 @@ class SiteConfiguration(SingletonModel):
choices=[(str(x * 3), str(x * 3)) for x in range(2, 11)], choices=[(str(x * 3), str(x * 3)) for x in range(2, 11)],
default="6", default="6",
) )
items_ordering = models.CharField(
max_length=10,
choices=[
("type", "By rolling stock type"),
("company", "By company name"),
("identifier", "By rolling stock class"),
],
default="type",
)
footer = models.TextField(blank=True) footer = models.TextField(blank=True)
footer_extended = models.TextField(blank=True) footer_extended = models.TextField(blank=True)
show_version = models.BooleanField(default=True) show_version = models.BooleanField(default=True)

View File

@@ -6,6 +6,11 @@
display: inline-block; display: inline-block;
} }
a.badge, a.badge:hover {
text-decoration: none;
color: #fff;
}
.tab-pane { .tab-pane {
min-height: 300px; min-height: 300px;
} }

View File

@@ -8,12 +8,14 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=""> <meta name="color-scheme" content="light dark">
<meta name="description" content="{{ site_conf.about}}">
<meta name="author" content="{{ site_conf.site_author }}"> <meta name="author" content="{{ site_conf.site_author }}">
<meta name="generator" content="Django Framework"> <meta name="generator" content="Django Framework">
<title>{{ site_conf.site_name }}</title> <title>{{ site_conf.site_name }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> <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="{% static "css/main.css" %}" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style> <style>
.bd-placeholder-img { .bd-placeholder-img {
font-size: 1.125rem; font-size: 1.125rem;
@@ -22,12 +24,15 @@
-moz-user-select: none; -moz-user-select: none;
user-select: none; user-select: none;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.bd-placeholder-img-lg { .bd-placeholder-img-lg {
font-size: 3.5rem; font-size: 3.5rem;
} }
} }
.d-light-inline { display: inline !important; }
.d-dark-inline { display: none !important; }
html.dark .d-light-inline { display: none !important; }
html.dark .d-dark-inline { display: inline !important; }
</style> </style>
</head> </head>
<body> <body>
@@ -40,7 +45,10 @@
</svg> </svg>
<strong>{{ site_conf.site_name }}</strong> <strong>{{ site_conf.site_name }}</strong>
</a> </a>
{% include 'includes/login.html' %} <div class="btn-group" role="group" aria-label="Basic example">
{% include 'includes/login.html' %}
<a id="darkmode-button" class="btn btn-sm btn-outline-dark"><i class="fa fa-moon-o fa-fw d-none d-light-inline" title="Switch to dark mode"></i><i class="fa fa-sun-o fa-fw d-none d-dark-inline" title="Switch to light mode"></i></a>
</div>
</div> </div>
</div> </div>
</header> </header>
@@ -57,6 +65,12 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'consists' %}">Consists</a> <a class="nav-link" href="{% url 'consists' %}">Consists</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{% url 'companies' %}">Companies</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'scales' %}">Scales</a>
</li>
</ul> </ul>
{% include 'includes/search.html' %} {% include 'includes/search.html' %}
</div> </div>
@@ -82,11 +96,14 @@
{% if i.is_thumbnail %}<a href="{{r.get_absolute_url}}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %} {% if i.is_thumbnail %}<a href="{{r.get_absolute_url}}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %}
{% endfor %} {% endfor %}
<div class="card-body"> <div class="card-body">
<p class="card-text"><strong>{{ r }}</strong></p> <p class="card-text" style="position: relative;">
<strong>{{ r }}</strong>
<a class="stretched-link" href="{{ r.get_absolute_url }}"></a>
</p>
{% if r.tags.all %} {% if r.tags.all %}
<p class="card-text"><small>Tags:</small> <p class="card-text"><small>Tags:</small>
{% for t in r.tags.all %}<span class="badge bg-primary"> {% for t in r.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</span>{# new line is required #} {{ t.name }}</a>{# new line is required #}
{% endfor %} {% endfor %}
</p> </p>
{% endif %} {% endif %}
@@ -123,7 +140,7 @@
</tr> </tr>
<tr> <tr>
<th scope="row">Scale</th> <th scope="row">Scale</th>
<td><abbr title="{{ r.scale.ratio }} - {{ r.scale.gauge }}">{{ r.scale }}</abbr></td> <td><a href="{% url 'filtered' _filter="scale" search=r.scale %}"><abbr title="{{ r.scale.ratio }} - {{ r.scale.tracks }}">{{ r.scale }}</abbr></a></td>
</tr> </tr>
<tr> <tr>
<th scope="row">SKU</th> <th scope="row">SKU</th>
@@ -150,13 +167,10 @@
</tbody> </tbody>
</table> </table>
{% endif %} {% endif %}
<div class="btn-group mb-4"> <div class="d-grid gap-2 mb-1 d-md-block">
<a class="btn btn-sm btn-outline-primary" href="{{r.get_absolute_url}}">Show all data</a> <a class="btn btn-sm btn-outline-primary" href="{{r.get_absolute_url}}">Show all data</a>
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:roster_rollingstock_change' r.pk %}">Edit</a>{% endif %} {% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:roster_rollingstock_change' r.pk %}">Edit</a>{% endif %}
</div> </div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">Updated {{ r.updated_time | date:"M d, Y H:m" }}</small>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -170,6 +184,12 @@
</main> </main>
{% include 'includes/footer.html' %} {% include 'includes/footer.html' %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js" integrity="sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D" crossorigin="anonymous" async></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/js/darkmode.min.js"></script>
<!-- script src="https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js" integrity="sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D" crossorigin="anonymous" async></script -->
<script>
document.querySelector("#darkmode-button").onclick = function(e){
darkmode.toggleDarkMode();
}
</script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,95 @@
{% extends "base.html" %}
{% load markdown %}
{% block header %}
<h1 class="fw-light">Companies</h1>
{% endblock %}
{% block cards %}
{% for c in company %}
<div class="col">
<div class="card shadow-sm">
<div class="card-body">
<p class="card-text" style="position: relative;">
<strong>{{ c.name }}</strong>
</p>
<table class="table table-striped">
<thead>
<tr>
<th colspan="2" scope="row">Company</th>
</tr>
</thead>
<tbody>
{% if c.logo %}
<tr>
<th width="35%" scope="row">Logo</th>
<td><img style="max-height: 48px" src="{{ c.logo.url }}" /></td>
</tr>
{% endif %}
<tr>
<th width="35%" scope="row">Name</th>
<td>{{ c.extended_name }}</td>
</tr>
<tr>
<th width="35%" scope="row">Abbreviation</th>
<td>{{ c }}</td>
</tr>
<tr>
<th width="35%" scope="row">Country</th>
<td>{{ c.country.name }} <img src="{{ c.country.flag }}" alt="{{ c.country }}" />
</tr>
{% if c.freelance %}
<tr>
<th width="35%" scope="row">Notes</th>
<td>A <em>freelance</em> company</td>
</tr>
{% endif %}
</tbody>
</table>
<div class="d-grid gap-2 mb-1 d-md-block">
<a class="btn btn-sm btn-outline-primary" href="{% url 'filtered' _filter="company" search=c %}">Show all rolling stock</a>
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:metadata_company_change' c.pk %}">Edit</a>{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% endblock %}
{% block pagination %}
{% if company.has_other_pages %}
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center mt-4">
{% 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>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Previous</span>
</li>
{% endif %}
{% for i in page_range %}
{% if company.number == i %}
<li class="page-item active">
<span class="page-link">{{ i }}</span></span>
</li>
{% else %}
{% 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>
{% 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>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Next</span>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}

View File

@@ -2,14 +2,15 @@
{% load markdown %} {% load markdown %}
{% block header %} {% block header %}
<h1 class="fw-light">{{ consist }}</h1> <h1 class="fw-light">{{ consist }}</h1>
{% if consist.tags.all %} {% if consist.tags.all %}
<p><small>Tags:</small> <p><small>Tags:</small>
{% for t in consist.tags.all %}<span class="badge bg-primary"> {% for t in consist.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</span>{# new line is required #} {{ t.name }}</a>{# new line is required #}
{% endfor %} {% endfor %}
</p> </p>
{% endif %} <small class="text-muted">Updated {{ consist.updated_time | date:"M d, Y H:i" }}</small>
{% endif %}
{% endblock %} {% endblock %}
{% block cards %} {% block cards %}
{% for r in rolling_stock %} {% for r in rolling_stock %}
@@ -19,11 +20,14 @@
{% if i.is_thumbnail %}<a href="{{r.rolling_stock.get_absolute_url}}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %} {% if i.is_thumbnail %}<a href="{{r.rolling_stock.get_absolute_url}}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %}
{% endfor %} {% endfor %}
<div class="card-body"> <div class="card-body">
<p class="card-text"><strong>{{ r }}</strong></p> <p class="card-text" style="position: relative;">
<strong>{{ r }}</strong>
<a class="stretched-link" href="{{ r.rolling_stock.get_absolute_url }}"></a>
</p>
{% if r.rolling_stock.tags.all %} {% if r.rolling_stock.tags.all %}
<p class="card-text"><small>Tags:</small> <p class="card-text"><small>Tags:</small>
{% for t in r.rolling_stock.tags.all %}<span class="badge bg-primary"> {% for t in r.rolling_stock.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</span>{# new line is required #} {{ t.name }}</a>{# new line is required #}
{% endfor %} {% endfor %}
</p> </p>
{% endif %} {% endif %}
@@ -60,7 +64,7 @@
</tr> </tr>
<tr> <tr>
<th scope="row">Scale</th> <th scope="row">Scale</th>
<td><abbr title="{{ r.rolling_stock.scale.ratio }} - {{ r.rolling_stock.scale.gauge }}">{{ r.rolling_stock.scale }}</abbr></td> <td><a href="{% url 'filtered' _filter="scale" search=r.rolling_stock.scale %}"><abbr title="{{ r.rolling_stock.scale.ratio }} - {{ r.rolling_stock.scale.tracks }}">{{ r.rolling_stock.scale }}</abbr></a></td>
</tr> </tr>
<tr> <tr>
<th scope="row">SKU</th> <th scope="row">SKU</th>
@@ -87,13 +91,10 @@
</tbody> </tbody>
</table> </table>
{% endif %} {% endif %}
<div class="btn-group mb-4"> <div class="d-grid gap-2 mb-1 d-md-block">
<a class="btn btn-sm btn-outline-primary" href="{{r.rolling_stock.get_absolute_url}}">Show all data</a> <a class="btn btn-sm btn-outline-primary" href="{{r.rolling_stock.get_absolute_url}}">Show all data</a>
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:roster_rollingstock_change' r.rolling_stock.pk %}">Edit</a>{% endif %} {% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:roster_rollingstock_change' r.rolling_stock.pk %}">Edit</a>{% endif %}
</div> </div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">Updated {{ r.rolling_stock.updated_time | date:"M d, Y H:m" }}</small>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -112,13 +113,17 @@
<span class="page-link">Previous</span> <span class="page-link">Previous</span>
</li> </li>
{% endif %} {% endif %}
{% for i in rolling_stock.paginator.page_range %} {% for i in page_range %}
{% if rolling_stock.number == i %} {% if rolling_stock.number == i %}
<li class="page-item active"> <li class="page-item active">
<span class="page-link">{{ i }}</span></span> <span class="page-link">{{ i }}</span></span>
</li> </li>
{% else %} {% else %}
<li class="page-item"><a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=i %}#rolling-stock">{{ i }}</a></li> {% if i == rolling_stock.paginator.ELLIPSIS %}
<li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=i %}#rolling-stock">{{ i }}</a></li>
{% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if rolling_stock.has_next %} {% if rolling_stock.has_next %}

View File

@@ -10,11 +10,14 @@
<div class="card shadow-sm"> <div class="card shadow-sm">
{% if c.image %}<a href="{{ c.get_absolute_url }}"><img src="{{ c.image.url }}" alt="Card image cap"></a>{% endif %} {% if c.image %}<a href="{{ c.get_absolute_url }}"><img src="{{ c.image.url }}" alt="Card image cap"></a>{% endif %}
<div class="card-body"> <div class="card-body">
<p class="card-text"><strong>{{ c.identifier }}</strong></p> <p class="card-text" style="position: relative;">
<strong>{{ c }}</strong>
<a class="stretched-link" href="{{ c.get_absolute_url }}"></a>
</p>
{% if c.tags.all %} {% if c.tags.all %}
<p class="card-text"><small>Tags:</small> <p class="card-text"><small>Tags:</small>
{% for t in c.tags.all %}<span class="badge bg-primary"> {% for t in c.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</span>{# new line is required #} {{ t.name }}</a>{# new line is required #}
{% endfor %} {% endfor %}
</p> </p>
{% endif %} {% endif %}
@@ -45,13 +48,10 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="btn-group mb-4"> <div class="d-grid gap-2 mb-1 d-md-block">
<a class="btn btn-sm btn-outline-primary" href="{{ c.get_absolute_url }}">Show all data</a> <a class="btn btn-sm btn-outline-primary" href="{{ c.get_absolute_url }}">Show all data</a>
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:consist_consist_change' c.pk %}">Edit</a>{% endif %} {% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:consist_consist_change' c.pk %}">Edit</a>{% endif %}
</div> </div>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">Updated {{ c.updated_time | date:"M d, Y H:m" }}</small>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -70,13 +70,17 @@
<span class="page-link">Previous</span> <span class="page-link">Previous</span>
</li> </li>
{% endif %} {% endif %}
{% for i in consist.paginator.page_range %} {% for i in page_range %}
{% if consist.number == i %} {% if consist.number == i %}
<li class="page-item active"> <li class="page-item active">
<span class="page-link">{{ i }}</span></span> <span class="page-link">{{ i }}</span></span>
</li> </li>
{% else %} {% else %}
<li class="page-item"><a class="page-link" href="{% url 'consists_pagination' page=i %}#rolling-stock">{{ i }}</a></li> {% if i == consist.paginator.ELLIPSIS %}
<li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="{% url 'consists_pagination' page=i %}#rolling-stock">{{ i }}</a></li>
{% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if consist.has_next %} {% if consist.has_next %}

View File

@@ -19,13 +19,17 @@
<span class="page-link">Previous</span> <span class="page-link">Previous</span>
</li> </li>
{% endif %} {% endif %}
{% for i in rolling_stock.paginator.page_range %} {% for i in page_range %}
{% if rolling_stock.number == i %} {% if rolling_stock.number == i %}
<li class="page-item active"> <li class="page-item active">
<span class="page-link">{{ i }}</span></span> <span class="page-link">{{ i }}</span></span>
</li> </li>
{% else %} {% else %}
<li class="page-item"><a class="page-link" href="{% url 'index_pagination' page=i %}#rolling-stock">{{ i }}</a></li> {% if i == rolling_stock.paginator.ELLIPSIS %}
<li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="{% url 'index_pagination' page=i %}#rolling-stock">{{ i }}</a></li>
{% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if rolling_stock.has_next %} {% if rolling_stock.has_next %}

View File

@@ -1,5 +1,24 @@
<form class="d-flex" action="{% url 'search' %}" method="post"> <form class="d-flex needs-validation" action="{% url 'search' %}" method="post" novalidate>
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" name="search"> <div class="input-group has-validation">
<button class="btn btn-outline-primary" type="submit">Search</button> <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" name="search" id="searchValidation" required>
<button class="btn btn-outline-primary" type="submit">Search</button>
</div>
</form> </form>
<script>
(function () {
'use strict'
// Fetch all the forms we want to apply custom Bootstrap validation styles to
var forms = document.querySelectorAll('.needs-validation')
// Loop over them and prevent submission
Array.prototype.slice.call(forms)
.forEach(function (form) {
form.addEventListener('submit', function (event) {
if (!form.checkValidity()) {
form.classList.add('was-validated')
event.preventDefault()
event.stopPropagation()
}
}, false)
})
})()
</script>

View File

@@ -5,11 +5,12 @@
<h1 class="fw-light">{{ rolling_stock }}</h1> <h1 class="fw-light">{{ rolling_stock }}</h1>
{% if rolling_stock.tags.all %} {% if rolling_stock.tags.all %}
<p><small>Tags:</small> <p><small>Tags:</small>
{% for t in rolling_stock.tags.all %}<span class="badge bg-primary"> {% for t in rolling_stock.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</span>{# new line is required #} {{ t.name }}</a>{# new line is required #}
{% endfor %} {% endfor %}
</p> </p>
{% endif %} {% endif %}
<small class="text-muted">Updated {{ rolling_stock.updated_time | date:"M d, Y H:i" }}</small>
{% endblock %} {% endblock %}
{% block cards %} {% block cards %}
{% for t in rolling_stock.image.all %} {% for t in rolling_stock.image.all %}
@@ -72,11 +73,11 @@
<tbody> <tbody>
<tr> <tr>
<th width="35%" scope="row">Manufacturer</th> <th width="35%" scope="row">Manufacturer</th>
<td>{% if rolling_stock.manufacturer.website %}<a href="{{ rolling_stock.manufacturer.website }}">{% endif %}{{ rolling_stock.manufacturer }}{% if rolling_stock.manufacturer.website %}</a>{% endif %}</th> <td>{% if rolling_stock.manufacturer.website %}<a href="{{ rolling_stock.manufacturer.website }}">{% endif %}{{ rolling_stock.manufacturer|default_if_none:"" }}{% if rolling_stock.manufacturer.website %}</a>{% endif %}</th>
</tr> </tr>
<tr> <tr>
<th scope="row">Scale</th> <th scope="row">Scale</th>
<td><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.gauge }}">{{ rolling_stock.scale }}</abbr></td> <td><a href="{% url 'filtered' _filter="scale" search=rolling_stock.scale %}"><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.tracks }}">{{ rolling_stock.scale }}</abbr></a></td>
</tr> </tr>
<tr> <tr>
<th scope="row">SKU</th> <th scope="row">SKU</th>
@@ -114,18 +115,18 @@
<tbody> <tbody>
<tr> <tr>
<th width="35%" scope="row">Manufacturer</th> <th width="35%" scope="row">Manufacturer</th>
<td>{% if rolling_stock.manufacturer.website %}<a href="{{ rolling_stock.manufacturer.website }}">{% endif %}{{ rolling_stock.manufacturer }}{% if rolling_stock.manufacturer.website %}</a>{% endif %}</th> <td>{% if rolling_stock.manufacturer.website %}<a href="{{ rolling_stock.manufacturer.website }}">{% endif %}{{ rolling_stock.manufacturer|default_if_none:"" }}{% if rolling_stock.manufacturer.website %}</a>{% endif %}</th>
</tr> </tr>
<tr> <tr>
<th scope="row">Scale</th> <th scope="row">Scale</th>
<td><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.gauge }}">{{ rolling_stock.scale }}</abbr></td> <td><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.tracks }}">{{ rolling_stock.scale }}</abbr></td>
</tr> </tr>
<tr> <tr>
<th scope="row">SKU</th> <th scope="row">SKU</th>
<td>{{ rolling_stock.sku }}</td> <td>{{ rolling_stock.sku }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">ERA</th> <th scope="row">Era</th>
<td>{{ rolling_stock.era }}</td> <td>{{ rolling_stock.era }}</td>
</tr> </tr>
<tr> <tr>
@@ -166,7 +167,7 @@
<tbody> <tbody>
<tr> <tr>
<th width="35%" scope="row">Class</th> <th width="35%" scope="row">Class</th>
<td>{{ rolling_stock.rolling_class }}</td> <td>{{ rolling_stock.rolling_class.identifier }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">Type</th> <th scope="row">Type</th>
@@ -182,7 +183,7 @@
</tr> </tr>
<tr> <tr>
<th scope="row">Manufacturer</th> <th scope="row">Manufacturer</th>
<td>{{ rolling_stock.rolling_class.manufacturer }}</td> <td>{{ rolling_stock.rolling_class.manufacturer|default_if_none:"" }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -222,7 +223,7 @@
</tr> </tr>
<tr> <tr>
<th scope="row">Manufacturer</th> <th scope="row">Manufacturer</th>
<td>{{ rolling_stock.decoder.manufacturer }}</td> <td>{{ rolling_stock.decoder.manufacturer|default_if_none:"" }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">Version</th> <th scope="row">Version</th>

View File

@@ -0,0 +1,85 @@
{% extends "base.html" %}
{% load markdown %}
{% block header %}
<h1 class="fw-light">Scales</h1>
{% endblock %}
{% block cards %}
{% for s in scale %}
<div class="col">
<div class="card shadow-sm">
<div class="card-body">
<p class="card-text"><strong>{{ s }}</strong></p>
<table class="table table-striped">
<thead>
<tr>
<th colspan="2" scope="row">Scale</th>
</tr>
</thead>
<tbody>
<tr>
<th width="35%" scope="row">Name</th>
<td>{{ s.scale }}</td>
</tr>
<tr>
<th width="35%" scope="row">Ratio</th>
<td>{{ s.ratio }}</td>
</tr>
<tr>
<th width="35%" scope="row">Gauge</th>
<td>{{ s.gauge }}</td>
</tr>
<tr>
<th width="35%" scope="row">Tracks</th>
<td>{{ s.tracks }}</td>
</tr>
</tbody>
</table>
<div class="d-grid gap-2 mb-1 d-md-block">
<a class="btn btn-sm btn-outline-primary" href="{% url 'filtered' _filter="scale" search=s %}">Show all rolling stock</a>
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:metadata_scale_change' s.pk %}">Edit</a>{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% endblock %}
{% block pagination %}
{% if scale.has_other_pages %}
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center mt-4">
{% if scale.has_previous %}
<li class="page-item">
<a class="page-link" href="{% url 'scale_pagination' page=scale.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Previous</span>
</li>
{% endif %}
{% for i in page_range %}
{% if scale.number == i %}
<li class="page-item active">
<span class="page-link">{{ i }}</span></span>
</li>
{% else %}
{% if i == scale.paginator.ELLIPSIS %}
<li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="{% url 'scale_pagination' page=i %}#rolling-stock">{{ i }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% if scale.has_next %}
<li class="page-item">
<a class="page-link" href="{% url 'scale_pagination' page=scale.next_page_number %}#rolling-stock" tabindex="-1">Next</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Next</span>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}

View File

@@ -10,25 +10,29 @@
<ul class="pagination justify-content-center mt-4"> <ul class="pagination justify-content-center mt-4">
{% if rolling_stock.has_previous %} {% if rolling_stock.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'search_pagination' search=search page=rolling_stock.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=rolling_stock.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
<span class="page-link">Previous</span> <span class="page-link">Previous</span>
</li> </li>
{% endif %} {% endif %}
{% for i in rolling_stock.paginator.page_range %} {% for i in page_range %}
{% if rolling_stock.number == i %} {% if rolling_stock.number == i %}
<li class="page-item active"> <li class="page-item active">
<span class="page-link">{{ i }}</span></span> <span class="page-link">{{ i }}</span></span>
</li> </li>
{% else %} {% else %}
<li class="page-item"><a class="page-link" href="{% url 'search_pagination' search=search page=i %}#rolling-stock">{{ i }}</a></li> {% if i == rolling_stock.paginator.ELLIPSIS %}
<li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=i %}#rolling-stock">{{ i }}</a></li>
{% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if rolling_stock.has_next %} {% if rolling_stock.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'search_pagination' search=search page=rolling_stock.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=rolling_stock.next_page_number %}#rolling-stock" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -6,6 +6,8 @@ from portal.views import (
GetRollingStock, GetRollingStock,
GetConsist, GetConsist,
Consists, Consists,
Companies,
Scales,
) )
urlpatterns = [ urlpatterns = [
@@ -16,16 +18,9 @@ urlpatterns = [
GetHomeFiltered.as_view(http_method_names=["post"]), GetHomeFiltered.as_view(http_method_names=["post"]),
name="search", name="search",
), ),
path("search/<str:search>", GetHomeFiltered.as_view(), name="search"),
path(
"search/<str:search>/<int:page>",
GetHomeFiltered.as_view(),
name="search_pagination",
),
path("consists", Consists.as_view(), name="consists"), path("consists", Consists.as_view(), name="consists"),
path( path(
"consists/<int:page>", "consists/<int:page>", Consists.as_view(), name="consists_pagination"
Consists.as_view(), name="consists_pagination"
), ),
path("consist/<uuid:uuid>", GetConsist.as_view(), name="consist"), path("consist/<uuid:uuid>", GetConsist.as_view(), name="consist"),
path( path(
@@ -33,6 +28,18 @@ urlpatterns = [
GetConsist.as_view(), GetConsist.as_view(),
name="consist_pagination", name="consist_pagination",
), ),
path("companies", Companies.as_view(), name="companies"),
path(
"companies/<int:page>",
Companies.as_view(),
name="companies_pagination"
),
path("scales", Scales.as_view(), name="scales"),
path(
"scales/<int:page>",
Scales.as_view(),
name="scales_pagination"
),
path( path(
"<str:_filter>/<str:search>", "<str:_filter>/<str:search>",
GetHomeFiltered.as_view(), GetHomeFiltered.as_view(),

View File

@@ -11,28 +11,48 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from portal.utils import get_site_conf from portal.utils import get_site_conf
from roster.models import RollingStock from roster.models import RollingStock
from consist.models import Consist from consist.models import Consist
from metadata.models import Company, Scale
def order_by_fields():
order_by = get_site_conf().items_ordering
fields = [
"rolling_class__type",
"rolling_class__company",
"rolling_class__identifier",
"road_number_int",
]
if order_by == "type":
return (fields[0], fields[1], fields[2], fields[3])
elif order_by == "company":
return (fields[1], fields[0], fields[2], fields[3])
elif order_by == "identifier":
return (fields[2], fields[0], fields[1], fields[3])
class GetHome(View): class GetHome(View):
def get(self, request, page=1): def get(self, request, page=1):
site_conf = get_site_conf() site_conf = get_site_conf()
rolling_stock = RollingStock.objects.all() rolling_stock = RollingStock.objects.order_by(*order_by_fields())
paginator = Paginator(rolling_stock, site_conf.items_per_page) paginator = Paginator(rolling_stock, site_conf.items_per_page)
rolling_stock = paginator.get_page(page)
page_range = paginator.get_elided_page_range(
rolling_stock.number, on_each_side=2, on_ends=1
)
try: return render(
rolling_stock = paginator.page(page) request,
except PageNotAnInteger: "home.html",
rolling_stock = paginator.page(1) {"rolling_stock": rolling_stock, "page_range": page_range},
except EmptyPage: )
rolling_stock = paginator.page(paginator.num_pages)
return render(request, "home.html", {"rolling_stock": rolling_stock})
class GetHomeFiltered(View): class GetHomeFiltered(View):
def run_search(self, request, search, _filter, page=1): def run_search(self, request, search, _filter, page=1):
site_conf = get_site_conf() site_conf = get_site_conf()
if _filter is None: if _filter == "search":
query = reduce( query = reduce(
operator.or_, operator.or_,
( (
@@ -56,25 +76,28 @@ class GetHomeFiltered(View):
| Q(rolling_class__company__extended_name__icontains=search) | Q(rolling_class__company__extended_name__icontains=search)
) )
elif _filter == "scale": elif _filter == "scale":
query = Q(scale__scale__icontains=search) query = Q(scale__scale__iexact=search)
elif _filter == "tag":
query = Q(tags__slug__iexact=search)
else: else:
raise Http404 raise Http404
rolling_stock = RollingStock.objects.filter(query) rolling_stock = RollingStock.objects.filter(query).order_by(
*order_by_fields()
)
matches = len(rolling_stock) matches = len(rolling_stock)
paginator = Paginator(rolling_stock, site_conf.items_per_page) paginator = Paginator(rolling_stock, site_conf.items_per_page)
rolling_stock = paginator.get_page(page)
page_range = paginator.get_elided_page_range(
rolling_stock.number, on_each_side=2, on_ends=1
)
try: return rolling_stock, matches, page_range
rolling_stock = paginator.page(page)
except PageNotAnInteger:
rolling_stock = paginator.page(1)
except EmptyPage:
rolling_stock = paginator.page(paginator.num_pages)
return rolling_stock, matches def get(self, request, search, _filter="search", page=1):
rolling_stock, matches, page_range = self.run_search(
def get(self, request, search, _filter=None, page=1): request, search, _filter, page
rolling_stock, matches = self.run_search( )
request, search, _filter, page)
return render( return render(
request, request,
@@ -84,15 +107,17 @@ class GetHomeFiltered(View):
"filter": _filter, "filter": _filter,
"matches": matches, "matches": matches,
"rolling_stock": rolling_stock, "rolling_stock": rolling_stock,
"page_range": page_range,
}, },
) )
def post(self, request, _filter=None, page=1): def post(self, request, _filter="search", page=1):
search = request.POST.get("search") search = request.POST.get("search")
if not search: if not search:
raise Http404 raise Http404
rolling_stock, matches = self.run_search( rolling_stock, matches, page_range = self.run_search(
request, search, _filter, page) request, search, _filter, page
)
return render( return render(
request, request,
@@ -102,6 +127,7 @@ class GetHomeFiltered(View):
"filter": _filter, "filter": _filter,
"matches": matches, "matches": matches,
"rolling_stock": rolling_stock, "rolling_stock": rolling_stock,
"page_range": page_range,
}, },
) )
@@ -114,15 +140,16 @@ class GetRollingStock(View):
raise Http404 raise Http404
class_properties = ( class_properties = (
rolling_stock.rolling_class.property.all() if rolling_stock.rolling_class.property.all()
request.user.is_authenticated else if request.user.is_authenticated
rolling_stock.rolling_class.property.filter( else rolling_stock.rolling_class.property.filter(
property__private=False) property__private=False
)
) )
rolling_stock_properties = ( rolling_stock_properties = (
rolling_stock.property.all() if rolling_stock.property.all()
request.user.is_authenticated else if request.user.is_authenticated
rolling_stock.property.filter(property__private=False) else rolling_stock.property.filter(property__private=False)
) )
return render( return render(
@@ -140,16 +167,18 @@ class Consists(View):
def get(self, request, page=1): def get(self, request, page=1):
site_conf = get_site_conf() site_conf = get_site_conf()
consist = Consist.objects.all() consist = Consist.objects.all()
paginator = Paginator(consist, site_conf.items_per_page) paginator = Paginator(consist, site_conf.items_per_page)
consist = paginator.get_page(page)
page_range = paginator.get_elided_page_range(
consist.number, on_each_side=2, on_ends=1
)
try: return render(
consist = paginator.page(page) request,
except PageNotAnInteger: "consists.html",
consist = paginator.page(1) {"consist": consist, "page_range": page_range},
except EmptyPage: )
consist = paginator.page(paginator.num_pages)
return render(request, "consists.html", {"consist": consist})
class GetConsist(View): class GetConsist(View):
@@ -160,17 +189,55 @@ class GetConsist(View):
except ObjectDoesNotExist: except ObjectDoesNotExist:
raise Http404 raise Http404
rolling_stock = consist.consist_item.all() rolling_stock = consist.consist_item.all()
paginator = Paginator(rolling_stock, site_conf.items_per_page)
try: paginator = Paginator(rolling_stock, site_conf.items_per_page)
rolling_stock = paginator.page(page) rolling_stock = paginator.get_page(page)
except PageNotAnInteger: page_range = paginator.get_elided_page_range(
rolling_stock = paginator.page(1) rolling_stock.number, on_each_side=2, on_ends=1
except EmptyPage: )
rolling_stock = paginator.page(paginator.num_pages)
return render( return render(
request, request,
"consist.html", "consist.html",
{"consist": consist, "rolling_stock": rolling_stock}, {
"consist": consist,
"rolling_stock": rolling_stock,
"page_range": page_range,
},
)
class Companies(View):
def get(self, request, page=1):
site_conf = get_site_conf()
company = Company.objects.all()
paginator = Paginator(company, site_conf.items_per_page)
company = paginator.get_page(page)
page_range = paginator.get_elided_page_range(
company.number, on_each_side=2, on_ends=1
)
return render(
request,
"companies.html",
{"company": company, "page_range": page_range},
)
class Scales(View):
def get(self, request, page=1):
site_conf = get_site_conf()
scale = Scale.objects.all()
paginator = Paginator(scale, site_conf.items_per_page)
scale = paginator.get_page(page)
page_range = paginator.get_elided_page_range(
scale.number, on_each_side=2, on_ends=1
)
return render(
request,
"scales.html",
{"scale": scale, "page_range": page_range},
) )

View File

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

View File

@@ -34,13 +34,21 @@ if settings.DEBUG:
from rest_framework.schemas import get_schema_view from rest_framework.schemas import get_schema_view
urlpatterns += [ urlpatterns += [
path('swagger/', TemplateView.as_view( path(
template_name='swagger.html', "swagger/",
extra_context={'schema_url': 'openapi-schema'} TemplateView.as_view(
), name='swagger'), template_name="swagger.html",
path('openapi', get_schema_view( extra_context={"schema_url": "openapi-schema"},
title="RAM - Railroad Assets Manager", ),
description="RAM API", name="swagger",
version="1.0.0" ),
), name='openapi-schema'), path(
"openapi",
get_schema_view(
title="RAM - Railroad Assets Manager",
description="RAM API",
version="1.0.0",
),
name="openapi-schema",
),
] ]

View File

@@ -20,7 +20,11 @@ class RollingClass(admin.ModelAdmin):
inlines = (RollingClassPropertyInline,) inlines = (RollingClassPropertyInline,)
list_display = ("__str__", "type", "company") list_display = ("__str__", "type", "company")
list_filter = ("company", "type__category", "type") list_filter = ("company", "type__category", "type")
search_fields = list_display search_fields = (
"identifier",
"company__name",
"type__type",
)
class RollingStockDocInline(admin.TabularInline): class RollingStockDocInline(admin.TabularInline):
@@ -62,10 +66,18 @@ class RollingStockAdmin(admin.ModelAdmin):
list_filter = ( list_filter = (
"rolling_class__type__category", "rolling_class__type__category",
"rolling_class__type", "rolling_class__type",
"rolling_class__company__name",
"scale", "scale",
"manufacturer", "manufacturer",
) )
search_fields = list_display search_fields = (
"rolling_class__identifier",
"rolling_class__company__name",
"manufacturer__name",
"road_number",
"address",
"sku",
)
fieldsets = ( fieldsets = (
( (

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.0.6 on 2022-07-15 15:24
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('metadata', '0004_alter_rollingstocktype_options_and_more'),
('roster', '0006_alter_rollingclassproperty_rolling_class_and_more'),
]
operations = [
migrations.AlterField(
model_name='rollingclass',
name='company',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='metadata.company'),
),
migrations.AlterField(
model_name='rollingclass',
name='type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='metadata.rollingstocktype'),
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 4.0.6 on 2022-07-15 15:55
from django.db import migrations, models
def gen_road_number_cleaned(apps, schema_editor):
RollingStock = apps.get_model('roster', 'RollingStock')
for row in RollingStock.objects.all():
row.road_number_cleaned = row.road_number.lstrip('#').lstrip('0')
row.save(update_fields=['road_number_cleaned'])
class Migration(migrations.Migration):
dependencies = [
('roster', '0007_alter_rollingclass_company_alter_rollingclass_type'),
]
operations = [
migrations.AddField(
model_name='rollingstock',
name='road_number_cleaned',
field=models.CharField(default='', max_length=128),
preserve_default=False,
),
migrations.RunPython(
gen_road_number_cleaned,
reverse_code=migrations.RunPython.noop
),
migrations.AlterModelOptions(
name='rollingstock',
options={'ordering': ['rolling_class', 'road_number_cleaned'], 'verbose_name_plural': 'Rolling stock'},
),
]

View File

@@ -0,0 +1,41 @@
# Generated by Django 4.0.6 on 2022-07-16 15:38
import re
from django.db import migrations, models
def gen_road_number_cleaned(apps, schema_editor):
RollingStock = apps.get_model('roster', 'RollingStock')
for row in RollingStock.objects.all():
try:
row.road_number_int = int(re.findall(r"\d+", row.road_number)[0])
row.save(update_fields=['road_number_int'])
except IndexError:
pass
class Migration(migrations.Migration):
dependencies = [
('roster', '0008_rollingstock_road_number_cleaned'),
]
operations = [
migrations.AlterModelOptions(
name='rollingstock',
options={'ordering': ['rolling_class', 'road_number_int'], 'verbose_name_plural': 'Rolling stock'},
),
migrations.RemoveField(
model_name='rollingstock',
name='road_number_cleaned',
),
migrations.AddField(
model_name='rollingstock',
name='road_number_int',
field=models.PositiveSmallIntegerField(default=0),
),
migrations.RunPython(
gen_road_number_cleaned,
reverse_code=migrations.RunPython.noop
),
]

View File

@@ -1,10 +1,11 @@
import os import os
import re
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.dispatch import receiver
# from django.core.files.storage import FileSystemStorage # from django.core.files.storage import FileSystemStorage
# from django.dispatch import receiver
from ram.utils import get_image_preview from ram.utils import get_image_preview
from metadata.models import ( from metadata.models import (
@@ -25,12 +26,8 @@ from metadata.models import (
class RollingClass(models.Model): class RollingClass(models.Model):
identifier = models.CharField(max_length=128, unique=False) identifier = models.CharField(max_length=128, unique=False)
type = models.ForeignKey( type = models.ForeignKey(RollingStockType, on_delete=models.CASCADE)
RollingStockType, on_delete=models.CASCADE, null=True, blank=True company = models.ForeignKey(Company, on_delete=models.CASCADE)
)
company = models.ForeignKey(
Company, on_delete=models.CASCADE, null=True, blank=True
)
description = models.CharField(max_length=256, blank=True) description = models.CharField(max_length=256, blank=True)
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
Manufacturer, Manufacturer,
@@ -79,6 +76,7 @@ class RollingStock(models.Model):
verbose_name="Class", verbose_name="Class",
) )
road_number = models.CharField(max_length=128, unique=False) road_number = models.CharField(max_length=128, unique=False)
road_number_int = models.PositiveSmallIntegerField(default=0, unique=False)
manufacturer = models.ForeignKey( manufacturer = models.ForeignKey(
Manufacturer, Manufacturer,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@@ -103,7 +101,7 @@ class RollingStock(models.Model):
updated_time = models.DateTimeField(auto_now=True) updated_time = models.DateTimeField(auto_now=True)
class Meta: class Meta:
ordering = ["rolling_class", "road_number"] ordering = ["rolling_class", "road_number_int"]
verbose_name_plural = "Rolling stock" verbose_name_plural = "Rolling stock"
def __str__(self): def __str__(self):
@@ -119,6 +117,16 @@ class RollingStock(models.Model):
return str(self.rolling_class.company) return str(self.rolling_class.company)
@receiver(models.signals.pre_save, sender=RollingStock)
def pre_save_running_number(sender, instance, *args, **kwargs):
try:
instance.road_number_int = int(
re.findall(r"\d+", instance.road_number)[0]
)
except IndexError:
pass
class RollingStockDocument(models.Model): class RollingStockDocument(models.Model):
rolling_stock = models.ForeignKey( rolling_stock = models.ForeignKey(
RollingStock, on_delete=models.CASCADE, related_name="document" RollingStock, on_delete=models.CASCADE, related_name="document"

View File

@@ -15,17 +15,13 @@ class RosterGet(RetrieveAPIView):
serializer_class = RollingStockSerializer serializer_class = RollingStockSerializer
lookup_field = "uuid" lookup_field = "uuid"
schema = AutoSchema( schema = AutoSchema(operation_id_base="retrieveRollingStockByUUID")
operation_id_base="retrieveRollingStockByUUID"
)
class RosterAddress(ListAPIView): class RosterAddress(ListAPIView):
serializer_class = RollingStockSerializer serializer_class = RollingStockSerializer
schema = AutoSchema( schema = AutoSchema(operation_id_base="retrieveRollingStockByAddress")
operation_id_base="retrieveRollingStockByAddress"
)
def get_queryset(self): def get_queryset(self):
address = self.kwargs["address"] address = self.kwargs["address"]
@@ -35,9 +31,7 @@ class RosterAddress(ListAPIView):
class RosterClass(ListAPIView): class RosterClass(ListAPIView):
serializer_class = RollingStockSerializer serializer_class = RollingStockSerializer
schema = AutoSchema( schema = AutoSchema(operation_id_base="retrieveRollingStockByClass")
operation_id_base="retrieveRollingStockByClass"
)
def get_queryset(self): def get_queryset(self):
_class = self.kwargs["class"] _class = self.kwargs["class"]