22 Commits

Author SHA1 Message Date
575c938205 Hotfix for document filename 2022-08-22 18:23:38 +02:00
2af772a722 Black'ed 2022-08-22 17:13:10 +02:00
f580bcffc5 Documents section in admin 2022-08-22 17:12:22 +02:00
6accb66006 Enable search by sku 2022-08-21 16:53:18 +02:00
f56accb4ff Use lead unit thumbnail if not provided in consist 2022-07-23 23:45:11 +02:00
5a7b7fd79e Update README.md 2022-07-23 22:55:58 +02:00
dcdad71b1b Update README.md 2022-07-23 22:54:46 +02:00
321ae1065e Update README.md 2022-07-23 22:51:24 +02:00
e8efa5d87a Update README.md 2022-07-23 22:50:58 +02:00
97254b302c Fix a typo 2022-07-23 16:15:56 +02:00
b8aa34ce1d Add modal for pictures 2022-07-23 11:58:17 +02:00
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
18 changed files with 450 additions and 74 deletions

View File

@@ -2,8 +2,7 @@
[![Django CI](https://github.com/daniviga/django-rma/actions/workflows/django.yml/badge.svg)](https://github.com/daniviga/django-rma/actions/workflows/django.yml) [![Django CI](https://github.com/daniviga/django-rma/actions/workflows/django.yml/badge.svg)](https://github.com/daniviga/django-rma/actions/workflows/django.yml)
![image](https://user-images.githubusercontent.com/1818657/175789825-9a03f0ff-a95e-42a2-9611-e14d2817e22f.png) ![Screenshot 2022-07-23 at 22-40-17 Railroad Assets Manager](https://user-images.githubusercontent.com/1818657/180622177-a4bba00e-47da-42b3-a7f6-b24773e69936.png)
A `jff` (just for fun) project that aims to create a A `jff` (just for fun) project that aims to create a
model railroad assets manager that allows to: model railroad assets manager that allows to:
@@ -26,6 +25,8 @@ This project probably doesn't match you needs nor expectations. Be aware.
Your model train may also catch fire while using this software. Your model train may also catch fire while using this software.
Check out [my own instance](https://daniele.mynarrowgauge.org).
## Components ## Components
Project is based on the following technologies and components: Project is based on the following technologies and components:
@@ -138,14 +139,18 @@ To be continued ...
## Screenshots ## Screenshots
### Frontend ### Frontend
![Screenshot 2022-07-23 at 22-41-44 Railroad Assets Manager](https://user-images.githubusercontent.com/1818657/180622406-760774a9-f028-44fc-b332-fa74e43307df.png)
---
![Screenshot 2022-07-23 at 22-44-35 Railroad Assets Manager](https://user-images.githubusercontent.com/1818657/180622342-40586d75-239a-400c-93a1-1cb9583a7d17.png)
---
![Screenshot 2022-07-23 at 22-44-46 Railroad Assets Manager](https://user-images.githubusercontent.com/1818657/180622321-1ab76440-9c6e-4667-9247-dbbcf6c6055c.png)
#### Dark mode
![Screenshot 2022-07-23 at 22-53-43 Railroad Assets Manager](https://user-images.githubusercontent.com/1818657/180622629-65d81eaf-cca4-4f44-b39b-3b0077b43a34.png)
![image](https://user-images.githubusercontent.com/1818657/175789897-9ec4a9bb-9c65-48ef-9b57-ae94e094e6a7.png)
--- ---
![image](https://user-images.githubusercontent.com/1818657/175789901-ef50acd7-8c05-4788-92a2-1bb1280d598c.png)
---
![image](https://user-images.githubusercontent.com/1818657/175790004-18926d23-28f9-45bb-b279-6c26575ae3a5.png)
---
![image](https://user-images.githubusercontent.com/1818657/175790008-62eea2cc-1c41-42df-9026-4cf6e8ef712c.png)
### Backoffice ### Backoffice
@@ -158,8 +163,7 @@ To be continued ...
### Rest API ### Rest API
![image](https://user-images.githubusercontent.com/1818657/175790064-23ec038e-e8bf-4c39-964c-3118e4295b59.png) ![image](https://user-images.githubusercontent.com/1818657/180622471-ade06c84-c73b-41d5-a2a7-02a95b2ffc02.png)

View File

@@ -22,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
@@ -59,4 +59,4 @@ class TagAdmin(admin.ModelAdmin):
class RollingStockTypeAdmin(SortableAdminMixin, 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,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

@@ -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"]

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="color-scheme" content="light dark">
<meta name="description" content="{{ site_conf.about}}"> <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,7 +96,10 @@
{% 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 %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary"> {% for t in r.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
@@ -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>

View File

@@ -8,9 +8,22 @@
{% for c in consist %} {% for c in consist %}
<div class="col"> <div class="col">
<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 %} <a href="{{ c.get_absolute_url }}">
{% if c.image %}
<img src="{{ c.image.url }}" alt="Card image cap">
{% 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 %}
{% endfor %}
{% endwith %}
{% endif %}
</a>
<div class="card-body"> <div class="card-body">
<p class="card-text"><strong>{{ c }}</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 %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary"> {% for t in c.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
@@ -45,13 +58,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>
@@ -76,7 +86,7 @@
<span class="page-link">{{ i }}</span></span> <span class="page-link">{{ i }}</span></span>
</li> </li>
{% else %} {% else %}
{% if i == rolling_stock.paginator.ELLIPSIS %} {% if i == consist.paginator.ELLIPSIS %}
<li class="page-item"><span class="page-link">{{ i }}</span></li> <li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %} {% else %}
<li class="page-item"><a class="page-link" href="{% url 'consists_pagination' page=i %}#rolling-stock">{{ i }}</a></li> <li class="page-item"><a class="page-link" href="{% url 'consists_pagination' page=i %}#rolling-stock">{{ i }}</a></li>

View File

@@ -10,11 +10,29 @@
{% 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 %}
<div class="col"> <div class="col">
<img class="img-thumbnail" src="{{ t.image.url }}" alt="Rolling stock image"> <a href="" data-bs-toggle="modal" data-bs-target="#pictureModal{{ forloop.counter }}"><img class="img-thumbnail" src="{{ t.image.url }}" alt="Rolling stock image"></a>
</div>
<!-- Modal -->
<div class="modal fade" id="pictureModal{{ forloop.counter }}" tabindex="-1" aria-labelledby="pictureModalLabel{{ forloop.counter }}" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="pictureModalLabel{{ forloop.counter }}">{{ rolling_stock }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center">
<img class="rounded img-fluid" src="{{ t.image.url }}" alt="Rolling stock image">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div> </div>
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
@@ -76,7 +94,7 @@
</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>
@@ -118,14 +136,14 @@
</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 +184,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>

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,7 +10,7 @@
<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">
@@ -26,13 +26,13 @@
{% if i == rolling_stock.paginator.ELLIPSIS %} {% if i == rolling_stock.paginator.ELLIPSIS %}
<li class="page-item"><span class="page-link">{{ i }}</span></li> <li class="page-item"><span class="page-link">{{ i }}</span></li>
{% else %} {% else %}
<li class="page-item"><a class="page-link" href="{% url 'index_pagination' page=i %}#rolling-stock">{{ i }}</a></li> <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 %} {% 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,12 +18,6 @@ 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.as_view(), name="consists_pagination" "consists/<int:page>", Consists.as_view(), name="consists_pagination"
@@ -32,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,6 +11,7 @@ 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(): def order_by_fields():
@@ -37,7 +38,9 @@ class GetHome(View):
paginator = Paginator(rolling_stock, site_conf.items_per_page) paginator = Paginator(rolling_stock, site_conf.items_per_page)
rolling_stock = paginator.get_page(page) rolling_stock = paginator.get_page(page)
page_range = paginator.get_elided_page_range(rolling_stock.number) page_range = paginator.get_elided_page_range(
rolling_stock.number, on_each_side=2, on_ends=1
)
return render( return render(
request, request,
@@ -49,7 +52,7 @@ class GetHome(View):
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_,
( (
@@ -58,6 +61,7 @@ class GetHomeFiltered(View):
| Q(rolling_class__description__icontains=s) | Q(rolling_class__description__icontains=s)
| Q(rolling_class__type__type__icontains=s) | Q(rolling_class__type__type__icontains=s)
| Q(road_number__icontains=s) | Q(road_number__icontains=s)
| Q(sku=s)
| Q(rolling_class__company__name__icontains=s) | Q(rolling_class__company__name__icontains=s)
| Q(rolling_class__company__country__icontains=s) | Q(rolling_class__company__country__icontains=s)
| Q(manufacturer__name__icontains=s) | Q(manufacturer__name__icontains=s)
@@ -73,9 +77,9 @@ 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": elif _filter == "tag":
query = Q(tags__slug__icontains=search) query = Q(tags__slug__iexact=search)
else: else:
raise Http404 raise Http404
rolling_stock = RollingStock.objects.filter(query).order_by( rolling_stock = RollingStock.objects.filter(query).order_by(
@@ -85,11 +89,13 @@ class GetHomeFiltered(View):
paginator = Paginator(rolling_stock, site_conf.items_per_page) paginator = Paginator(rolling_stock, site_conf.items_per_page)
rolling_stock = paginator.get_page(page) rolling_stock = paginator.get_page(page)
page_range = paginator.get_elided_page_range(rolling_stock.number) page_range = paginator.get_elided_page_range(
rolling_stock.number, on_each_side=2, on_ends=1
)
return rolling_stock, matches, page_range return rolling_stock, matches, page_range
def get(self, request, search, _filter=None, page=1): def get(self, request, search, _filter="search", page=1):
rolling_stock, matches, page_range = self.run_search( rolling_stock, matches, page_range = self.run_search(
request, search, _filter, page request, search, _filter, page
) )
@@ -106,7 +112,7 @@ class GetHomeFiltered(View):
}, },
) )
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
@@ -165,7 +171,9 @@ class Consists(View):
paginator = Paginator(consist, site_conf.items_per_page) paginator = Paginator(consist, site_conf.items_per_page)
consist = paginator.get_page(page) consist = paginator.get_page(page)
page_range = paginator.get_elided_page_range(consist.number) page_range = paginator.get_elided_page_range(
consist.number, on_each_side=2, on_ends=1
)
return render( return render(
request, request,
@@ -185,7 +193,9 @@ class GetConsist(View):
paginator = Paginator(rolling_stock, site_conf.items_per_page) paginator = Paginator(rolling_stock, site_conf.items_per_page)
rolling_stock = paginator.get_page(page) rolling_stock = paginator.get_page(page)
page_range = paginator.get_elided_page_range(rolling_stock.number) page_range = paginator.get_elided_page_range(
rolling_stock.number, on_each_side=2, on_ends=1
)
return render( return render(
request, request,
@@ -196,3 +206,39 @@ class GetConsist(View):
"page_range": page_range, "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.6" __version__ = "0.0.14"
__version__ += git_suffix(__file__) __version__ += git_suffix(__file__)

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):
@@ -42,6 +46,22 @@ class RollingStockPropertyInline(admin.TabularInline):
extra = 0 extra = 0
@admin.register(RollingStockDocument)
class RollingStockDocumentAdmin(admin.ModelAdmin):
list_display = (
"__str__",
"rolling_stock",
"description",
"download",
)
search_fields = (
"rolling_stock__rolling_class__identifier",
"rolling_stock__sku",
"description",
"file",
)
@admin.register(RollingStock) @admin.register(RollingStock)
class RollingStockAdmin(admin.ModelAdmin): class RollingStockAdmin(admin.ModelAdmin):
inlines = ( inlines = (
@@ -62,10 +82,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

@@ -4,6 +4,7 @@ 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.dispatch import receiver
from django.utils.safestring import mark_safe
# from django.core.files.storage import FileSystemStorage # from django.core.files.storage import FileSystemStorage
@@ -141,7 +142,12 @@ class RollingStockDocument(models.Model):
return "{0}".format(os.path.basename(self.file.name)) return "{0}".format(os.path.basename(self.file.name))
def filename(self): def filename(self):
return os.path.basename(self.file.name) return self.__str__()
def download(self):
return mark_safe(
'<a href="{0}" target="_blank">Link</a>'.format(self.file.url)
)
class RollingStockImage(models.Model): class RollingStockImage(models.Model):