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:
|
strategy:
|
||||||
max-parallel: 2
|
max-parallel: 2
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.12', '3.13']
|
python-version: ['3.9', '3.10', '3.11', '3.12']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@ __pycache__/
|
|||||||
.Python
|
.Python
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
|
dist/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
|
@@ -49,7 +49,7 @@ It has been developed with:
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Python 3.10+
|
- Python 3.9+
|
||||||
- A USB port when running Arduino hardware (and adaptors if you have a Mac)
|
- A USB port when running Arduino hardware (and adaptors if you have a Mac)
|
||||||
|
|
||||||
## Web portal installation
|
## 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 django.contrib import admin
|
||||||
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
|
from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin
|
||||||
|
|
||||||
from bookshelf.models import (
|
from bookshelf.models import BookProperty, BookImage, Book, Author, Publisher
|
||||||
BaseBookProperty,
|
|
||||||
BaseBookImage,
|
|
||||||
BaseBookDocument,
|
|
||||||
Book,
|
|
||||||
Author,
|
|
||||||
Publisher,
|
|
||||||
Catalog,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BookImageInline(SortableInlineAdminMixin, admin.TabularInline):
|
class BookImageInline(SortableInlineAdminMixin, admin.TabularInline):
|
||||||
model = BaseBookImage
|
model = BookImage
|
||||||
min_num = 0
|
min_num = 0
|
||||||
extra = 0
|
extra = 0
|
||||||
readonly_fields = ("image_thumbnail",)
|
readonly_fields = ("image_thumbnail",)
|
||||||
classes = ["collapse"]
|
classes = ["collapse"]
|
||||||
verbose_name = "Image"
|
|
||||||
|
|
||||||
|
|
||||||
class BookDocInline(admin.TabularInline):
|
|
||||||
model = BaseBookDocument
|
|
||||||
min_num = 0
|
|
||||||
extra = 0
|
|
||||||
classes = ["collapse"]
|
|
||||||
|
|
||||||
|
|
||||||
class BookPropertyInline(admin.TabularInline):
|
class BookPropertyInline(admin.TabularInline):
|
||||||
model = BaseBookProperty
|
model = BookProperty
|
||||||
min_num = 0
|
min_num = 0
|
||||||
extra = 0
|
extra = 0
|
||||||
autocomplete_fields = ("property",)
|
|
||||||
verbose_name = "Property"
|
|
||||||
verbose_name_plural = "Properties"
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Book)
|
@admin.register(Book)
|
||||||
class BookAdmin(SortableAdminBase, admin.ModelAdmin):
|
class BookAdmin(SortableAdminBase, admin.ModelAdmin):
|
||||||
inlines = (
|
inlines = (BookImageInline, BookPropertyInline,)
|
||||||
BookPropertyInline,
|
|
||||||
BookImageInline,
|
|
||||||
BookDocInline,
|
|
||||||
)
|
|
||||||
list_display = (
|
list_display = (
|
||||||
"title",
|
"title",
|
||||||
"get_authors",
|
"get_authors",
|
||||||
"get_publisher",
|
"get_publisher",
|
||||||
"publication_year",
|
"publication_year",
|
||||||
"number_of_pages",
|
"number_of_pages"
|
||||||
"published",
|
|
||||||
)
|
)
|
||||||
autocomplete_fields = ("authors", "publisher")
|
|
||||||
readonly_fields = ("creation_time", "updated_time")
|
|
||||||
search_fields = ("title", "publisher__name", "authors__last_name")
|
search_fields = ("title", "publisher__name", "authors__last_name")
|
||||||
list_filter = ("publisher__name", "authors")
|
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")
|
@admin.display(description="Publisher")
|
||||||
def get_publisher(self, obj):
|
def get_publisher(self, obj):
|
||||||
return obj.publisher.name
|
return obj.publisher.name
|
||||||
@@ -100,10 +42,7 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Author)
|
@admin.register(Author)
|
||||||
class AuthorAdmin(admin.ModelAdmin):
|
class AuthorAdmin(admin.ModelAdmin):
|
||||||
search_fields = (
|
search_fields = ("first_name", "last_name",)
|
||||||
"first_name",
|
|
||||||
"last_name",
|
|
||||||
)
|
|
||||||
list_filter = ("last_name",)
|
list_filter = ("last_name",)
|
||||||
|
|
||||||
|
|
||||||
@@ -111,59 +50,3 @@ class AuthorAdmin(admin.ModelAdmin):
|
|||||||
class PublisherAdmin(admin.ModelAdmin):
|
class PublisherAdmin(admin.ModelAdmin):
|
||||||
list_display = ("name", "country")
|
list_display = ("name", "country")
|
||||||
search_fields = ("name",)
|
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
|
# 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
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import uuid
|
import uuid
|
||||||
@@ -48,8 +47,7 @@ class Migration(migrations.Migration):
|
|||||||
("ISBN", models.CharField(max_length=13, unique=True)),
|
("ISBN", models.CharField(max_length=13, unique=True)),
|
||||||
("publication_year", models.SmallIntegerField(blank=True, null=True)),
|
("publication_year", models.SmallIntegerField(blank=True, null=True)),
|
||||||
("purchase_date", models.DateField(blank=True, null=True)),
|
("purchase_date", models.DateField(blank=True, null=True)),
|
||||||
# ("notes", ckeditor_uploader.fields.RichTextUploadingField(blank=True)),
|
("notes", ckeditor_uploader.fields.RichTextUploadingField(blank=True)),
|
||||||
("notes", models.TextField(blank=True)),
|
|
||||||
("creation_time", models.DateTimeField(auto_now_add=True)),
|
("creation_time", models.DateTimeField(auto_now_add=True)),
|
||||||
("updated_time", models.DateTimeField(auto_now=True)),
|
("updated_time", models.DateTimeField(auto_now=True)),
|
||||||
("authors", models.ManyToManyField(to="bookshelf.author")),
|
("authors", models.ManyToManyField(to="bookshelf.author")),
|
||||||
|
@@ -12,7 +12,7 @@ from django.conf import settings
|
|||||||
|
|
||||||
def move_images(apps, schema_editor):
|
def move_images(apps, schema_editor):
|
||||||
sys.stdout.write("\n Processing files. Please await...")
|
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)
|
fname = os.path.basename(r.image.path)
|
||||||
new_image = bookshelf.models.book_image_upload(r, fname)
|
new_image = bookshelf.models.book_image_upload(r, fname)
|
||||||
new_path = os.path.join(settings.MEDIA_ROOT, new_image)
|
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"),
|
("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 = [
|
operations = [
|
||||||
# migrations.AlterField(
|
migrations.AlterField(
|
||||||
# model_name="bookimage",
|
model_name="bookimage",
|
||||||
# name="image",
|
name="image",
|
||||||
# field=models.ImageField(
|
field=models.ImageField(
|
||||||
# blank=True,
|
blank=True,
|
||||||
# null=True,
|
null=True,
|
||||||
# storage=ram.utils.DeduplicatedStorage,
|
storage=ram.utils.DeduplicatedStorage,
|
||||||
# upload_to=bookshelf.models.book_image_upload,
|
upload_to=bookshelf.models.book_image_upload,
|
||||||
# ),
|
),
|
||||||
# ),
|
),
|
||||||
# migrations.RunPython(
|
migrations.RunPython(
|
||||||
# move_images,
|
move_images,
|
||||||
# reverse_code=migrations.RunPython.noop
|
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 os
|
||||||
import shutil
|
import shutil
|
||||||
|
from uuid import uuid4
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -9,8 +10,7 @@ from tinymce import models as tinymce
|
|||||||
|
|
||||||
from metadata.models import Tag
|
from metadata.models import Tag
|
||||||
from ram.utils import DeduplicatedStorage
|
from ram.utils import DeduplicatedStorage
|
||||||
from ram.models import BaseModel, Image, Document, PropertyInstance
|
from ram.models import Image, PropertyInstance
|
||||||
from metadata.models import Scale, Manufacturer
|
|
||||||
|
|
||||||
|
|
||||||
class Publisher(models.Model):
|
class Publisher(models.Model):
|
||||||
@@ -39,7 +39,11 @@ class Author(models.Model):
|
|||||||
return f"{self.last_name} {self.first_name[0]}."
|
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
|
ISBN = models.CharField(max_length=17, blank=True) # 13 + dashes
|
||||||
language = models.CharField(
|
language = models.CharField(
|
||||||
max_length=7,
|
max_length=7,
|
||||||
@@ -48,65 +52,13 @@ class BaseBook(BaseModel):
|
|||||||
)
|
)
|
||||||
number_of_pages = models.SmallIntegerField(null=True, blank=True)
|
number_of_pages = models.SmallIntegerField(null=True, blank=True)
|
||||||
publication_year = 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)
|
purchase_date = models.DateField(null=True, blank=True)
|
||||||
tags = models.ManyToManyField(
|
tags = models.ManyToManyField(
|
||||||
Tag, related_name="bookshelf", blank=True
|
Tag, related_name="bookshelf", blank=True
|
||||||
)
|
)
|
||||||
|
notes = tinymce.HTMLField(blank=True)
|
||||||
def delete(self, *args, **kwargs):
|
creation_time = models.DateTimeField(auto_now_add=True)
|
||||||
shutil.rmtree(
|
updated_time = models.DateTimeField(auto_now=True)
|
||||||
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)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["title"]
|
ordering = ["title"]
|
||||||
@@ -118,33 +70,42 @@ class Book(BaseBook):
|
|||||||
return self.publisher.name
|
return self.publisher.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse("book", kwargs={"uuid": self.uuid})
|
||||||
"bookshelf_item",
|
|
||||||
kwargs={"selector": "book", "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):
|
def book_image_upload(instance, filename):
|
||||||
manufacturer = models.ForeignKey(
|
return os.path.join(
|
||||||
Manufacturer,
|
"images",
|
||||||
on_delete=models.CASCADE,
|
"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):
|
class BookImage(Image):
|
||||||
scales = self.get_scales
|
book = models.ForeignKey(
|
||||||
return "%s %s %s" % (self.manufacturer.name, self.years, scales)
|
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
|
class BookProperty(PropertyInstance):
|
||||||
def get_scales(self):
|
book = models.ForeignKey(
|
||||||
return "/".join([s.scale for s in self.scales.all()])
|
Book,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
related_name="property",
|
||||||
|
)
|
||||||
|
@@ -3,5 +3,5 @@ from bookshelf.views import BookList, BookGet
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("book/list", BookList.as_view()),
|
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):
|
class BookList(ListAPIView):
|
||||||
|
queryset = Book.objects.all()
|
||||||
serializer_class = BookSerializer
|
serializer_class = BookSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Book.objects.get_published(self.request.user)
|
|
||||||
|
|
||||||
|
|
||||||
class BookGet(RetrieveAPIView):
|
class BookGet(RetrieveAPIView):
|
||||||
|
queryset = Book.objects.all()
|
||||||
serializer_class = BookSerializer
|
serializer_class = BookSerializer
|
||||||
lookup_field = "uuid"
|
lookup_field = "uuid"
|
||||||
schema = AutoSchema(operation_id_base="retrieveBookByUUID")
|
|
||||||
|
|
||||||
def get_queryset(self):
|
schema = AutoSchema(operation_id_base="retrieveBookByUUID")
|
||||||
return Book.objects.get_published(self.request.user)
|
|
||||||
|
@@ -8,8 +8,7 @@ class ConsistItemInline(SortableInlineAdminMixin, admin.TabularInline):
|
|||||||
model = ConsistItem
|
model = ConsistItem
|
||||||
min_num = 1
|
min_num = 1
|
||||||
extra = 0
|
extra = 0
|
||||||
autocomplete_fields = ("rolling_stock",)
|
readonly_fields = ("address", "type", "company", "era")
|
||||||
readonly_fields = ("preview", "published", "address", "type", "company", "era")
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Consist)
|
@admin.register(Consist)
|
||||||
@@ -19,7 +18,7 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
|
|||||||
"creation_time",
|
"creation_time",
|
||||||
"updated_time",
|
"updated_time",
|
||||||
)
|
)
|
||||||
list_display = ("identifier", "published", "company", "era")
|
list_display = ("identifier", "company", "era")
|
||||||
list_filter = list_display
|
list_filter = list_display
|
||||||
search_fields = list_display
|
search_fields = list_display
|
||||||
save_as = True
|
save_as = True
|
||||||
@@ -29,7 +28,6 @@ class ConsistAdmin(SortableAdminBase, admin.ModelAdmin):
|
|||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"fields": (
|
"fields": (
|
||||||
"published",
|
|
||||||
"identifier",
|
"identifier",
|
||||||
"consist_address",
|
"consist_address",
|
||||||
"company",
|
"company",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
# Generated by Django 4.1 on 2022-08-23 15:54
|
# 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
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
@@ -12,9 +11,9 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
# migrations.AlterField(
|
migrations.AlterField(
|
||||||
# model_name="consist",
|
model_name="consist",
|
||||||
# name="notes",
|
name="notes",
|
||||||
# field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
|
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
|
import os
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
|
|
||||||
from ram.models import BaseModel
|
from tinymce import models as tinymce
|
||||||
|
|
||||||
from ram.utils import DeduplicatedStorage
|
from ram.utils import DeduplicatedStorage
|
||||||
from metadata.models import Company, Tag
|
from metadata.models import Company, Tag
|
||||||
from roster.models import RollingStock
|
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)
|
identifier = models.CharField(max_length=128, unique=False)
|
||||||
tags = models.ManyToManyField(Tag, related_name="consist", blank=True)
|
tags = models.ManyToManyField(Tag, related_name="consist", blank=True)
|
||||||
consist_address = models.SmallIntegerField(
|
consist_address = models.SmallIntegerField(
|
||||||
default=None,
|
default=None, null=True, blank=True
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
help_text="DCC consist address if enabled",
|
|
||||||
)
|
)
|
||||||
company = models.ForeignKey(Company, on_delete=models.CASCADE)
|
company = models.ForeignKey(Company, on_delete=models.CASCADE)
|
||||||
era = models.CharField(
|
era = models.CharField(max_length=32, blank=True)
|
||||||
max_length=32,
|
|
||||||
blank=True,
|
|
||||||
help_text="Era or epoch of the consist",
|
|
||||||
)
|
|
||||||
image = models.ImageField(
|
image = models.ImageField(
|
||||||
upload_to=os.path.join("images", "consists"),
|
upload_to=os.path.join("images", "consists"),
|
||||||
storage=DeduplicatedStorage,
|
storage=DeduplicatedStorage,
|
||||||
null=True,
|
null=True,
|
||||||
blank=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):
|
def __str__(self):
|
||||||
return "{0} {1}".format(self.company, self.identifier)
|
return "{0} {1}".format(self.company, self.identifier)
|
||||||
@@ -39,12 +36,6 @@ class Consist(BaseModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("consist", kwargs={"uuid": self.uuid})
|
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:
|
class Meta:
|
||||||
ordering = ["company", "-creation_time"]
|
ordering = ["company", "-creation_time"]
|
||||||
|
|
||||||
@@ -62,13 +53,6 @@ class ConsistItem(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{0}".format(self.rolling_stock)
|
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):
|
def type(self):
|
||||||
return self.rolling_stock.rolling_class.type
|
return self.rolling_stock.rolling_class.type
|
||||||
|
|
||||||
@@ -80,14 +64,3 @@ class ConsistItem(models.Model):
|
|||||||
|
|
||||||
def era(self):
|
def era(self):
|
||||||
return self.rolling_stock.era
|
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):
|
class ConsistList(ListAPIView):
|
||||||
|
queryset = Consist.objects.all()
|
||||||
serializer_class = ConsistSerializer
|
serializer_class = ConsistSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return Consist.objects.get_published(self.request.user)
|
|
||||||
|
|
||||||
|
|
||||||
class ConsistGet(RetrieveAPIView):
|
class ConsistGet(RetrieveAPIView):
|
||||||
|
queryset = Consist.objects.all()
|
||||||
serializer_class = ConsistSerializer
|
serializer_class = ConsistSerializer
|
||||||
lookup_field = "uuid"
|
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.urls import reverse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.dispatch.dispatcher import receiver
|
from django.dispatch.dispatcher import receiver
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django_countries.fields import CountryField
|
from django_countries.fields import CountryField
|
||||||
|
|
||||||
from ram.models import Document
|
from ram.models import Document
|
||||||
from ram.utils import DeduplicatedStorage, get_image_preview, slugify
|
from ram.utils import DeduplicatedStorage, get_image_preview, slugify
|
||||||
from ram.managers import PublicManager
|
|
||||||
|
|
||||||
|
|
||||||
class Property(models.Model):
|
class Property(models.Model):
|
||||||
name = models.CharField(max_length=128, unique=True)
|
name = models.CharField(max_length=128, unique=True)
|
||||||
private = models.BooleanField(
|
private = models.BooleanField(default=False)
|
||||||
default=False,
|
|
||||||
help_text="Property will be only visible to logged users",
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = "Properties"
|
verbose_name_plural = "Properties"
|
||||||
@@ -25,8 +20,6 @@ class Property(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
objects = PublicManager()
|
|
||||||
|
|
||||||
|
|
||||||
class Manufacturer(models.Model):
|
class Manufacturer(models.Model):
|
||||||
name = models.CharField(max_length=128, unique=True)
|
name = models.CharField(max_length=128, unique=True)
|
||||||
@@ -50,11 +43,10 @@ class Manufacturer(models.Model):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
"filtered",
|
"filtered", kwargs={
|
||||||
kwargs={
|
|
||||||
"_filter": "manufacturer",
|
"_filter": "manufacturer",
|
||||||
"search": self.slug,
|
"search": self.slug,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def logo_thumbnail(self):
|
def logo_thumbnail(self):
|
||||||
@@ -85,11 +77,10 @@ class Company(models.Model):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
"filtered",
|
"filtered", kwargs={
|
||||||
kwargs={
|
|
||||||
"_filter": "company",
|
"_filter": "company",
|
||||||
"search": self.slug,
|
"search": self.slug,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def extended_name_pp(self):
|
def extended_name_pp(self):
|
||||||
@@ -118,7 +109,7 @@ class Decoder(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
ordering = ["manufacturer__name", "name"]
|
ordering = ["manufacturer", "name"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{0} - {1}".format(self.manufacturer, self.name)
|
return "{0} - {1}".format(self.manufacturer, self.name)
|
||||||
@@ -138,49 +129,28 @@ class DecoderDocument(Document):
|
|||||||
unique_together = ("decoder", "file")
|
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):
|
class Scale(models.Model):
|
||||||
scale = models.CharField(max_length=32, unique=True)
|
scale = models.CharField(max_length=32, unique=True)
|
||||||
slug = models.CharField(max_length=32, unique=True, editable=False)
|
slug = models.CharField(max_length=32, unique=True, editable=False)
|
||||||
ratio = models.CharField(max_length=16, validators=[calculate_ratio])
|
ratio = models.CharField(max_length=16, blank=True)
|
||||||
ratio_int = models.SmallIntegerField(editable=False, default=0)
|
gauge = models.CharField(max_length=16, blank=True)
|
||||||
tracks = models.FloatField(
|
tracks = models.CharField(max_length=16, blank=True)
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["-ratio_int", "-tracks", "scale"]
|
ordering = ["scale"]
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
"filtered",
|
"filtered", kwargs={
|
||||||
kwargs={
|
|
||||||
"_filter": "scale",
|
"_filter": "scale",
|
||||||
"search": self.slug,
|
"search": self.slug,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.scale)
|
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):
|
class RollingStockType(models.Model):
|
||||||
type = models.CharField(max_length=64)
|
type = models.CharField(max_length=64)
|
||||||
order = models.PositiveSmallIntegerField()
|
order = models.PositiveSmallIntegerField()
|
||||||
@@ -195,11 +165,10 @@ class RollingStockType(models.Model):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
"filtered",
|
"filtered", kwargs={
|
||||||
kwargs={
|
|
||||||
"_filter": "type",
|
"_filter": "type",
|
||||||
"search": self.slug,
|
"search": self.slug,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -218,11 +187,10 @@ class Tag(models.Model):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse(
|
return reverse(
|
||||||
"filtered",
|
"filtered", kwargs={
|
||||||
kwargs={
|
|
||||||
"_filter": "tag",
|
"_filter": "tag",
|
||||||
"search": self.slug,
|
"search": self.slug,
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
# Generated by Django 4.1 on 2022-08-23 15:54
|
# Generated by Django 4.1 on 2022-08-23 15:54
|
||||||
|
|
||||||
# ckeditor dependency removal
|
import ckeditor.fields
|
||||||
# import ckeditor.fields
|
import ckeditor_uploader.fields
|
||||||
# import ckeditor_uploader.fields
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
@@ -13,24 +12,24 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
# migrations.AlterField(
|
migrations.AlterField(
|
||||||
# model_name="flatpage",
|
model_name="flatpage",
|
||||||
# name="content",
|
name="content",
|
||||||
# field=ckeditor_uploader.fields.RichTextUploadingField(),
|
field=ckeditor_uploader.fields.RichTextUploadingField(),
|
||||||
# ),
|
),
|
||||||
# migrations.AlterField(
|
migrations.AlterField(
|
||||||
# model_name="siteconfiguration",
|
model_name="siteconfiguration",
|
||||||
# name="about",
|
name="about",
|
||||||
# field=ckeditor.fields.RichTextField(blank=True),
|
field=ckeditor.fields.RichTextField(blank=True),
|
||||||
# ),
|
),
|
||||||
# migrations.AlterField(
|
migrations.AlterField(
|
||||||
# model_name="siteconfiguration",
|
model_name="siteconfiguration",
|
||||||
# name="footer",
|
name="footer",
|
||||||
# field=ckeditor.fields.RichTextField(blank=True),
|
field=ckeditor.fields.RichTextField(blank=True),
|
||||||
# ),
|
),
|
||||||
# migrations.AlterField(
|
migrations.AlterField(
|
||||||
# model_name="siteconfiguration",
|
model_name="siteconfiguration",
|
||||||
# name="footer_extended",
|
name="footer_extended",
|
||||||
# field=ckeditor.fields.RichTextField(blank=True),
|
field=ckeditor.fields.RichTextField(blank=True),
|
||||||
# ),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -9,7 +9,6 @@ from solo.models import SingletonModel
|
|||||||
from tinymce import models as tinymce
|
from tinymce import models as tinymce
|
||||||
|
|
||||||
from ram import __version__ as app_version
|
from ram import __version__ as app_version
|
||||||
from ram.managers import PublicManager
|
|
||||||
from ram.utils import slugify
|
from ram.utils import slugify
|
||||||
|
|
||||||
|
|
||||||
@@ -73,8 +72,6 @@ class Flatpage(models.Model):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = PublicManager()
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(models.signals.pre_save, sender=Flatpage)
|
@receiver(models.signals.pre_save, sender=Flatpage)
|
||||||
def tag_pre_save(sender, instance, **kwargs):
|
def tag_pre_save(sender, instance, **kwargs):
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
/*!
|
/*!
|
||||||
* Bootstrap Icons v1.11.3 (https://icons.getbootstrap.com/)
|
* Bootstrap Icons v1.11.1 (https://icons.getbootstrap.com/)
|
||||||
* Copyright 2019-2024 The Bootstrap Authors
|
* Copyright 2019-2023 The Bootstrap Authors
|
||||||
* Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE)
|
* Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-display: block;
|
font-display: block;
|
||||||
font-family: "bootstrap-icons";
|
font-family: "bootstrap-icons";
|
||||||
src: url("./fonts/bootstrap-icons.woff2?dd67030699838ea613ee6dbda90effa6") format("woff2"),
|
src: url("./fonts/bootstrap-icons.woff2?2820a3852bdb9a5832199cc61cec4e65") format("woff2"),
|
||||||
url("./fonts/bootstrap-icons.woff?dd67030699838ea613ee6dbda90effa6") format("woff");
|
url("./fonts/bootstrap-icons.woff?2820a3852bdb9a5832199cc61cec4e65") format("woff");
|
||||||
}
|
}
|
||||||
|
|
||||||
.bi::before,
|
.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.png" %}" sizes="any">
|
||||||
<link rel="icon" href="{% static "favicon.svg" %}" type="image/svg+xml">
|
<link rel="icon" href="{% static "favicon.svg" %}" type="image/svg+xml">
|
||||||
{% if site_conf.use_cdn %}
|
{% 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@5.3.2/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-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
{% else %}
|
{% else %}
|
||||||
<link href="{% static "bootstrap@5.3.3/dist/css/bootstrap.min.css" %}" rel="stylesheet">
|
<link href="{% static "bootstrap@5.3.2/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-icons@1.11.1/font/bootstrap-icons.css" %}" rel="stylesheet">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<link href="{% static "css/main.css" %}?v={{ site_conf.version }}" rel="stylesheet">
|
<link href="{% static "css/main.css" %}?v={{ site_conf.version }}" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
@@ -180,13 +180,13 @@
|
|||||||
<li><a class="dropdown-item" href="{% url 'manufacturers' category='model' %}">Manufacturer</a></li>
|
<li><a class="dropdown-item" href="{% url 'manufacturers' category='model' %}">Manufacturer</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li class="ps-2 text-secondary">Prototype</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 'companies' %}">Company</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'manufacturers' category='real' %}">Manufacturer</a></li>
|
<li><a class="dropdown-item" href="{% url 'manufacturers' category='real' %}">Manufacturer</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% show_bookshelf_menu %}
|
{% show_bookshelf_menu %}
|
||||||
{% show_flatpages_menu user %}
|
{% show_flatpages_menu %}
|
||||||
</ul>
|
</ul>
|
||||||
{% include 'includes/search.html' %}
|
{% include 'includes/search.html' %}
|
||||||
</div>
|
</div>
|
||||||
@@ -216,9 +216,9 @@
|
|||||||
</main>
|
</main>
|
||||||
{% include 'includes/footer.html' %}
|
{% include 'includes/footer.html' %}
|
||||||
{% if site_conf.use_cdn %}
|
{% 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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load dynamic_url %}
|
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
{% if book.tags.all %}
|
{% if book.tags.all %}
|
||||||
@@ -28,11 +27,11 @@
|
|||||||
{% if book.image.count > 1 %}
|
{% if book.image.count > 1 %}
|
||||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselControls" data-bs-slide="prev">
|
<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="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>
|
||||||
<button class="carousel-control-next" type="button" data-bs-target="#carouselControls" data-bs-slide="next">
|
<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="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>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -46,53 +45,35 @@
|
|||||||
<div class="mx-auto">
|
<div class="mx-auto">
|
||||||
<nav class="nav nav-tabs d-none d-lg-flex flex-row mb-2" id="nav-tab" role="tablist">
|
<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>
|
<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 %}
|
{% 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>
|
</nav>
|
||||||
<select class="form-select d-lg-none mb-2" id="tabSelector" aria-label="Tab selector">
|
<select class="form-select d-lg-none mb-2" id="tabSelector" aria-label="Tab selector">
|
||||||
<option value="nav-summary" selected>Summary</option>
|
<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 %}
|
{% if book.notes %}<option value="nav-notes">Notes</option>{% endif %}
|
||||||
</select>
|
</select>
|
||||||
<div class="tab-content" id="nav-tabContent">
|
<div class="tab-content" id="nav-tabContent">
|
||||||
<div class="tab-pane show active" id="nav-summary" role="tabpanel" aria-labelledby="nav-summary-tab">
|
<div class="tab-pane show active" id="nav-summary" role="tabpanel" aria-labelledby="nav-summary-tab">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
{{ book.description | safe }}
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% if type == "catalog" %}
|
|
||||||
<th colspan="2" scope="row">Catalog</th>
|
|
||||||
{% elif type == "book" %}
|
|
||||||
<th colspan="2" scope="row">Book</th>
|
<th colspan="2" scope="row">Book</th>
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="table-group-divider">
|
<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>
|
<tr>
|
||||||
<th class="w-33" scope="row">Title</th>
|
<th class="w-33" scope="row">Title</th>
|
||||||
<td>{{ book.title }}</td>
|
<td>{{ book.title }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-33" scope="row">Authors</th>
|
<th scope="row">Authors</th>
|
||||||
<td>
|
<td>
|
||||||
<ul class="mb-0 list-unstyled">{% for a in book.authors.all %}<li>{{ a }}</li>{% endfor %}</ul>
|
<ul class="mb-0 list-unstyled">{% for a in book.authors.all %}<li>{{ a }}</li>{% endfor %}</ul>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-33" scope="row">Publisher</th>
|
<th scope="row">Publisher</th>
|
||||||
<td>{{ book.publisher }}</td>
|
<td>{{ book.publisher }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">ISBN</th>
|
<th scope="row">ISBN</th>
|
||||||
<td>{{ book.ISBN|default:"-" }}</td>
|
<td>{{ book.ISBN|default:"-" }}</td>
|
||||||
@@ -115,7 +96,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% if properties %}
|
{% if book_properties %}
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -123,7 +104,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="table-group-divider">
|
<tbody class="table-group-divider">
|
||||||
{% for p in properties %}
|
{% for p in book_properties %}
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-33" scope="row">{{ p.property }}</th>
|
<th class="w-33" scope="row">{{ p.property }}</th>
|
||||||
<td>{{ p.value }}</td>
|
<td>{{ p.value }}</td>
|
||||||
@@ -133,30 +114,12 @@
|
|||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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">
|
<div class="tab-pane" id="nav-notes" role="tabpanel" aria-labelledby="nav-notes-tab">
|
||||||
{{ book.notes | safe }}
|
{{ book.notes | safe }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,14 +2,14 @@
|
|||||||
{% block pagination %}
|
{% block pagination %}
|
||||||
{% if data.has_other_pages %}
|
{% if data.has_other_pages %}
|
||||||
<nav aria-label="Page navigation example">
|
<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 %}
|
{% if data.has_previous %}
|
||||||
<li class="page-item">
|
<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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
<span class="page-link">Previous</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for i in page_range %}
|
{% for i in page_range %}
|
||||||
@@ -21,17 +21,17 @@
|
|||||||
{% if i == data.paginator.ELLIPSIS %}
|
{% if i == data.paginator.ELLIPSIS %}
|
||||||
<li class="page-item"><span class="page-link">{{ i }}</span></li>
|
<li class="page-item"><span class="page-link">{{ i }}</span></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item"><a class="page-link" href="{% url '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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if data.has_next %}
|
{% if data.has_next %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url '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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
<span class="page-link">Next</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
@@ -4,12 +4,7 @@
|
|||||||
Bookshelf
|
Bookshelf
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="bookshelfDropdownMenuLink">
|
<ul class="dropdown-menu" aria-labelledby="bookshelfDropdownMenuLink">
|
||||||
{% if books_menu %}
|
|
||||||
<li><a class="dropdown-item" href="{% url 'books' %}">Books</a></li>
|
<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>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3">
|
||||||
{% block cards %}
|
{% block cards %}
|
||||||
{% for d in data %}
|
{% for d in data %}
|
||||||
{% if d.type == "roster" %}
|
{% if d.type == "rolling_stock" %}
|
||||||
{% include "cards/roster.html" %}
|
{% include "cards/roster.html" %}
|
||||||
{% elif d.type == "company" %}
|
{% elif d.type == "company" %}
|
||||||
{% include "cards/company.html" %}
|
{% include "cards/company.html" %}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{% include "cards/consist.html" %}
|
{% include "cards/consist.html" %}
|
||||||
{% elif d.type == "manufacturer" %}
|
{% elif d.type == "manufacturer" %}
|
||||||
{% include "cards/manufacturer.html" %}
|
{% include "cards/manufacturer.html" %}
|
||||||
{% elif d.type == "book" or d.type == "catalog" %}
|
{% elif d.type == "book" %}
|
||||||
{% include "cards/book.html" %}
|
{% include "cards/book.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
{% load dynamic_url %}
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
{% if d.item.image.exists %}
|
{% if d.item.image.exists %}
|
||||||
@@ -19,24 +18,10 @@
|
|||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% if d.type == "catalog" %}
|
|
||||||
<th colspan="2" scope="row">Catalog</th>
|
|
||||||
{% elif d.type == "book" %}
|
|
||||||
<th colspan="2" scope="row">Book</th>
|
<th colspan="2" scope="row">Book</th>
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="table-group-divider">
|
<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>
|
<tr>
|
||||||
<th class="w-33" scope="row">Authors</th>
|
<th class="w-33" scope="row">Authors</th>
|
||||||
<td>
|
<td>
|
||||||
@@ -47,7 +32,6 @@
|
|||||||
<th class="w-33" scope="row">Publisher</th>
|
<th class="w-33" scope="row">Publisher</th>
|
||||||
<td>{{ d.item.publisher }}</td>
|
<td>{{ d.item.publisher }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Language</th>
|
<th scope="row">Language</th>
|
||||||
<td>{{ d.item.get_language_display }}</td>
|
<td>{{ d.item.get_language_display }}</td>
|
||||||
@@ -64,7 +48,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<div class="d-grid gap-2 mb-1 d-md-block">
|
<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>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -56,11 +56,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Scale</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Item number</th>
|
<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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -17,18 +17,18 @@
|
|||||||
<th class="w-33" scope="row">Ratio</th>
|
<th class="w-33" scope="row">Ratio</th>
|
||||||
<td>{{ d.item.ratio }}</td>
|
<td>{{ d.item.ratio }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th class="w-33" scope="row">Tracks</th>
|
|
||||||
<td>{{ d.item.tracks }} mm</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-33" scope="row">Gauge</th>
|
<th class="w-33" scope="row">Gauge</th>
|
||||||
<td>{{ d.item.gauge }}</td>
|
<td>{{ d.item.gauge }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="w-33" scope="row">Tracks</th>
|
||||||
|
<td>{{ d.item.tracks }}</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="d-grid gap-2 mb-1 d-md-block">
|
<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 %}
|
{% 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>
|
||||||
</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 %}
|
{% block pagination %}
|
||||||
{% if data.has_other_pages %}
|
{% if data.has_other_pages %}
|
||||||
<nav aria-label="Page navigation example">
|
<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 %}
|
{% if data.has_previous %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.previous_page_number %}#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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
<span class="page-link">Previous</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for i in page_range %}
|
{% for i in page_range %}
|
||||||
@@ -51,11 +51,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if data.has_next %}
|
{% if data.has_next %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'consist_pagination' uuid=consist.uuid page=data.next_page_number %}#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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
<span class="page-link">Next</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</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 %}
|
{% block pagination %}
|
||||||
{% if data.has_other_pages %}
|
{% if data.has_other_pages %}
|
||||||
<nav aria-label="Page navigation example">
|
<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 %}
|
{% if data.has_previous %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=data.previous_page_number %}#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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
<span class="page-link">Previous</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for i in page_range %}
|
{% for i in page_range %}
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if data.has_next %}
|
{% if data.has_next %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'filtered_pagination' _filter=filter search=search page=data.next_page_number %}#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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
<span class="page-link">Next</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{% extends "pagination.html" %}
|
{% extends "roster.html" %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="text-muted">{{ site_conf.about | safe }}</div>
|
<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:index' %}">Admin</a></li>
|
||||||
<li><a class="dropdown-item" href="{% url 'admin:portal_siteconfiguration_changelist' %}">Site configuration</a></li>
|
<li><a class="dropdown-item" href="{% url 'admin:portal_siteconfiguration_changelist' %}">Site configuration</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li>
|
<li><a class="dropdown-item text-danger" href="{% url 'admin:logout' %}?next={{ request.path }}">Logout</a></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>
|
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="nav-link" href="{% url 'admin:login' %}?next={{ request.path }}">Log in</a>
|
<a class="nav-link" href="{% url 'admin:login' %}?next={{ request.path }}">Log in</a>
|
||||||
|
@@ -3,14 +3,14 @@
|
|||||||
{% if data.has_other_pages %}
|
{% if data.has_other_pages %}
|
||||||
{% with data.0.item.category as c %}
|
{% with data.0.item.category as c %}
|
||||||
<nav aria-label="Page navigation example">
|
<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 %}
|
{% if data.has_previous %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.previous_page_number %}#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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
<span class="page-link">Previous</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for i in page_range %}
|
{% for i in page_range %}
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if data.has_next %}
|
{% if data.has_next %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'manufacturers_pagination' category=c page=data.next_page_number %}#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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
<span class="page-link">Next</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
@@ -27,11 +27,11 @@
|
|||||||
{% if rolling_stock.image.count > 1 %}
|
{% if rolling_stock.image.count > 1 %}
|
||||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselControls" data-bs-slide="prev">
|
<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="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>
|
||||||
<button class="carousel-control-next" type="button" data-bs-target="#carouselControls" data-bs-slide="next">
|
<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="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>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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 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 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 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 %}
|
{% 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>
|
</nav>
|
||||||
<select class="form-select d-lg-none mb-2" id="tabSelector" aria-label="Tab selector">
|
<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 documents or decoder_documents %}<option value="nav-documents">Documents</option>{% endif %}
|
||||||
{% if journal %}<option value="nav-journal">Journal</option>{% endif %}
|
{% if journal %}<option value="nav-journal">Journal</option>{% endif %}
|
||||||
{% if rolling_stock.notes %}<option value="nav-notes">Notes</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 %}
|
{% if consists %}<option value="nav-consists">Consists</option>{% endif %}
|
||||||
</select>
|
</select>
|
||||||
{% with class=rolling_stock.rolling_class company=rolling_stock.rolling_class.company %}
|
{% with class=rolling_stock.rolling_class company=rolling_stock.rolling_class.company %}
|
||||||
@@ -116,11 +114,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Scale</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Item number</th>
|
<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>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -151,7 +149,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="nav-model" role="tabpanel" aria-labelledby="nav-model-tab">
|
<div class="tab-pane" id="nav-model" role="tabpanel" aria-labelledby="nav-model-tab">
|
||||||
{{ rolling_stock.description | safe }}
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -169,11 +166,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Scale</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Item number</th>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Era</th>
|
<th scope="row">Era</th>
|
||||||
@@ -208,7 +205,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="nav-class" role="tabpanel" aria-labelledby="nav-class-tab">
|
<div class="tab-pane" id="nav-class" role="tabpanel" aria-labelledby="nav-class-tab">
|
||||||
{{ class.description | safe }}
|
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -376,13 +372,6 @@
|
|||||||
<div class="tab-pane" id="nav-notes" role="tabpanel" aria-labelledby="nav-notes-tab">
|
<div class="tab-pane" id="nav-notes" role="tabpanel" aria-labelledby="nav-notes-tab">
|
||||||
{{ rolling_stock.notes | safe }}
|
{{ rolling_stock.notes | safe }}
|
||||||
</div>
|
</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="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">
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-3 mb-3">
|
||||||
{% for d in consists %}
|
{% for d in consists %}
|
||||||
|
@@ -1,17 +1,16 @@
|
|||||||
{% extends "cards.html" %}
|
{% extends "cards.html" %}
|
||||||
{% load dynamic_url %}
|
|
||||||
|
|
||||||
{% block pagination %}
|
{% block pagination %}
|
||||||
{% if data.has_other_pages %}
|
{% if data.has_other_pages %}
|
||||||
<nav aria-label="Page navigation example">
|
<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 %}
|
{% if data.has_previous %}
|
||||||
<li class="page-item">
|
<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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
<span class="page-link">Previous</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for i in page_range %}
|
{% for i in page_range %}
|
||||||
@@ -23,17 +22,17 @@
|
|||||||
{% if i == data.paginator.ELLIPSIS %}
|
{% if i == data.paginator.ELLIPSIS %}
|
||||||
<li class="page-item"><span class="page-link">{{ i }}</span></li>
|
<li class="page-item"><span class="page-link">{{ i }}</span></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item"><a class="page-link" href="{% 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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if data.has_next %}
|
{% if data.has_next %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% 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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
<span class="page-link">Next</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</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 %}
|
{% block pagination %}
|
||||||
{% if data.has_other_pages %}
|
{% if data.has_other_pages %}
|
||||||
<nav aria-label="Page navigation example">
|
<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 %}
|
{% if data.has_previous %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.previous_page_number %}#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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
<span class="page-link">Previous</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for i in page_range %}
|
{% for i in page_range %}
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if data.has_next %}
|
{% if data.has_next %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'search_pagination' search=encoded_search page=data.next_page_number %}#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>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
<span class="page-link">Next</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</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 django import template
|
||||||
from portal.models import Flatpage
|
from portal.models import Flatpage
|
||||||
from bookshelf.models import Book, Catalog
|
from bookshelf.models import Book
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('bookshelf/bookshelf_menu.html')
|
@register.inclusion_tag('bookshelf/bookshelf_menu.html')
|
||||||
def show_bookshelf_menu():
|
def show_bookshelf_menu():
|
||||||
# FIXME: Filter out unpublished books and catalogs?
|
return {"bookshelf_menu": Book.objects.exists()}
|
||||||
return {
|
|
||||||
"bookshelf_menu": (Book.objects.exists() or Catalog.objects.exists()),
|
|
||||||
"books_menu": Book.objects.exists(),
|
|
||||||
"catalogs_menu": Catalog.objects.exists(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('flatpages/flatpages_menu.html')
|
@register.inclusion_tag('flatpages/flatpages_menu.html')
|
||||||
def show_flatpages_menu(user):
|
def show_flatpages_menu():
|
||||||
menu = Flatpage.objects.get_published(user).order_by("name")
|
menu = Flatpage.objects.filter(published=True).order_by("name")
|
||||||
return {"flatpages_menu": menu}
|
return {"flatpages_menu": menu}
|
||||||
|
@@ -4,7 +4,6 @@ from portal.views import (
|
|||||||
GetData,
|
GetData,
|
||||||
GetRoster,
|
GetRoster,
|
||||||
GetObjectsFiltered,
|
GetObjectsFiltered,
|
||||||
GetManufacturerItem,
|
|
||||||
GetFlatpage,
|
GetFlatpage,
|
||||||
GetRollingStock,
|
GetRollingStock,
|
||||||
GetConsist,
|
GetConsist,
|
||||||
@@ -14,19 +13,14 @@ from portal.views import (
|
|||||||
Scales,
|
Scales,
|
||||||
Types,
|
Types,
|
||||||
Books,
|
Books,
|
||||||
Catalogs,
|
GetBook,
|
||||||
GetBookCatalog,
|
|
||||||
SearchObjects,
|
SearchObjects,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", GetData.as_view(template="home.html"), name="index"),
|
path("", GetData.as_view(template="home.html"), name="index"),
|
||||||
path("roster", GetRoster.as_view(), name="roster"),
|
path("roster", GetRoster.as_view(), name="roster"),
|
||||||
path(
|
path("roster/<int:page>", GetRoster.as_view(), name="roster_pagination"),
|
||||||
"roster/page/<int:page>",
|
|
||||||
GetRoster.as_view(),
|
|
||||||
name="rosters_pagination"
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"page/<str:flatpage>",
|
"page/<str:flatpage>",
|
||||||
GetFlatpage.as_view(),
|
GetFlatpage.as_view(),
|
||||||
@@ -34,122 +28,88 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"consists",
|
"consists",
|
||||||
Consists.as_view(),
|
Consists.as_view(template="consists.html"),
|
||||||
name="consists"
|
name="consists"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"consists/page/<int:page>",
|
"consists/<int:page>",
|
||||||
Consists.as_view(),
|
Consists.as_view(template="consists.html"),
|
||||||
name="consists_pagination"
|
name="consists_pagination"
|
||||||
),
|
),
|
||||||
path("consist/<uuid:uuid>", GetConsist.as_view(), name="consist"),
|
path("consist/<uuid:uuid>", GetConsist.as_view(), name="consist"),
|
||||||
path(
|
path(
|
||||||
"consist/<uuid:uuid>/page/<int:page>",
|
"consist/<uuid:uuid>/<int:page>",
|
||||||
GetConsist.as_view(),
|
GetConsist.as_view(),
|
||||||
name="consist_pagination",
|
name="consist_pagination",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"companies",
|
"companies",
|
||||||
Companies.as_view(),
|
Companies.as_view(template="companies.html"),
|
||||||
name="companies"
|
name="companies"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"companies/page/<int:page>",
|
"companies/<int:page>",
|
||||||
Companies.as_view(),
|
Companies.as_view(template="companies.html"),
|
||||||
name="companies_pagination",
|
name="companies_pagination",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"manufacturers/<str:category>",
|
"manufacturers/<str:category>",
|
||||||
Manufacturers.as_view(template="pagination_manufacturers.html"),
|
Manufacturers.as_view(template="manufacturers.html"),
|
||||||
name="manufacturers"
|
name="manufacturers"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"manufacturers/<str:category>/page/<int:page>",
|
"manufacturers/<str:category>/<int:page>",
|
||||||
Manufacturers.as_view(template="pagination_manufacturers.html"),
|
Manufacturers.as_view(template="manufacturers.html"),
|
||||||
name="manufacturers_pagination",
|
name="manufacturers_pagination",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"scales",
|
"scales",
|
||||||
Scales.as_view(),
|
Scales.as_view(template="scales.html"),
|
||||||
name="scales"
|
name="scales"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"scales/page/<int:page>",
|
"scales/<int:page>",
|
||||||
Scales.as_view(),
|
Scales.as_view(template="scales.html"),
|
||||||
name="scales_pagination"
|
name="scales_pagination"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"types",
|
"types",
|
||||||
Types.as_view(),
|
Types.as_view(template="types.html"),
|
||||||
name="rolling_stock_types"
|
name="types"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"types/page/<int:page>",
|
"types/<int:page>",
|
||||||
Types.as_view(),
|
Types.as_view(template="types.html"),
|
||||||
name="rolling_stock_types_pagination"
|
name="types_pagination"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"bookshelf/books",
|
"bookshelf/books",
|
||||||
Books.as_view(),
|
Books.as_view(template="bookshelf/books.html"),
|
||||||
name="books"
|
name="books"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"bookshelf/books/page/<int:page>",
|
"bookshelf/books/<int:page>",
|
||||||
Books.as_view(),
|
Books.as_view(template="bookshelf/books.html"),
|
||||||
name="books_pagination"
|
name="books_pagination"
|
||||||
),
|
),
|
||||||
path(
|
path("bookshelf/book/<uuid:uuid>", GetBook.as_view(), name="book"),
|
||||||
"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(
|
path(
|
||||||
"search",
|
"search",
|
||||||
SearchObjects.as_view(http_method_names=["post"]),
|
SearchObjects.as_view(http_method_names=["post"]),
|
||||||
name="search",
|
name="search",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"search/<str:search>/page/<int:page>",
|
"search/<str:search>/<int:page>",
|
||||||
SearchObjects.as_view(),
|
SearchObjects.as_view(),
|
||||||
name="search_pagination",
|
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(
|
path(
|
||||||
"<str:_filter>/<str:search>",
|
"<str:_filter>/<str:search>",
|
||||||
GetObjectsFiltered.as_view(),
|
GetObjectsFiltered.as_view(),
|
||||||
name="filtered",
|
name="filtered",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<str:_filter>/<str:search>/page/<int:page>",
|
"<str:_filter>/<str:search>/<int:page>",
|
||||||
GetObjectsFiltered.as_view(),
|
GetObjectsFiltered.as_view(),
|
||||||
name="filtered_pagination",
|
name="filtered_pagination",
|
||||||
),
|
),
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
import base64
|
import base64
|
||||||
import operator
|
import operator
|
||||||
from itertools import chain
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.http import Http404, HttpResponseBadRequest
|
from django.http import Http404, HttpResponseBadRequest
|
||||||
from django.db.utils import OperationalError, ProgrammingError
|
from django.db.utils import OperationalError, ProgrammingError
|
||||||
from django.db.models import Q, Count
|
from django.db.models import Q
|
||||||
from django.shortcuts import render, get_object_or_404, get_list_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
|
||||||
@@ -16,25 +15,13 @@ from portal.utils import get_site_conf
|
|||||||
from portal.models import Flatpage
|
from portal.models import Flatpage
|
||||||
from roster.models import RollingStock
|
from roster.models import RollingStock
|
||||||
from consist.models import Consist
|
from consist.models import Consist
|
||||||
from bookshelf.models import Book, Catalog
|
from bookshelf.models import Book
|
||||||
from metadata.models import (
|
from metadata.models import (
|
||||||
Company,
|
Company, Manufacturer, Scale, DecoderDocument, RollingStockType, Tag
|
||||||
Manufacturer,
|
|
||||||
Scale,
|
|
||||||
RollingStockType,
|
|
||||||
Tag,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_items_per_page():
|
def order_by_fields():
|
||||||
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():
|
|
||||||
try:
|
try:
|
||||||
order_by = get_site_conf().items_ordering
|
order_by = get_site_conf().items_ordering
|
||||||
except (OperationalError, ProgrammingError):
|
except (OperationalError, ProgrammingError):
|
||||||
@@ -55,33 +42,26 @@ def get_order_by_field():
|
|||||||
return (fields[2], fields[0], fields[1], fields[3])
|
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):
|
class GetData(View):
|
||||||
title = "Home"
|
title = "Home"
|
||||||
template = "pagination.html"
|
template = "roster.html"
|
||||||
item_type = "roster"
|
item_type = "rolling_stock"
|
||||||
filter = Q() # empty filter by default
|
queryset = RollingStock.objects.order_by(*order_by_fields())
|
||||||
|
|
||||||
def get_data(self, request):
|
|
||||||
return (
|
|
||||||
RollingStock.objects.get_published(request.user)
|
|
||||||
.order_by(*get_order_by_field())
|
|
||||||
.filter(self.filter)
|
|
||||||
)
|
|
||||||
|
|
||||||
def get(self, request, page=1):
|
def get(self, request, page=1):
|
||||||
data = []
|
site_conf = get_site_conf()
|
||||||
for item in self.get_data(request):
|
|
||||||
data.append({"type": self.item_type, "item": item})
|
|
||||||
|
|
||||||
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)
|
data = paginator.get_page(page)
|
||||||
page_range = paginator.get_elided_page_range(
|
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(
|
return render(
|
||||||
@@ -98,17 +78,14 @@ class GetData(View):
|
|||||||
|
|
||||||
|
|
||||||
class GetRoster(GetData):
|
class GetRoster(GetData):
|
||||||
title = "The Roster"
|
title = "Roster"
|
||||||
item_type = "roster"
|
item_type = "rolling_stock"
|
||||||
|
queryset = RollingStock.objects.order_by(*order_by_fields())
|
||||||
def get_data(self, request):
|
|
||||||
return RollingStock.objects.get_published(request.user).order_by(
|
|
||||||
*get_order_by_field()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SearchObjects(View):
|
class SearchObjects(View):
|
||||||
def run_search(self, request, search, _filter, page=1):
|
def run_search(self, request, search, _filter, page=1):
|
||||||
|
site_conf = get_site_conf()
|
||||||
if _filter is None:
|
if _filter is None:
|
||||||
query = reduce(
|
query = reduce(
|
||||||
operator.or_,
|
operator.or_,
|
||||||
@@ -149,22 +126,20 @@ class SearchObjects(View):
|
|||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
# FIXME duplicated code!
|
# FIXME duplicated code!
|
||||||
# FIXME see if it makes sense to filter calatogs and books by scale
|
|
||||||
# and manufacturer as well
|
|
||||||
data = []
|
data = []
|
||||||
roster = (
|
rolling_stock = (
|
||||||
RollingStock.objects.get_published(request.user)
|
RollingStock.objects.filter(query)
|
||||||
.filter(query)
|
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by(*get_order_by_field())
|
.order_by(*order_by_fields())
|
||||||
)
|
)
|
||||||
for item in roster:
|
for item in rolling_stock:
|
||||||
data.append({"type": "roster", "item": item})
|
data.append({
|
||||||
|
"type": "rolling_stock",
|
||||||
|
"item": item
|
||||||
|
})
|
||||||
if _filter is None:
|
if _filter is None:
|
||||||
consists = (
|
consists = (
|
||||||
Consist.objects.get_published(request.user)
|
Consist.objects.filter(
|
||||||
.filter(
|
|
||||||
Q(
|
Q(
|
||||||
Q(identifier__icontains=search)
|
Q(identifier__icontains=search)
|
||||||
| Q(company__name__icontains=search)
|
| Q(company__name__icontains=search)
|
||||||
@@ -173,24 +148,24 @@ class SearchObjects(View):
|
|||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
for item in consists:
|
for item in consists:
|
||||||
data.append({"type": "consist", "item": item})
|
data.append({
|
||||||
|
"type": "consist",
|
||||||
|
"item": item
|
||||||
|
})
|
||||||
books = (
|
books = (
|
||||||
Book.objects.get_published(request.user)
|
Book.objects.filter(title__icontains=search)
|
||||||
.filter(title__icontains=search)
|
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
catalogs = (
|
for item in books:
|
||||||
Catalog.objects.get_published(request.user)
|
data.append({
|
||||||
.filter(manufacturer__name__icontains=search)
|
"type": "book",
|
||||||
.distinct()
|
"item": item
|
||||||
)
|
})
|
||||||
for item in list(chain(books, catalogs)):
|
|
||||||
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)
|
data = paginator.get_page(page)
|
||||||
page_range = paginator.get_elided_page_range(
|
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
|
return data, paginator.count, page_range
|
||||||
@@ -215,7 +190,8 @@ class SearchObjects(View):
|
|||||||
encoded_search = search
|
encoded_search = search
|
||||||
search = base64.b64decode(search.encode()).decode()
|
search = base64.b64decode(search.encode()).decode()
|
||||||
except Exception:
|
except Exception:
|
||||||
encoded_search = base64.b64encode(search.encode()).decode()
|
encoded_search = base64.b64encode(
|
||||||
|
search.encode()).decode()
|
||||||
_filter, keyword = self.split_search(search)
|
_filter, keyword = self.split_search(search)
|
||||||
data, matches, page_range = self.run_search(
|
data, matches, page_range = self.run_search(
|
||||||
request, keyword, _filter, page
|
request, keyword, _filter, page
|
||||||
@@ -225,7 +201,7 @@ class SearchObjects(View):
|
|||||||
request,
|
request,
|
||||||
"search.html",
|
"search.html",
|
||||||
{
|
{
|
||||||
"title": 'Search: "{}"'.format(search),
|
"title": "Search: \"{}\"".format(search),
|
||||||
"search": search,
|
"search": search,
|
||||||
"encoded_search": encoded_search,
|
"encoded_search": encoded_search,
|
||||||
"data": data,
|
"data": data,
|
||||||
@@ -239,64 +215,10 @@ class SearchObjects(View):
|
|||||||
return self.get(request, search, page)
|
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):
|
class GetObjectsFiltered(View):
|
||||||
def run_filter(self, request, search, _filter, page=1):
|
def run_filter(self, request, search, _filter, page=1):
|
||||||
|
site_conf = get_site_conf()
|
||||||
|
|
||||||
if _filter == "type":
|
if _filter == "type":
|
||||||
title = get_object_or_404(RollingStockType, slug__iexact=search)
|
title = get_object_or_404(RollingStockType, slug__iexact=search)
|
||||||
query = Q(rolling_class__type__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)
|
title = get_object_or_404(Company, slug__iexact=search)
|
||||||
query = Q(rolling_class__company__slug__iexact=search)
|
query = Q(rolling_class__company__slug__iexact=search)
|
||||||
query_2nd = Q(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":
|
elif _filter == "scale":
|
||||||
title = get_object_or_404(Scale, slug__iexact=search)
|
title = get_object_or_404(Scale, slug__iexact=search)
|
||||||
query = Q(scale__slug__iexact=search)
|
query = Q(scale__slug__iexact=search)
|
||||||
@@ -317,47 +245,46 @@ class GetObjectsFiltered(View):
|
|||||||
else:
|
else:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
roster = (
|
rolling_stock = (
|
||||||
RollingStock.objects.get_published(request.user)
|
RollingStock.objects.filter(query)
|
||||||
.filter(query)
|
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by(*get_order_by_field())
|
.order_by(*order_by_fields())
|
||||||
)
|
)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for item in roster:
|
for item in rolling_stock:
|
||||||
data.append({"type": "roster", "item": item})
|
data.append({
|
||||||
|
"type": "rolling_stock",
|
||||||
|
"item": item
|
||||||
|
})
|
||||||
|
|
||||||
try: # Execute only if query_2nd is defined
|
try: # Execute only if query_2nd is defined
|
||||||
consists = (
|
consists = (
|
||||||
Consist.objects.get_published(request.user)
|
Consist.objects.filter(query_2nd)
|
||||||
.filter(query_2nd)
|
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
for item in consists:
|
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
|
if _filter == "tag": # Books can be filtered only by tag
|
||||||
books = (
|
books = (
|
||||||
Book.objects.get_published(request.user)
|
Book.objects.filter(query_2nd)
|
||||||
.filter(query_2nd)
|
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
for item in books:
|
for item in books:
|
||||||
data.append({"type": "book", "item": item})
|
data.append({
|
||||||
catalogs = (
|
"type": "book",
|
||||||
Catalog.objects.get_published(request.user)
|
"item": item
|
||||||
.filter(query_2nd)
|
})
|
||||||
.distinct()
|
|
||||||
)
|
|
||||||
for item in catalogs:
|
|
||||||
data.append({"type": "catalog", "item": item})
|
|
||||||
except NameError:
|
except NameError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
paginator = Paginator(data, get_items_per_page())
|
paginator = Paginator(data, site_conf.items_per_page)
|
||||||
data = paginator.get_page(page)
|
data = paginator.get_page(page)
|
||||||
page_range = paginator.get_elided_page_range(
|
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
|
return data, title, paginator.count, page_range
|
||||||
@@ -371,7 +298,8 @@ class GetObjectsFiltered(View):
|
|||||||
request,
|
request,
|
||||||
"filter.html",
|
"filter.html",
|
||||||
{
|
{
|
||||||
"title": "{0}: {1}".format(_filter.capitalize(), title),
|
"title": "{0}: {1}".format(
|
||||||
|
_filter.capitalize(), title),
|
||||||
"search": search,
|
"search": search,
|
||||||
"filter": _filter,
|
"filter": _filter,
|
||||||
"data": data,
|
"data": data,
|
||||||
@@ -384,44 +312,40 @@ class GetObjectsFiltered(View):
|
|||||||
class GetRollingStock(View):
|
class GetRollingStock(View):
|
||||||
def get(self, request, uuid):
|
def get(self, request, uuid):
|
||||||
try:
|
try:
|
||||||
rolling_stock = RollingStock.objects.get_published(
|
rolling_stock = RollingStock.objects.get(uuid=uuid)
|
||||||
request.user
|
|
||||||
).get(uuid=uuid)
|
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
# FIXME there's likely a better and more efficient way of doing this
|
# FIXME there's likely a better and more efficient way of doing this
|
||||||
# but keeping KISS for now
|
# but keeping KISS for now
|
||||||
decoder_documents = []
|
decoder_documents = []
|
||||||
class_properties = rolling_stock.rolling_class.property.get_public(
|
if request.user.is_authenticated:
|
||||||
request.user
|
class_properties = rolling_stock.rolling_class.property.all()
|
||||||
)
|
properties = rolling_stock.property.all()
|
||||||
properties = rolling_stock.property.get_public(request.user)
|
documents = rolling_stock.document.all()
|
||||||
documents = rolling_stock.document.get_public(request.user)
|
journal = rolling_stock.journal.all()
|
||||||
journal = rolling_stock.journal.get_public(request.user)
|
if rolling_stock.decoder:
|
||||||
if rolling_stock.decoder:
|
decoder_documents = rolling_stock.decoder.document.all()
|
||||||
decoder_documents = rolling_stock.decoder.document.get_public(
|
else:
|
||||||
request.user
|
class_properties = rolling_stock.rolling_class.property.filter(
|
||||||
|
property__private=False
|
||||||
)
|
)
|
||||||
|
properties = rolling_stock.property.filter(
|
||||||
consists = [
|
property__private=False
|
||||||
{"type": "consist", "item": c}
|
|
||||||
for c in Consist.objects.get_published(request.user).filter(
|
|
||||||
consist_item__rolling_stock=rolling_stock
|
|
||||||
)
|
)
|
||||||
] # A dict with "item" is required by the consists card
|
documents = rolling_stock.document.filter(private=False)
|
||||||
|
journal = rolling_stock.journal.filter(private=False)
|
||||||
set = [
|
if rolling_stock.decoder:
|
||||||
{"type": "set", "item": s}
|
decoder_documents = rolling_stock.decoder.document.filter(
|
||||||
for s in RollingStock.objects.get_published(request.user)
|
private=False
|
||||||
.filter(
|
|
||||||
Q(
|
|
||||||
Q(item_number__exact=rolling_stock.item_number)
|
|
||||||
& Q(set=True)
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
.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(
|
return render(
|
||||||
request,
|
request,
|
||||||
@@ -434,7 +358,6 @@ class GetRollingStock(View):
|
|||||||
"decoder_documents": decoder_documents,
|
"decoder_documents": decoder_documents,
|
||||||
"documents": documents,
|
"documents": documents,
|
||||||
"journal": journal,
|
"journal": journal,
|
||||||
"set": set,
|
|
||||||
"consists": consists,
|
"consists": consists,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -443,33 +366,25 @@ class GetRollingStock(View):
|
|||||||
class Consists(GetData):
|
class Consists(GetData):
|
||||||
title = "Consists"
|
title = "Consists"
|
||||||
item_type = "consist"
|
item_type = "consist"
|
||||||
|
queryset = Consist.objects.all()
|
||||||
def get_data(self, request):
|
|
||||||
return Consist.objects.get_published(request.user).all()
|
|
||||||
|
|
||||||
|
|
||||||
class GetConsist(View):
|
class GetConsist(View):
|
||||||
def get(self, request, uuid, page=1):
|
def get(self, request, uuid, page=1):
|
||||||
|
site_conf = get_site_conf()
|
||||||
try:
|
try:
|
||||||
consist = Consist.objects.get_published(request.user).get(
|
consist = Consist.objects.get(uuid=uuid)
|
||||||
uuid=uuid
|
|
||||||
)
|
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
data = [
|
data = [{
|
||||||
{
|
"type": "rolling_stock",
|
||||||
"type": "roster",
|
"item": RollingStock.objects.get(uuid=r.rolling_stock_id)
|
||||||
"item": RollingStock.objects.get_published(request.user).get(
|
} for r in consist.consist_item.all()]
|
||||||
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)
|
data = paginator.get_page(page)
|
||||||
page_range = paginator.get_elided_page_range(
|
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(
|
return render(
|
||||||
@@ -487,25 +402,20 @@ class GetConsist(View):
|
|||||||
class Manufacturers(GetData):
|
class Manufacturers(GetData):
|
||||||
title = "Manufacturers"
|
title = "Manufacturers"
|
||||||
item_type = "manufacturer"
|
item_type = "manufacturer"
|
||||||
|
queryset = None # Set via method get
|
||||||
def get_data(self, request):
|
|
||||||
return Manufacturer.objects.filter(self.filter)
|
|
||||||
|
|
||||||
# overload get method to filter by category
|
# overload get method to filter by category
|
||||||
def get(self, request, category, page=1):
|
def get(self, request, category, page=1):
|
||||||
if category not in ("real", "model"):
|
if category not in ("real", "model"):
|
||||||
raise Http404
|
raise Http404
|
||||||
self.filter = Q(category=category)
|
self.queryset = Manufacturer.objects.filter(category=category)
|
||||||
|
|
||||||
return super().get(request, page)
|
return super().get(request, page)
|
||||||
|
|
||||||
|
|
||||||
class Companies(GetData):
|
class Companies(GetData):
|
||||||
title = "Companies"
|
title = "Companies"
|
||||||
item_type = "company"
|
item_type = "company"
|
||||||
|
queryset = Company.objects.all()
|
||||||
def get_data(self, request):
|
|
||||||
return Company.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
class Scales(GetData):
|
class Scales(GetData):
|
||||||
@@ -513,72 +423,50 @@ class Scales(GetData):
|
|||||||
item_type = "scale"
|
item_type = "scale"
|
||||||
queryset = Scale.objects.all()
|
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):
|
class Types(GetData):
|
||||||
title = "Types"
|
title = "Types"
|
||||||
item_type = "rolling_stock_type"
|
item_type = "rolling_stock_type"
|
||||||
|
queryset = RollingStockType.objects.all()
|
||||||
def get_data(self, request):
|
|
||||||
return RollingStockType.objects.all()
|
|
||||||
|
|
||||||
|
|
||||||
class Books(GetData):
|
class Books(GetData):
|
||||||
title = "Books"
|
title = "Books"
|
||||||
item_type = "book"
|
item_type = "book"
|
||||||
|
queryset = Book.objects.all()
|
||||||
def get_data(self, request):
|
|
||||||
return Book.objects.get_published(request.user).all()
|
|
||||||
|
|
||||||
|
|
||||||
class Catalogs(GetData):
|
class GetBook(View):
|
||||||
title = "Catalogs"
|
def get(self, request, uuid):
|
||||||
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):
|
|
||||||
try:
|
try:
|
||||||
book = self.get_object(request, uuid, selector)
|
book = Book.objects.get(uuid=uuid)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
properties = book.property.get_public(request.user)
|
book_properties = (
|
||||||
documents = book.document.get_public(request.user)
|
book.property.all()
|
||||||
|
if request.user.is_authenticated
|
||||||
|
else book.property.filter(property__private=False)
|
||||||
|
)
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"bookshelf/book.html",
|
"bookshelf/book.html",
|
||||||
{
|
{
|
||||||
"title": book,
|
"title": book,
|
||||||
|
"book_properties": book_properties,
|
||||||
"book": book,
|
"book": book,
|
||||||
"documents": documents,
|
|
||||||
"properties": properties,
|
|
||||||
"type": selector
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetFlatpage(View):
|
class GetFlatpage(View):
|
||||||
def get(self, request, flatpage):
|
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:
|
try:
|
||||||
flatpage = Flatpage.objects.get_published(request.user).get(
|
flatpage = Flatpage.objects.filter(_filter).get(path=flatpage)
|
||||||
path=flatpage
|
|
||||||
)
|
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from ram.utils import git_suffix
|
from ram.utils import git_suffix
|
||||||
|
|
||||||
__version__ = "0.14.3"
|
__version__ = "0.10.0"
|
||||||
__version__ += git_suffix(__file__)
|
__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
|
import os
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from tinymce import models as tinymce
|
|
||||||
|
|
||||||
from ram.utils import DeduplicatedStorage, get_image_preview
|
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):
|
class Document(models.Model):
|
||||||
@@ -32,7 +16,6 @@ class Document(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
verbose_name_plural = "Documents"
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{0}".format(os.path.basename(self.file.name))
|
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)
|
'<a href="{0}" target="_blank">Link</a>'.format(self.file.url)
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = PublicManager()
|
|
||||||
|
|
||||||
|
|
||||||
class Image(models.Model):
|
class Image(models.Model):
|
||||||
order = models.PositiveIntegerField(default=0, blank=False, null=False)
|
order = models.PositiveIntegerField(default=0, blank=False, null=False)
|
||||||
@@ -55,8 +36,8 @@ class Image(models.Model):
|
|||||||
storage=DeduplicatedStorage,
|
storage=DeduplicatedStorage,
|
||||||
)
|
)
|
||||||
|
|
||||||
def image_thumbnail(self, max_size=150):
|
def image_thumbnail(self):
|
||||||
return get_image_preview(self.image.url, max_size)
|
return get_image_preview(self.image.url)
|
||||||
|
|
||||||
image_thumbnail.short_description = "Preview"
|
image_thumbnail.short_description = "Preview"
|
||||||
|
|
||||||
@@ -66,9 +47,6 @@ class Image(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
ordering = ["order"]
|
ordering = ["order"]
|
||||||
verbose_name_plural = "Images"
|
|
||||||
|
|
||||||
objects = PublicManager()
|
|
||||||
|
|
||||||
|
|
||||||
class PropertyInstance(models.Model):
|
class PropertyInstance(models.Model):
|
||||||
@@ -84,5 +62,3 @@ class PropertyInstance(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
verbose_name_plural = "Properties"
|
verbose_name_plural = "Properties"
|
||||||
|
|
||||||
objects = PublicManager()
|
|
||||||
|
@@ -171,7 +171,6 @@ SITE_NAME = "Railroad Assets Manger"
|
|||||||
DEFAULT_CARD_IMAGE = "coming_soon.svg"
|
DEFAULT_CARD_IMAGE = "coming_soon.svg"
|
||||||
|
|
||||||
DECODER_INTERFACES = [
|
DECODER_INTERFACES = [
|
||||||
(0, "Built-in"),
|
|
||||||
(1, "NEM651"),
|
(1, "NEM651"),
|
||||||
(2, "NEM652"),
|
(2, "NEM652"),
|
||||||
(3, "PluX"),
|
(3, "PluX"),
|
||||||
|
@@ -13,7 +13,6 @@ Including another URLconf
|
|||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
@@ -22,14 +21,15 @@ from django.contrib import admin
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
|
||||||
from ram.views import UploadImage
|
from ram.views import UploadImage
|
||||||
from portal.views import Render404
|
|
||||||
|
|
||||||
handler404 = Render404.as_view()
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", lambda r: redirect("portal/")),
|
path("", lambda r: redirect("portal/")),
|
||||||
path("tinymce/", include("tinymce.urls")),
|
path('tinymce/', include('tinymce.urls')),
|
||||||
path("tinymce/upload_image", UploadImage.as_view(), name="upload_image"),
|
path(
|
||||||
|
"tinymce/upload_image",
|
||||||
|
UploadImage.as_view(),
|
||||||
|
name="upload_image"
|
||||||
|
),
|
||||||
path("portal/", include("portal.urls")),
|
path("portal/", include("portal.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/v1/consist/", include("consist.urls")),
|
path("api/v1/consist/", include("consist.urls")),
|
||||||
|
@@ -44,10 +44,10 @@ def git_suffix(fname):
|
|||||||
return gh
|
return gh
|
||||||
|
|
||||||
|
|
||||||
def get_image_preview(url, max_size=150):
|
def get_image_preview(url):
|
||||||
return format_html(
|
return format_html(
|
||||||
'<img src="{src}" style="max-width: {size}px; max-height: {size}px;'
|
'<img src="%s" style="max-width: 150px; max-height: 150px;'
|
||||||
'background-color: #eee;" />'.format(src=url, size=max_size)
|
'background-color: #eee;" />' % url
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -8,29 +8,29 @@ from PIL import Image, UnidentifiedImageError
|
|||||||
from django.views import View
|
from django.views import View
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import (
|
from django.http import (
|
||||||
HttpResponseBadRequest,
|
JsonResponse, HttpResponseForbidden, HttpResponse
|
||||||
HttpResponseForbidden,
|
|
||||||
JsonResponse,
|
|
||||||
)
|
)
|
||||||
from django.utils.text import slugify as slugify
|
from django.utils.text import slugify as slugify
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(csrf_exempt, name="dispatch")
|
@method_decorator(csrf_exempt, name='dispatch')
|
||||||
class UploadImage(View):
|
class UploadImage(View):
|
||||||
def post(self, request):
|
def post(self, request, application=None, model=None):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
raise HttpResponseForbidden()
|
raise HttpResponseForbidden()
|
||||||
|
|
||||||
file_obj = request.FILES["file"]
|
file_obj = request.FILES['file']
|
||||||
file_name, file_extension = os.path.splitext(file_obj.name)
|
file_name, file_extension = os.path.splitext(file_obj.name)
|
||||||
file_name = slugify(file_name) + file_extension
|
file_name = slugify(file_name) + file_extension
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Image.open(file_obj)
|
Image.open(file_obj)
|
||||||
except UnidentifiedImageError:
|
except UnidentifiedImageError:
|
||||||
return HttpResponseBadRequest()
|
response = HttpResponse("Invalid extension") # FIXME
|
||||||
|
response.status_code = 400
|
||||||
|
return response
|
||||||
|
|
||||||
today = datetime.date.today()
|
today = datetime.date.today()
|
||||||
container = (
|
container = (
|
||||||
@@ -40,23 +40,20 @@ class UploadImage(View):
|
|||||||
today.strftime("%d"),
|
today.strftime("%d"),
|
||||||
)
|
)
|
||||||
|
|
||||||
dir_path = os.path.join(settings.MEDIA_ROOT, *(p for p in container))
|
file_path = os.path.join(
|
||||||
file_path = os.path.normpath(os.path.join(dir_path, file_name))
|
settings.MEDIA_ROOT,
|
||||||
# even if we apply slugify to the file name, add more hardening
|
*(p for p in container)
|
||||||
# to avoid any path transversal risk
|
)
|
||||||
if not file_path.startswith(str(settings.MEDIA_ROOT)):
|
Path(file_path).mkdir(parents=True, exist_ok=True)
|
||||||
return HttpResponseBadRequest()
|
with open(os.path.join(file_path, file_name), 'wb+') as f:
|
||||||
|
|
||||||
Path(dir_path).mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(file_path, "wb+") as f:
|
|
||||||
for chunk in file_obj.chunks():
|
for chunk in file_obj.chunks():
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
|
|
||||||
return JsonResponse(
|
return JsonResponse({
|
||||||
{
|
'message': 'Image uploaded successfully',
|
||||||
"message": "Image uploaded successfully",
|
'location': posixpath.join(
|
||||||
"location": posixpath.join(
|
settings.MEDIA_URL,
|
||||||
settings.MEDIA_URL, *(p for p in container), file_name
|
*(p for p in container),
|
||||||
),
|
file_name
|
||||||
}
|
)
|
||||||
)
|
})
|
||||||
|
@@ -16,13 +16,11 @@ class RollingClassPropertyInline(admin.TabularInline):
|
|||||||
model = RollingClassProperty
|
model = RollingClassProperty
|
||||||
min_num = 0
|
min_num = 0
|
||||||
extra = 0
|
extra = 0
|
||||||
autocomplete_fields = ("property",)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(RollingClass)
|
@admin.register(RollingClass)
|
||||||
class RollingClass(admin.ModelAdmin):
|
class RollingClass(admin.ModelAdmin):
|
||||||
inlines = (RollingClassPropertyInline,)
|
inlines = (RollingClassPropertyInline,)
|
||||||
autocomplete_fields = ("manufacturer",)
|
|
||||||
list_display = ("__str__", "type", "company")
|
list_display = ("__str__", "type", "company")
|
||||||
list_filter = ("company", "type__category", "type")
|
list_filter = ("company", "type__category", "type")
|
||||||
search_fields = (
|
search_fields = (
|
||||||
@@ -52,7 +50,6 @@ class RollingStockPropertyInline(admin.TabularInline):
|
|||||||
model = RollingStockProperty
|
model = RollingStockProperty
|
||||||
min_num = 0
|
min_num = 0
|
||||||
extra = 0
|
extra = 0
|
||||||
autocomplete_fields = ("property",)
|
|
||||||
|
|
||||||
|
|
||||||
class RollingStockJournalInline(admin.TabularInline):
|
class RollingStockJournalInline(admin.TabularInline):
|
||||||
@@ -107,11 +104,9 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
|
|||||||
RollingStockDocInline,
|
RollingStockDocInline,
|
||||||
RollingStockJournalInline,
|
RollingStockJournalInline,
|
||||||
)
|
)
|
||||||
autocomplete_fields = ("rolling_class",)
|
readonly_fields = ("creation_time", "updated_time")
|
||||||
readonly_fields = ("preview", "creation_time", "updated_time")
|
|
||||||
list_display = (
|
list_display = (
|
||||||
"__str__",
|
"__str__",
|
||||||
"published",
|
|
||||||
"address",
|
"address",
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
"scale",
|
"scale",
|
||||||
@@ -141,16 +136,12 @@ class RollingStockAdmin(SortableAdminBase, admin.ModelAdmin):
|
|||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"fields": (
|
"fields": (
|
||||||
"preview",
|
|
||||||
"published",
|
|
||||||
"rolling_class",
|
"rolling_class",
|
||||||
"road_number",
|
"road_number",
|
||||||
"scale",
|
"scale",
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
"item_number",
|
"item_number",
|
||||||
"set",
|
|
||||||
"era",
|
"era",
|
||||||
"description",
|
|
||||||
"production_year",
|
"production_year",
|
||||||
"purchase_date",
|
"purchase_date",
|
||||||
"notes",
|
"notes",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
# Generated by Django 4.1 on 2022-08-23 15:54
|
# 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
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
@@ -12,9 +11,9 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
# migrations.AlterField(
|
migrations.AlterField(
|
||||||
# model_name="rollingstock",
|
model_name="rollingstock",
|
||||||
# name="notes",
|
name="notes",
|
||||||
# field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
|
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True),
|
||||||
# ),
|
),
|
||||||
]
|
]
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
# Generated by Django 4.1 on 2022-08-27 12:43
|
# 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
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
@@ -26,8 +25,7 @@ class Migration(migrations.Migration):
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
("date", models.DateField()),
|
("date", models.DateField()),
|
||||||
# ("log", ckeditor_uploader.fields.RichTextUploadingField()),
|
("log", ckeditor_uploader.fields.RichTextUploadingField()),
|
||||||
("log", models.TextField()),
|
|
||||||
("private", models.BooleanField(default=False)),
|
("private", models.BooleanField(default=False)),
|
||||||
("creation_time", models.DateTimeField(auto_now_add=True)),
|
("creation_time", models.DateTimeField(auto_now_add=True)),
|
||||||
("updated_time", models.DateTimeField(auto_now=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 os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
from uuid import uuid4
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@@ -8,9 +9,8 @@ from django.dispatch import receiver
|
|||||||
|
|
||||||
from tinymce import models as tinymce
|
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.utils import DeduplicatedStorage
|
||||||
from ram.managers import PublicManager
|
|
||||||
from metadata.models import (
|
from metadata.models import (
|
||||||
Scale,
|
Scale,
|
||||||
Manufacturer,
|
Manufacturer,
|
||||||
@@ -25,7 +25,7 @@ class RollingClass(models.Model):
|
|||||||
identifier = models.CharField(max_length=128, unique=False)
|
identifier = models.CharField(max_length=128, unique=False)
|
||||||
type = models.ForeignKey(RollingStockType, on_delete=models.CASCADE)
|
type = models.ForeignKey(RollingStockType, on_delete=models.CASCADE)
|
||||||
company = models.ForeignKey(Company, 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 = models.ForeignKey(
|
||||||
Manufacturer,
|
Manufacturer,
|
||||||
on_delete=models.CASCADE,
|
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(
|
rolling_class = models.ForeignKey(
|
||||||
RollingClass,
|
RollingClass,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@@ -73,20 +74,7 @@ class RollingStock(BaseModel):
|
|||||||
limit_choices_to={"category": "model"},
|
limit_choices_to={"category": "model"},
|
||||||
)
|
)
|
||||||
scale = models.ForeignKey(Scale, on_delete=models.CASCADE)
|
scale = models.ForeignKey(Scale, on_delete=models.CASCADE)
|
||||||
item_number = models.CharField(
|
item_number = models.CharField(max_length=32, blank=True)
|
||||||
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",
|
|
||||||
)
|
|
||||||
decoder_interface = models.PositiveSmallIntegerField(
|
decoder_interface = models.PositiveSmallIntegerField(
|
||||||
choices=settings.DECODER_INTERFACES, null=True, blank=True
|
choices=settings.DECODER_INTERFACES, null=True, blank=True
|
||||||
)
|
)
|
||||||
@@ -94,17 +82,15 @@ class RollingStock(BaseModel):
|
|||||||
Decoder, on_delete=models.CASCADE, null=True, blank=True
|
Decoder, on_delete=models.CASCADE, null=True, blank=True
|
||||||
)
|
)
|
||||||
address = models.SmallIntegerField(default=None, null=True, blank=True)
|
address = models.SmallIntegerField(default=None, null=True, blank=True)
|
||||||
era = models.CharField(
|
era = models.CharField(max_length=32, blank=True)
|
||||||
max_length=32,
|
|
||||||
blank=True,
|
|
||||||
help_text="Era or epoch of the model",
|
|
||||||
)
|
|
||||||
production_year = models.SmallIntegerField(null=True, blank=True)
|
production_year = models.SmallIntegerField(null=True, blank=True)
|
||||||
purchase_date = models.DateField(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(
|
tags = models.ManyToManyField(
|
||||||
Tag, related_name="rolling_stock", blank=True
|
Tag, related_name="rolling_stock", blank=True
|
||||||
)
|
)
|
||||||
|
creation_time = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_time = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["rolling_class", "road_number_int"]
|
ordering = ["rolling_class", "road_number_int"]
|
||||||
@@ -116,9 +102,6 @@ class RollingStock(BaseModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("rolling_stock", kwargs={"uuid": self.uuid})
|
return reverse("rolling_stock", kwargs={"uuid": self.uuid})
|
||||||
|
|
||||||
def preview(self):
|
|
||||||
return self.image.first().image_thumbnail(350)
|
|
||||||
|
|
||||||
def country(self):
|
def country(self):
|
||||||
return str(self.rolling_class.company.country)
|
return str(self.rolling_class.company.country)
|
||||||
|
|
||||||
@@ -193,10 +176,7 @@ class RollingStockJournal(models.Model):
|
|||||||
)
|
)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
log = tinymce.HTMLField()
|
log = tinymce.HTMLField()
|
||||||
private = models.BooleanField(
|
private = models.BooleanField(default=False)
|
||||||
default=False,
|
|
||||||
help_text="Journal log will be visible only to logged users",
|
|
||||||
)
|
|
||||||
creation_time = models.DateTimeField(auto_now_add=True)
|
creation_time = models.DateTimeField(auto_now_add=True)
|
||||||
updated_time = models.DateTimeField(auto_now=True)
|
updated_time = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
@@ -206,8 +186,6 @@ class RollingStockJournal(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["date", "rolling_stock"]
|
ordering = ["date", "rolling_stock"]
|
||||||
|
|
||||||
objects = PublicManager()
|
|
||||||
|
|
||||||
|
|
||||||
# @receiver(models.signals.post_delete, sender=Cab)
|
# @receiver(models.signals.post_delete, sender=Cab)
|
||||||
# def post_save_image(sender, instance, *args, **kwargs):
|
# def post_save_image(sender, instance, *args, **kwargs):
|
||||||
|
@@ -3,7 +3,7 @@ from roster.views import RosterList, RosterGet, RosterAddress, RosterClass
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("list", RosterList.as_view()),
|
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("address/<int:address>", RosterAddress.as_view()),
|
||||||
path("class/<str:class>", RosterClass.as_view()),
|
path("class/<str:class>", RosterClass.as_view()),
|
||||||
]
|
]
|
||||||
|
@@ -6,30 +6,26 @@ from roster.serializers import RollingStockSerializer
|
|||||||
|
|
||||||
|
|
||||||
class RosterList(ListAPIView):
|
class RosterList(ListAPIView):
|
||||||
|
queryset = RollingStock.objects.all()
|
||||||
serializer_class = RollingStockSerializer
|
serializer_class = RollingStockSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return RollingStock.objects.get_published(self.request.user)
|
|
||||||
|
|
||||||
|
|
||||||
class RosterGet(RetrieveAPIView):
|
class RosterGet(RetrieveAPIView):
|
||||||
|
queryset = RollingStock.objects.all()
|
||||||
serializer_class = RollingStockSerializer
|
serializer_class = RollingStockSerializer
|
||||||
lookup_field = "uuid"
|
lookup_field = "uuid"
|
||||||
schema = AutoSchema(operation_id_base="retrieveRollingStockByUUID")
|
|
||||||
|
|
||||||
def get_queryset(self):
|
schema = AutoSchema(operation_id_base="retrieveRollingStockByUUID")
|
||||||
return RollingStock.objects.get_published(self.request.user)
|
|
||||||
|
|
||||||
|
|
||||||
class RosterAddress(ListAPIView):
|
class RosterAddress(ListAPIView):
|
||||||
serializer_class = RollingStockSerializer
|
serializer_class = RollingStockSerializer
|
||||||
|
|
||||||
schema = AutoSchema(operation_id_base="retrieveRollingStockByAddress")
|
schema = AutoSchema(operation_id_base="retrieveRollingStockByAddress")
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
address = self.kwargs["address"]
|
address = self.kwargs["address"]
|
||||||
return RollingStock.objects.get_published(self.request.user).filter(
|
return RollingStock.objects.filter(address=address)
|
||||||
address=address
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RosterClass(ListAPIView):
|
class RosterClass(ListAPIView):
|
||||||
@@ -39,6 +35,4 @@ class RosterClass(ListAPIView):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
_class = self.kwargs["class"]
|
_class = self.kwargs["class"]
|
||||||
return RollingStock.objects.get_published(self.request.user).filter(
|
return RollingStock.objects.filter(rolling_class__identifier=_class)
|
||||||
rolling_class__identifier=_class
|
|
||||||
)
|
|
||||||
|
Reference in New Issue
Block a user