mirror of
https://github.com/daniviga/django-ram.git
synced 2025-08-07 14:47:49 +02:00
Compare commits
2 Commits
v0.14.3
...
fix-warnin
Author | SHA1 | Date | |
---|---|---|---|
9cb3fb1d8a
|
|||
ed8ffb5ece
|
2
.github/workflows/django.yml
vendored
2
.github/workflows/django.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
python-version: ['3.12', '3.13']
|
||||
python-version: ['3.9', '3.10', '3.11', '3.12']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@ __pycache__/
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
|
@@ -49,7 +49,7 @@ It has been developed with:
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- Python 3.9+
|
||||
- A USB port when running Arduino hardware (and adaptors if you have a Mac)
|
||||
|
||||
## Web portal installation
|
||||
|
Submodule arduino/CommandStation-EX updated: fe2f705fa9...2db2b0ecc6
Submodule arduino/WebThrottle-EX updated: d82dcc9ecf...c67e4080d0
Submodule arduino/arduino-cli updated: a0d912da18...048415c5e6
Submodule arduino/dcc-ex.github.io updated: cb286e9d76...9acc446358
Submodule arduino/vim-arduino updated: 2ded67cdf0...111db616db
@@ -1,94 +1,36 @@
|
||||
from django.contrib import admin
|
||||
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
|
||||
|
||||
from bookshelf.models import (
|
||||
BaseBookProperty,
|
||||
BaseBookImage,
|
||||
BaseBookDocument,
|
||||
Book,
|
||||
Author,
|
||||
Publisher,
|
||||
Catalog,
|
||||
)
|
||||
from bookshelf.models import BookProperty, BookImage, Book, Author, Publisher
|
||||
|
||||
|
||||
class BookImageInline(SortableInlineAdminMixin, admin.TabularInline):
|
||||
model = BaseBookImage
|
||||
model = BookImage
|
||||
min_num = 0
|
||||
extra = 0
|
||||
readonly_fields = ("image_thumbnail",)
|
||||
classes = ["collapse"]
|
||||
verbose_name = "Image"
|
||||
|
||||
|
||||
class BookDocInline(admin.TabularInline):
|
||||
model = BaseBookDocument
|
||||
min_num = 0
|
||||
extra = 0
|
||||
classes = ["collapse"]
|
||||
|
||||
|
||||
class BookPropertyInline(admin.TabularInline):
|
||||
model = BaseBookProperty
|
||||
model = BookProperty
|
||||
min_num = 0
|
||||
extra = 0
|
||||
autocomplete_fields = ("property",)
|
||||
verbose_name = "Property"
|
||||
verbose_name_plural = "Properties"
|
||||
|
||||
|
||||
@admin.register(Book)
|
||||
class BookAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||
inlines = (
|
||||
BookPropertyInline,
|
||||
BookImageInline,
|
||||
BookDocInline,
|
||||
)
|
||||
inlines = (BookImageInline, BookPropertyInline,)
|
||||
list_display = (
|
||||
"title",
|
||||
"get_authors",
|
||||
"get_publisher",
|
||||
"publication_year",
|
||||
"number_of_pages",
|
||||
"published",
|
||||
"number_of_pages"
|
||||
)
|
||||
autocomplete_fields = ("authors", "publisher")
|
||||
readonly_fields = ("creation_time", "updated_time")
|
||||
search_fields = ("title", "publisher__name", "authors__last_name")
|
||||
list_filter = ("publisher__name", "authors")
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"published",
|
||||
"title",
|
||||
"authors",
|
||||
"publisher",
|
||||
"ISBN",
|
||||
"language",
|
||||
"number_of_pages",
|
||||
"publication_year",
|
||||
"description",
|
||||
"purchase_date",
|
||||
"notes",
|
||||
"tags",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Audit",
|
||||
{
|
||||
"classes": ("collapse",),
|
||||
"fields": (
|
||||
"creation_time",
|
||||
"updated_time",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@admin.display(description="Publisher")
|
||||
def get_publisher(self, obj):
|
||||
return obj.publisher.name
|
||||
@@ -100,10 +42,7 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||
|
||||
@admin.register(Author)
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
search_fields = (
|
||||
"first_name",
|
||||
"last_name",
|
||||
)
|
||||
search_fields = ("first_name", "last_name",)
|
||||
list_filter = ("last_name",)
|
||||
|
||||
|
||||
@@ -111,59 +50,3 @@ class AuthorAdmin(admin.ModelAdmin):
|
||||
class PublisherAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "country")
|
||||
search_fields = ("name",)
|
||||
|
||||
|
||||
@admin.register(Catalog)
|
||||
class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||
inlines = (
|
||||
BookPropertyInline,
|
||||
BookImageInline,
|
||||
BookDocInline,
|
||||
)
|
||||
list_display = (
|
||||
"__str__",
|
||||
"manufacturer",
|
||||
"years",
|
||||
"get_scales",
|
||||
"published",
|
||||
)
|
||||
autocomplete_fields = ("manufacturer",)
|
||||
readonly_fields = ("creation_time", "updated_time")
|
||||
search_fields = ("manufacturer__name", "years", "scales__scale")
|
||||
list_filter = ("manufacturer__name", "publication_year", "scales__scale")
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"published",
|
||||
"manufacturer",
|
||||
"years",
|
||||
"scales",
|
||||
"ISBN",
|
||||
"language",
|
||||
"number_of_pages",
|
||||
"publication_year",
|
||||
"description",
|
||||
"purchase_date",
|
||||
"notes",
|
||||
"tags",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Audit",
|
||||
{
|
||||
"classes": ("collapse",),
|
||||
"fields": (
|
||||
"creation_time",
|
||||
"updated_time",
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@admin.display(description="Scales")
|
||||
def get_scales(self, obj):
|
||||
return "/".join(s.scale for s in obj.scales.all())
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# Generated by Django 4.2.5 on 2023-10-01 20:16
|
||||
|
||||
# ckeditor removal
|
||||
# import ckeditor_uploader.fields
|
||||
import ckeditor_uploader.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
@@ -48,8 +47,7 @@ class Migration(migrations.Migration):
|
||||
("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)),
|
||||
("notes", models.TextField(blank=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")),
|
||||
|
@@ -12,7 +12,7 @@ from django.conf import settings
|
||||
|
||||
def move_images(apps, schema_editor):
|
||||
sys.stdout.write("\n Processing files. Please await...")
|
||||
for r in bookshelf.models.BaseBookImage.objects.all():
|
||||
for r in bookshelf.models.BookImage.objects.all():
|
||||
fname = os.path.basename(r.image.path)
|
||||
new_image = bookshelf.models.book_image_upload(r, fname)
|
||||
new_path = os.path.join(settings.MEDIA_ROOT, new_image)
|
||||
@@ -31,21 +31,19 @@ class Migration(migrations.Migration):
|
||||
("bookshelf", "0008_alter_author_options_alter_publisher_options"),
|
||||
]
|
||||
|
||||
# Migration is stale and shouldn't be used since model hes been heavily
|
||||
# modified since then. Leaving it here for reference.
|
||||
operations = [
|
||||
# migrations.AlterField(
|
||||
# model_name="bookimage",
|
||||
# name="image",
|
||||
# field=models.ImageField(
|
||||
# blank=True,
|
||||
# null=True,
|
||||
# storage=ram.utils.DeduplicatedStorage,
|
||||
# upload_to=bookshelf.models.book_image_upload,
|
||||
# ),
|
||||
# ),
|
||||
# migrations.RunPython(
|
||||
# move_images,
|
||||
# reverse_code=migrations.RunPython.noop
|
||||
# ),
|
||||
migrations.AlterField(
|
||||
model_name="bookimage",
|
||||
name="image",
|
||||
field=models.ImageField(
|
||||
blank=True,
|
||||
null=True,
|
||||
storage=ram.utils.DeduplicatedStorage,
|
||||
upload_to=bookshelf.models.book_image_upload,
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
move_images,
|
||||
reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
|
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.2 on 2024-03-02 14:31
|
||||
|
||||
import tinymce.models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookshelf", "0012_alter_book_notes"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="book",
|
||||
name="description",
|
||||
field=tinymce.models.HTMLField(blank=True),
|
||||
),
|
||||
]
|
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-04 13:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookshelf", "0013_book_description"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="book",
|
||||
name="published",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-26 22:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookshelf", "0014_book_published"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="book",
|
||||
name="authors",
|
||||
field=models.ManyToManyField(blank=True, to="bookshelf.author"),
|
||||
),
|
||||
]
|
@@ -1,141 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-27 16:35
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def basebook_to_book(apps, schema_editor):
|
||||
basebook = apps.get_model("bookshelf", "BaseBook")
|
||||
book = apps.get_model("bookshelf", "Book")
|
||||
for row in basebook.objects.all():
|
||||
b = book.objects.create(
|
||||
basebook_ptr=row,
|
||||
title=row.old_title,
|
||||
publisher=row.old_publisher,
|
||||
)
|
||||
b.authors.set(row.old_authors.all())
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookshelf", "0015_alter_book_authors"),
|
||||
("metadata", "0019_alter_scale_gauge"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="Book",
|
||||
options={"ordering": ["creation_time"]},
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name="BookImage",
|
||||
new_name="BaseBookImage",
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name="BookProperty",
|
||||
new_name="BaseBookProperty",
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name="Book",
|
||||
new_name="BaseBook",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basebook",
|
||||
old_name="title",
|
||||
new_name="old_title",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basebook",
|
||||
old_name="authors",
|
||||
new_name="old_authors",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="basebook",
|
||||
old_name="publisher",
|
||||
new_name="old_publisher",
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="basebookimage",
|
||||
options={"ordering": ["order"], "verbose_name_plural": "Images"},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Book",
|
||||
fields=[
|
||||
(
|
||||
"basebook_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="bookshelf.basebook",
|
||||
),
|
||||
),
|
||||
("title", models.CharField(max_length=200)),
|
||||
(
|
||||
"authors",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
to="bookshelf.author"
|
||||
),
|
||||
),
|
||||
(
|
||||
"publisher",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="bookshelf.publisher"
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ["title"],
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
basebook_to_book,
|
||||
reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="basebook",
|
||||
name="old_title",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="basebook",
|
||||
name="old_authors",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="basebook",
|
||||
name="old_publisher",
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Catalog",
|
||||
fields=[
|
||||
(
|
||||
"basebook_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="bookshelf.basebook",
|
||||
),
|
||||
),
|
||||
("years", models.CharField(max_length=12)),
|
||||
(
|
||||
"manufacturer",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="metadata.manufacturer",
|
||||
),
|
||||
),
|
||||
("scales", models.ManyToManyField(to="metadata.scale")),
|
||||
],
|
||||
options={
|
||||
"ordering": ["manufacturer", "publication_year"],
|
||||
},
|
||||
bases=("bookshelf.basebook",),
|
||||
),
|
||||
]
|
@@ -1,52 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-12-22 20:38
|
||||
|
||||
import django.db.models.deletion
|
||||
import ram.utils
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookshelf", "0016_basebook_book_catalogue"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="basebook",
|
||||
options={},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="BaseBookDocument",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("description", models.CharField(blank=True, max_length=128)),
|
||||
(
|
||||
"file",
|
||||
models.FileField(
|
||||
storage=ram.utils.DeduplicatedStorage(), upload_to="files/"
|
||||
),
|
||||
),
|
||||
("private", models.BooleanField(default=False)),
|
||||
(
|
||||
"book",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="document",
|
||||
to="bookshelf.basebook",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"unique_together": {("book", "file")},
|
||||
},
|
||||
),
|
||||
]
|
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.1.4 on 2024-12-22 20:44
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookshelf", "0017_alter_basebook_options_basebookdocument"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="basebookdocument",
|
||||
options={"verbose_name_plural": "Documents"},
|
||||
),
|
||||
]
|
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import shutil
|
||||
from uuid import uuid4
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
@@ -9,8 +10,7 @@ from tinymce import models as tinymce
|
||||
|
||||
from metadata.models import Tag
|
||||
from ram.utils import DeduplicatedStorage
|
||||
from ram.models import BaseModel, Image, Document, PropertyInstance
|
||||
from metadata.models import Scale, Manufacturer
|
||||
from ram.models import Image, PropertyInstance
|
||||
|
||||
|
||||
class Publisher(models.Model):
|
||||
@@ -39,7 +39,11 @@ class Author(models.Model):
|
||||
return f"{self.last_name} {self.first_name[0]}."
|
||||
|
||||
|
||||
class BaseBook(BaseModel):
|
||||
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=17, blank=True) # 13 + dashes
|
||||
language = models.CharField(
|
||||
max_length=7,
|
||||
@@ -48,65 +52,13 @@ class BaseBook(BaseModel):
|
||||
)
|
||||
number_of_pages = models.SmallIntegerField(null=True, blank=True)
|
||||
publication_year = models.SmallIntegerField(null=True, blank=True)
|
||||
description = tinymce.HTMLField(blank=True)
|
||||
purchase_date = models.DateField(null=True, blank=True)
|
||||
tags = models.ManyToManyField(
|
||||
Tag, related_name="bookshelf", blank=True
|
||||
)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
shutil.rmtree(
|
||||
os.path.join(
|
||||
settings.MEDIA_ROOT, "images", "books", str(self.uuid)
|
||||
),
|
||||
ignore_errors=True
|
||||
)
|
||||
super(BaseBook, self).delete(*args, **kwargs)
|
||||
|
||||
|
||||
def book_image_upload(instance, filename):
|
||||
return os.path.join(
|
||||
"images",
|
||||
"books",
|
||||
str(instance.book.uuid),
|
||||
filename
|
||||
)
|
||||
|
||||
|
||||
class BaseBookImage(Image):
|
||||
book = models.ForeignKey(
|
||||
BaseBook, on_delete=models.CASCADE, related_name="image"
|
||||
)
|
||||
image = models.ImageField(
|
||||
upload_to=book_image_upload,
|
||||
storage=DeduplicatedStorage,
|
||||
)
|
||||
|
||||
|
||||
class BaseBookDocument(Document):
|
||||
book = models.ForeignKey(
|
||||
BaseBook, on_delete=models.CASCADE, related_name="document"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Documents"
|
||||
unique_together = ("book", "file")
|
||||
|
||||
|
||||
class BaseBookProperty(PropertyInstance):
|
||||
book = models.ForeignKey(
|
||||
BaseBook,
|
||||
on_delete=models.CASCADE,
|
||||
null=False,
|
||||
blank=False,
|
||||
related_name="property",
|
||||
)
|
||||
|
||||
|
||||
class Book(BaseBook):
|
||||
title = models.CharField(max_length=200)
|
||||
authors = models.ManyToManyField(Author, blank=True)
|
||||
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
|
||||
notes = tinymce.HTMLField(blank=True)
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
updated_time = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["title"]
|
||||
@@ -118,33 +70,42 @@ class Book(BaseBook):
|
||||
return self.publisher.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"bookshelf_item",
|
||||
kwargs={"selector": "book", "uuid": self.uuid}
|
||||
return reverse("book", kwargs={"uuid": self.uuid})
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
shutil.rmtree(
|
||||
os.path.join(
|
||||
settings.MEDIA_ROOT, "images", "books", str(self.uuid)
|
||||
),
|
||||
ignore_errors=True
|
||||
)
|
||||
super(Book, self).delete(*args, **kwargs)
|
||||
|
||||
|
||||
class Catalog(BaseBook):
|
||||
manufacturer = models.ForeignKey(
|
||||
Manufacturer,
|
||||
on_delete=models.CASCADE,
|
||||
def book_image_upload(instance, filename):
|
||||
return os.path.join(
|
||||
"images",
|
||||
"books",
|
||||
str(instance.book.uuid),
|
||||
filename
|
||||
)
|
||||
years = models.CharField(max_length=12)
|
||||
scales = models.ManyToManyField(Scale)
|
||||
|
||||
class Meta:
|
||||
ordering = ["manufacturer", "publication_year"]
|
||||
|
||||
def __str__(self):
|
||||
scales = self.get_scales
|
||||
return "%s %s %s" % (self.manufacturer.name, self.years, scales)
|
||||
class BookImage(Image):
|
||||
book = models.ForeignKey(
|
||||
Book, on_delete=models.CASCADE, related_name="image"
|
||||
)
|
||||
image = models.ImageField(
|
||||
upload_to=book_image_upload,
|
||||
storage=DeduplicatedStorage,
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"bookshelf_item",
|
||||
kwargs={"selector": "catalog", "uuid": self.uuid}
|
||||
)
|
||||
|
||||
@property
|
||||
def get_scales(self):
|
||||
return "/".join([s.scale for s in self.scales.all()])
|
||||
class BookProperty(PropertyInstance):
|
||||
book = models.ForeignKey(
|
||||
Book,
|
||||
on_delete=models.CASCADE,
|
||||
null=False,
|
||||
blank=False,
|
||||
related_name="property",
|
||||
)
|
||||
|
@@ -3,5 +3,5 @@ from bookshelf.views import BookList, BookGet
|
||||
|
||||
urlpatterns = [
|
||||
path("book/list", BookList.as_view()),
|
||||
path("book/get/<uuid:uuid>", BookGet.as_view()),
|
||||
path("book/get/<str:uuid>", BookGet.as_view()),
|
||||
]
|
||||
|
@@ -6,16 +6,13 @@ from bookshelf.serializers import BookSerializer
|
||||
|
||||
|
||||
class BookList(ListAPIView):
|
||||
queryset = Book.objects.all()
|
||||
serializer_class = BookSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Book.objects.get_published(self.request.user)
|
||||
|
||||
|
||||
class BookGet(RetrieveAPIView):
|
||||
queryset = Book.objects.all()
|
||||
serializer_class = BookSerializer
|
||||
lookup_field = "uuid"
|
||||
schema = AutoSchema(operation_id_base="retrieveBookByUUID")
|
||||
|
||||
def get_queryset(self):
|
||||
return Book.objects.get_published(self.request.user)
|
||||
schema = AutoSchema(operation_id_base="retrieveBookByUUID")
|
||||
|
@@ -8,8 +8,7 @@ class ConsistItemInline(SortableInlineAdminMixin, admin.TabularInline):
|
||||
model = ConsistItem
|
||||
min_num = 1
|
||||
extra = 0
|
||||
autocomplete_fields = ("rolling_stock",)
|
||||
readonly_fields = ("preview", "published", "address", "type", "company", "era")
|
||||
readonly_fields = ("address", "type", "company", "era")
|
||||
|
||||
|
||||
@admin.register(Consist)
|
||||
@@ -19,7 +18,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||
"creation_time",
|
||||
"updated_time",
|
||||
)
|
||||
list_display = ("identifier", "published", "company", "era")
|
||||
list_display = ("identifier", "company", "era")
|
||||
list_filter = list_display
|
||||
search_fields = list_display
|
||||
save_as = True
|
||||
@@ -29,7 +28,6 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"published",
|
||||
"identifier",
|
||||
"consist_address",
|
||||
"company",
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# Generated by Django 4.1 on 2022-08-23 15:54
|
||||
|
||||
# ckeditor removal
|
||||
# import ckeditor_uploader.fields
|
||||
import ckeditor_uploader.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
@@ -12,9 +11,9 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
# migrations.AlterField(
|
||||
# model_name="consist",
|
||||
# name="notes",
|
||||
# field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
|
||||
# ),
|
||||
migrations.AlterField(
|
||||
model_name="consist",
|
||||
name="notes",
|
||||
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
|
||||
),
|
||||
]
|
||||
|
@@ -1,30 +0,0 @@
|
||||
# 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
|
||||
),
|
||||
),
|
||||
]
|
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-04 12:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("consist", "0011_alter_consist_consist_address_alter_consist_era"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="consist",
|
||||
name="published",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
@@ -1,37 +1,34 @@
|
||||
import os
|
||||
|
||||
from uuid import uuid4
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.dispatch import receiver
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from ram.models import BaseModel
|
||||
from tinymce import models as tinymce
|
||||
|
||||
from ram.utils import DeduplicatedStorage
|
||||
from metadata.models import Company, Tag
|
||||
from roster.models import RollingStock
|
||||
|
||||
|
||||
class Consist(BaseModel):
|
||||
class Consist(models.Model):
|
||||
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
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,
|
||||
help_text="DCC consist address if enabled",
|
||||
default=None, null=True, blank=True
|
||||
)
|
||||
company = models.ForeignKey(Company, on_delete=models.CASCADE)
|
||||
era = models.CharField(
|
||||
max_length=32,
|
||||
blank=True,
|
||||
help_text="Era or epoch of the consist",
|
||||
)
|
||||
era = models.CharField(max_length=32, blank=True)
|
||||
image = models.ImageField(
|
||||
upload_to=os.path.join("images", "consists"),
|
||||
storage=DeduplicatedStorage,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
notes = tinymce.HTMLField(blank=True)
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
updated_time = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return "{0} {1}".format(self.company, self.identifier)
|
||||
@@ -39,12 +36,6 @@ class Consist(BaseModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse("consist", kwargs={"uuid": self.uuid})
|
||||
|
||||
def clean(self):
|
||||
if self.consist_item.filter(rolling_stock__published=False).exists():
|
||||
raise ValidationError(
|
||||
"You must publish all items in the consist before publishing the consist." # noqa: E501
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["company", "-creation_time"]
|
||||
|
||||
@@ -62,13 +53,6 @@ class ConsistItem(models.Model):
|
||||
def __str__(self):
|
||||
return "{0}".format(self.rolling_stock)
|
||||
|
||||
def published(self):
|
||||
return self.rolling_stock.published
|
||||
published.boolean = True
|
||||
|
||||
def preview(self):
|
||||
return self.rolling_stock.image.first().image_thumbnail(100)
|
||||
|
||||
def type(self):
|
||||
return self.rolling_stock.rolling_class.type
|
||||
|
||||
@@ -80,14 +64,3 @@ class ConsistItem(models.Model):
|
||||
|
||||
def era(self):
|
||||
return self.rolling_stock.era
|
||||
|
||||
|
||||
# Unpublish any consist that contains an unpublished rolling stock
|
||||
# this signal is called after a rolling stock is saved
|
||||
# it is hosted here to avoid circular imports
|
||||
@receiver(models.signals.post_save, sender=RollingStock)
|
||||
def post_save_unpublish_consist(sender, instance, *args, **kwargs):
|
||||
consists = Consist.objects.filter(consist_item__rolling_stock=instance)
|
||||
for consist in consists:
|
||||
consist.published = False
|
||||
consist.save()
|
||||
|
@@ -5,15 +5,11 @@ from consist.serializers import ConsistSerializer
|
||||
|
||||
|
||||
class ConsistList(ListAPIView):
|
||||
queryset = Consist.objects.all()
|
||||
serializer_class = ConsistSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Consist.objects.get_published(self.request.user)
|
||||
|
||||
|
||||
class ConsistGet(RetrieveAPIView):
|
||||
queryset = Consist.objects.all()
|
||||
serializer_class = ConsistSerializer
|
||||
lookup_field = "uuid"
|
||||
|
||||
def get_queryset(self):
|
||||
return Consist.objects.get_published(self.request.user)
|
||||
|
@@ -1,20 +0,0 @@
|
||||
# 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"
|
||||
),
|
||||
),
|
||||
]
|
@@ -1,69 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-04 21:17
|
||||
|
||||
import django.db.migrations.operations.special
|
||||
import metadata.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def gen_ratio(apps, schema_editor):
|
||||
Scale = apps.get_model('metadata', 'Scale')
|
||||
for row in Scale.objects.all():
|
||||
row.ratio_int = metadata.models.calculate_ratio(row.ratio)
|
||||
row.save(update_fields=['ratio_int'])
|
||||
|
||||
|
||||
def convert_tarcks(apps, schema_editor):
|
||||
Scale = apps.get_model("metadata", "Scale")
|
||||
for row in Scale.objects.all():
|
||||
row.tracks = "".join(
|
||||
filter(
|
||||
lambda x: str.isdigit(x) or x == "." or x == ",",
|
||||
row.tracks
|
||||
)
|
||||
)
|
||||
row.save(update_fields=["tracks"])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('metadata', '0017_alter_property_private'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='decoder',
|
||||
options={'ordering': ['manufacturer__name', 'name']},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='scale',
|
||||
options={'ordering': ['ratio_int', 'scale']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scale',
|
||||
name='ratio_int',
|
||||
field=models.SmallIntegerField(default=0, editable=False),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=gen_ratio,
|
||||
reverse_code=django.db.migrations.operations.special.RunPython.noop,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='scale',
|
||||
name='ratio',
|
||||
field=models.CharField(max_length=16, validators=[metadata.models.calculate_ratio]),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='scale',
|
||||
options={'ordering': ['-ratio_int', '-tracks', 'scale']},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=convert_tarcks,
|
||||
reverse_code=django.db.migrations.operations.special.RunPython.noop,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='scale',
|
||||
name='tracks',
|
||||
field=models.FloatField(help_text='Distance between model tracks in mm'),
|
||||
),
|
||||
]
|
@@ -1,22 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-04 21:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("metadata", "0018_alter_decoder_options_alter_scale_options_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="scale",
|
||||
name="gauge",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Distance between real tracks. Please specify the unit (mm, in, ...)",
|
||||
max_length=16,
|
||||
),
|
||||
),
|
||||
]
|
@@ -3,20 +3,15 @@ from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.dispatch.dispatcher import receiver
|
||||
from django.core.exceptions import ValidationError
|
||||
from django_countries.fields import CountryField
|
||||
|
||||
from ram.models import Document
|
||||
from ram.utils import DeduplicatedStorage, get_image_preview, slugify
|
||||
from ram.managers import PublicManager
|
||||
|
||||
|
||||
class Property(models.Model):
|
||||
name = models.CharField(max_length=128, unique=True)
|
||||
private = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Property will be only visible to logged users",
|
||||
)
|
||||
private = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Properties"
|
||||
@@ -25,8 +20,6 @@ class Property(models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
objects = PublicManager()
|
||||
|
||||
|
||||
class Manufacturer(models.Model):
|
||||
name = models.CharField(max_length=128, unique=True)
|
||||
@@ -50,11 +43,10 @@ class Manufacturer(models.Model):
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"filtered",
|
||||
kwargs={
|
||||
"filtered", kwargs={
|
||||
"_filter": "manufacturer",
|
||||
"search": self.slug,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def logo_thumbnail(self):
|
||||
@@ -85,11 +77,10 @@ class Company(models.Model):
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"filtered",
|
||||
kwargs={
|
||||
"filtered", kwargs={
|
||||
"_filter": "company",
|
||||
"search": self.slug,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def extended_name_pp(self):
|
||||
@@ -118,7 +109,7 @@ class Decoder(models.Model):
|
||||
)
|
||||
|
||||
class Meta(object):
|
||||
ordering = ["manufacturer__name", "name"]
|
||||
ordering = ["manufacturer", "name"]
|
||||
|
||||
def __str__(self):
|
||||
return "{0} - {1}".format(self.manufacturer, self.name)
|
||||
@@ -138,49 +129,28 @@ class DecoderDocument(Document):
|
||||
unique_together = ("decoder", "file")
|
||||
|
||||
|
||||
def calculate_ratio(ratio):
|
||||
try:
|
||||
num, den = ratio.split(":")
|
||||
return int(num) / float(den) * 10000
|
||||
except (ValueError, ZeroDivisionError):
|
||||
raise ValidationError("Invalid ratio format")
|
||||
|
||||
|
||||
class Scale(models.Model):
|
||||
scale = models.CharField(max_length=32, unique=True)
|
||||
slug = models.CharField(max_length=32, unique=True, editable=False)
|
||||
ratio = models.CharField(max_length=16, validators=[calculate_ratio])
|
||||
ratio_int = models.SmallIntegerField(editable=False, default=0)
|
||||
tracks = models.FloatField(
|
||||
help_text="Distance between model tracks in mm",
|
||||
)
|
||||
gauge = models.CharField(
|
||||
max_length=16,
|
||||
blank=True,
|
||||
help_text="Distance between real tracks. Please specify the unit (mm, in, ...)", # noqa: E501
|
||||
)
|
||||
ratio = models.CharField(max_length=16, blank=True)
|
||||
gauge = models.CharField(max_length=16, blank=True)
|
||||
tracks = models.CharField(max_length=16, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-ratio_int", "-tracks", "scale"]
|
||||
ordering = ["scale"]
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"filtered",
|
||||
kwargs={
|
||||
"filtered", kwargs={
|
||||
"_filter": "scale",
|
||||
"search": self.slug,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.scale)
|
||||
|
||||
|
||||
@receiver(models.signals.pre_save, sender=Scale)
|
||||
def scale_save(sender, instance, **kwargs):
|
||||
instance.ratio_int = calculate_ratio(instance.ratio)
|
||||
|
||||
|
||||
class RollingStockType(models.Model):
|
||||
type = models.CharField(max_length=64)
|
||||
order = models.PositiveSmallIntegerField()
|
||||
@@ -195,11 +165,10 @@ class RollingStockType(models.Model):
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"filtered",
|
||||
kwargs={
|
||||
"filtered", kwargs={
|
||||
"_filter": "type",
|
||||
"search": self.slug,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
@@ -218,11 +187,10 @@ class Tag(models.Model):
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"filtered",
|
||||
kwargs={
|
||||
"filtered", kwargs={
|
||||
"_filter": "tag",
|
||||
"search": self.slug,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
@@ -1,8 +1,7 @@
|
||||
# Generated by Django 4.1 on 2022-08-23 15:54
|
||||
|
||||
# ckeditor dependency removal
|
||||
# import ckeditor.fields
|
||||
# import ckeditor_uploader.fields
|
||||
import ckeditor.fields
|
||||
import ckeditor_uploader.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
@@ -13,24 +12,24 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
# migrations.AlterField(
|
||||
# model_name="flatpage",
|
||||
# name="content",
|
||||
# field=ckeditor_uploader.fields.RichTextUploadingField(),
|
||||
# ),
|
||||
# migrations.AlterField(
|
||||
# model_name="siteconfiguration",
|
||||
# name="about",
|
||||
# field=ckeditor.fields.RichTextField(blank=True),
|
||||
# ),
|
||||
# migrations.AlterField(
|
||||
# model_name="siteconfiguration",
|
||||
# name="footer",
|
||||
# field=ckeditor.fields.RichTextField(blank=True),
|
||||
# ),
|
||||
# migrations.AlterField(
|
||||
# model_name="siteconfiguration",
|
||||
# name="footer_extended",
|
||||
# field=ckeditor.fields.RichTextField(blank=True),
|
||||
# ),
|
||||
migrations.AlterField(
|
||||
model_name="flatpage",
|
||||
name="content",
|
||||
field=ckeditor_uploader.fields.RichTextUploadingField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="siteconfiguration",
|
||||
name="about",
|
||||
field=ckeditor.fields.RichTextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="siteconfiguration",
|
||||
name="footer",
|
||||
field=ckeditor.fields.RichTextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="siteconfiguration",
|
||||
name="footer_extended",
|
||||
field=ckeditor.fields.RichTextField(blank=True),
|
||||
),
|
||||
]
|
||||
|
@@ -9,7 +9,6 @@ from solo.models import SingletonModel
|
||||
from tinymce import models as tinymce
|
||||
|
||||
from ram import __version__ as app_version
|
||||
from ram.managers import PublicManager
|
||||
from ram.utils import slugify
|
||||
|
||||
|
||||
@@ -73,8 +72,6 @@ class Flatpage(models.Model):
|
||||
)
|
||||
)
|
||||
|
||||
objects = PublicManager()
|
||||
|
||||
|
||||
@receiver(models.signals.pre_save, sender=Flatpage)
|
||||
def tag_pre_save(sender, instance, **kwargs):
|
||||
|
@@ -1,14 +1,14 @@
|
||||
/*!
|
||||
* Bootstrap Icons v1.11.3 (https://icons.getbootstrap.com/)
|
||||
* Copyright 2019-2024 The Bootstrap Authors
|
||||
* Bootstrap Icons v1.11.1 (https://icons.getbootstrap.com/)
|
||||
* Copyright 2019-2023 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE)
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-display: block;
|
||||
font-family: "bootstrap-icons";
|
||||
src: url("./fonts/bootstrap-icons.woff2?dd67030699838ea613ee6dbda90effa6") format("woff2"),
|
||||
url("./fonts/bootstrap-icons.woff?dd67030699838ea613ee6dbda90effa6") format("woff");
|
||||
src: url("./fonts/bootstrap-icons.woff2?2820a3852bdb9a5832199cc61cec4e65") format("woff2"),
|
||||
url("./fonts/bootstrap-icons.woff?2820a3852bdb9a5832199cc61cec4e65") format("woff");
|
||||
}
|
||||
|
||||
.bi::before,
|
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,11 +16,11 @@
|
||||
<link rel="icon" href="{% static "favicon.png" %}" sizes="any">
|
||||
<link rel="icon" href="{% static "favicon.svg" %}" type="image/svg+xml">
|
||||
{% if site_conf.use_cdn %}
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
|
||||
{% else %}
|
||||
<link href="{% static "bootstrap@5.3.3/dist/css/bootstrap.min.css" %}" rel="stylesheet">
|
||||
<link href="{% static "bootstrap-icons@1.11.3/font/bootstrap-icons.css" %}" rel="stylesheet">
|
||||
<link href="{% static "bootstrap@5.3.2/dist/css/bootstrap.min.css" %}" rel="stylesheet">
|
||||
<link href="{% static "bootstrap-icons@1.11.1/font/bootstrap-icons.css" %}" rel="stylesheet">
|
||||
{% endif %}
|
||||
<link href="{% static "css/main.css" %}?v={{ site_conf.version }}" rel="stylesheet">
|
||||
<style>
|
||||
@@ -180,13 +180,13 @@
|
||||
<li><a class="dropdown-item" href="{% url 'manufacturers' category='model' %}">Manufacturer</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li class="ps-2 text-secondary">Prototype</li>
|
||||
<li><a class="dropdown-item" href="{% url 'rolling_stock_types' %}">Type</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'types' %}">Type</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'companies' %}">Company</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'manufacturers' category='real' %}">Manufacturer</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% show_bookshelf_menu %}
|
||||
{% show_flatpages_menu user %}
|
||||
{% show_flatpages_menu %}
|
||||
</ul>
|
||||
{% include 'includes/search.html' %}
|
||||
</div>
|
||||
@@ -216,9 +216,9 @@
|
||||
</main>
|
||||
{% include 'includes/footer.html' %}
|
||||
{% if site_conf.use_cdn %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% else %}
|
||||
<script src="{% static "bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" %}"></script>
|
||||
<script src="{% static "bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" %}"></script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -1,5 +1,4 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load dynamic_url %}
|
||||
|
||||
{% block header %}
|
||||
{% if book.tags.all %}
|
||||
@@ -28,11 +27,11 @@
|
||||
{% 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"><i class="bi bi-chevron-left"></i></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"><i class="bi bi-chevron-right"></i></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -46,53 +45,35 @@
|
||||
<div class="mx-auto">
|
||||
<nav class="nav nav-tabs d-none d-lg-flex flex-row mb-2" 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 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 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 %}
|
||||
</nav>
|
||||
<select class="form-select d-lg-none mb-2" id="tabSelector" aria-label="Tab selector">
|
||||
<option value="nav-summary" selected>Summary</option>
|
||||
{% if documents %}<option value="nav-documents">Documents</option>{% endif %}
|
||||
{% if book.notes %}<option value="nav-notes">Notes</option>{% endif %}
|
||||
</select>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
<div class="tab-pane show active" id="nav-summary" role="tabpanel" aria-labelledby="nav-summary-tab">
|
||||
<table class="table table-striped">
|
||||
{{ book.description | safe }}
|
||||
<thead>
|
||||
<tr>
|
||||
{% if type == "catalog" %}
|
||||
<th colspan="2" scope="row">Catalog</th>
|
||||
{% elif type == "book" %}
|
||||
<th colspan="2" scope="row">Book</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-group-divider">
|
||||
{% if type == "catalog" %}
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Manufacturer</th>
|
||||
<td>{{ book.manufacturer }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Scales</th>
|
||||
<td>{{ book.get_scales }}</td>
|
||||
</tr>
|
||||
{% elif type == "book" %}
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Title</th>
|
||||
<td>{{ book.title }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Authors</th>
|
||||
<td>
|
||||
<ul class="mb-0 list-unstyled">{% for a in book.authors.all %}<li>{{ a }}</li>{% endfor %}</ul>
|
||||
</td>
|
||||
<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 class="w-33" scope="row">Publisher</th>
|
||||
<th scope="row">Publisher</th>
|
||||
<td>{{ book.publisher }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th scope="row">ISBN</th>
|
||||
<td>{{ book.ISBN|default:"-" }}</td>
|
||||
@@ -115,7 +96,7 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% if properties %}
|
||||
{% if book_properties %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -123,7 +104,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-group-divider">
|
||||
{% for p in properties %}
|
||||
{% for p in book_properties %}
|
||||
<tr>
|
||||
<th class="w-33" scope="row">{{ p.property }}</th>
|
||||
<td>{{ p.value }}</td>
|
||||
@@ -133,30 +114,12 @@
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane" 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 class="table-group-divider">
|
||||
{% for d in documents.all %}
|
||||
<tr>
|
||||
<td class="w-33">{{ d.description }}</td>
|
||||
<td><a href="{{ d.file.url }}" target="_blank">{{ d.filename }}</a></td>
|
||||
<td class="text-end">{{ d.file.size | filesizeformat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="tab-pane" 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="{% dynamic_admin_url 'bookshelf' type book.pk %}">Edit</a>{% endif %}
|
||||
{% 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>
|
||||
|
@@ -2,14 +2,14 @@
|
||||
{% block pagination %}
|
||||
{% if data.has_other_pages %}
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
||||
<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.slug search=search page=data.previous_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-left"></i></a>
|
||||
<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"><i class="bi bi-chevron-left"></i></span>
|
||||
<span class="page-link">Previous</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for i in page_range %}
|
||||
@@ -21,17 +21,17 @@
|
||||
{% 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.slug search=search page=i %}#main-content">{{ i }}</a></li>
|
||||
<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 'manufacturer_pagination' manufacturer=manufacturer.slug search=search page=data.next_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-right"></i></a>
|
||||
<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"><i class="bi bi-chevron-right"></i></span>
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
@@ -4,12 +4,7 @@
|
||||
Bookshelf
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="bookshelfDropdownMenuLink">
|
||||
{% if books_menu %}
|
||||
<li><a class="dropdown-item" href="{% url 'books' %}">Books</a></li>
|
||||
{% endif %}
|
||||
{% if catalogs_menu %}
|
||||
<li><a class="dropdown-item" href="{% url 'catalogs' %}">Catalogs</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
|
||||
{% block cards %}
|
||||
{% for d in data %}
|
||||
{% if d.type == "roster" %}
|
||||
{% if d.type == "rolling_stock" %}
|
||||
{% include "cards/roster.html" %}
|
||||
{% elif d.type == "company" %}
|
||||
{% include "cards/company.html" %}
|
||||
@@ -18,7 +18,7 @@
|
||||
{% include "cards/consist.html" %}
|
||||
{% elif d.type == "manufacturer" %}
|
||||
{% include "cards/manufacturer.html" %}
|
||||
{% elif d.type == "book" or d.type == "catalog" %}
|
||||
{% elif d.type == "book" %}
|
||||
{% include "cards/book.html" %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
{% load dynamic_url %}
|
||||
<div class="col">
|
||||
<div class="card shadow-sm">
|
||||
{% if d.item.image.exists %}
|
||||
@@ -19,24 +18,10 @@
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
{% if d.type == "catalog" %}
|
||||
<th colspan="2" scope="row">Catalog</th>
|
||||
{% elif d.type == "book" %}
|
||||
<th colspan="2" scope="row">Book</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-group-divider">
|
||||
{% if d.type == "catalog" %}
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Manufacturer</th>
|
||||
<td>{{ d.item.manufacturer }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Scales</th>
|
||||
<td>{{ d.item.get_scales }}</td>
|
||||
</tr>
|
||||
{% elif d.type == "book" %}
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Authors</th>
|
||||
<td>
|
||||
@@ -47,7 +32,6 @@
|
||||
<th class="w-33" scope="row">Publisher</th>
|
||||
<td>{{ d.item.publisher }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th scope="row">Language</th>
|
||||
<td>{{ d.item.get_language_display }}</td>
|
||||
@@ -64,7 +48,7 @@
|
||||
</table>
|
||||
<div class="d-grid gap-2 mb-1 d-md-block">
|
||||
<a class="btn btn-sm btn-outline-primary" href="{{ d.item.get_absolute_url }}">Show all data</a>
|
||||
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% dynamic_admin_url 'bookshelf' d.type d.item.pk %}">Edit</a>{% endif %}
|
||||
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:bookshelf_book_change' d.item.pk %}">Edit</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -56,11 +56,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Scale</th>
|
||||
<td><a href="{% url 'filtered' _filter="scale" search=d.item.scale.slug %}"><abbr title="{{ d.item.scale.ratio }} - {{ d.item.scale.tracks }} mm">{{ d.item.scale }}</abbr></a></td>
|
||||
<td><a href="{% url 'filtered' _filter="scale" search=d.item.scale.slug %}"><abbr title="{{ d.item.scale.ratio }} - {{ d.item.scale.tracks }}">{{ d.item.scale }}</abbr></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Item number</th>
|
||||
<td>{{ d.item.item_number }}{%if d.item.set %} | <a class="badge text-bg-primary" href="{% url 'manufacturer' manufacturer=d.item.manufacturer.slug search=d.item.item_number_slug %}">SET</a>{% endif %}</td>
|
||||
<td>{{ d.item.item_number }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -17,18 +17,18 @@
|
||||
<th class="w-33" scope="row">Ratio</th>
|
||||
<td>{{ d.item.ratio }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Tracks</th>
|
||||
<td>{{ d.item.tracks }} mm</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Gauge</th>
|
||||
<td>{{ d.item.gauge }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="w-33" scope="row">Tracks</th>
|
||||
<td>{{ d.item.tracks }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="d-grid gap-2 mb-1 d-md-block">
|
||||
<a class="btn btn-sm btn-outline-primary{% if d.item.num_items == 0 %} disabled{% endif %}" href="{% url 'filtered' _filter="scale" search=d.item.slug %}">Show all rolling stock</a>
|
||||
<a class="btn btn-sm btn-outline-primary" href="{% url 'filtered' _filter="scale" search=d.item.slug %}">Show all rolling stock</a>
|
||||
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:metadata_scale_change' d.item.pk %}">Edit</a>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
40
ram/portal/templates/companies.html
Normal file
40
ram/portal/templates/companies.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 'companies_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 'companies_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 'companies_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 %}
|
@@ -26,14 +26,14 @@
|
||||
{% block pagination %}
|
||||
{% if data.has_other_pages %}
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
||||
<ul class="pagination justify-content-center mt-4 mb-0">
|
||||
{% if data.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.previous_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-left"></i></a>
|
||||
<a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
||||
<span class="page-link">Previous</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for i in page_range %}
|
||||
@@ -51,11 +51,11 @@
|
||||
{% endfor %}
|
||||
{% if data.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.next_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-right"></i></a>
|
||||
<a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
40
ram/portal/templates/consists.html
Normal file
40
ram/portal/templates/consists.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 'consists_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 'consists_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 'consists_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 %}
|
@@ -2,14 +2,14 @@
|
||||
{% block pagination %}
|
||||
{% if data.has_other_pages %}
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
||||
<ul class="pagination justify-content-center mt-4 mb-0">
|
||||
{% if data.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=data.previous_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-left"></i></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>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
||||
<span class="page-link">Previous</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for i in page_range %}
|
||||
@@ -27,11 +27,11 @@
|
||||
{% endfor %}
|
||||
{% if data.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=data.next_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-right"></i></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>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{% extends "pagination.html" %}
|
||||
{% extends "roster.html" %}
|
||||
|
||||
{% block header %}
|
||||
<div class="text-muted">{{ site_conf.about | safe }}</div>
|
||||
|
@@ -18,12 +18,7 @@
|
||||
<li><a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
<li><a class="dropdown-item" href="{% url 'admin:portal_siteconfiguration_changelist' %}">Site configuration</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<form id="logout-form" method="post" action="{% url 'admin:logout' %}?next={{ request.path }}">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-link dropdown-item text-danger" type="submit">Log out</button>
|
||||
</form>
|
||||
</li>
|
||||
<li><a class="dropdown-item text-danger" href="{% url 'admin:logout' %}?next={{ request.path }}">Logout</a></li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<a class="nav-link" href="{% url 'admin:login' %}?next={{ request.path }}">Log in</a>
|
||||
|
@@ -3,14 +3,14 @@
|
||||
{% if data.has_other_pages %}
|
||||
{% with data.0.item.category as c %}
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
||||
<ul class="pagination justify-content-center mt-4 mb-0">
|
||||
{% if data.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.previous_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-left"></i></a>
|
||||
<a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
||||
<span class="page-link">Previous</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for i in page_range %}
|
||||
@@ -28,11 +28,11 @@
|
||||
{% endfor %}
|
||||
{% if data.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.next_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-right"></i></a>
|
||||
<a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
@@ -27,11 +27,11 @@
|
||||
{% if rolling_stock.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"><i class="bi bi-chevron-left"></i></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"><i class="bi bi-chevron-right"></i></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -52,7 +52,6 @@
|
||||
{% 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">
|
||||
@@ -64,7 +63,6 @@
|
||||
{% 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 %}
|
||||
@@ -116,11 +114,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Scale</th>
|
||||
<td><a href="{% url 'filtered' _filter="scale" search=rolling_stock.scale %}"><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.tracks }} mm">{{ 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>
|
||||
<th scope="row">Item number</th>
|
||||
<td>{{ rolling_stock.item_number }}{%if rolling_stock.set %} | <a class="badge text-bg-primary" href="{% url 'manufacturer' manufacturer=rolling_stock.manufacturer.slug search=rolling_stock.item_number_slug %}">SET</a>{% endif %}</td>
|
||||
<td>{{ rolling_stock.item_number }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -151,7 +149,6 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane" id="nav-model" role="tabpanel" aria-labelledby="nav-model-tab">
|
||||
{{ rolling_stock.description | safe }}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -169,11 +166,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Scale</th>
|
||||
<td><a href="{% url 'filtered' _filter="scale" search=rolling_stock.scale %}"><abbr title="{{ rolling_stock.scale.ratio }} - {{ rolling_stock.scale.tracks }} mm">{{ 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>
|
||||
<th scope="row">Item number</th>
|
||||
<td>{{ rolling_stock.item_number }}{%if rolling_stock.set %} | <a class="badge text-bg-primary" href="{% url 'manufacturer' manufacturer=rolling_stock.manufacturer.slug search=rolling_stock.item_number_slug %}">SET</a>{% endif %}</td>
|
||||
<td>{{ rolling_stock.item_number }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Era</th>
|
||||
@@ -208,7 +205,6 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tab-pane" id="nav-class" role="tabpanel" aria-labelledby="nav-class-tab">
|
||||
{{ class.description | safe }}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -376,13 +372,6 @@
|
||||
<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 %}
|
||||
|
@@ -1,17 +1,16 @@
|
||||
{% extends "cards.html" %}
|
||||
{% load dynamic_url %}
|
||||
|
||||
{% block pagination %}
|
||||
{% if data.has_other_pages %}
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
||||
<ul class="pagination justify-content-center mt-4 mb-0">
|
||||
{% if data.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% dynamic_pagination type page=data.previous_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-left"></i></a>
|
||||
<a class="page-link" href="{% url 'roster_pagination' page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
||||
<span class="page-link">Previous</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for i in page_range %}
|
||||
@@ -23,17 +22,17 @@
|
||||
{% 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="{% dynamic_pagination type page=i %}#main-content">{{ i }}</a></li>
|
||||
<li class="page-item"><a class="page-link" href="{% url 'roster_pagination' page=i %}#main-content">{{ i }}</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if data.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% dynamic_pagination type page=data.next_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-right"></i></a>
|
||||
<a class="page-link" href="{% url 'roster_pagination' page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
40
ram/portal/templates/scales.html
Normal file
40
ram/portal/templates/scales.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 'scales_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 'scales_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 'scales_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 %}
|
@@ -3,14 +3,14 @@
|
||||
{% block pagination %}
|
||||
{% if data.has_other_pages %}
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
||||
<ul class="pagination justify-content-center mt-4 mb-0">
|
||||
{% if data.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.previous_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-left"></i></a>
|
||||
<a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.previous_page_number %}#main-content" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
||||
<span class="page-link">Previous</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for i in page_range %}
|
||||
@@ -28,11 +28,11 @@
|
||||
{% endfor %}
|
||||
{% if data.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.next_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-right"></i></a>
|
||||
<a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.next_page_number %}#main-content" tabindex="-1">Next</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
||||
<span class="page-link">Next</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
40
ram/portal/templates/types.html
Normal file
40
ram/portal/templates/types.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 'types_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 'types_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 'types_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 %}
|
@@ -1,21 +0,0 @@
|
||||
from django import template
|
||||
from django.urls import reverse
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def dynamic_admin_url(app_name, model_name, object_id=None):
|
||||
if object_id:
|
||||
return reverse(
|
||||
f'admin:{app_name}_{model_name}_change',
|
||||
args=[object_id]
|
||||
)
|
||||
return reverse(f'admin:{app_name}_{model_name}_changelist')
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def dynamic_pagination(reverse_name, page):
|
||||
if reverse_name.endswith('y'):
|
||||
return reverse(f'{reverse_name[:-1]}ies_pagination', args=[page])
|
||||
return reverse(f'{reverse_name}s_pagination', args=[page])
|
@@ -1,21 +1,16 @@
|
||||
from django import template
|
||||
from portal.models import Flatpage
|
||||
from bookshelf.models import Book, Catalog
|
||||
from bookshelf.models import Book
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag('bookshelf/bookshelf_menu.html')
|
||||
def show_bookshelf_menu():
|
||||
# FIXME: Filter out unpublished books and catalogs?
|
||||
return {
|
||||
"bookshelf_menu": (Book.objects.exists() or Catalog.objects.exists()),
|
||||
"books_menu": Book.objects.exists(),
|
||||
"catalogs_menu": Catalog.objects.exists(),
|
||||
}
|
||||
return {"bookshelf_menu": Book.objects.exists()}
|
||||
|
||||
|
||||
@register.inclusion_tag('flatpages/flatpages_menu.html')
|
||||
def show_flatpages_menu(user):
|
||||
menu = Flatpage.objects.get_published(user).order_by("name")
|
||||
def show_flatpages_menu():
|
||||
menu = Flatpage.objects.filter(published=True).order_by("name")
|
||||
return {"flatpages_menu": menu}
|
||||
|
@@ -4,7 +4,6 @@ from portal.views import (
|
||||
GetData,
|
||||
GetRoster,
|
||||
GetObjectsFiltered,
|
||||
GetManufacturerItem,
|
||||
GetFlatpage,
|
||||
GetRollingStock,
|
||||
GetConsist,
|
||||
@@ -14,19 +13,14 @@ from portal.views import (
|
||||
Scales,
|
||||
Types,
|
||||
Books,
|
||||
Catalogs,
|
||||
GetBookCatalog,
|
||||
GetBook,
|
||||
SearchObjects,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("", GetData.as_view(template="home.html"), name="index"),
|
||||
path("roster", GetRoster.as_view(), name="roster"),
|
||||
path(
|
||||
"roster/page/<int:page>",
|
||||
GetRoster.as_view(),
|
||||
name="rosters_pagination"
|
||||
),
|
||||
path("roster/<int:page>", GetRoster.as_view(), name="roster_pagination"),
|
||||
path(
|
||||
"page/<str:flatpage>",
|
||||
GetFlatpage.as_view(),
|
||||
@@ -34,122 +28,88 @@ urlpatterns = [
|
||||
),
|
||||
path(
|
||||
"consists",
|
||||
Consists.as_view(),
|
||||
Consists.as_view(template="consists.html"),
|
||||
name="consists"
|
||||
),
|
||||
path(
|
||||
"consists/page/<int:page>",
|
||||
Consists.as_view(),
|
||||
"consists/<int:page>",
|
||||
Consists.as_view(template="consists.html"),
|
||||
name="consists_pagination"
|
||||
),
|
||||
path("consist/<uuid:uuid>", GetConsist.as_view(), name="consist"),
|
||||
path(
|
||||
"consist/<uuid:uuid>/page/<int:page>",
|
||||
"consist/<uuid:uuid>/<int:page>",
|
||||
GetConsist.as_view(),
|
||||
name="consist_pagination",
|
||||
),
|
||||
path(
|
||||
"companies",
|
||||
Companies.as_view(),
|
||||
Companies.as_view(template="companies.html"),
|
||||
name="companies"
|
||||
),
|
||||
path(
|
||||
"companies/page/<int:page>",
|
||||
Companies.as_view(),
|
||||
"companies/<int:page>",
|
||||
Companies.as_view(template="companies.html"),
|
||||
name="companies_pagination",
|
||||
),
|
||||
path(
|
||||
"manufacturers/<str:category>",
|
||||
Manufacturers.as_view(template="pagination_manufacturers.html"),
|
||||
Manufacturers.as_view(template="manufacturers.html"),
|
||||
name="manufacturers"
|
||||
),
|
||||
path(
|
||||
"manufacturers/<str:category>/page/<int:page>",
|
||||
Manufacturers.as_view(template="pagination_manufacturers.html"),
|
||||
"manufacturers/<str:category>/<int:page>",
|
||||
Manufacturers.as_view(template="manufacturers.html"),
|
||||
name="manufacturers_pagination",
|
||||
),
|
||||
path(
|
||||
"scales",
|
||||
Scales.as_view(),
|
||||
Scales.as_view(template="scales.html"),
|
||||
name="scales"
|
||||
),
|
||||
path(
|
||||
"scales/page/<int:page>",
|
||||
Scales.as_view(),
|
||||
"scales/<int:page>",
|
||||
Scales.as_view(template="scales.html"),
|
||||
name="scales_pagination"
|
||||
),
|
||||
path(
|
||||
"types",
|
||||
Types.as_view(),
|
||||
name="rolling_stock_types"
|
||||
Types.as_view(template="types.html"),
|
||||
name="types"
|
||||
),
|
||||
path(
|
||||
"types/page/<int:page>",
|
||||
Types.as_view(),
|
||||
name="rolling_stock_types_pagination"
|
||||
"types/<int:page>",
|
||||
Types.as_view(template="types.html"),
|
||||
name="types_pagination"
|
||||
),
|
||||
path(
|
||||
"bookshelf/books",
|
||||
Books.as_view(),
|
||||
Books.as_view(template="bookshelf/books.html"),
|
||||
name="books"
|
||||
),
|
||||
path(
|
||||
"bookshelf/books/page/<int:page>",
|
||||
Books.as_view(),
|
||||
"bookshelf/books/<int:page>",
|
||||
Books.as_view(template="bookshelf/books.html"),
|
||||
name="books_pagination"
|
||||
),
|
||||
path(
|
||||
"bookshelf/<str:selector>/<uuid:uuid>",
|
||||
GetBookCatalog.as_view(),
|
||||
name="bookshelf_item"
|
||||
),
|
||||
path(
|
||||
"bookshelf/catalogs",
|
||||
Catalogs.as_view(),
|
||||
name="catalogs"
|
||||
),
|
||||
path(
|
||||
"bookshelf/catalogs/page/<int:page>",
|
||||
Catalogs.as_view(),
|
||||
name="catalogs_pagination"
|
||||
),
|
||||
path("bookshelf/book/<uuid:uuid>", GetBook.as_view(), name="book"),
|
||||
path(
|
||||
"search",
|
||||
SearchObjects.as_view(http_method_names=["post"]),
|
||||
name="search",
|
||||
),
|
||||
path(
|
||||
"search/<str:search>/page/<int:page>",
|
||||
"search/<str:search>/<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>/page/<int:page>",
|
||||
"<str:_filter>/<str:search>/<int:page>",
|
||||
GetObjectsFiltered.as_view(),
|
||||
name="filtered_pagination",
|
||||
),
|
||||
|
@@ -1,14 +1,13 @@
|
||||
import base64
|
||||
import operator
|
||||
from itertools import chain
|
||||
from functools import reduce
|
||||
from urllib.parse import unquote
|
||||
|
||||
from django.views import View
|
||||
from django.http import Http404, HttpResponseBadRequest
|
||||
from django.db.utils import OperationalError, ProgrammingError
|
||||
from django.db.models import Q, Count
|
||||
from django.shortcuts import render, get_object_or_404, get_list_or_404
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
@@ -16,25 +15,13 @@ from portal.utils import get_site_conf
|
||||
from portal.models import Flatpage
|
||||
from roster.models import RollingStock
|
||||
from consist.models import Consist
|
||||
from bookshelf.models import Book, Catalog
|
||||
from bookshelf.models import Book
|
||||
from metadata.models import (
|
||||
Company,
|
||||
Manufacturer,
|
||||
Scale,
|
||||
RollingStockType,
|
||||
Tag,
|
||||
Company, Manufacturer, Scale, DecoderDocument, RollingStockType, Tag
|
||||
)
|
||||
|
||||
|
||||
def get_items_per_page():
|
||||
try:
|
||||
items_per_page = get_site_conf().items_per_page
|
||||
except (OperationalError, ProgrammingError):
|
||||
items_per_page = 6
|
||||
return items_per_page
|
||||
|
||||
|
||||
def get_order_by_field():
|
||||
def order_by_fields():
|
||||
try:
|
||||
order_by = get_site_conf().items_ordering
|
||||
except (OperationalError, ProgrammingError):
|
||||
@@ -55,33 +42,26 @@ def get_order_by_field():
|
||||
return (fields[2], fields[0], fields[1], fields[3])
|
||||
|
||||
|
||||
class Render404(View):
|
||||
def get(self, request, exception):
|
||||
return render(request, "base.html", {"title": "404 page not found"})
|
||||
|
||||
|
||||
class GetData(View):
|
||||
title = "Home"
|
||||
template = "pagination.html"
|
||||
item_type = "roster"
|
||||
filter = Q() # empty filter by default
|
||||
|
||||
def get_data(self, request):
|
||||
return (
|
||||
RollingStock.objects.get_published(request.user)
|
||||
.order_by(*get_order_by_field())
|
||||
.filter(self.filter)
|
||||
)
|
||||
template = "roster.html"
|
||||
item_type = "rolling_stock"
|
||||
queryset = RollingStock.objects.order_by(*order_by_fields())
|
||||
|
||||
def get(self, request, page=1):
|
||||
data = []
|
||||
for item in self.get_data(request):
|
||||
data.append({"type": self.item_type, "item": item})
|
||||
site_conf = get_site_conf()
|
||||
|
||||
paginator = Paginator(data, get_items_per_page())
|
||||
data = []
|
||||
for item in self.queryset:
|
||||
data.append({
|
||||
"type": self.item_type,
|
||||
"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=1, on_ends=1
|
||||
data.number, on_each_side=2, on_ends=1
|
||||
)
|
||||
|
||||
return render(
|
||||
@@ -98,17 +78,14 @@ class GetData(View):
|
||||
|
||||
|
||||
class GetRoster(GetData):
|
||||
title = "The Roster"
|
||||
item_type = "roster"
|
||||
|
||||
def get_data(self, request):
|
||||
return RollingStock.objects.get_published(request.user).order_by(
|
||||
*get_order_by_field()
|
||||
)
|
||||
title = "Roster"
|
||||
item_type = "rolling_stock"
|
||||
queryset = RollingStock.objects.order_by(*order_by_fields())
|
||||
|
||||
|
||||
class SearchObjects(View):
|
||||
def run_search(self, request, search, _filter, page=1):
|
||||
site_conf = get_site_conf()
|
||||
if _filter is None:
|
||||
query = reduce(
|
||||
operator.or_,
|
||||
@@ -149,22 +126,20 @@ class SearchObjects(View):
|
||||
raise Http404
|
||||
|
||||
# FIXME duplicated code!
|
||||
# FIXME see if it makes sense to filter calatogs and books by scale
|
||||
# and manufacturer as well
|
||||
data = []
|
||||
roster = (
|
||||
RollingStock.objects.get_published(request.user)
|
||||
.filter(query)
|
||||
rolling_stock = (
|
||||
RollingStock.objects.filter(query)
|
||||
.distinct()
|
||||
.order_by(*get_order_by_field())
|
||||
.order_by(*order_by_fields())
|
||||
)
|
||||
for item in roster:
|
||||
data.append({"type": "roster", "item": item})
|
||||
|
||||
for item in rolling_stock:
|
||||
data.append({
|
||||
"type": "rolling_stock",
|
||||
"item": item
|
||||
})
|
||||
if _filter is None:
|
||||
consists = (
|
||||
Consist.objects.get_published(request.user)
|
||||
.filter(
|
||||
Consist.objects.filter(
|
||||
Q(
|
||||
Q(identifier__icontains=search)
|
||||
| Q(company__name__icontains=search)
|
||||
@@ -173,24 +148,24 @@ class SearchObjects(View):
|
||||
.distinct()
|
||||
)
|
||||
for item in consists:
|
||||
data.append({"type": "consist", "item": item})
|
||||
data.append({
|
||||
"type": "consist",
|
||||
"item": item
|
||||
})
|
||||
books = (
|
||||
Book.objects.get_published(request.user)
|
||||
.filter(title__icontains=search)
|
||||
Book.objects.filter(title__icontains=search)
|
||||
.distinct()
|
||||
)
|
||||
catalogs = (
|
||||
Catalog.objects.get_published(request.user)
|
||||
.filter(manufacturer__name__icontains=search)
|
||||
.distinct()
|
||||
)
|
||||
for item in list(chain(books, catalogs)):
|
||||
data.append({"type": "book", "item": item})
|
||||
for item in books:
|
||||
data.append({
|
||||
"type": "book",
|
||||
"item": item
|
||||
})
|
||||
|
||||
paginator = Paginator(data, get_items_per_page())
|
||||
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=1, on_ends=1
|
||||
data.number, on_each_side=2, on_ends=1
|
||||
)
|
||||
|
||||
return data, paginator.count, page_range
|
||||
@@ -215,7 +190,8 @@ class SearchObjects(View):
|
||||
encoded_search = search
|
||||
search = base64.b64decode(search.encode()).decode()
|
||||
except Exception:
|
||||
encoded_search = base64.b64encode(search.encode()).decode()
|
||||
encoded_search = base64.b64encode(
|
||||
search.encode()).decode()
|
||||
_filter, keyword = self.split_search(search)
|
||||
data, matches, page_range = self.run_search(
|
||||
request, keyword, _filter, page
|
||||
@@ -225,7 +201,7 @@ class SearchObjects(View):
|
||||
request,
|
||||
"search.html",
|
||||
{
|
||||
"title": 'Search: "{}"'.format(search),
|
||||
"title": "Search: \"{}\"".format(search),
|
||||
"search": search,
|
||||
"encoded_search": encoded_search,
|
||||
"data": data,
|
||||
@@ -239,64 +215,10 @@ class SearchObjects(View):
|
||||
return self.get(request, search, page)
|
||||
|
||||
|
||||
class GetManufacturerItem(View):
|
||||
def get(self, request, manufacturer, search="all", page=1):
|
||||
manufacturer = get_object_or_404(
|
||||
Manufacturer, slug__iexact=manufacturer
|
||||
)
|
||||
|
||||
if search != "all":
|
||||
roster = get_list_or_404(
|
||||
RollingStock.objects.get_published(request.user).order_by(
|
||||
*get_order_by_field()
|
||||
),
|
||||
Q(
|
||||
Q(manufacturer=manufacturer)
|
||||
& Q(item_number_slug__exact=search)
|
||||
),
|
||||
)
|
||||
title = "{0}: {1}".format(
|
||||
manufacturer,
|
||||
# all returned records must have the same `item_number``;
|
||||
# just pick it up the first result, otherwise `search`
|
||||
roster[0].item_number if roster else search,
|
||||
)
|
||||
else:
|
||||
roster = (
|
||||
RollingStock.objects.get_published(request.user)
|
||||
.order_by(*get_order_by_field())
|
||||
.filter(
|
||||
Q(manufacturer=manufacturer)
|
||||
| Q(rolling_class__manufacturer=manufacturer)
|
||||
)
|
||||
)
|
||||
title = "Manufacturer: {0}".format(manufacturer)
|
||||
|
||||
data = []
|
||||
for item in roster:
|
||||
data.append({"type": "roster", "item": item})
|
||||
|
||||
paginator = Paginator(data, get_items_per_page())
|
||||
data = paginator.get_page(page)
|
||||
page_range = paginator.get_elided_page_range(
|
||||
data.number, on_each_side=1, 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()
|
||||
|
||||
if _filter == "type":
|
||||
title = get_object_or_404(RollingStockType, slug__iexact=search)
|
||||
query = Q(rolling_class__type__slug__iexact=search)
|
||||
@@ -304,6 +226,12 @@ 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)
|
||||
@@ -317,47 +245,46 @@ class GetObjectsFiltered(View):
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
roster = (
|
||||
RollingStock.objects.get_published(request.user)
|
||||
.filter(query)
|
||||
rolling_stock = (
|
||||
RollingStock.objects.filter(query)
|
||||
.distinct()
|
||||
.order_by(*get_order_by_field())
|
||||
.order_by(*order_by_fields())
|
||||
)
|
||||
|
||||
data = []
|
||||
for item in roster:
|
||||
data.append({"type": "roster", "item": item})
|
||||
for item in rolling_stock:
|
||||
data.append({
|
||||
"type": "rolling_stock",
|
||||
"item": item
|
||||
})
|
||||
|
||||
try: # Execute only if query_2nd is defined
|
||||
consists = (
|
||||
Consist.objects.get_published(request.user)
|
||||
.filter(query_2nd)
|
||||
Consist.objects.filter(query_2nd)
|
||||
.distinct()
|
||||
)
|
||||
for item in consists:
|
||||
data.append({"type": "consist", "item": item})
|
||||
data.append({
|
||||
"type": "consist",
|
||||
"item": item
|
||||
})
|
||||
if _filter == "tag": # Books can be filtered only by tag
|
||||
books = (
|
||||
Book.objects.get_published(request.user)
|
||||
.filter(query_2nd)
|
||||
Book.objects.filter(query_2nd)
|
||||
.distinct()
|
||||
)
|
||||
for item in books:
|
||||
data.append({"type": "book", "item": item})
|
||||
catalogs = (
|
||||
Catalog.objects.get_published(request.user)
|
||||
.filter(query_2nd)
|
||||
.distinct()
|
||||
)
|
||||
for item in catalogs:
|
||||
data.append({"type": "catalog", "item": item})
|
||||
data.append({
|
||||
"type": "book",
|
||||
"item": item
|
||||
})
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
paginator = Paginator(data, get_items_per_page())
|
||||
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=1, on_ends=1
|
||||
data.number, on_each_side=2, on_ends=1
|
||||
)
|
||||
|
||||
return data, title, paginator.count, page_range
|
||||
@@ -371,7 +298,8 @@ class GetObjectsFiltered(View):
|
||||
request,
|
||||
"filter.html",
|
||||
{
|
||||
"title": "{0}: {1}".format(_filter.capitalize(), title),
|
||||
"title": "{0}: {1}".format(
|
||||
_filter.capitalize(), title),
|
||||
"search": search,
|
||||
"filter": _filter,
|
||||
"data": data,
|
||||
@@ -384,44 +312,40 @@ class GetObjectsFiltered(View):
|
||||
class GetRollingStock(View):
|
||||
def get(self, request, uuid):
|
||||
try:
|
||||
rolling_stock = RollingStock.objects.get_published(
|
||||
request.user
|
||||
).get(uuid=uuid)
|
||||
rolling_stock = RollingStock.objects.get(uuid=uuid)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404
|
||||
|
||||
# FIXME there's likely a better and more efficient way of doing this
|
||||
# but keeping KISS for now
|
||||
decoder_documents = []
|
||||
class_properties = rolling_stock.rolling_class.property.get_public(
|
||||
request.user
|
||||
)
|
||||
properties = rolling_stock.property.get_public(request.user)
|
||||
documents = rolling_stock.document.get_public(request.user)
|
||||
journal = rolling_stock.journal.get_public(request.user)
|
||||
if rolling_stock.decoder:
|
||||
decoder_documents = rolling_stock.decoder.document.get_public(
|
||||
request.user
|
||||
if request.user.is_authenticated:
|
||||
class_properties = rolling_stock.rolling_class.property.all()
|
||||
properties = rolling_stock.property.all()
|
||||
documents = rolling_stock.document.all()
|
||||
journal = rolling_stock.journal.all()
|
||||
if rolling_stock.decoder:
|
||||
decoder_documents = rolling_stock.decoder.document.all()
|
||||
else:
|
||||
class_properties = rolling_stock.rolling_class.property.filter(
|
||||
property__private=False
|
||||
)
|
||||
|
||||
consists = [
|
||||
{"type": "consist", "item": c}
|
||||
for c in Consist.objects.get_published(request.user).filter(
|
||||
consist_item__rolling_stock=rolling_stock
|
||||
properties = rolling_stock.property.filter(
|
||||
property__private=False
|
||||
)
|
||||
] # A dict with "item" is required by the consists card
|
||||
|
||||
set = [
|
||||
{"type": "set", "item": s}
|
||||
for s in RollingStock.objects.get_published(request.user)
|
||||
.filter(
|
||||
Q(
|
||||
Q(item_number__exact=rolling_stock.item_number)
|
||||
& Q(set=True)
|
||||
documents = rolling_stock.document.filter(private=False)
|
||||
journal = rolling_stock.journal.filter(private=False)
|
||||
if rolling_stock.decoder:
|
||||
decoder_documents = rolling_stock.decoder.document.filter(
|
||||
private=False
|
||||
)
|
||||
)
|
||||
.order_by(*get_order_by_field())
|
||||
]
|
||||
|
||||
consists = [{
|
||||
"type": "consist",
|
||||
"item": c
|
||||
} for c in Consist.objects.filter(
|
||||
consist_item__rolling_stock=rolling_stock
|
||||
)] # A dict with "item" is required by the consists card
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -434,7 +358,6 @@ class GetRollingStock(View):
|
||||
"decoder_documents": decoder_documents,
|
||||
"documents": documents,
|
||||
"journal": journal,
|
||||
"set": set,
|
||||
"consists": consists,
|
||||
},
|
||||
)
|
||||
@@ -443,33 +366,25 @@ class GetRollingStock(View):
|
||||
class Consists(GetData):
|
||||
title = "Consists"
|
||||
item_type = "consist"
|
||||
|
||||
def get_data(self, request):
|
||||
return Consist.objects.get_published(request.user).all()
|
||||
queryset = Consist.objects.all()
|
||||
|
||||
|
||||
class GetConsist(View):
|
||||
def get(self, request, uuid, page=1):
|
||||
site_conf = get_site_conf()
|
||||
try:
|
||||
consist = Consist.objects.get_published(request.user).get(
|
||||
uuid=uuid
|
||||
)
|
||||
consist = Consist.objects.get(uuid=uuid)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404
|
||||
data = [
|
||||
{
|
||||
"type": "roster",
|
||||
"item": RollingStock.objects.get_published(request.user).get(
|
||||
uuid=r.rolling_stock_id
|
||||
),
|
||||
}
|
||||
for r in consist.consist_item.all()
|
||||
]
|
||||
data = [{
|
||||
"type": "rolling_stock",
|
||||
"item": RollingStock.objects.get(uuid=r.rolling_stock_id)
|
||||
} for r in consist.consist_item.all()]
|
||||
|
||||
paginator = Paginator(data, get_items_per_page())
|
||||
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=1, on_ends=1
|
||||
data.number, on_each_side=2, on_ends=1
|
||||
)
|
||||
|
||||
return render(
|
||||
@@ -487,25 +402,20 @@ class GetConsist(View):
|
||||
class Manufacturers(GetData):
|
||||
title = "Manufacturers"
|
||||
item_type = "manufacturer"
|
||||
|
||||
def get_data(self, request):
|
||||
return Manufacturer.objects.filter(self.filter)
|
||||
queryset = None # Set via method get
|
||||
|
||||
# overload get method to filter by category
|
||||
def get(self, request, category, page=1):
|
||||
if category not in ("real", "model"):
|
||||
raise Http404
|
||||
self.filter = Q(category=category)
|
||||
|
||||
self.queryset = Manufacturer.objects.filter(category=category)
|
||||
return super().get(request, page)
|
||||
|
||||
|
||||
class Companies(GetData):
|
||||
title = "Companies"
|
||||
item_type = "company"
|
||||
|
||||
def get_data(self, request):
|
||||
return Company.objects.all()
|
||||
queryset = Company.objects.all()
|
||||
|
||||
|
||||
class Scales(GetData):
|
||||
@@ -513,72 +423,50 @@ class Scales(GetData):
|
||||
item_type = "scale"
|
||||
queryset = Scale.objects.all()
|
||||
|
||||
def get_data(self, request):
|
||||
return Scale.objects.annotate(
|
||||
num_items=Count("rollingstock")
|
||||
) # .filter(num_items__gt=0) to filter data with no items
|
||||
|
||||
|
||||
class Types(GetData):
|
||||
title = "Types"
|
||||
item_type = "rolling_stock_type"
|
||||
|
||||
def get_data(self, request):
|
||||
return RollingStockType.objects.all()
|
||||
queryset = RollingStockType.objects.all()
|
||||
|
||||
|
||||
class Books(GetData):
|
||||
title = "Books"
|
||||
item_type = "book"
|
||||
|
||||
def get_data(self, request):
|
||||
return Book.objects.get_published(request.user).all()
|
||||
queryset = Book.objects.all()
|
||||
|
||||
|
||||
class Catalogs(GetData):
|
||||
title = "Catalogs"
|
||||
item_type = "catalog"
|
||||
|
||||
def get_data(self, request):
|
||||
return Catalog.objects.get_published(request.user).all()
|
||||
|
||||
|
||||
class GetBookCatalog(View):
|
||||
def get_object(self, request, uuid, selector):
|
||||
if selector == "book":
|
||||
return Book.objects.get_published(request.user).get(uuid=uuid)
|
||||
elif selector == "catalog":
|
||||
return Catalog.objects.get_published(request.user).get(uuid=uuid)
|
||||
else:
|
||||
raise Http404
|
||||
|
||||
def get(self, request, uuid, selector):
|
||||
class GetBook(View):
|
||||
def get(self, request, uuid):
|
||||
try:
|
||||
book = self.get_object(request, uuid, selector)
|
||||
book = Book.objects.get(uuid=uuid)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404
|
||||
|
||||
properties = book.property.get_public(request.user)
|
||||
documents = book.document.get_public(request.user)
|
||||
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,
|
||||
"documents": documents,
|
||||
"properties": properties,
|
||||
"type": selector
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class GetFlatpage(View):
|
||||
def get(self, request, flatpage):
|
||||
_filter = Q(published=True) # Show only published pages
|
||||
if request.user.is_authenticated:
|
||||
_filter = Q() # Reset the filter if user is authenticated
|
||||
|
||||
try:
|
||||
flatpage = Flatpage.objects.get_published(request.user).get(
|
||||
path=flatpage
|
||||
)
|
||||
flatpage = Flatpage.objects.filter(_filter).get(path=flatpage)
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from ram.utils import git_suffix
|
||||
|
||||
__version__ = "0.14.3"
|
||||
__version__ = "0.10.0"
|
||||
__version__ += git_suffix(__file__)
|
||||
|
@@ -1,13 +0,0 @@
|
||||
from django.core.cache import cache
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Clear the application cache"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
cache.clear()
|
||||
self.stdout.write(self.style.SUCCESS("Cache cleared"))
|
||||
except Exception:
|
||||
raise CommandError("Cache is not active")
|
@@ -1,19 +0,0 @@
|
||||
from django.db import models
|
||||
from django.core.exceptions import FieldError
|
||||
|
||||
|
||||
class PublicManager(models.Manager):
|
||||
def get_published(self, user):
|
||||
if user.is_authenticated:
|
||||
return self.get_queryset()
|
||||
else:
|
||||
return self.get_queryset().filter(published=True)
|
||||
|
||||
def get_public(self, user):
|
||||
if user.is_authenticated:
|
||||
return self.get_queryset()
|
||||
else:
|
||||
try:
|
||||
return self.get_queryset().filter(private=False)
|
||||
except FieldError:
|
||||
return self.get_queryset().filter(property__private=False)
|
@@ -1,25 +1,9 @@
|
||||
import os
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from django.utils.safestring import mark_safe
|
||||
from tinymce import models as tinymce
|
||||
|
||||
from ram.utils import DeduplicatedStorage, get_image_preview
|
||||
from ram.managers import PublicManager
|
||||
|
||||
|
||||
class BaseModel(models.Model):
|
||||
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
notes = tinymce.HTMLField(blank=True)
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
updated_time = models.DateTimeField(auto_now=True)
|
||||
published = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
objects = PublicManager()
|
||||
|
||||
|
||||
class Document(models.Model):
|
||||
@@ -32,7 +16,6 @@ class Document(models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name_plural = "Documents"
|
||||
|
||||
def __str__(self):
|
||||
return "{0}".format(os.path.basename(self.file.name))
|
||||
@@ -45,8 +28,6 @@ class Document(models.Model):
|
||||
'<a href="{0}" target="_blank">Link</a>'.format(self.file.url)
|
||||
)
|
||||
|
||||
objects = PublicManager()
|
||||
|
||||
|
||||
class Image(models.Model):
|
||||
order = models.PositiveIntegerField(default=0, blank=False, null=False)
|
||||
@@ -55,8 +36,8 @@ class Image(models.Model):
|
||||
storage=DeduplicatedStorage,
|
||||
)
|
||||
|
||||
def image_thumbnail(self, max_size=150):
|
||||
return get_image_preview(self.image.url, max_size)
|
||||
def image_thumbnail(self):
|
||||
return get_image_preview(self.image.url)
|
||||
|
||||
image_thumbnail.short_description = "Preview"
|
||||
|
||||
@@ -66,9 +47,6 @@ class Image(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ["order"]
|
||||
verbose_name_plural = "Images"
|
||||
|
||||
objects = PublicManager()
|
||||
|
||||
|
||||
class PropertyInstance(models.Model):
|
||||
@@ -84,5 +62,3 @@ class PropertyInstance(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name_plural = "Properties"
|
||||
|
||||
objects = PublicManager()
|
||||
|
@@ -171,7 +171,6 @@ SITE_NAME = "Railroad Assets Manger"
|
||||
DEFAULT_CARD_IMAGE = "coming_soon.svg"
|
||||
|
||||
DECODER_INTERFACES = [
|
||||
(0, "Built-in"),
|
||||
(1, "NEM651"),
|
||||
(2, "NEM652"),
|
||||
(3, "PluX"),
|
||||
|
@@ -13,7 +13,6 @@ Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect
|
||||
@@ -22,14 +21,15 @@ from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
from ram.views import UploadImage
|
||||
from portal.views import Render404
|
||||
|
||||
handler404 = Render404.as_view()
|
||||
|
||||
urlpatterns = [
|
||||
path("", lambda r: redirect("portal/")),
|
||||
path("tinymce/", include("tinymce.urls")),
|
||||
path("tinymce/upload_image", UploadImage.as_view(), name="upload_image"),
|
||||
path('tinymce/', include('tinymce.urls')),
|
||||
path(
|
||||
"tinymce/upload_image",
|
||||
UploadImage.as_view(),
|
||||
name="upload_image"
|
||||
),
|
||||
path("portal/", include("portal.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
path("api/v1/consist/", include("consist.urls")),
|
||||
|
@@ -44,10 +44,10 @@ def git_suffix(fname):
|
||||
return gh
|
||||
|
||||
|
||||
def get_image_preview(url, max_size=150):
|
||||
def get_image_preview(url):
|
||||
return format_html(
|
||||
'<img src="{src}" style="max-width: {size}px; max-height: {size}px;'
|
||||
'background-color: #eee;" />'.format(src=url, size=max_size)
|
||||
'<img src="%s" style="max-width: 150px; max-height: 150px;'
|
||||
'background-color: #eee;" />' % url
|
||||
)
|
||||
|
||||
|
||||
|
@@ -8,29 +8,29 @@ from PIL import Image, UnidentifiedImageError
|
||||
from django.views import View
|
||||
from django.conf import settings
|
||||
from django.http import (
|
||||
HttpResponseBadRequest,
|
||||
HttpResponseForbidden,
|
||||
JsonResponse,
|
||||
JsonResponse, HttpResponseForbidden, HttpResponse
|
||||
)
|
||||
from django.utils.text import slugify as slugify
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class UploadImage(View):
|
||||
def post(self, request):
|
||||
def post(self, request, application=None, model=None):
|
||||
if not request.user.is_authenticated:
|
||||
raise HttpResponseForbidden()
|
||||
|
||||
file_obj = request.FILES["file"]
|
||||
file_obj = request.FILES['file']
|
||||
file_name, file_extension = os.path.splitext(file_obj.name)
|
||||
file_name = slugify(file_name) + file_extension
|
||||
|
||||
try:
|
||||
Image.open(file_obj)
|
||||
except UnidentifiedImageError:
|
||||
return HttpResponseBadRequest()
|
||||
response = HttpResponse("Invalid extension") # FIXME
|
||||
response.status_code = 400
|
||||
return response
|
||||
|
||||
today = datetime.date.today()
|
||||
container = (
|
||||
@@ -40,23 +40,20 @@ class UploadImage(View):
|
||||
today.strftime("%d"),
|
||||
)
|
||||
|
||||
dir_path = os.path.join(settings.MEDIA_ROOT, *(p for p in container))
|
||||
file_path = os.path.normpath(os.path.join(dir_path, file_name))
|
||||
# even if we apply slugify to the file name, add more hardening
|
||||
# to avoid any path transversal risk
|
||||
if not file_path.startswith(str(settings.MEDIA_ROOT)):
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
Path(dir_path).mkdir(parents=True, exist_ok=True)
|
||||
with open(file_path, "wb+") as f:
|
||||
file_path = os.path.join(
|
||||
settings.MEDIA_ROOT,
|
||||
*(p for p in container)
|
||||
)
|
||||
Path(file_path).mkdir(parents=True, exist_ok=True)
|
||||
with open(os.path.join(file_path, file_name), 'wb+') as f:
|
||||
for chunk in file_obj.chunks():
|
||||
f.write(chunk)
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"message": "Image uploaded successfully",
|
||||
"location": posixpath.join(
|
||||
settings.MEDIA_URL, *(p for p in container), file_name
|
||||
),
|
||||
}
|
||||
)
|
||||
return JsonResponse({
|
||||
'message': 'Image uploaded successfully',
|
||||
'location': posixpath.join(
|
||||
settings.MEDIA_URL,
|
||||
*(p for p in container),
|
||||
file_name
|
||||
)
|
||||
})
|
||||
|
@@ -16,13 +16,11 @@ class RollingClassPropertyInline(admin.TabularInline):
|
||||
model = RollingClassProperty
|
||||
min_num = 0
|
||||
extra = 0
|
||||
autocomplete_fields = ("property",)
|
||||
|
||||
|
||||
@admin.register(RollingClass)
|
||||
class RollingClass(admin.ModelAdmin):
|
||||
inlines = (RollingClassPropertyInline,)
|
||||
autocomplete_fields = ("manufacturer",)
|
||||
list_display = ("__str__", "type", "company")
|
||||
list_filter = ("company", "type__category", "type")
|
||||
search_fields = (
|
||||
@@ -52,7 +50,6 @@ class RollingStockPropertyInline(admin.TabularInline):
|
||||
model = RollingStockProperty
|
||||
min_num = 0
|
||||
extra = 0
|
||||
autocomplete_fields = ("property",)
|
||||
|
||||
|
||||
class RollingStockJournalInline(admin.TabularInline):
|
||||
@@ -107,11 +104,9 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||
RollingStockDocInline,
|
||||
RollingStockJournalInline,
|
||||
)
|
||||
autocomplete_fields = ("rolling_class",)
|
||||
readonly_fields = ("preview", "creation_time", "updated_time")
|
||||
readonly_fields = ("creation_time", "updated_time")
|
||||
list_display = (
|
||||
"__str__",
|
||||
"published",
|
||||
"address",
|
||||
"manufacturer",
|
||||
"scale",
|
||||
@@ -141,16 +136,12 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||
None,
|
||||
{
|
||||
"fields": (
|
||||
"preview",
|
||||
"published",
|
||||
"rolling_class",
|
||||
"road_number",
|
||||
"scale",
|
||||
"manufacturer",
|
||||
"item_number",
|
||||
"set",
|
||||
"era",
|
||||
"description",
|
||||
"production_year",
|
||||
"purchase_date",
|
||||
"notes",
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# Generated by Django 4.1 on 2022-08-23 15:54
|
||||
|
||||
# ckeditor removal
|
||||
# import ckeditor_uploader.fields
|
||||
import ckeditor_uploader.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
@@ -12,9 +11,9 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
# migrations.AlterField(
|
||||
# model_name="rollingstock",
|
||||
# name="notes",
|
||||
# field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
|
||||
# ),
|
||||
migrations.AlterField(
|
||||
model_name="rollingstock",
|
||||
name="notes",
|
||||
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
|
||||
),
|
||||
]
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# Generated by Django 4.1 on 2022-08-27 12:43
|
||||
|
||||
# ckeditor removal
|
||||
# import ckeditor_uploader.fields
|
||||
import ckeditor_uploader.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
@@ -26,8 +25,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
),
|
||||
("date", models.DateField()),
|
||||
# ("log", ckeditor_uploader.fields.RichTextUploadingField()),
|
||||
("log", models.TextField()),
|
||||
("log", ckeditor_uploader.fields.RichTextUploadingField()),
|
||||
("private", models.BooleanField(default=False)),
|
||||
("creation_time", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_time", models.DateTimeField(auto_now=True)),
|
||||
|
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.2 on 2024-03-02 13:30
|
||||
|
||||
import tinymce.models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("roster", "0022_alter_rollingstock_notes_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="rollingclass",
|
||||
name="description",
|
||||
field=tinymce.models.HTMLField(blank=True),
|
||||
),
|
||||
]
|
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.0.2 on 2024-03-02 14:30
|
||||
|
||||
import tinymce.models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("roster", "0023_alter_rollingclass_description"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="rollingstock",
|
||||
name="description",
|
||||
field=tinymce.models.HTMLField(blank=True),
|
||||
),
|
||||
]
|
@@ -1,40 +0,0 @@
|
||||
# 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",
|
||||
),
|
||||
),
|
||||
]
|
@@ -1,31 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-23 21:10
|
||||
|
||||
from django.db import migrations, models
|
||||
from ram.utils import slugify
|
||||
|
||||
|
||||
def gen_item_number_slug(apps, schema_editor):
|
||||
RollingStock = apps.get_model('roster', 'RollingStock')
|
||||
for row in RollingStock.objects.all():
|
||||
if row.item_number:
|
||||
row.item_number_slug = slugify(row.item_number)
|
||||
row.save(update_fields=['item_number_slug'])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("roster", "0025_rollingstock_set_alter_rollingstock_era_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="rollingstock",
|
||||
name="item_number_slug",
|
||||
field=models.CharField(blank=True, editable=False, max_length=32),
|
||||
),
|
||||
migrations.RunPython(
|
||||
gen_item_number_slug,
|
||||
reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
@@ -1,29 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-30 09:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("roster", "0026_rollingstock_item_number_slug"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="rollingstock",
|
||||
name="decoder_interface",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
blank=True,
|
||||
choices=[
|
||||
(0, "Built-in"),
|
||||
(1, "NEM651"),
|
||||
(2, "NEM652"),
|
||||
(3, "PluX"),
|
||||
(4, "21MTC"),
|
||||
(5, "Next18/Next18S"),
|
||||
],
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-04 12:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("roster", "0027_alter_rollingstock_decoder_interface"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="rollingstock",
|
||||
name="published",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-12-22 20:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("roster", "0028_rollingstock_published"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="rollingstockimage",
|
||||
options={"ordering": ["order"], "verbose_name_plural": "Images"},
|
||||
),
|
||||
]
|
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from uuid import uuid4
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
@@ -8,9 +9,8 @@ from django.dispatch import receiver
|
||||
|
||||
from tinymce import models as tinymce
|
||||
|
||||
from ram.models import BaseModel, Document, Image, PropertyInstance
|
||||
from ram.models import Document, Image, PropertyInstance
|
||||
from ram.utils import DeduplicatedStorage
|
||||
from ram.managers import PublicManager
|
||||
from metadata.models import (
|
||||
Scale,
|
||||
Manufacturer,
|
||||
@@ -25,7 +25,7 @@ class RollingClass(models.Model):
|
||||
identifier = models.CharField(max_length=128, unique=False)
|
||||
type = models.ForeignKey(RollingStockType, on_delete=models.CASCADE)
|
||||
company = models.ForeignKey(Company, on_delete=models.CASCADE)
|
||||
description = tinymce.HTMLField(blank=True)
|
||||
description = models.CharField(max_length=256, blank=True)
|
||||
manufacturer = models.ForeignKey(
|
||||
Manufacturer,
|
||||
on_delete=models.CASCADE,
|
||||
@@ -54,7 +54,8 @@ class RollingClassProperty(PropertyInstance):
|
||||
)
|
||||
|
||||
|
||||
class RollingStock(BaseModel):
|
||||
class RollingStock(models.Model):
|
||||
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
rolling_class = models.ForeignKey(
|
||||
RollingClass,
|
||||
on_delete=models.CASCADE,
|
||||
@@ -73,20 +74,7 @@ class RollingStock(BaseModel):
|
||||
limit_choices_to={"category": "model"},
|
||||
)
|
||||
scale = models.ForeignKey(Scale, on_delete=models.CASCADE)
|
||||
item_number = models.CharField(
|
||||
max_length=32,
|
||||
blank=True,
|
||||
help_text="Catalog item number or code",
|
||||
)
|
||||
item_number_slug = models.CharField(
|
||||
max_length=32,
|
||||
blank=True,
|
||||
editable=False
|
||||
)
|
||||
set = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Part of a set",
|
||||
)
|
||||
item_number = models.CharField(max_length=32, blank=True)
|
||||
decoder_interface = models.PositiveSmallIntegerField(
|
||||
choices=settings.DECODER_INTERFACES, null=True, blank=True
|
||||
)
|
||||
@@ -94,17 +82,15 @@ class RollingStock(BaseModel):
|
||||
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,
|
||||
help_text="Era or epoch of the model",
|
||||
)
|
||||
era = models.CharField(max_length=32, blank=True)
|
||||
production_year = models.SmallIntegerField(null=True, blank=True)
|
||||
purchase_date = models.DateField(null=True, blank=True)
|
||||
description = tinymce.HTMLField(blank=True)
|
||||
notes = tinymce.HTMLField(blank=True)
|
||||
tags = models.ManyToManyField(
|
||||
Tag, related_name="rolling_stock", blank=True
|
||||
)
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
updated_time = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["rolling_class", "road_number_int"]
|
||||
@@ -116,9 +102,6 @@ class RollingStock(BaseModel):
|
||||
def get_absolute_url(self):
|
||||
return reverse("rolling_stock", kwargs={"uuid": self.uuid})
|
||||
|
||||
def preview(self):
|
||||
return self.image.first().image_thumbnail(350)
|
||||
|
||||
def country(self):
|
||||
return str(self.rolling_class.company.country)
|
||||
|
||||
@@ -193,10 +176,7 @@ class RollingStockJournal(models.Model):
|
||||
)
|
||||
date = models.DateField()
|
||||
log = tinymce.HTMLField()
|
||||
private = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Journal log will be visible only to logged users",
|
||||
)
|
||||
private = models.BooleanField(default=False)
|
||||
creation_time = models.DateTimeField(auto_now_add=True)
|
||||
updated_time = models.DateTimeField(auto_now=True)
|
||||
|
||||
@@ -206,8 +186,6 @@ class RollingStockJournal(models.Model):
|
||||
class Meta:
|
||||
ordering = ["date", "rolling_stock"]
|
||||
|
||||
objects = PublicManager()
|
||||
|
||||
|
||||
# @receiver(models.signals.post_delete, sender=Cab)
|
||||
# def post_save_image(sender, instance, *args, **kwargs):
|
||||
|
@@ -3,7 +3,7 @@ from roster.views import RosterList, RosterGet, RosterAddress, RosterClass
|
||||
|
||||
urlpatterns = [
|
||||
path("list", RosterList.as_view()),
|
||||
path("get/<uuid:uuid>", RosterGet.as_view()),
|
||||
path("get/<str:uuid>", RosterGet.as_view()),
|
||||
path("address/<int:address>", RosterAddress.as_view()),
|
||||
path("class/<str:class>", RosterClass.as_view()),
|
||||
]
|
||||
|
@@ -6,30 +6,26 @@ from roster.serializers import RollingStockSerializer
|
||||
|
||||
|
||||
class RosterList(ListAPIView):
|
||||
queryset = RollingStock.objects.all()
|
||||
serializer_class = RollingStockSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return RollingStock.objects.get_published(self.request.user)
|
||||
|
||||
|
||||
class RosterGet(RetrieveAPIView):
|
||||
queryset = RollingStock.objects.all()
|
||||
serializer_class = RollingStockSerializer
|
||||
lookup_field = "uuid"
|
||||
schema = AutoSchema(operation_id_base="retrieveRollingStockByUUID")
|
||||
|
||||
def get_queryset(self):
|
||||
return RollingStock.objects.get_published(self.request.user)
|
||||
schema = AutoSchema(operation_id_base="retrieveRollingStockByUUID")
|
||||
|
||||
|
||||
class RosterAddress(ListAPIView):
|
||||
serializer_class = RollingStockSerializer
|
||||
|
||||
schema = AutoSchema(operation_id_base="retrieveRollingStockByAddress")
|
||||
|
||||
def get_queryset(self):
|
||||
address = self.kwargs["address"]
|
||||
return RollingStock.objects.get_published(self.request.user).filter(
|
||||
address=address
|
||||
)
|
||||
return RollingStock.objects.filter(address=address)
|
||||
|
||||
|
||||
class RosterClass(ListAPIView):
|
||||
@@ -39,6 +35,4 @@ class RosterClass(ListAPIView):
|
||||
|
||||
def get_queryset(self):
|
||||
_class = self.kwargs["class"]
|
||||
return RollingStock.objects.get_published(self.request.user).filter(
|
||||
rolling_class__identifier=_class
|
||||
)
|
||||
return RollingStock.objects.filter(rolling_class__identifier=_class)
|
||||
|
Reference in New Issue
Block a user