mirror of
https://github.com/daniviga/django-ram.git
synced 2025-08-04 21:27:49 +02:00
Merge pull request #7 from daniviga/search
Add search bar and refactor templates
This commit is contained in:
@@ -26,9 +26,7 @@ class Consist(models.Model):
|
|||||||
|
|
||||||
class ConsistItem(models.Model):
|
class ConsistItem(models.Model):
|
||||||
consist = models.ForeignKey(
|
consist = models.ForeignKey(
|
||||||
Consist,
|
Consist, on_delete=models.CASCADE, related_name="consist_item"
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="consist_item"
|
|
||||||
)
|
)
|
||||||
rolling_stock = models.ForeignKey(RollingStock, on_delete=models.CASCADE)
|
rolling_stock = models.ForeignKey(RollingStock, on_delete=models.CASCADE)
|
||||||
order = models.PositiveIntegerField(default=0, blank=False, null=False)
|
order = models.PositiveIntegerField(default=0, blank=False, null=False)
|
||||||
|
@@ -6,23 +6,16 @@ from solo.models import SingletonModel
|
|||||||
|
|
||||||
class DriverConfiguration(SingletonModel):
|
class DriverConfiguration(SingletonModel):
|
||||||
remote_host = models.GenericIPAddressField(
|
remote_host = models.GenericIPAddressField(
|
||||||
protocol="IPv4",
|
protocol="IPv4", default="192.168.4.1"
|
||||||
default="192.168.4.1"
|
|
||||||
)
|
)
|
||||||
remote_port = models.SmallIntegerField(default=2560)
|
remote_port = models.SmallIntegerField(default=2560)
|
||||||
timeout = models.SmallIntegerField(default=250)
|
timeout = models.SmallIntegerField(default=250)
|
||||||
|
|
||||||
network = models.GenericIPAddressField(
|
network = models.GenericIPAddressField(
|
||||||
protocol="IPv4",
|
protocol="IPv4", default="192.168.4.0", blank=True, null=True
|
||||||
default="192.168.4.0",
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
)
|
||||||
subnet_mask = models.GenericIPAddressField(
|
subnet_mask = models.GenericIPAddressField(
|
||||||
protocol="IPv4",
|
protocol="IPv4", default="255.255.255.0", blank=True, null=True
|
||||||
default="255.255.255.0",
|
|
||||||
blank=True,
|
|
||||||
null=True
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -31,8 +24,7 @@ class DriverConfiguration(SingletonModel):
|
|||||||
def clean(self, *args, **kwargs):
|
def clean(self, *args, **kwargs):
|
||||||
if self.network:
|
if self.network:
|
||||||
try:
|
try:
|
||||||
IPv4Network(
|
IPv4Network("{0}/{1}".format(self.network, self.subnet_mask))
|
||||||
"{0}/{1}".format(self.network, self.subnet_mask))
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise ValidationError(e)
|
raise ValidationError(e)
|
||||||
super().clean(*args, **kwargs)
|
super().clean(*args, **kwargs)
|
||||||
|
@@ -7,7 +7,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.permissions import (
|
from rest_framework.permissions import (
|
||||||
IsAuthenticated,
|
IsAuthenticated,
|
||||||
BasePermission,
|
BasePermission,
|
||||||
SAFE_METHODS
|
SAFE_METHODS,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ram.parsers import PlainTextParser
|
from ram.parsers import PlainTextParser
|
||||||
@@ -40,16 +40,15 @@ class Firewall(BasePermission):
|
|||||||
if not config.network:
|
if not config.network:
|
||||||
return request.method in SAFE_METHODS
|
return request.method in SAFE_METHODS
|
||||||
|
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
||||||
if x_forwarded_for:
|
if x_forwarded_for:
|
||||||
ip = IPv4Address(x_forwarded_for.split(',')[0])
|
ip = IPv4Address(x_forwarded_for.split(",")[0])
|
||||||
else:
|
else:
|
||||||
ip = IPv4Address(request.META.get("REMOTE_ADDR"))
|
ip = IPv4Address(request.META.get("REMOTE_ADDR"))
|
||||||
|
|
||||||
network = IPv4Network("{0}/{1}".format(
|
network = IPv4Network(
|
||||||
config.network,
|
"{0}/{1}".format(config.network, config.subnet_mask)
|
||||||
config.subnet_mask
|
)
|
||||||
))
|
|
||||||
|
|
||||||
# accept IP configured is settings or localhost
|
# accept IP configured is settings or localhost
|
||||||
if ip in network or ip in IPv4Network("127.0.0.0/8"):
|
if ip in network or ip in IPv4Network("127.0.0.0/8"):
|
||||||
@@ -101,6 +100,7 @@ class Function(APIView):
|
|||||||
"""
|
"""
|
||||||
Send "Function" commands to a valid DCC address
|
Send "Function" commands to a valid DCC address
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permission_classes = [IsAuthenticated | Firewall]
|
permission_classes = [IsAuthenticated | Firewall]
|
||||||
|
|
||||||
def put(self, request, address):
|
def put(self, request, address):
|
||||||
@@ -117,6 +117,7 @@ class Cab(APIView):
|
|||||||
"""
|
"""
|
||||||
Send "Cab" commands to a valid DCC address
|
Send "Cab" commands to a valid DCC address
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permission_classes = [IsAuthenticated | Firewall]
|
permission_classes = [IsAuthenticated | Firewall]
|
||||||
|
|
||||||
def put(self, request, address):
|
def put(self, request, address):
|
||||||
@@ -132,6 +133,7 @@ class Infra(APIView):
|
|||||||
"""
|
"""
|
||||||
Send "Infra" commands to a valid DCC address
|
Send "Infra" commands to a valid DCC address
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permission_classes = [IsAuthenticated | Firewall]
|
permission_classes = [IsAuthenticated | Firewall]
|
||||||
|
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
@@ -147,6 +149,7 @@ class Emergency(APIView):
|
|||||||
"""
|
"""
|
||||||
Send an "Emergency" stop, no matter the HTTP method used
|
Send an "Emergency" stop, no matter the HTTP method used
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permission_classes = [IsAuthenticated | Firewall]
|
permission_classes = [IsAuthenticated | Firewall]
|
||||||
|
|
||||||
def put(self, request):
|
def put(self, request):
|
||||||
|
@@ -2,5 +2,5 @@ from django.apps import AppConfig
|
|||||||
|
|
||||||
|
|
||||||
class PortalConfig(AppConfig):
|
class PortalConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
name = 'portal'
|
name = "portal"
|
||||||
|
@@ -7,14 +7,14 @@ from solo.models import SingletonModel
|
|||||||
|
|
||||||
class SiteConfiguration(SingletonModel):
|
class SiteConfiguration(SingletonModel):
|
||||||
site_name = models.CharField(
|
site_name = models.CharField(
|
||||||
max_length=256,
|
max_length=256, default="Railroad Assets Manager"
|
||||||
default="Railroad Assets Manager")
|
)
|
||||||
site_author = models.CharField(max_length=256, blank=True)
|
site_author = models.CharField(max_length=256, blank=True)
|
||||||
about = models.TextField(blank=True)
|
about = models.TextField(blank=True)
|
||||||
items_per_page = models.CharField(
|
items_per_page = models.CharField(
|
||||||
max_length=2, choices=[
|
max_length=2,
|
||||||
(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",
|
||||||
)
|
)
|
||||||
footer = models.TextField(blank=True)
|
footer = models.TextField(blank=True)
|
||||||
footer_extended = models.TextField(blank=True)
|
footer_extended = models.TextField(blank=True)
|
||||||
|
166
ram/portal/templates/base.html
Normal file
166
ram/portal/templates/base.html
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
{% load static %}
|
||||||
|
{% load solo_tags %}
|
||||||
|
{% load markdown %}
|
||||||
|
{% get_solo 'portal.SiteConfiguration' as site_conf %}
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="{{ site_conf.site_author }}">
|
||||||
|
<meta name="generator" content="Django Framework">
|
||||||
|
<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="{% static "css/main.css" %}" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.bd-placeholder-img {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
text-anchor: middle;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.bd-placeholder-img-lg {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="navbar navbar-dark bg-dark shadow-sm">
|
||||||
|
<div class="container">
|
||||||
|
<a href="/" class="navbar-brand d-flex align-items-center">
|
||||||
|
<svg class="me-2" width="26" height="16" enable-background="new 0 0 26 26" version="1" viewBox="0 0 26 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m2.8125 0.0010991a1.0001 1.0001 0 0 0-0.8125 1c0 0.55455-0.44545 1-1 1a1.0001 1.0001 0 0 0-1 1v10a1.0001 1.0001 0 0 0 1 1c0.55455 0 1 0.44546 1 1a1.0001 1.0001 0 0 0 1 1h20a1.0001 1.0001 0 0 0 1-1c0-0.55454 0.44546-1 1-1a1.0001 1.0001 0 0 0 1-1v-10a1.0001 1.0001 0 0 0-1-1c-0.55454 0-1-0.44545-1-1a1.0001 1.0001 0 0 0-1-1h-20a1.0001 1.0001 0 0 0-0.09375 0 1.0001 1.0001 0 0 0-0.09375 0zm0.78125 2h14.406v1h2v-1h2.4062c0.30628 0.76906 0.82469 1.2875 1.5938 1.5938v8.8125c-0.76906 0.30628-1.2875 0.82469-1.5938 1.5938h-2.4062v-1h-2v1h-14.406c-0.30628-0.76906-0.82469-1.2875-1.5938-1.5938v-8.8125c0.76906-0.30628 1.2875-0.82469 1.5938-1.5938zm14.406 2v2h2v-2zm0 3v2h2v-2zm0 3v2h2v-2z" enable-background="accumulate" fill="#fff" overflow="visible" stroke-width="2" style="text-indent:0;text-transform:none"/>
|
||||||
|
</svg>
|
||||||
|
<strong>{{ site_conf.site_name }}</strong>
|
||||||
|
</a>
|
||||||
|
{% include 'includes/login.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="py-2 container">
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light">
|
||||||
|
<div class="container-fluid g-0">
|
||||||
|
<a class="navbar-brand" href="/">Home</a>
|
||||||
|
{% include 'includes/search.html' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<section class="py-4 text-center container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="mx-auto">
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="album py-5 bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<a id="rolling-stock"></a>
|
||||||
|
<div data-masonry='{"percentPosition": true }' class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
|
||||||
|
{% block cards %}
|
||||||
|
{% for r in rolling_stock %}
|
||||||
|
<div class="col">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
{% for i in r.image.all %}
|
||||||
|
{% if i.is_thumbnail %}<a href="/portal/{{ r.uuid }}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="card-text"><strong>{{ r }}</strong></p>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">Data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th width="35%" scope="row">Type</th>
|
||||||
|
<td>{{ r.rolling_class.type }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Company</th>
|
||||||
|
<td><abbr title="{{ r.rolling_class.company.extended_name }}">{{ r.rolling_class.company }}</abbr></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Class</th>
|
||||||
|
<td>{{ r.rolling_class.identifier }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Road number</th>
|
||||||
|
<td>{{ r.road_number }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Era</th>
|
||||||
|
<td>{{ r.era }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th width="35%" scope="row">Manufacturer</th>
|
||||||
|
<td>{% if r.manufacturer.website %}<a href="{{ r.manufacturer.website }}">{% endif %}{{ r.manufacturer }}{% if r.manufacturer.website %}</a>{% endif %}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Scale</th>
|
||||||
|
<td><abbr title="{{ r.scale.ratio }} - {{ r.scale.gauge }}">{{ r.scale }}</abbr></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">SKU</th>
|
||||||
|
<td>{{ r.sku }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% if r.decoder %}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">DCC data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th width="35%" scope="row">Decoder</th>
|
||||||
|
<td>{{ r.decoder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Address</th>
|
||||||
|
<td>{{ r.address }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
<div class="btn-group mb-4">
|
||||||
|
<a class="btn btn-sm btn-outline-primary" href="/portal/{{ r.uuid }}">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 %}
|
||||||
|
</div>
|
||||||
|
{% if r.tags.all %}
|
||||||
|
<p class="card-text"><small>Tags:</small>
|
||||||
|
{% for t in r.tags.all %}<span class="badge bg-primary">
|
||||||
|
{{ t.name }}</span>{# new line is required #}
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<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>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">{% block pagination %}{% endblock %}</div>
|
||||||
|
</div>
|
||||||
|
{% block extra_content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
{% 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/masonry-layout@4.2.2/dist/masonry.pkgd.min.js" integrity="sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D" crossorigin="anonymous" async></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -1,20 +0,0 @@
|
|||||||
{% load markdown %}
|
|
||||||
|
|
||||||
<footer class="text-muted py-4">
|
|
||||||
<div class="container">
|
|
||||||
<p class="float-end mb-1">
|
|
||||||
<a href="#">Back to top</a>
|
|
||||||
</p>
|
|
||||||
<div id="footer" class="mb-1">
|
|
||||||
<p>© {% now "Y" %}</p> {{ site_conf.footer | markdown | safe }}
|
|
||||||
</div>
|
|
||||||
<div id="footer_extended" class="mb-0">
|
|
||||||
{{ site_conf.footer_extended | markdown | safe }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if site_conf.show_version %}
|
|
||||||
<div class="container">
|
|
||||||
<p class="small text-muted">Version: {{ site_conf.version }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</footer>
|
|
@@ -1,175 +1,12 @@
|
|||||||
{% load static %}
|
{% extends "base.html" %}
|
||||||
{% load solo_tags %}
|
|
||||||
{% load markdown %}
|
{% load markdown %}
|
||||||
{% get_solo 'portal.SiteConfiguration' as site_conf %}
|
|
||||||
|
|
||||||
<!doctype html>
|
{% block header %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
|
|
||||||
<meta name="generator" content="Hugo 0.88.1">
|
|
||||||
<title>{{ site_conf.site_name }}</title>
|
|
||||||
<link rel="canonical" href="https://getbootstrap.com/docs/5.1/examples/album/">
|
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="{% static "css/main.css" %}" rel="stylesheet">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bd-placeholder-img {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
text-anchor: middle;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.bd-placeholder-img-lg {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<div class="navbar navbar-dark bg-dark shadow-sm">
|
|
||||||
<div class="container">
|
|
||||||
<a href="/" class="navbar-brand d-flex align-items-center">
|
|
||||||
<svg class="me-2" width="26" height="16" enable-background="new 0 0 26 26" version="1" viewBox="0 0 26 16" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="m2.8125 0.0010991a1.0001 1.0001 0 0 0-0.8125 1c0 0.55455-0.44545 1-1 1a1.0001 1.0001 0 0 0-1 1v10a1.0001 1.0001 0 0 0 1 1c0.55455 0 1 0.44546 1 1a1.0001 1.0001 0 0 0 1 1h20a1.0001 1.0001 0 0 0 1-1c0-0.55454 0.44546-1 1-1a1.0001 1.0001 0 0 0 1-1v-10a1.0001 1.0001 0 0 0-1-1c-0.55454 0-1-0.44545-1-1a1.0001 1.0001 0 0 0-1-1h-20a1.0001 1.0001 0 0 0-0.09375 0 1.0001 1.0001 0 0 0-0.09375 0zm0.78125 2h14.406v1h2v-1h2.4062c0.30628 0.76906 0.82469 1.2875 1.5938 1.5938v8.8125c-0.76906 0.30628-1.2875 0.82469-1.5938 1.5938h-2.4062v-1h-2v1h-14.406c-0.30628-0.76906-0.82469-1.2875-1.5938-1.5938v-8.8125c0.76906-0.30628 1.2875-0.82469 1.5938-1.5938zm14.406 2v2h2v-2zm0 3v2h2v-2zm0 3v2h2v-2z" enable-background="accumulate" fill="#fff" overflow="visible" stroke-width="2" style="text-indent:0;text-transform:none"/>
|
|
||||||
</svg>
|
|
||||||
<strong>{{ site_conf.site_name }}</strong>
|
|
||||||
</a>
|
|
||||||
{% include 'login.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<section class="py-1 text-center container">
|
|
||||||
<div class="row py-lg-4">
|
|
||||||
<div class="mx-auto">
|
|
||||||
<h1 class="fw-light">About</h1>
|
<h1 class="fw-light">About</h1>
|
||||||
<p class="lead text-muted">{{ site_conf.about | markdown | safe }}</p>
|
<p class="lead text-muted">{{ site_conf.about | markdown | safe }}</p>
|
||||||
</div>
|
{% endblock %}
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="album py-5 bg-light">
|
{% block pagination %}
|
||||||
<div class="container">
|
|
||||||
<a id="rolling-stock"></a>
|
|
||||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
|
|
||||||
{% for r in rolling_stock %}
|
|
||||||
<div class="col">
|
|
||||||
<div class="card shadow-sm">
|
|
||||||
{% for i in r.image.all %}
|
|
||||||
{% if i.is_thumbnail %}<a href="/portal/{{ r.uuid }}"><img src="{{ i.image.url }}" alt="Card image cap"></a>{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="card-text"><strong>{{ r }}</strong></p>
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">Data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="35%" scope="row">Type</th>
|
|
||||||
<td>{{ r.rolling_class.type }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Company</th>
|
|
||||||
<td><abbr title="{{ r.rolling_class.company.extended_name }}">{{ r.rolling_class.company }}</abbr></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Class</th>
|
|
||||||
<td>{{ r.rolling_class.identifier }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Road number</th>
|
|
||||||
<td>{{ r.road_number }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Era</th>
|
|
||||||
<td>{{ r.era }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="collapse" id="collapseModel{{ r.pk }}">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">Model data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="35%" scope="row">Manufacturer</th>
|
|
||||||
<td>{% if r.manufacturer.website %}<a href="{{ r.manufacturer.website }}">{% endif %}{{ r.manufacturer }}{% if r.manufacturer.website %}</a>{% endif %}</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Scale</th>
|
|
||||||
<td><abbr title="{{ r.scale.ratio }} - {{ r.scale.gauge }}">{{ r.scale }}</abbr></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">SKU</th>
|
|
||||||
<td>{{ r.sku }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% if r.decoder %}
|
|
||||||
<div class="collapse" id="collapseDCC{{ r.pk }}">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">DCC data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="35%" scope="row">Decoder</th>
|
|
||||||
<td>{{ r.decoder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Address</th>
|
|
||||||
<td>{{ r.address }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="btn-group mb-4">
|
|
||||||
<button class="btn btn-outline-secondary btn-sm" type="button" data-bs-toggle="button"><span data-bs-toggle="collapse" data-bs-target="#collapseModel{{ r.pk }}" aria-expanded="false" aria-controls="collapseModel{{ r.pk }}">Model data</span></button>
|
|
||||||
{% if r.decoder %}
|
|
||||||
<button class="btn btn-outline-secondary btn-sm" type="button" data-bs-toggle="button"> <span data-bs-toggle="collapse" data-bs-target="#collapseDCC{{ r.pk }}" aria-expanded="false" aria-controls="collapseDCC{{ r.pk }}">DCC data</span></button>
|
|
||||||
{% endif %}
|
|
||||||
<a class="btn btn-sm btn-outline-primary" href="/portal/{{ r.uuid }}">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 %}
|
|
||||||
</div>
|
|
||||||
{% if r.tags.all %}
|
|
||||||
<p class="card-text"><small>Tags:</small>
|
|
||||||
{% for t in r.tags.all %}<span class="badge bg-primary">
|
|
||||||
{{ t.name }}</span>{# new line is required #}
|
|
||||||
{% endfor %}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<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>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
|
||||||
{% if rolling_stock.has_other_pages %}
|
{% if rolling_stock.has_other_pages %}
|
||||||
<nav aria-label="Page navigation example">
|
<nav aria-label="Page navigation example">
|
||||||
<ul class="pagination justify-content-center mt-4">
|
<ul class="pagination justify-content-center mt-4">
|
||||||
@@ -203,12 +40,4 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
{% include 'footer.html' %}
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
20
ram/portal/templates/includes/footer.html
Normal file
20
ram/portal/templates/includes/footer.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{% load markdown %}
|
||||||
|
|
||||||
|
<footer class="text-muted py-4">
|
||||||
|
<div class="container">
|
||||||
|
<p class="float-end mb-1">
|
||||||
|
<a href="#">Back to top</a>
|
||||||
|
</p>
|
||||||
|
<div id="footer" class="mb-1">
|
||||||
|
<p>© {% now "Y" %}</p> {{ site_conf.footer | markdown | safe }}
|
||||||
|
</div>
|
||||||
|
<div id="footer_extended" class="mb-0">
|
||||||
|
{{ site_conf.footer_extended | markdown | safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if site_conf.show_version %}
|
||||||
|
<div class="container">
|
||||||
|
<p class="small text-muted">Version: {{ site_conf.version }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</footer>
|
5
ram/portal/templates/includes/search.html
Normal file
5
ram/portal/templates/includes/search.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<form class="d-flex" action="/portal/search" method="POST">
|
||||||
|
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search" value="{{ search }}" name="search">
|
||||||
|
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
|
@@ -1,318 +1,250 @@
|
|||||||
{% load static %}
|
{% extends 'base.html' %}
|
||||||
{% load solo_tags %}
|
|
||||||
{% load markdown %}
|
{% load markdown %}
|
||||||
{% get_solo 'portal.SiteConfiguration' as site_conf %}
|
|
||||||
|
|
||||||
<!doctype html>
|
{% block header %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
|
|
||||||
<meta name="generator" content="Hugo 0.88.1">
|
|
||||||
<title>{{ site_conf.site_name }}</title>
|
|
||||||
<link rel="canonical" href="https://getbootstrap.com/docs/5.1/examples/album/">
|
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="{% static "css/main.css" %}" rel="stylesheet">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bd-placeholder-img {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
text-anchor: middle;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.bd-placeholder-img-lg {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<header>
|
|
||||||
<div class="navbar navbar-dark bg-dark shadow-sm">
|
|
||||||
<div class="container">
|
|
||||||
<a href="/" class="navbar-brand d-flex align-items-center">
|
|
||||||
<svg class="me-2" width="26" height="16" enable-background="new 0 0 26 26" version="1" viewBox="0 0 26 16" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="m2.8125 0.0010991a1.0001 1.0001 0 0 0-0.8125 1c0 0.55455-0.44545 1-1 1a1.0001 1.0001 0 0 0-1 1v10a1.0001 1.0001 0 0 0 1 1c0.55455 0 1 0.44546 1 1a1.0001 1.0001 0 0 0 1 1h20a1.0001 1.0001 0 0 0 1-1c0-0.55454 0.44546-1 1-1a1.0001 1.0001 0 0 0 1-1v-10a1.0001 1.0001 0 0 0-1-1c-0.55454 0-1-0.44545-1-1a1.0001 1.0001 0 0 0-1-1h-20a1.0001 1.0001 0 0 0-0.09375 0 1.0001 1.0001 0 0 0-0.09375 0zm0.78125 2h14.406v1h2v-1h2.4062c0.30628 0.76906 0.82469 1.2875 1.5938 1.5938v8.8125c-0.76906 0.30628-1.2875 0.82469-1.5938 1.5938h-2.4062v-1h-2v1h-14.406c-0.30628-0.76906-0.82469-1.2875-1.5938-1.5938v-8.8125c0.76906-0.30628 1.2875-0.82469 1.5938-1.5938zm14.406 2v2h2v-2zm0 3v2h2v-2zm0 3v2h2v-2z" enable-background="accumulate" fill="#fff" overflow="visible" stroke-width="2" style="text-indent:0;text-transform:none"/>
|
|
||||||
</svg>
|
|
||||||
<strong>{{ site_conf.site_name }}</strong>
|
|
||||||
</a>
|
|
||||||
{% include 'login.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<section class="py-1 text-start container">
|
|
||||||
<div class="row py-lg-4">
|
|
||||||
<div class="mx-auto">
|
|
||||||
<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
|
||||||
<li class="breadcrumb-item active" aria-current="page">{{ rolling_stock }}</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
<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 %}<span class="badge bg-primary">
|
||||||
{{ t.name }}</span>{# new line is required #}
|
{{ t.name }}</span>{# new line is required #}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
{% endblock %}
|
||||||
</div>
|
{% block cards %}
|
||||||
</section>
|
|
||||||
<section class="py-1 text-start container">
|
|
||||||
<div class="row py-lg-2">
|
|
||||||
<div class="mx-auto">
|
|
||||||
<nav>
|
|
||||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
|
||||||
<button class="nav-link active" id="nav-summary-tab" data-bs-toggle="tab" data-bs-target="#nav-summary" type="button" role="tab" aria-controls="nav-summary" aria-selected="true">Summary</button>
|
|
||||||
<button class="nav-link" id="nav-model-tab" data-bs-toggle="tab" data-bs-target="#nav-model" type="button" role="tab" aria-controls="nav-model" aria-selected="false">Model data</button>
|
|
||||||
<button class="nav-link" id="nav-class-tab" data-bs-toggle="tab" data-bs-target="#nav-class" type="button" role="tab" aria-controls="nav-class" aria-selected="false">Class data</button>
|
|
||||||
{% if rolling_stock.decoder %}<button class="nav-link" id="nav-dcc-tab" data-bs-toggle="tab" data-bs-target="#nav-dcc" type="button" role="tab" aria-controls="nav-dcc" aria-selected="false">DCC</button>{% endif %}
|
|
||||||
{% if rolling_stock.notes %}<button class="nav-link" id="nav-notes-tab" data-bs-toggle="tab" data-bs-target="#nav-notes" type="button" role="tab" aria-controls="nav-notes" aria-selected="false">Notes</button>{% endif %}
|
|
||||||
{% if rolling_stock.property.count > 0 %}<button class="nav-link" id="nav-properties-tab" data-bs-toggle="tab" data-bs-target="#nav-properties" type="button" role="tab" aria-controls="nav-properties" aria-selected="false">Properties</button>{% endif %}
|
|
||||||
{% if rolling_stock.document.count > 0 %}<button class="nav-link" id="nav-documents-tab" data-bs-toggle="tab" data-bs-target="#nav-documents" type="button" role="tab" aria-controls="nav-documents" aria-selected="false">Documents</button>{% endif %}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div class="tab-content" id="nav-tabContent">
|
|
||||||
<div class="tab-pane fade show active" id="nav-summary" role="tabpanel" aria-labelledby="nav-summary-tab">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">Data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="35%" scope="row">Type</th>
|
|
||||||
<td>{{ rolling_stock.rolling_class.type }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Company</th>
|
|
||||||
<td><abbr title="{{ rolling_stock.rolling_class.company.extended_name }}">{{ rolling_stock.rolling_class.company }}</abbr></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Class</th>
|
|
||||||
<td>{{ rolling_stock.rolling_class.identifier }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Road number</th>
|
|
||||||
<td>{{ rolling_stock.road_number }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Era</th>
|
|
||||||
<td>{{ rolling_stock.era }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">Model data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Scale</th>
|
|
||||||
<td><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.gauge }}">{{ rolling_stock.scale }}</abbr></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">SKU</th>
|
|
||||||
<td>{{ rolling_stock.sku }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% if rolling_stock.decoder %}
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">DCC data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="35%" scope="row">Decoder</th>
|
|
||||||
<td>{{ rolling_stock.decoder }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Address</th>
|
|
||||||
<td>{{ rolling_stock.address }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="nav-model" role="tabpanel" aria-labelledby="nav-model-tab">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">Model data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Scale</th>
|
|
||||||
<td><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.gauge }}">{{ rolling_stock.scale }}</abbr></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">SKU</th>
|
|
||||||
<td>{{ rolling_stock.sku }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">ERA</th>
|
|
||||||
<td>{{ rolling_stock.era }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Production year</th>
|
|
||||||
<td>{{ rolling_stock.production_year | default_if_none:"" }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Purchase date</th>
|
|
||||||
<td>{{ rolling_stock.purchase_date | default_if_none:"" }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="nav-class" role="tabpanel" aria-labelledby="nav-class-tab">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">Class data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="35%" scope="row">Class</th>
|
|
||||||
<td>{{ rolling_stock.rolling_class }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Type</th>
|
|
||||||
<td>{{ rolling_stock.rolling_class.type }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Company</th>
|
|
||||||
<td>{{ rolling_stock.rolling_class.company }} ({{ rolling_stock.rolling_class.company.extended_name }})</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Country</th>
|
|
||||||
<td>{{ rolling_stock.rolling_class.company.country.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Manufacturer</th>
|
|
||||||
<td>{{ rolling_stock.rolling_class.manufacturer }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="nav-dcc" role="tabpanel" aria-labelledby="nav-dcc-tab">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">DCC data</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th width="35%" scope="row">Name</th>
|
|
||||||
<td>{{ rolling_stock.decoder.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Manufacturer</th>
|
|
||||||
<td>{{ rolling_stock.decoder.manufacturer }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="nav-notes" role="tabpanel" aria-labelledby="nav-notes-tab">
|
|
||||||
{{ rolling_stock.notes | markdown | safe }}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="nav-properties" role="tabpanel" aria-labelledby="nav-properties-tab">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="2" scope="row">Properties</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for p in rolling_stock.property.all %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">{{ p.property }}</th>
|
|
||||||
<td>{{ p.value }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane fade" id="nav-documents" role="tabpanel" aria-labelledby="nav-documents-tab">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="3" scope="row">Documents</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for d in rolling_stock.document.all %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ d.description }}</td>
|
|
||||||
<td><a href="{{ d.file.url }}">{{ d.filename }}</a></td>
|
|
||||||
<td class="text-end">{{ d.file.size | filesizeformat }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
|
||||||
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:roster_rollingstock_change' rolling_stock.pk %}">Edit</a>{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<div class="album py-5 bg-light">
|
|
||||||
<div class="container">
|
|
||||||
<a id="rolling-stock"></a>
|
|
||||||
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
|
|
||||||
{% 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">
|
<img class="img-thumbnail" src="{{ t.image.url }}" alt="Rolling stock image">
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
{% endblock %}
|
||||||
</div>
|
{% block extra_content %}
|
||||||
</div>
|
<section class="py-4 text-start container">
|
||||||
</main>
|
<div class="row">
|
||||||
{% include 'footer.html' %}
|
<div class="mx-auto">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
<nav>
|
||||||
</body>
|
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
</html>
|
<button class="nav-link active" id="nav-summary-tab" data-bs-toggle="tab" data-bs-target="#nav-summary" type="button" role="tab" aria-controls="nav-summary" aria-selected="true">Summary</button>
|
||||||
|
<button class="nav-link" id="nav-model-tab" data-bs-toggle="tab" data-bs-target="#nav-model" type="button" role="tab" aria-controls="nav-model" aria-selected="false">Model data</button>
|
||||||
|
<button class="nav-link" id="nav-class-tab" data-bs-toggle="tab" data-bs-target="#nav-class" type="button" role="tab" aria-controls="nav-class" aria-selected="false">Class data</button>
|
||||||
|
{% if rolling_stock.decoder %}<button class="nav-link" id="nav-dcc-tab" data-bs-toggle="tab" data-bs-target="#nav-dcc" type="button" role="tab" aria-controls="nav-dcc" aria-selected="false">DCC</button>{% endif %}
|
||||||
|
{% if rolling_stock.notes %}<button class="nav-link" id="nav-notes-tab" data-bs-toggle="tab" data-bs-target="#nav-notes" type="button" role="tab" aria-controls="nav-notes" aria-selected="false">Notes</button>{% endif %}
|
||||||
|
{% if rolling_stock.property.count > 0 %}<button class="nav-link" id="nav-properties-tab" data-bs-toggle="tab" data-bs-target="#nav-properties" type="button" role="tab" aria-controls="nav-properties" aria-selected="false">Properties</button>{% endif %}
|
||||||
|
{% if rolling_stock.document.count > 0 %}<button class="nav-link" id="nav-documents-tab" data-bs-toggle="tab" data-bs-target="#nav-documents" type="button" role="tab" aria-controls="nav-documents" aria-selected="false">Documents</button>{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="tab-content" id="nav-tabContent">
|
||||||
|
<div class="tab-pane fade show active" id="nav-summary" role="tabpanel" aria-labelledby="nav-summary-tab">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">Data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th width="35%" scope="row">Type</th>
|
||||||
|
<td>{{ rolling_stock.rolling_class.type }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Company</th>
|
||||||
|
<td><abbr title="{{ rolling_stock.rolling_class.company.extended_name }}">{{ rolling_stock.rolling_class.company }}</abbr></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Class</th>
|
||||||
|
<td>{{ rolling_stock.rolling_class.identifier }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Road number</th>
|
||||||
|
<td>{{ rolling_stock.road_number }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Era</th>
|
||||||
|
<td>{{ rolling_stock.era }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">Model data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Scale</th>
|
||||||
|
<td><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.gauge }}">{{ rolling_stock.scale }}</abbr></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">SKU</th>
|
||||||
|
<td>{{ rolling_stock.sku }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% if rolling_stock.decoder %}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">DCC data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th width="35%" scope="row">Decoder</th>
|
||||||
|
<td>{{ rolling_stock.decoder }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Address</th>
|
||||||
|
<td>{{ rolling_stock.address }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-model" role="tabpanel" aria-labelledby="nav-model-tab">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">Model data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Scale</th>
|
||||||
|
<td><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.gauge }}">{{ rolling_stock.scale }}</abbr></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">SKU</th>
|
||||||
|
<td>{{ rolling_stock.sku }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">ERA</th>
|
||||||
|
<td>{{ rolling_stock.era }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Production year</th>
|
||||||
|
<td>{{ rolling_stock.production_year | default_if_none:"" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Purchase date</th>
|
||||||
|
<td>{{ rolling_stock.purchase_date | default_if_none:"" }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-class" role="tabpanel" aria-labelledby="nav-class-tab">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">Class data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th width="35%" scope="row">Class</th>
|
||||||
|
<td>{{ rolling_stock.rolling_class }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Type</th>
|
||||||
|
<td>{{ rolling_stock.rolling_class.type }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Company</th>
|
||||||
|
<td>{{ rolling_stock.rolling_class.company }} ({{ rolling_stock.rolling_class.company.extended_name }})</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Country</th>
|
||||||
|
<td>{{ rolling_stock.rolling_class.company.country.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Manufacturer</th>
|
||||||
|
<td>{{ rolling_stock.rolling_class.manufacturer }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-dcc" role="tabpanel" aria-labelledby="nav-dcc-tab">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">DCC data</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th width="35%" scope="row">Name</th>
|
||||||
|
<td>{{ rolling_stock.decoder.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Manufacturer</th>
|
||||||
|
<td>{{ rolling_stock.decoder.manufacturer }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-notes" role="tabpanel" aria-labelledby="nav-notes-tab">
|
||||||
|
{{ rolling_stock.notes | markdown | safe }}
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-properties" role="tabpanel" aria-labelledby="nav-properties-tab">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2" scope="row">Properties</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for p in rolling_stock.property.all %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{ p.property }}</th>
|
||||||
|
<td>{{ p.value }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="nav-documents" role="tabpanel" aria-labelledby="nav-documents-tab">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="3" scope="row">Documents</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for d in rolling_stock.document.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ d.description }}</td>
|
||||||
|
<td><a href="{{ d.file.url }}">{{ d.filename }}</a></td>
|
||||||
|
<td class="text-end">{{ d.file.size | filesizeformat }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
|
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:roster_rollingstock_change' rolling_stock.pk %}">Edit</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
|
41
ram/portal/templates/search.html
Normal file
41
ram/portal/templates/search.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1 class="fw-light">Search: {{ search }}</h1>
|
||||||
|
<p class="lead text-muted">Results found: {{ rolling_stock | length }}</p>
|
||||||
|
{% endblock %}
|
||||||
|
{% block pagination %}
|
||||||
|
{% if rolling_stock.has_other_pages %}
|
||||||
|
<nav aria-label="Page navigation example">
|
||||||
|
<ul class="pagination justify-content-center mt-4">
|
||||||
|
{% if rolling_stock.has_previous %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="/portal/search/{{ search }}/{{ rolling_stock.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 rolling_stock.paginator.page_range %}
|
||||||
|
{% if rolling_stock.number == i %}
|
||||||
|
<li class="page-item active">
|
||||||
|
<span class="page-link">{{ i }}</span></span>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="page-item"><a class="page-link" href="/portal/search/{{ search }}/{{ i }}#rolling-stock">{{ i }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if rolling_stock.has_next %}
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="/portal/search/{{ search }}/{{ rolling_stock.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 %}
|
@@ -9,4 +9,4 @@ register = template.Library()
|
|||||||
@register.filter
|
@register.filter
|
||||||
@stringfilter
|
@stringfilter
|
||||||
def markdown(value):
|
def markdown(value):
|
||||||
return md.markdown(value, extensions=['markdown.extensions.fenced_code'])
|
return md.markdown(value, extensions=["markdown.extensions.fenced_code"])
|
||||||
|
@@ -1,8 +1,22 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from portal.views import GetHome, GetRollingStock
|
from portal.views import GetHome, GetHomeFiltered, GetRollingStock
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("<int:page>", GetHome.as_view(), name='index_pagination'),
|
path("", GetHome.as_view(), name="index"),
|
||||||
path("<uuid:uuid>", GetRollingStock.as_view(), name='rolling_stock'),
|
path("<int:page>", GetHome.as_view(), name="index_pagination"),
|
||||||
|
path(
|
||||||
|
"search",
|
||||||
|
GetHomeFiltered.as_view(http_method_names=["post"]),
|
||||||
|
name="index_filtered",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"search/<str:search>", GetHomeFiltered.as_view(), name="index_filtered"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"search/<str:search>/<int:page>",
|
||||||
|
GetHomeFiltered.as_view(),
|
||||||
|
name="index_filtered_pagination",
|
||||||
|
),
|
||||||
|
path("<uuid:uuid>", GetRollingStock.as_view(), name="rolling_stock"),
|
||||||
]
|
]
|
||||||
|
@@ -2,5 +2,5 @@ from django.apps import apps
|
|||||||
|
|
||||||
|
|
||||||
def get_site_conf():
|
def get_site_conf():
|
||||||
SiteConfiguration = apps.get_model('portal', 'SiteConfiguration')
|
SiteConfiguration = apps.get_model("portal", "SiteConfiguration")
|
||||||
return SiteConfiguration.get_solo()
|
return SiteConfiguration.get_solo()
|
||||||
|
@@ -1,16 +1,20 @@
|
|||||||
|
import operator
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from django.http import Http404
|
||||||
|
from django.db.models import Q
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
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, RollingStockImage
|
from roster.models import RollingStock
|
||||||
|
|
||||||
|
|
||||||
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.all()
|
||||||
thumbnails = RollingStockImage.objects.filter(is_thumbnail=True)
|
|
||||||
paginator = Paginator(rolling_stock, site_conf.items_per_page)
|
paginator = Paginator(rolling_stock, site_conf.items_per_page)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -20,16 +24,76 @@ class GetHome(View):
|
|||||||
except EmptyPage:
|
except EmptyPage:
|
||||||
rolling_stock = paginator.page(paginator.num_pages)
|
rolling_stock = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
return render(request, 'home.html', {
|
return render(request, "home.html", {"rolling_stock": rolling_stock})
|
||||||
'rolling_stock': rolling_stock,
|
|
||||||
'thumbnails': thumbnails
|
|
||||||
})
|
class GetHomeFiltered(View):
|
||||||
|
def run_search(self, request, search, page=1):
|
||||||
|
# if not hasattr(RollingStock, _filter):
|
||||||
|
# raise Http404
|
||||||
|
site_conf = get_site_conf()
|
||||||
|
# query = {
|
||||||
|
# _filter: _value
|
||||||
|
# }
|
||||||
|
query = reduce(
|
||||||
|
operator.or_,
|
||||||
|
(
|
||||||
|
Q(
|
||||||
|
Q(rolling_class__identifier__icontains=s)
|
||||||
|
| Q(rolling_class__description__icontains=s)
|
||||||
|
| Q(rolling_class__type__type__icontains=s)
|
||||||
|
| Q(road_number__icontains=s)
|
||||||
|
| Q(rolling_class__company__name__icontains=s)
|
||||||
|
| Q(rolling_class__company__country__icontains=s)
|
||||||
|
| Q(manufacturer__name__icontains=s)
|
||||||
|
| Q(scale__scale__icontains=s)
|
||||||
|
| Q(tags__name__icontains=s)
|
||||||
|
)
|
||||||
|
for s in search.split()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
rolling_stock = RollingStock.objects.filter(query)
|
||||||
|
paginator = Paginator(rolling_stock, site_conf.items_per_page)
|
||||||
|
|
||||||
|
try:
|
||||||
|
rolling_stock = paginator.page(page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
rolling_stock = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
rolling_stock = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
|
return rolling_stock
|
||||||
|
|
||||||
|
def get(self, request, search, page=1):
|
||||||
|
rolling_stock = self.run_search(request, search, page)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"search.html",
|
||||||
|
{"search": search, "rolling_stock": rolling_stock},
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, request, page=1):
|
||||||
|
search = request.POST.get("search")
|
||||||
|
if not search:
|
||||||
|
raise Http404
|
||||||
|
rolling_stock = self.run_search(request, search, page)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"search.html",
|
||||||
|
{"search": search, "rolling_stock": rolling_stock},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetRollingStock(View):
|
class GetRollingStock(View):
|
||||||
def get(self, request, uuid):
|
def get(self, request, uuid):
|
||||||
rolling_stock = RollingStock.objects.get(uuid=uuid)
|
rolling_stock = RollingStock.objects.get(uuid=uuid)
|
||||||
|
|
||||||
return render(request, 'page.html', {
|
return render(
|
||||||
'rolling_stock': rolling_stock,
|
request,
|
||||||
})
|
"page.html",
|
||||||
|
{
|
||||||
|
"rolling_stock": rolling_stock,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from ram.utils import git_suffix
|
from ram.utils import git_suffix
|
||||||
|
|
||||||
__version__ = '0.0.1'
|
__version__ = "0.0.1"
|
||||||
__version__ += git_suffix(__file__)
|
__version__ += git_suffix(__file__)
|
||||||
|
@@ -28,7 +28,7 @@ SECRET_KEY = (
|
|||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@@ -152,10 +152,7 @@ DECODER_INTERFACES = [
|
|||||||
(5, "Next18/Next18S"),
|
(5, "Next18/Next18S"),
|
||||||
]
|
]
|
||||||
|
|
||||||
MANUFACTURER_TYPES = [
|
MANUFACTURER_TYPES = [("model", "Model"), ("real", "Real")]
|
||||||
("model", "Model"),
|
|
||||||
("real", "Real")
|
|
||||||
]
|
|
||||||
|
|
||||||
ROLLING_STOCK_TYPES = [
|
ROLLING_STOCK_TYPES = [
|
||||||
("engine", "Engine"),
|
("engine", "Engine"),
|
||||||
|
@@ -14,18 +14,18 @@ Including another URLconf
|
|||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.shortcuts import redirect
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
|
||||||
from portal.utils import get_site_conf
|
from portal.utils import get_site_conf
|
||||||
from portal.views import GetHome
|
|
||||||
|
|
||||||
site_conf = get_site_conf()
|
site_conf = get_site_conf()
|
||||||
admin.site.site_header = site_conf.site_name
|
admin.site.site_header = site_conf.site_name
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", GetHome.as_view(), name="index"),
|
path("", lambda r: redirect("/portal/")),
|
||||||
path("portal/", include("portal.urls")),
|
path("portal/", include("portal.urls")),
|
||||||
path("ht/", include("health_check.urls")),
|
path("ht/", include("health_check.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
|
@@ -11,13 +11,14 @@ def git_suffix(fname):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
gh = subprocess.check_output(
|
gh = subprocess.check_output(
|
||||||
['git', 'rev-parse', '--short', 'HEAD'],
|
["git", "rev-parse", "--short", "HEAD"],
|
||||||
stderr=open(os.devnull, 'w')).strip()
|
stderr=open(os.devnull, "w"),
|
||||||
gh = "-git" + gh.decode() if gh else ''
|
).strip()
|
||||||
|
gh = "-git" + gh.decode() if gh else ""
|
||||||
except Exception:
|
except Exception:
|
||||||
# trapping everything on purpose; git may not be installed or it
|
# trapping everything on purpose; git may not be installed or it
|
||||||
# may not work properly
|
# may not work properly
|
||||||
gh = ''
|
gh = ""
|
||||||
|
|
||||||
return gh
|
return gh
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ import os
|
|||||||
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.core.files.storage import FileSystemStorage
|
# from django.core.files.storage import FileSystemStorage
|
||||||
# from django.dispatch import receiver
|
# from django.dispatch import receiver
|
||||||
|
|
||||||
@@ -32,8 +33,11 @@ class RollingClass(models.Model):
|
|||||||
)
|
)
|
||||||
description = models.CharField(max_length=256, blank=True)
|
description = models.CharField(max_length=256, blank=True)
|
||||||
manufacturer = models.ForeignKey(
|
manufacturer = models.ForeignKey(
|
||||||
Manufacturer, on_delete=models.CASCADE, null=True, blank=True,
|
Manufacturer,
|
||||||
limit_choices_to={"category": "real"}
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
limit_choices_to={"category": "real"},
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -74,8 +78,11 @@ class RollingStock(models.Model):
|
|||||||
)
|
)
|
||||||
road_number = models.CharField(max_length=128, unique=False)
|
road_number = models.CharField(max_length=128, unique=False)
|
||||||
manufacturer = models.ForeignKey(
|
manufacturer = models.ForeignKey(
|
||||||
Manufacturer, on_delete=models.CASCADE, null=True, blank=True,
|
Manufacturer,
|
||||||
limit_choices_to={"category": "model"}
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
limit_choices_to={"category": "model"},
|
||||||
)
|
)
|
||||||
scale = models.ForeignKey(Scale, on_delete=models.CASCADE)
|
scale = models.ForeignKey(Scale, on_delete=models.CASCADE)
|
||||||
sku = models.CharField(max_length=32, blank=True)
|
sku = models.CharField(max_length=32, blank=True)
|
||||||
@@ -112,9 +119,7 @@ class RollingStock(models.Model):
|
|||||||
|
|
||||||
class RollingStockDocument(models.Model):
|
class RollingStockDocument(models.Model):
|
||||||
rolling_stock = models.ForeignKey(
|
rolling_stock = models.ForeignKey(
|
||||||
RollingStock,
|
RollingStock, on_delete=models.CASCADE, related_name="document"
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="document"
|
|
||||||
)
|
)
|
||||||
description = models.CharField(max_length=128, blank=True)
|
description = models.CharField(max_length=128, blank=True)
|
||||||
file = models.FileField(upload_to="files/", null=True, blank=True)
|
file = models.FileField(upload_to="files/", null=True, blank=True)
|
||||||
@@ -131,9 +136,7 @@ class RollingStockDocument(models.Model):
|
|||||||
|
|
||||||
class RollingStockImage(models.Model):
|
class RollingStockImage(models.Model):
|
||||||
rolling_stock = models.ForeignKey(
|
rolling_stock = models.ForeignKey(
|
||||||
RollingStock,
|
RollingStock, on_delete=models.CASCADE, related_name="image"
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name="image"
|
|
||||||
)
|
)
|
||||||
image = models.ImageField(upload_to="images/", null=True, blank=True)
|
image = models.ImageField(upload_to="images/", null=True, blank=True)
|
||||||
is_thumbnail = models.BooleanField()
|
is_thumbnail = models.BooleanField()
|
||||||
@@ -149,7 +152,8 @@ class RollingStockImage(models.Model):
|
|||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
if self.is_thumbnail:
|
if self.is_thumbnail:
|
||||||
RollingStockImage.objects.filter(
|
RollingStockImage.objects.filter(
|
||||||
rolling_stock=self.rolling_stock).update(is_thumbnail=False)
|
rolling_stock=self.rolling_stock
|
||||||
|
).update(is_thumbnail=False)
|
||||||
super().save(**kwargs)
|
super().save(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -159,7 +163,7 @@ class RollingStockProperty(models.Model):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="property",
|
related_name="property",
|
||||||
null=False,
|
null=False,
|
||||||
blank=False
|
blank=False,
|
||||||
)
|
)
|
||||||
property = models.ForeignKey(Property, on_delete=models.CASCADE)
|
property = models.ForeignKey(Property, on_delete=models.CASCADE)
|
||||||
value = models.CharField(max_length=256)
|
value = models.CharField(max_length=256)
|
||||||
|
Reference in New Issue
Block a user