Merge pull request #24 from daniviga/bookshelf

Implement a bookshelf
This commit is contained in:
2023-10-03 22:18:11 +02:00
committed by GitHub
41 changed files with 947 additions and 94 deletions

View File

52
ram/bookshelf/admin.py Normal file
View File

@@ -0,0 +1,52 @@
from django.contrib import admin
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
from bookshelf.models import BookProperty, BookImage, Book, Author, Publisher
class BookImageInline(SortableInlineAdminMixin, admin.TabularInline):
model = BookImage
min_num = 0
extra = 0
readonly_fields = ("image_thumbnail",)
classes = ["collapse"]
class BookPropertyInline(admin.TabularInline):
model = BookProperty
min_num = 0
extra = 0
@admin.register(Book)
class BookAdmin(SortableAdminBase, admin.ModelAdmin):
inlines = (BookImageInline, BookPropertyInline,)
list_display = (
"title",
"get_authors",
"get_publisher",
"publication_year",
"number_of_pages"
)
search_fields = ("title", "publisher__name", "authors__last_name")
list_filter = ("publisher__name", "authors")
@admin.display(description="Publisher")
def get_publisher(self, obj):
return obj.publisher.name
@admin.display(description="Authors")
def get_authors(self, obj):
return ", ".join(a.short_name() for a in obj.authors.all())
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
search_fields = ("first_name", "last_name",)
list_filter = ("last_name",)
@admin.register(Publisher)
class PublisherAdmin(admin.ModelAdmin):
list_display = ("name", "country")
search_fields = ("name",)

6
ram/bookshelf/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class BookshelfConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "bookshelf"

View File

@@ -0,0 +1,119 @@
# Generated by Django 4.2.5 on 2023-10-01 20:16
import ckeditor_uploader.fields
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
("metadata", "0012_alter_decoder_manufacturer_decoderdocument"),
]
operations = [
migrations.CreateModel(
name="Author",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("first_name", models.CharField(max_length=100)),
("last_name", models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name="Book",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("title", models.CharField(max_length=200)),
("ISBN", models.CharField(max_length=13, unique=True)),
("publication_year", models.SmallIntegerField(blank=True, null=True)),
("purchase_date", models.DateField(blank=True, null=True)),
("notes", ckeditor_uploader.fields.RichTextUploadingField(blank=True)),
("creation_time", models.DateTimeField(auto_now_add=True)),
("updated_time", models.DateTimeField(auto_now=True)),
("authors", models.ManyToManyField(to="bookshelf.author")),
],
),
migrations.CreateModel(
name="Publisher",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=200)),
("website", models.URLField()),
],
),
migrations.CreateModel(
name="BookProperty",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("value", models.CharField(max_length=256)),
(
"book",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="property",
to="bookshelf.book",
),
),
(
"property",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="metadata.property",
),
),
],
options={
"verbose_name_plural": "Properties",
},
),
migrations.AddField(
model_name="book",
name="publisher",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="bookshelf.publisher"
),
),
migrations.AddField(
model_name="book",
name="tags",
field=models.ManyToManyField(
blank=True, related_name="bookshelf", to="metadata.tag"
),
),
]

View File

@@ -0,0 +1,142 @@
# Generated by Django 4.2.5 on 2023-10-01 21:33
from django.db import migrations, models
import django_countries.fields
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="book",
name="language",
field=models.CharField(
choices=[
("af", "Afrikaans"),
("ar", "Arabic"),
("ar-dz", "Algerian Arabic"),
("ast", "Asturian"),
("az", "Azerbaijani"),
("bg", "Bulgarian"),
("be", "Belarusian"),
("bn", "Bengali"),
("br", "Breton"),
("bs", "Bosnian"),
("ca", "Catalan"),
("ckb", "Central Kurdish (Sorani)"),
("cs", "Czech"),
("cy", "Welsh"),
("da", "Danish"),
("de", "German"),
("dsb", "Lower Sorbian"),
("el", "Greek"),
("en", "English"),
("en-au", "Australian English"),
("en-gb", "British English"),
("eo", "Esperanto"),
("es", "Spanish"),
("es-ar", "Argentinian Spanish"),
("es-co", "Colombian Spanish"),
("es-mx", "Mexican Spanish"),
("es-ni", "Nicaraguan Spanish"),
("es-ve", "Venezuelan Spanish"),
("et", "Estonian"),
("eu", "Basque"),
("fa", "Persian"),
("fi", "Finnish"),
("fr", "French"),
("fy", "Frisian"),
("ga", "Irish"),
("gd", "Scottish Gaelic"),
("gl", "Galician"),
("he", "Hebrew"),
("hi", "Hindi"),
("hr", "Croatian"),
("hsb", "Upper Sorbian"),
("hu", "Hungarian"),
("hy", "Armenian"),
("ia", "Interlingua"),
("id", "Indonesian"),
("ig", "Igbo"),
("io", "Ido"),
("is", "Icelandic"),
("it", "Italian"),
("ja", "Japanese"),
("ka", "Georgian"),
("kab", "Kabyle"),
("kk", "Kazakh"),
("km", "Khmer"),
("kn", "Kannada"),
("ko", "Korean"),
("ky", "Kyrgyz"),
("lb", "Luxembourgish"),
("lt", "Lithuanian"),
("lv", "Latvian"),
("mk", "Macedonian"),
("ml", "Malayalam"),
("mn", "Mongolian"),
("mr", "Marathi"),
("ms", "Malay"),
("my", "Burmese"),
("nb", "Norwegian Bokmål"),
("ne", "Nepali"),
("nl", "Dutch"),
("nn", "Norwegian Nynorsk"),
("os", "Ossetic"),
("pa", "Punjabi"),
("pl", "Polish"),
("pt", "Portuguese"),
("pt-br", "Brazilian Portuguese"),
("ro", "Romanian"),
("ru", "Russian"),
("sk", "Slovak"),
("sl", "Slovenian"),
("sq", "Albanian"),
("sr", "Serbian"),
("sr-latn", "Serbian Latin"),
("sv", "Swedish"),
("sw", "Swahili"),
("ta", "Tamil"),
("te", "Telugu"),
("tg", "Tajik"),
("th", "Thai"),
("tk", "Turkmen"),
("tr", "Turkish"),
("tt", "Tatar"),
("udm", "Udmurt"),
("uk", "Ukrainian"),
("ur", "Urdu"),
("uz", "Uzbek"),
("vi", "Vietnamese"),
("zh-hans", "Simplified Chinese"),
("zh-hant", "Traditional Chinese"),
],
default="en",
max_length=7,
),
),
migrations.AddField(
model_name="book",
name="numbers_of_pages",
field=models.SmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name="publisher",
name="country",
field=django_countries.fields.CountryField(blank=True, max_length=2),
),
migrations.AlterField(
model_name="book",
name="ISBN",
field=models.CharField(blank=True, max_length=13),
),
migrations.AlterField(
model_name="publisher",
name="website",
field=models.URLField(blank=True),
),
]

View File

@@ -0,0 +1,51 @@
# Generated by Django 4.2.5 on 2023-10-02 10:36
from django.db import migrations, models
import django.db.models.deletion
import ram.utils
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0002_book_language_book_numbers_of_pages_and_more"),
]
operations = [
migrations.CreateModel(
name="BookImage",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("order", models.PositiveIntegerField(default=0)),
(
"image",
models.ImageField(
blank=True,
null=True,
storage=ram.utils.DeduplicatedStorage,
upload_to="images/books/",
),
),
(
"book",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="image",
to="bookshelf.book",
),
),
],
options={
"ordering": ["order"],
"abstract": False,
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.5 on 2023-10-02 20:38
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0003_bookimage"),
]
operations = [
migrations.RenameField(
model_name="book",
old_name="numbers_of_pages",
new_name="number_of_pages",
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.2.5 on 2023-10-03 19:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0004_rename_numbers_of_pages_book_number_of_pages"),
]
operations = [
migrations.AlterModelOptions(
name="book",
options={
"ordering": ["authors__last_name", "title"],
"verbose_name_plural": "Rolling stock",
},
),
]

View File

88
ram/bookshelf/models.py Normal file
View File

@@ -0,0 +1,88 @@
from uuid import uuid4
from django.db import models
from django.conf import settings
from django.urls import reverse
from django_countries.fields import CountryField
from ckeditor_uploader.fields import RichTextUploadingField
from metadata.models import Tag
from ram.utils import DeduplicatedStorage
from ram.models import Image, PropertyInstance
class Publisher(models.Model):
name = models.CharField(max_length=200)
country = CountryField(blank=True)
website = models.URLField(blank=True)
def __str__(self):
return self.name
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
def __str__(self):
return f"{self.last_name}, {self.first_name}"
def short_name(self):
return f"{self.last_name} {self.first_name[0]}."
class Book(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
title = models.CharField(max_length=200)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
ISBN = models.CharField(max_length=13, blank=True)
language = models.CharField(
max_length=7,
choices=settings.LANGUAGES,
default='en'
)
number_of_pages = models.SmallIntegerField(null=True, blank=True)
publication_year = models.SmallIntegerField(null=True, blank=True)
purchase_date = models.DateField(null=True, blank=True)
tags = models.ManyToManyField(
Tag, related_name="bookshelf", blank=True
)
notes = RichTextUploadingField(blank=True)
creation_time = models.DateTimeField(auto_now_add=True)
updated_time = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["authors__last_name", "title"]
verbose_name_plural = "Rolling stock"
def __str__(self):
return self.title
def publisher_name(self):
return self.publisher.name
def get_absolute_url(self):
return reverse("book", kwargs={"uuid": self.uuid})
class BookImage(Image):
book = models.ForeignKey(
Book, on_delete=models.CASCADE, related_name="image"
)
image = models.ImageField(
upload_to="images/books/", # FIXME, find a better way to replace this
storage=DeduplicatedStorage,
null=True,
blank=True
)
class BookProperty(PropertyInstance):
book = models.ForeignKey(
Book,
on_delete=models.CASCADE,
null=False,
blank=False,
related_name="property",
)

View File

@@ -0,0 +1,26 @@
from rest_framework import serializers
from bookshelf.models import Book, Author, Publisher
from metadata.serializers import TagSerializer
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = "__all__"
class PublisherSerializer(serializers.ModelSerializer):
class Meta:
model = Publisher
fields = "__all__"
class BookSerializer(serializers.ModelSerializer):
authors = AuthorSerializer(many=True)
publisher = PublisherSerializer()
tags = TagSerializer(many=True)
class Meta:
model = Book
fields = "__all__"
read_only_fields = ("creation_time", "updated_time")

3
ram/bookshelf/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

7
ram/bookshelf/urls.py Normal file
View File

@@ -0,0 +1,7 @@
from django.urls import path
from bookshelf.views import BookList, BookGet
urlpatterns = [
path("book/list", BookList.as_view()),
path("book/get/<str:uuid>", BookGet.as_view()),
]

18
ram/bookshelf/views.py Normal file
View File

@@ -0,0 +1,18 @@
from rest_framework.generics import ListAPIView, RetrieveAPIView
from rest_framework.schemas.openapi import AutoSchema
from bookshelf.models import Book
from bookshelf.serializers import BookSerializer
class BookList(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookGet(RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_field = "uuid"
schema = AutoSchema(operation_id_base="retrieveBookByUUID")

View File

@@ -171,7 +171,8 @@
<li><a class="dropdown-item" href="{% url 'manufacturers' category='real' %}">Real</a></li> <li><a class="dropdown-item" href="{% url 'manufacturers' category='real' %}">Real</a></li>
</ul> </ul>
</li> </li>
{% show_menu %} {% show_flatpages_menu %}
{% show_bookshelf_menu %}
</ul> </ul>
{% include 'includes/search.html' %} {% include 'includes/search.html' %}
</div> </div>
@@ -191,7 +192,7 @@
<div class="container"> <div class="container">
{% block carousel %} {% block carousel %}
{% endblock %} {% endblock %}
<a id="rolling-stock"></a> <a id="main-content"></a>
{% block cards_layout %} {% block cards_layout %}
{% endblock %} {% endblock %}
</div> </div>

View File

@@ -0,0 +1,125 @@
{% extends 'base.html' %}
{% block header %}
{% if book.tags.all %}
<p><small>Tags:</small>
{% for t in book.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</a>{# new line is required #}
{% endfor %}
</p>
{% endif %}
<small class="text-muted">Updated {{ book.updated_time | date:"M d, Y H:i" }}</small>
{% endblock %}
{% block carousel %}
<div class="row">
<div id="carouselControls" class="carousel carousel-dark slide" data-bs-ride="carousel" data-bs-interval="10000">
<div class="carousel-inner">
{% for t in book.image.all %}
{% if forloop.first %}
<div class="carousel-item active">
{% else %}
<div class="carousel-item">
{% endif %}
<img src="{{ t.image.url }}" class="d-block w-100 rounded img-thumbnail" alt="...">
</div>
{% endfor %}
</div>
{% if book.image.count > 1 %}
<button class="carousel-control-prev" type="button" data-bs-target="#carouselControls" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselControls" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
{% endif %}
</div>
</div>
{% endblock %}
{% block cards %}
{% endblock %}
{% block extra_content %}
<section class="py-4 text-start container">
<div class="row">
<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>
{% if book.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 %}
</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">Book</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tr>
<th class="w-33" scope="row">Title</th>
<td>{{ book.title }}</td>
</tr>
<tr>
<th scope="row">Authors</th>
<td>
<ul class="mb-0 list-unstyled">{% for a in book.authors.all %}<li>{{ a }}</li>{% endfor %}</ul>
</td>
</tr>
<tr>
<th scope="row">Publisher</th>
<td>{{ book.publisher }}</td>
</tr>
<tr>
<th scope="row">ISBN</th>
<td>{{ book.ISBN|default:"-" }}</td>
</tr>
<tr>
<th scope="row">Language</th>
<td>{{ book.get_language_display }}</td>
</tr>
<tr>
<th scope="row">Number of pages</th>
<td>{{ book.number_of_pages|default:"-" }}</td>
</tr>
<tr>
<th scope="row">Publication year</th>
<td>{{ book.publication_year|default:"-" }}</td>
</tr>
<tr>
<th scope="row">Purchase date</th>
<td>{{ book.purchase_date|default:"-" }}</td>
</tr>
</tbody>
</table>
{% if book_properties %}
<table class="table table-striped">
<thead>
<tr>
<th colspan="2" scope="row">Properties</th>
</tr>
</thead>
<tbody class="table-group-divider">
{% for p in book_properties %}
<tr>
<th class="w-33" scope="row">{{ p.property }}</th>
<td>{{ p.value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
<div class="tab-pane fade" id="nav-notes" role="tabpanel" aria-labelledby="nav-notes-tab">
{{ book.notes | safe }}
</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:bookshelf_book_change' book.pk %}">Edit</a>{% endif %}
</div>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,104 @@
{% extends "cards.html" %}
{% block cards %}
{% for d in data %}
<div class="col">
<div class="card shadow-sm">
{% if d.image.all %}
<a href="{{ d.get_absolute_url }}">
{% for r in d.image.all %}
{% if forloop.first %}<img src="{{ r.image.url }}" alt="Card image cap">{% endif %}
{% endfor %}
</a>
{% endif %}
<div class="card-body">
<p class="card-text" style="position: relative;">
<strong>{{ d }}</strong>
<a class="stretched-link" href="{{ d.get_absolute_url }}"></a>
</p>
{% if d.tags.all %}
<p class="card-text"><small>Tags:</small>
{% for t in d.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</a>{# new line is required #}
{% endfor %}
</p>
{% endif %}
<table class="table table-striped">
<thead>
<tr>
<th colspan="2" scope="row">Book</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tr>
<th class="w-33" scope="row">Authors</th>
<td>
<ul class="mb-0 list-unstyled">{% for a in d.authors.all %}<li>{{ a }}</li>{% endfor %}</ul>
</td>
</tr>
<tr>
<th class="w-33" scope="row">Publisher</th>
<td>{{ d.publisher }}</td>
</tr>
<tr>
<th scope="row">Language</th>
<td>{{ d.get_language_display }}</td>
</tr>
<tr>
<th scope="row">Pages</th>
<td>{{ d.number_of_pages|default:"-" }}</td>
</tr>
<tr>
<th scope="row">Year</th>
<td>{{ d.publication_year|default:"-" }}</td>
</tr>
</tbody>
</table>
<div class="d-grid gap-2 mb-1 d-md-block">
<a class="btn btn-sm btn-outline-primary" href="{{ d.get_absolute_url }}">Show all data</a>
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:bookshelf_book_change' d.pk %}">Edit</a>{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% endblock %}
{% 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 'books_pagination' 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 'books_pagination' page=i %}#main-content">{{ i }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% if data.has_next %}
<li class="page-item">
<a class="page-link" href="{% url 'books_pagination' 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 %}

View File

@@ -0,0 +1,10 @@
{% if bookshelf_menu %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="bookshelfDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Bookshelf
</a>
<ul class="dropdown-menu" aria-labelledby="bookshelfDropdownMenuLink">
<li><a class="nav-link" href="{% url 'books' %}">Books</a></li>
</ul>
</li>
{% endif %}

View File

@@ -68,8 +68,8 @@
<td><a href="{% url 'filtered' _filter="scale" search=d.scale.slug %}"><abbr title="{{ d.scale.ratio }} - {{ d.scale.tracks }}">{{ d.scale }}</abbr></a></td> <td><a href="{% url 'filtered' _filter="scale" search=d.scale.slug %}"><abbr title="{{ d.scale.ratio }} - {{ d.scale.tracks }}">{{ d.scale }}</abbr></a></td>
</tr> </tr>
<tr> <tr>
<th scope="row">SKU</th> <th scope="row">Item number</th>
<td>{{ d.sku }}</td> <td>{{ d.item_number }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -56,7 +56,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'companies_pagination' page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'companies_pagination' page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -72,13 +72,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 'companies_pagination' page=i %}#rolling-stock">{{ i }}</a></li> <li class="page-item"><a class="page-link" href="{% url 'companies_pagination' page=i %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'companies_pagination' page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'companies_pagination' page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -29,7 +29,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -45,13 +45,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 'consist_pagination' uuid=consist.uuid page=i %}#rolling-stock">{{ i }}</a></li> <li class="page-item"><a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=i %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -69,7 +69,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'consists_pagination' page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'consists_pagination' page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -85,13 +85,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'consists_pagination' page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'consists_pagination' page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -6,7 +6,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -22,13 +22,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 'filtered_pagination' _filter=filter search=search 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 %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -1,10 +1,10 @@
{% if menu %} {% if flatpages_menu %}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="flatpageDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Articles Articles
</a> </a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> <ul class="dropdown-menu" aria-labelledby="flatpageDropdownMenuLink">
{% for m in menu %} {% for m in flatpages_menu %}
<li><a class="dropdown-item" href="{{ m.get_absolute_url }}">{{ m.name }}</a></li> <li><a class="dropdown-item" href="{{ m.get_absolute_url }}">{{ m.name }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@@ -49,7 +49,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -65,13 +65,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 'manufacturers_pagination' category=c page=i %}#rolling-stock">{{ i }}</a></li> <li class="page-item"><a class="page-link" href="{% url 'manufacturers_pagination' category=c page=i %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -105,8 +105,8 @@
<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> <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">Item number</th>
<td>{{ rolling_stock.sku }}</td> <td>{{ rolling_stock.item_number }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -155,8 +155,8 @@
<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> <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">Item number</th>
<td>{{ rolling_stock.sku }}</td> <td>{{ rolling_stock.item_number }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">Era</th> <th scope="row">Era</th>
@@ -164,11 +164,11 @@
</tr> </tr>
<tr> <tr>
<th scope="row">Production year</th> <th scope="row">Production year</th>
<td>{{ rolling_stock.production_year | default_if_none:"" }}</td> <td>{{ rolling_stock.production_year|default:"-" }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">Purchase date</th> <th scope="row">Purchase date</th>
<td>{{ rolling_stock.purchase_date | default_if_none:"" }}</td> <td>{{ rolling_stock.purchase_date|default:"-" }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -264,7 +264,7 @@
</tr> </tr>
<tr> <tr>
<th scope="row">Manufacturer</th> <th scope="row">Manufacturer</th>
<td>{{ rolling_stock.decoder.manufacturer|default_if_none:"" }}</td> <td>{{ rolling_stock.decoder.manufacturer|default:"-" }}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">Version</th> <th scope="row">Version</th>

View File

@@ -6,7 +6,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'roster_pagination' page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'roster_pagination' page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -22,13 +22,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 'roster_pagination' page=i %}#rolling-stock">{{ i }}</a></li> <li class="page-item"><a class="page-link" href="{% url 'roster_pagination' page=i %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'roster_pagination' page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'roster_pagination' page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -46,7 +46,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'scales_pagination' page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'scales_pagination' page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -62,13 +62,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 'scales_pagination' page=i %}#rolling-stock">{{ i }}</a></li> <li class="page-item"><a class="page-link" href="{% url 'scales_pagination' page=i %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'scales_pagination' page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'scales_pagination' page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -6,7 +6,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -22,13 +22,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 'search_pagination' search=encoded_search page=i %}#rolling-stock">{{ i }}</a></li> <li class="page-item"><a class="page-link" href="{% url 'search_pagination' search=encoded_search page=i %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -38,7 +38,7 @@
<ul class="pagination justify-content-center mt-4 mb-0"> <ul class="pagination justify-content-center mt-4 mb-0">
{% if data.has_previous %} {% if data.has_previous %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'scales_pagination' page=data.previous_page_number %}#rolling-stock" tabindex="-1">Previous</a> <a class="page-link" href="{% url 'scales_pagination' page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">
@@ -54,13 +54,13 @@
{% if i == data.paginator.ELLIPSIS %} {% if i == data.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 'scales_pagination' page=i %}#rolling-stock">{{ i }}</a></li> <li class="page-item"><a class="page-link" href="{% url 'scales_pagination' page=i %}#main-content">{{ i }}</a></li>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if data.has_next %} {% if data.has_next %}
<li class="page-item"> <li class="page-item">
<a class="page-link" href="{% url 'scales_pagination' page=data.next_page_number %}#rolling-stock" tabindex="-1">Next</a> <a class="page-link" href="{% url 'scales_pagination' page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
</li> </li>
{% else %} {% else %}
<li class="page-item disabled"> <li class="page-item disabled">

View File

@@ -1,10 +1,16 @@
from django import template from django import template
from portal.views import Flatpage from portal.models import Flatpage
from bookshelf.models import Book
register = template.Library() register = template.Library()
@register.inclusion_tag('flatpage_menu.html') @register.inclusion_tag('bookshelf/bookshelf_menu.html')
def show_menu(): def show_bookshelf_menu():
return {"bookshelf_menu": Book.objects.exists()}
@register.inclusion_tag('flatpages/flatpages_menu.html')
def show_flatpages_menu():
menu = Flatpage.objects.filter(published=True).order_by("name") menu = Flatpage.objects.filter(published=True).order_by("name")
return {"menu": menu} return {"flatpages_menu": menu}

View File

@@ -12,6 +12,8 @@ from portal.views import (
Manufacturers, Manufacturers,
Scales, Scales,
Types, Types,
Books,
GetBook,
SearchRoster, SearchRoster,
) )
@@ -54,6 +56,9 @@ urlpatterns = [
path("scales/<int:page>", Types.as_view(), name="scales_pagination"), path("scales/<int:page>", Types.as_view(), name="scales_pagination"),
path("types", Types.as_view(), name="types"), path("types", Types.as_view(), name="types"),
path("types/<int:page>", Types.as_view(), name="types_pagination"), path("types/<int:page>", Types.as_view(), name="types_pagination"),
path("bookshelf/books", Books.as_view(), name="books"),
path("bookshelf/books/<int:page>", Books.as_view(), name="books_pagination"),
path("bookshelf/book/<uuid:uuid>", GetBook.as_view(), name="book"),
path( path(
"search", "search",
SearchRoster.as_view(http_method_names=["post"]), SearchRoster.as_view(http_method_names=["post"]),

View File

@@ -14,6 +14,7 @@ from portal.utils import get_site_conf
from portal.models import Flatpage from portal.models import Flatpage
from roster.models import RollingStock from roster.models import RollingStock
from consist.models import Consist from consist.models import Consist
from bookshelf.models import Book
from metadata.models import Company, Manufacturer, Scale, RollingStockType, Tag from metadata.models import Company, Manufacturer, Scale, RollingStockType, Tag
@@ -80,7 +81,7 @@ class SearchRoster(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(item_number=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)
@@ -340,6 +341,36 @@ class Types(GetData):
self.data = RollingStockType.objects.all() self.data = RollingStockType.objects.all()
class Books(GetData):
def __init__(self):
self.title = "Books"
self.template = "bookshelf/books.html"
self.data = Book.objects.all()
class GetBook(View):
def get(self, request, uuid):
try:
book = Book.objects.get(uuid=uuid)
except ObjectDoesNotExist:
raise Http404
book_properties = (
book.property.all()
if request.user.is_authenticated
else book.property.filter(property__private=False)
)
return render(
request,
"bookshelf/book.html",
{
"title": book,
"book_properties": book_properties,
"book": book,
},
)
class GetFlatpage(View): class GetFlatpage(View):
def get(self, request, flatpage): def get(self, request, flatpage):
try: try:
@@ -351,6 +382,6 @@ class GetFlatpage(View):
return render( return render(
request, request,
"flatpage.html", "flatpages/flatpage.html",
{"title": flatpage.name, "flatpage": flatpage}, {"title": flatpage.name, "flatpage": flatpage},
) )

View File

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

View File

@@ -3,7 +3,7 @@ import os
from django.db import models from django.db import models
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from ram.utils import DeduplicatedStorage from ram.utils import DeduplicatedStorage, get_image_preview
class Document(models.Model): class Document(models.Model):
@@ -28,3 +28,37 @@ class Document(models.Model):
return mark_safe( return mark_safe(
'<a href="{0}" target="_blank">Link</a>'.format(self.file.url) '<a href="{0}" target="_blank">Link</a>'.format(self.file.url)
) )
class Image(models.Model):
order = models.PositiveIntegerField(default=0, blank=False, null=False)
image = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True
)
def image_thumbnail(self):
return get_image_preview(self.image.url)
image_thumbnail.short_description = "Preview"
def __str__(self):
return "{0}".format(os.path.basename(self.image.name))
class Meta:
abstract = True
ordering = ["order"]
class PropertyInstance(models.Model):
property = models.ForeignKey(
"metadata.Property", # To avoid circular dependencies
on_delete=models.CASCADE
)
value = models.CharField(max_length=256)
def __str__(self):
return self.property.name
class Meta:
abstract = True
verbose_name_plural = "Properties"

View File

@@ -55,6 +55,7 @@ INSTALLED_APPS = [
"metadata", "metadata",
"roster", "roster",
"consist", "consist",
"bookshelf",
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@@ -28,6 +28,7 @@ urlpatterns = [
path("api/v1/consist/", include("consist.urls")), path("api/v1/consist/", include("consist.urls")),
path("api/v1/roster/", include("roster.urls")), path("api/v1/roster/", include("roster.urls")),
path("api/v1/dcc/", include("driver.urls")), path("api/v1/dcc/", include("driver.urls")),
path("api/v1/bookshelf/", include("bookshelf.urls")),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG: if settings.DEBUG:

View File

@@ -68,7 +68,7 @@ class RollingStockDocumentAdmin(admin.ModelAdmin):
) )
search_fields = ( search_fields = (
"rolling_stock__rolling_class__identifier", "rolling_stock__rolling_class__identifier",
"rolling_stock__sku", "rolling_stock__item_number",
"description", "description",
"file", "file",
) )
@@ -89,7 +89,7 @@ class RollingJournalDocumentAdmin(admin.ModelAdmin):
search_fields = ( search_fields = (
"rolling_stock__rolling_class__identifier", "rolling_stock__rolling_class__identifier",
"rolling_stock__road_number", "rolling_stock__road_number",
"rolling_stock__sku", "rolling_stock__item_number",
"log", "log",
) )
@@ -108,7 +108,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
"address", "address",
"manufacturer", "manufacturer",
"scale", "scale",
"sku", "item_number",
"company", "company",
"country", "country",
) )
@@ -125,7 +125,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
"manufacturer__name", "manufacturer__name",
"road_number", "road_number",
"address", "address",
"sku", "item_number",
) )
save_as = True save_as = True
@@ -138,7 +138,7 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
"road_number", "road_number",
"scale", "scale",
"manufacturer", "manufacturer",
"sku", "item_number",
"era", "era",
"production_year", "production_year",
"purchase_date", "purchase_date",

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.5 on 2023-10-01 19:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("roster", "0017_remove_rollingstockimage_is_thumbnail"),
]
operations = [
migrations.RenameField(
model_name="rollingstock",
old_name="sku",
new_name="item_number",
),
]

View File

@@ -1,4 +1,3 @@
import os
import re import re
from uuid import uuid4 from uuid import uuid4
from django.db import models from django.db import models
@@ -8,10 +7,9 @@ from django.dispatch import receiver
from ckeditor_uploader.fields import RichTextUploadingField from ckeditor_uploader.fields import RichTextUploadingField
from ram.utils import DeduplicatedStorage, get_image_preview from ram.models import Document, Image, PropertyInstance
from ram.models import Document from ram.utils import get_image_preview
from metadata.models import ( from metadata.models import (
Property,
Scale, Scale,
Manufacturer, Manufacturer,
Decoder, Decoder,
@@ -43,7 +41,7 @@ class RollingClass(models.Model):
return "{0} {1}".format(self.company, self.identifier) return "{0} {1}".format(self.company, self.identifier)
class RollingClassProperty(models.Model): class RollingClassProperty(PropertyInstance):
rolling_class = models.ForeignKey( rolling_class = models.ForeignKey(
RollingClass, RollingClass,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@@ -52,14 +50,6 @@ class RollingClassProperty(models.Model):
related_name="property", related_name="property",
verbose_name="Class", verbose_name="Class",
) )
property = models.ForeignKey(Property, on_delete=models.CASCADE)
value = models.CharField(max_length=256)
def __str__(self):
return self.property.name
class Meta:
verbose_name_plural = "Properties"
class RollingStock(models.Model): class RollingStock(models.Model):
@@ -82,7 +72,7 @@ class RollingStock(models.Model):
limit_choices_to={"category": "model"}, 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) item_number = models.CharField(max_length=32, blank=True)
decoder_interface = models.PositiveSmallIntegerField( decoder_interface = models.PositiveSmallIntegerField(
choices=settings.DECODER_INTERFACES, null=True, blank=True choices=settings.DECODER_INTERFACES, null=True, blank=True
) )
@@ -136,28 +126,13 @@ class RollingStockDocument(Document):
unique_together = ("rolling_stock", "file") unique_together = ("rolling_stock", "file")
class RollingStockImage(models.Model): class RollingStockImage(Image):
order = models.PositiveIntegerField(default=0, blank=False, null=False)
rolling_stock = models.ForeignKey( rolling_stock = models.ForeignKey(
RollingStock, on_delete=models.CASCADE, related_name="image" RollingStock, on_delete=models.CASCADE, related_name="image"
) )
image = models.ImageField(
upload_to="images/", storage=DeduplicatedStorage, null=True, blank=True
)
def image_thumbnail(self):
return get_image_preview(self.image.url)
image_thumbnail.short_description = "Preview"
def __str__(self):
return "{0}".format(os.path.basename(self.image.name))
class Meta:
ordering = ["order"]
class RollingStockProperty(models.Model): class RollingStockProperty(PropertyInstance):
rolling_stock = models.ForeignKey( rolling_stock = models.ForeignKey(
RollingStock, RollingStock,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@@ -165,14 +140,6 @@ class RollingStockProperty(models.Model):
null=False, null=False,
blank=False, blank=False,
) )
property = models.ForeignKey(Property, on_delete=models.CASCADE)
value = models.CharField(max_length=256)
def __str__(self):
return self.property.name
class Meta:
verbose_name_plural = "Properties"
class RollingStockJournal(models.Model): class RollingStockJournal(models.Model):