mirror of
https://github.com/daniviga/django-ram.git
synced 2025-08-04 13:17:50 +02:00
Implement support for sets and other improvements (#34)
* Add a boolean to define item as part of a set * Add contextual help in admin * Introduce support to sets and to item code lookup Also review the url path for pagination
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-20 12:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("consist", "0010_alter_consist_notes"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="consist",
|
||||
name="consist_address",
|
||||
field=models.SmallIntegerField(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text="DCC consist address if enabled",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="consist",
|
||||
name="era",
|
||||
field=models.CharField(
|
||||
blank=True, help_text="Era or epoch of the consist", max_length=32
|
||||
),
|
||||
),
|
||||
]
|
@@ -16,10 +16,17 @@ class Consist(models.Model):
|
||||
identifier = models.CharField(max_length=128, unique=False)
|
||||
tags = models.ManyToManyField(Tag, related_name="consist", blank=True)
|
||||
consist_address = models.SmallIntegerField(
|
||||
default=None, null=True, blank=True
|
||||
default=None,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="DCC consist address if enabled",
|
||||
)
|
||||
company = models.ForeignKey(Company, on_delete=models.CASCADE)
|
||||
era = models.CharField(max_length=32, blank=True)
|
||||
era = models.CharField(
|
||||
max_length=32,
|
||||
blank=True,
|
||||
help_text="Era or epoch of the consist",
|
||||
)
|
||||
image = models.ImageField(
|
||||
upload_to=os.path.join("images", "consists"),
|
||||
storage=DeduplicatedStorage,
|
||||
|
20
ram/metadata/migrations/0017_alter_property_private.py
Normal file
20
ram/metadata/migrations/0017_alter_property_private.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-20 12:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("metadata", "0016_alter_decoderdocument_file"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="property",
|
||||
name="private",
|
||||
field=models.BooleanField(
|
||||
default=False, help_text="Property will be only visible to logged users"
|
||||
),
|
||||
),
|
||||
]
|
@@ -11,7 +11,10 @@ from ram.utils import DeduplicatedStorage, get_image_preview, slugify
|
||||
|
||||
class Property(models.Model):
|
||||
name = models.CharField(max_length=128, unique=True)
|
||||
private = models.BooleanField(default=False)
|
||||
private = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Property will be only visible to logged users",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Properties"
|
||||
|
@@ -60,7 +60,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Item number</th>
|
||||
<td>{{ d.item.item_number }}</td>
|
||||
<td>{{ d.item.item_number }}{%if d.item.set %} | <a class="badge text-bg-primary" href="{% url 'manufacturer' manufacturer=d.item.manufacturer search=d.item.item_number %}">SET</a>{% endif %}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
40
ram/portal/templates/manufacturer.html
Normal file
40
ram/portal/templates/manufacturer.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% extends "cards.html" %}
|
||||
{% block pagination %}
|
||||
{% if data.has_other_pages %}
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination justify-content-center mt-4 mb-0">
|
||||
{% if data.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'manufacturer_pagination' manufacturer=manufacturer search=search page=data.previous_page_number %}#main-content" 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 data.number == i %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ i }}</span>
|
||||
</li>
|
||||
{% else %}
|
||||
{% if i == data.paginator.ELLIPSIS %}
|
||||
<li class="page-item"><span class="page-link">{{ i }}</span></li>
|
||||
{% else %}
|
||||
<li class="page-item"><a class="page-link" href="{% url 'manufacturer_pagination' manufacturer=manufacturer search=search page=i %}#main-content">{{ i }}</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if data.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'manufacturer_pagination' manufacturer=manufacturer search=search page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@@ -52,6 +52,7 @@
|
||||
{% if documents or decoder_documents %}<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 %}
|
||||
{% if journal %}<button class="nav-link" id="nav-journal-tab" data-bs-toggle="tab" data-bs-target="#nav-journal" type="button" role="tab" aria-controls="nav-journal" aria-selected="false">Journal</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 set %}<button class="nav-link" id="nav-set-tab" data-bs-toggle="tab" data-bs-target="#nav-set" type="button" role="tab" aria-controls="nav-set" aria-selected="false">Set</button>{% endif %}
|
||||
{% if consists %}<button class="nav-link" id="nav-consists-tab" data-bs-toggle="tab" data-bs-target="#nav-consists" type="button" role="tab" aria-controls="nav-consists" aria-selected="false">Consists</button>{% endif %}
|
||||
</nav>
|
||||
<select class="form-select d-lg-none mb-2" id="tabSelector" aria-label="Tab selector">
|
||||
@@ -63,6 +64,7 @@
|
||||
{% if documents or decoder_documents %}<option value="nav-documents">Documents</option>{% endif %}
|
||||
{% if journal %}<option value="nav-journal">Journal</option>{% endif %}
|
||||
{% if rolling_stock.notes %}<option value="nav-notes">Notes</option>{% endif %}
|
||||
{% if set %}<option value="nav-set">Set</option>{% endif %}
|
||||
{% if consists %}<option value="nav-consists">Consists</option>{% endif %}
|
||||
</select>
|
||||
{% with class=rolling_stock.rolling_class company=rolling_stock.rolling_class.company %}
|
||||
@@ -118,7 +120,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Item number</th>
|
||||
<td>{{ rolling_stock.item_number }}</td>
|
||||
<td>{{ rolling_stock.item_number }}{%if rolling_stock.set %} | <a class="badge text-bg-primary" href="{% url 'manufacturer' manufacturer=rolling_stock.manufacturer search=rolling_stock.item_number %}">SET</a>{% endif %}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -171,7 +173,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Item number</th>
|
||||
<td>{{ rolling_stock.item_number }}</td>
|
||||
<td>{{ rolling_stock.item_number }}{%if rolling_stock.set %} | <a class="badge text-bg-primary" href="{% url 'manufacturer' manufacturer=rolling_stock.manufacturer search=rolling_stock.item_number %}">SET</a>{% endif %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Era</th>
|
||||
@@ -374,6 +376,13 @@
|
||||
<div class="tab-pane" id="nav-notes" role="tabpanel" aria-labelledby="nav-notes-tab">
|
||||
{{ rolling_stock.notes | safe }}
|
||||
</div>
|
||||
<div class="tab-pane" id="nav-set" role="tabpanel" aria-labelledby="nav-set-tab">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 mb-3">
|
||||
{% for d in set %}
|
||||
{% include "cards/roster.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="nav-consists" role="tabpanel" aria-labelledby="nav-cosists-tab">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 mb-3">
|
||||
{% for d in consists %}
|
||||
|
@@ -4,6 +4,7 @@ from portal.views import (
|
||||
GetData,
|
||||
GetRoster,
|
||||
GetObjectsFiltered,
|
||||
GetManufacturerItem,
|
||||
GetFlatpage,
|
||||
GetRollingStock,
|
||||
GetConsist,
|
||||
@@ -20,7 +21,11 @@ from portal.views import (
|
||||
urlpatterns = [
|
||||
path("", GetData.as_view(template="home.html"), name="index"),
|
||||
path("roster", GetRoster.as_view(), name="roster"),
|
||||
path("roster/<int:page>", GetRoster.as_view(), name="roster_pagination"),
|
||||
path(
|
||||
"roster/page/<int:page>",
|
||||
GetRoster.as_view(),
|
||||
name="roster_pagination"
|
||||
),
|
||||
path(
|
||||
"page/<str:flatpage>",
|
||||
GetFlatpage.as_view(),
|
||||
@@ -32,13 +37,13 @@ urlpatterns = [
|
||||
name="consists"
|
||||
),
|
||||
path(
|
||||
"consists/<int:page>",
|
||||
"consists/page/<int:page>",
|
||||
Consists.as_view(template="consists.html"),
|
||||
name="consists_pagination"
|
||||
),
|
||||
path("consist/<uuid:uuid>", GetConsist.as_view(), name="consist"),
|
||||
path(
|
||||
"consist/<uuid:uuid>/<int:page>",
|
||||
"consist/<uuid:uuid>/page/<int:page>",
|
||||
GetConsist.as_view(),
|
||||
name="consist_pagination",
|
||||
),
|
||||
@@ -48,7 +53,7 @@ urlpatterns = [
|
||||
name="companies"
|
||||
),
|
||||
path(
|
||||
"companies/<int:page>",
|
||||
"companies/page/<int:page>",
|
||||
Companies.as_view(template="companies.html"),
|
||||
name="companies_pagination",
|
||||
),
|
||||
@@ -58,7 +63,7 @@ urlpatterns = [
|
||||
name="manufacturers"
|
||||
),
|
||||
path(
|
||||
"manufacturers/<str:category>/<int:page>",
|
||||
"manufacturers/<str:category>/page/<int:page>",
|
||||
Manufacturers.as_view(template="manufacturers.html"),
|
||||
name="manufacturers_pagination",
|
||||
),
|
||||
@@ -68,7 +73,7 @@ urlpatterns = [
|
||||
name="scales"
|
||||
),
|
||||
path(
|
||||
"scales/<int:page>",
|
||||
"scales/page/<int:page>",
|
||||
Scales.as_view(template="scales.html"),
|
||||
name="scales_pagination"
|
||||
),
|
||||
@@ -78,7 +83,7 @@ urlpatterns = [
|
||||
name="types"
|
||||
),
|
||||
path(
|
||||
"types/<int:page>",
|
||||
"types/page/<int:page>",
|
||||
Types.as_view(template="types.html"),
|
||||
name="types_pagination"
|
||||
),
|
||||
@@ -88,7 +93,7 @@ urlpatterns = [
|
||||
name="books"
|
||||
),
|
||||
path(
|
||||
"bookshelf/books/<int:page>",
|
||||
"bookshelf/books/page/<int:page>",
|
||||
Books.as_view(template="bookshelf/books.html"),
|
||||
name="books_pagination"
|
||||
),
|
||||
@@ -99,17 +104,37 @@ urlpatterns = [
|
||||
name="search",
|
||||
),
|
||||
path(
|
||||
"search/<str:search>/<int:page>",
|
||||
"search/<str:search>/page/<int:page>",
|
||||
SearchObjects.as_view(),
|
||||
name="search_pagination",
|
||||
),
|
||||
path(
|
||||
"manufacturer/<str:manufacturer>",
|
||||
GetManufacturerItem.as_view(),
|
||||
name="manufacturer",
|
||||
),
|
||||
path(
|
||||
"manufacturer/<str:manufacturer>/page/<int:page>",
|
||||
GetManufacturerItem.as_view(),
|
||||
name="manufacturer_pagination",
|
||||
),
|
||||
path(
|
||||
"manufacturer/<str:manufacturer>/<str:search>",
|
||||
GetManufacturerItem.as_view(),
|
||||
name="manufacturer",
|
||||
),
|
||||
path(
|
||||
"manufacturer/<str:manufacturer>/<str:search>/page/<int:page>",
|
||||
GetManufacturerItem.as_view(),
|
||||
name="manufacturer_pagination",
|
||||
),
|
||||
path(
|
||||
"<str:_filter>/<str:search>",
|
||||
GetObjectsFiltered.as_view(),
|
||||
name="filtered",
|
||||
),
|
||||
path(
|
||||
"<str:_filter>/<str:search>/<int:page>",
|
||||
"<str:_filter>/<str:search>/page/<int:page>",
|
||||
GetObjectsFiltered.as_view(),
|
||||
name="filtered_pagination",
|
||||
),
|
||||
|
@@ -7,7 +7,7 @@ from django.views import View
|
||||
from django.http import Http404, HttpResponseBadRequest
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.shortcuts import render, get_object_or_404, get_list_or_404
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
@@ -215,6 +215,58 @@ class SearchObjects(View):
|
||||
return self.get(request, search, page)
|
||||
|
||||
|
||||
class GetManufacturerItem(View):
|
||||
def get(self, request, manufacturer, search="all", page=1):
|
||||
site_conf = get_site_conf()
|
||||
|
||||
if search != "all":
|
||||
rolling_stock = get_list_or_404(
|
||||
RollingStock.objects.order_by(*order_by_fields()),
|
||||
Q(
|
||||
Q(manufacturer__name__iexact=manufacturer)
|
||||
& Q(item_number__exact=search)
|
||||
)
|
||||
)
|
||||
title = "{0}: {1}".format(
|
||||
rolling_stock[0].manufacturer,
|
||||
search
|
||||
)
|
||||
else:
|
||||
rolling_stock = get_list_or_404(
|
||||
RollingStock.objects.order_by(*order_by_fields()),
|
||||
Q(rolling_class__manufacturer__slug__iexact=manufacturer)
|
||||
| Q(manufacturer__slug__iexact=manufacturer)
|
||||
)
|
||||
title = "Manufacturer: {0}".format(
|
||||
get_object_or_404(Manufacturer, slug__iexact=manufacturer)
|
||||
)
|
||||
|
||||
data = []
|
||||
for item in rolling_stock:
|
||||
data.append({
|
||||
"type": "rolling_stock",
|
||||
"item": item
|
||||
})
|
||||
|
||||
paginator = Paginator(data, site_conf.items_per_page)
|
||||
data = paginator.get_page(page)
|
||||
page_range = paginator.get_elided_page_range(
|
||||
data.number, on_each_side=2, on_ends=1
|
||||
)
|
||||
return render(
|
||||
request,
|
||||
"manufacturer.html",
|
||||
{
|
||||
"title": title,
|
||||
"manufacturer": manufacturer,
|
||||
"search": search,
|
||||
"data": data,
|
||||
"matches": paginator.count,
|
||||
"page_range": page_range,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class GetObjectsFiltered(View):
|
||||
def run_filter(self, request, search, _filter, page=1):
|
||||
site_conf = get_site_conf()
|
||||
@@ -226,12 +278,6 @@ class GetObjectsFiltered(View):
|
||||
title = get_object_or_404(Company, slug__iexact=search)
|
||||
query = Q(rolling_class__company__slug__iexact=search)
|
||||
query_2nd = Q(company__slug__iexact=search)
|
||||
elif _filter == "manufacturer":
|
||||
title = get_object_or_404(Manufacturer, slug__iexact=search)
|
||||
query = Q(
|
||||
Q(rolling_class__manufacturer__slug__iexact=search)
|
||||
| Q(manufacturer__slug__iexact=search)
|
||||
)
|
||||
elif _filter == "scale":
|
||||
title = get_object_or_404(Scale, slug__iexact=search)
|
||||
query = Q(scale__slug__iexact=search)
|
||||
@@ -347,6 +393,16 @@ class GetRollingStock(View):
|
||||
consist_item__rolling_stock=rolling_stock
|
||||
)] # A dict with "item" is required by the consists card
|
||||
|
||||
set = [{
|
||||
"type": "set",
|
||||
"item": s
|
||||
} for s in RollingStock.objects.filter(
|
||||
Q(
|
||||
Q(item_number__exact=rolling_stock.item_number)
|
||||
& Q(set=True)
|
||||
)
|
||||
)]
|
||||
|
||||
return render(
|
||||
request,
|
||||
"rollingstock.html",
|
||||
@@ -358,6 +414,7 @@ class GetRollingStock(View):
|
||||
"decoder_documents": decoder_documents,
|
||||
"documents": documents,
|
||||
"journal": journal,
|
||||
"set": set,
|
||||
"consists": consists,
|
||||
},
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from ram.utils import git_suffix
|
||||
|
||||
__version__ = "0.11.1"
|
||||
__version__ = "0.12.0"
|
||||
__version__ += git_suffix(__file__)
|
||||
|
@@ -141,6 +141,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||
"scale",
|
||||
"manufacturer",
|
||||
"item_number",
|
||||
"set",
|
||||
"era",
|
||||
"description",
|
||||
"production_year",
|
||||
|
@@ -0,0 +1,40 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-20 12:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("roster", "0024_rollingstock_description"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="rollingstock",
|
||||
name="set",
|
||||
field=models.BooleanField(default=False, help_text="Part of a set"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rollingstock",
|
||||
name="era",
|
||||
field=models.CharField(
|
||||
blank=True, help_text="Era or epoch of the model", max_length=32
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rollingstock",
|
||||
name="item_number",
|
||||
field=models.CharField(
|
||||
blank=True, help_text="Catalog item number or code", max_length=32
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="rollingstockjournal",
|
||||
name="private",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Journal log will be visible only to logged users",
|
||||
),
|
||||
),
|
||||
]
|
@@ -74,7 +74,15 @@ class RollingStock(models.Model):
|
||||
limit_choices_to={"category": "model"},
|
||||
)
|
||||
scale = models.ForeignKey(Scale, on_delete=models.CASCADE)
|
||||
item_number = models.CharField(max_length=32, blank=True)
|
||||
item_number = models.CharField(
|
||||
max_length=32,
|
||||
blank=True,
|
||||
help_text="Catalog item number or code",
|
||||
)
|
||||
set = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Part of a set",
|
||||
)
|
||||
decoder_interface = models.PositiveSmallIntegerField(
|
||||
choices=settings.DECODER_INTERFACES, null=True, blank=True
|
||||
)
|
||||
@@ -82,7 +90,11 @@ class RollingStock(models.Model):
|
||||
Decoder, on_delete=models.CASCADE, null=True, blank=True
|
||||
)
|
||||
address = models.SmallIntegerField(default=None, null=True, blank=True)
|
||||
era = models.CharField(max_length=32, blank=True)
|
||||
era = models.CharField(
|
||||
max_length=32,
|
||||
blank=True,
|
||||
help_text="Era or epoch of the model",
|
||||
)
|
||||
production_year = models.SmallIntegerField(null=True, blank=True)
|
||||
purchase_date = models.DateField(null=True, blank=True)
|
||||
description = tinymce.HTMLField(blank=True)
|
||||
@@ -177,7 +189,10 @@ class RollingStockJournal(models.Model):
|
||||
)
|
||||
date = models.DateField()
|
||||
log = tinymce.HTMLField()
|
||||
private = models.BooleanField(default=False)
|
||||
private = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Journal log will be visible only to logged users",
|
||||
)
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
updated_time = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
Reference in New Issue
Block a user