mirror of
https://github.com/daniviga/django-ram.git
synced 2025-08-04 13:17:50 +02:00
Merge pull request #40 from daniviga/catalogue
Introduce the concept of catalogs, improve books and code refactoring
This commit is contained in:
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.10', '3.11', '3.12']
|
python-version: ['3.12', '3.13']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
@@ -1,35 +1,58 @@
|
|||||||
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 BookProperty, BookImage, Book, Author, Publisher
|
from bookshelf.models import (
|
||||||
|
BaseBookProperty,
|
||||||
|
BaseBookImage,
|
||||||
|
BaseBookDocument,
|
||||||
|
Book,
|
||||||
|
Author,
|
||||||
|
Publisher,
|
||||||
|
Catalog,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BookImageInline(SortableInlineAdminMixin, admin.TabularInline):
|
class BookImageInline(SortableInlineAdminMixin, admin.TabularInline):
|
||||||
model = BookImage
|
model = BaseBookImage
|
||||||
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 = BookProperty
|
model = BaseBookProperty
|
||||||
min_num = 0
|
min_num = 0
|
||||||
extra = 0
|
extra = 0
|
||||||
autocomplete_fields = ("property",)
|
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 = (BookImageInline, BookPropertyInline,)
|
inlines = (
|
||||||
|
BookPropertyInline,
|
||||||
|
BookImageInline,
|
||||||
|
BookDocInline,
|
||||||
|
)
|
||||||
list_display = (
|
list_display = (
|
||||||
"title",
|
"title",
|
||||||
"published",
|
|
||||||
"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")
|
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")
|
||||||
@@ -77,7 +100,10 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Author)
|
@admin.register(Author)
|
||||||
class AuthorAdmin(admin.ModelAdmin):
|
class AuthorAdmin(admin.ModelAdmin):
|
||||||
search_fields = ("first_name", "last_name",)
|
search_fields = (
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
)
|
||||||
list_filter = ("last_name",)
|
list_filter = ("last_name",)
|
||||||
|
|
||||||
|
|
||||||
@@ -85,3 +111,58 @@ 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 = (
|
||||||
|
"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())
|
||||||
|
@@ -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.BookImage.objects.all():
|
for r in bookshelf.models.BaseBookImage.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,19 +31,21 @@ 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
|
||||||
),
|
# ),
|
||||||
]
|
]
|
||||||
|
141
ram/bookshelf/migrations/0016_basebook_book_catalogue.py
Normal file
141
ram/bookshelf/migrations/0016_basebook_book_catalogue.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# 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",),
|
||||||
|
),
|
||||||
|
]
|
@@ -0,0 +1,52 @@
|
|||||||
|
# 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")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@@ -0,0 +1,17 @@
|
|||||||
|
# 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"},
|
||||||
|
),
|
||||||
|
]
|
@@ -9,7 +9,8 @@ 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, PropertyInstance
|
from ram.models import BaseModel, Image, Document, PropertyInstance
|
||||||
|
from metadata.models import Scale, Manufacturer
|
||||||
|
|
||||||
|
|
||||||
class Publisher(models.Model):
|
class Publisher(models.Model):
|
||||||
@@ -38,10 +39,7 @@ class Author(models.Model):
|
|||||||
return f"{self.last_name} {self.first_name[0]}."
|
return f"{self.last_name} {self.first_name[0]}."
|
||||||
|
|
||||||
|
|
||||||
class Book(BaseModel):
|
class BaseBook(BaseModel):
|
||||||
title = models.CharField(max_length=200)
|
|
||||||
authors = models.ManyToManyField(Author, blank=True)
|
|
||||||
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,
|
||||||
@@ -56,18 +54,6 @@ class Book(BaseModel):
|
|||||||
Tag, related_name="bookshelf", blank=True
|
Tag, related_name="bookshelf", blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ["title"]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
def publisher_name(self):
|
|
||||||
return self.publisher.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse("book", kwargs={"uuid": self.uuid})
|
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
shutil.rmtree(
|
shutil.rmtree(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
@@ -75,7 +61,7 @@ class Book(BaseModel):
|
|||||||
),
|
),
|
||||||
ignore_errors=True
|
ignore_errors=True
|
||||||
)
|
)
|
||||||
super(Book, self).delete(*args, **kwargs)
|
super(BaseBook, self).delete(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def book_image_upload(instance, filename):
|
def book_image_upload(instance, filename):
|
||||||
@@ -87,9 +73,9 @@ def book_image_upload(instance, filename):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BookImage(Image):
|
class BaseBookImage(Image):
|
||||||
book = models.ForeignKey(
|
book = models.ForeignKey(
|
||||||
Book, on_delete=models.CASCADE, related_name="image"
|
BaseBook, on_delete=models.CASCADE, related_name="image"
|
||||||
)
|
)
|
||||||
image = models.ImageField(
|
image = models.ImageField(
|
||||||
upload_to=book_image_upload,
|
upload_to=book_image_upload,
|
||||||
@@ -97,11 +83,68 @@ class BookImage(Image):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BookProperty(PropertyInstance):
|
class BaseBookDocument(Document):
|
||||||
book = models.ForeignKey(
|
book = models.ForeignKey(
|
||||||
Book,
|
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,
|
on_delete=models.CASCADE,
|
||||||
null=False,
|
null=False,
|
||||||
blank=False,
|
blank=False,
|
||||||
related_name="property",
|
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:
|
||||||
|
ordering = ["title"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def publisher_name(self):
|
||||||
|
return self.publisher.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse(
|
||||||
|
"bookshelf_item",
|
||||||
|
kwargs={"selector": "book", "uuid": self.uuid}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Catalog(BaseBook):
|
||||||
|
manufacturer = models.ForeignKey(
|
||||||
|
Manufacturer,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
)
|
||||||
|
years = models.CharField(max_length=12)
|
||||||
|
scales = models.ManyToManyField(Scale)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["manufacturer", "publication_year"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
scales = self.get_scales
|
||||||
|
return "%s %s %s" % (self.manufacturer.name, self.years, scales)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse(
|
||||||
|
"bookshelf_item",
|
||||||
|
kwargs={"selector": "catalog", "uuid": self.uuid}
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_scales(self):
|
||||||
|
return "/".join([s.scale for s in self.scales.all()])
|
||||||
|
@@ -180,7 +180,7 @@
|
|||||||
<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 'types' %}">Type</a></li>
|
<li><a class="dropdown-item" href="{% url 'rolling_stock_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>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
{% load dynamic_url %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
{% if book.tags.all %}
|
{% if book.tags.all %}
|
||||||
@@ -57,24 +58,39 @@
|
|||||||
{{ book.description | safe }}
|
{{ 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 scope="row">Authors</th>
|
<th class="w-33" 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 scope="row">Publisher</th>
|
<th class="w-33" 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>
|
||||||
@@ -120,7 +136,7 @@
|
|||||||
</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="{% url 'admin:bookshelf_book_change' book.pk %}">Edit</a>{% endif %}
|
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% dynamic_admin_url 'bookshelf' type book.pk %}">Edit</a>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
{% extends "cards.html" %}
|
|
||||||
{% block pagination %}
|
|
||||||
{% if data.has_other_pages %}
|
|
||||||
<nav aria-label="Page navigation example">
|
|
||||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
|
||||||
{% if data.has_previous %}
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="{% url 'books_pagination' page=data.previous_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-left"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% for i in page_range %}
|
|
||||||
{% if data.number == i %}
|
|
||||||
<li class="page-item active">
|
|
||||||
<span class="page-link">{{ i }}</span>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
{% if i == data.paginator.ELLIPSIS %}
|
|
||||||
<li class="page-item"><span class="page-link">{{ i }}</span></li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item"><a class="page-link" href="{% url 'books_pagination' page=i %}#main-content">{{ i }}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if data.has_next %}
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="{% url 'books_pagination' page=data.next_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-right"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@@ -4,7 +4,12 @@
|
|||||||
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 == "rolling_stock" %}
|
{% if d.type == "roster" %}
|
||||||
{% 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" %}
|
{% elif d.type == "book" or d.type == "catalog" %}
|
||||||
{% include "cards/book.html" %}
|
{% include "cards/book.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
{% 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 %}
|
||||||
@@ -18,10 +19,24 @@
|
|||||||
<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>
|
||||||
@@ -32,6 +47,7 @@
|
|||||||
<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>
|
||||||
@@ -48,7 +64,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="{% url 'admin:bookshelf_book_change' d.item.pk %}">Edit</a>{% endif %}
|
{% 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 %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
{% extends "cards.html" %}
|
|
||||||
{% block pagination %}
|
|
||||||
{% if data.has_other_pages %}
|
|
||||||
<nav aria-label="Page navigation example">
|
|
||||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
|
||||||
{% 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"><i class="bi bi-chevron-left"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></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"><i class="bi bi-chevron-right"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@@ -1,40 +0,0 @@
|
|||||||
{% extends "cards.html" %}
|
|
||||||
{% block pagination %}
|
|
||||||
{% if data.has_other_pages %}
|
|
||||||
<nav aria-label="Page navigation example">
|
|
||||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
|
||||||
{% 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"><i class="bi bi-chevron-left"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></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"><i class="bi bi-chevron-right"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@@ -1,4 +1,4 @@
|
|||||||
{% extends "roster.html" %}
|
{% extends "pagination.html" %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div class="text-muted">{{ site_conf.about | safe }}</div>
|
<div class="text-muted">{{ site_conf.about | safe }}</div>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
{% extends "cards.html" %}
|
{% extends "cards.html" %}
|
||||||
|
{% load dynamic_url %}
|
||||||
|
|
||||||
{% block pagination %}
|
{% block pagination %}
|
||||||
{% if data.has_other_pages %}
|
{% if data.has_other_pages %}
|
||||||
@@ -6,7 +7,7 @@
|
|||||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
||||||
{% if data.has_previous %}
|
{% if data.has_previous %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'roster_pagination' page=data.previous_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-left"></i></a>
|
<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>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
@@ -22,13 +23,13 @@
|
|||||||
{% if i == data.paginator.ELLIPSIS %}
|
{% if i == data.paginator.ELLIPSIS %}
|
||||||
<li class="page-item"><span class="page-link">{{ i }}</span></li>
|
<li class="page-item"><span class="page-link">{{ i }}</span></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item"><a class="page-link" href="{% url 'roster_pagination' page=i %}#main-content">{{ i }}</a></li>
|
<li class="page-item"><a class="page-link" href="{% dynamic_pagination type page=i %}#main-content">{{ i }}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if data.has_next %}
|
{% if data.has_next %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a class="page-link" href="{% url 'roster_pagination' page=data.next_page_number %}#main-content" tabindex="-1"><i class="bi bi-chevron-right"></i></a>
|
<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>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
@@ -1,40 +0,0 @@
|
|||||||
{% extends "cards.html" %}
|
|
||||||
{% block pagination %}
|
|
||||||
{% if data.has_other_pages %}
|
|
||||||
<nav aria-label="Page navigation example">
|
|
||||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
|
||||||
{% 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"><i class="bi bi-chevron-left"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></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"><i class="bi bi-chevron-right"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@@ -1,40 +0,0 @@
|
|||||||
{% extends "cards.html" %}
|
|
||||||
{% block pagination %}
|
|
||||||
{% if data.has_other_pages %}
|
|
||||||
<nav aria-label="Page navigation example">
|
|
||||||
<ul class="pagination flex-wrap justify-content-center mt-4 mb-0">
|
|
||||||
{% 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"><i class="bi bi-chevron-left"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-left"></i></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"><i class="bi bi-chevron-right"></i></a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<span class="page-link"><i class="bi bi-chevron-right"></i></span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
21
ram/portal/templatetags/dynamic_url.py
Normal file
21
ram/portal/templatetags/dynamic_url.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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,13 +1,18 @@
|
|||||||
from django import template
|
from django import template
|
||||||
from portal.models import Flatpage
|
from portal.models import Flatpage
|
||||||
from bookshelf.models import Book
|
from bookshelf.models import Book, Catalog
|
||||||
|
|
||||||
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():
|
||||||
return {"bookshelf_menu": Book.objects.exists()}
|
# FIXME: Filter out unpublished books and catalogs?
|
||||||
|
return {
|
||||||
|
"bookshelf_menu": (Book.objects.exists() or Catalog.objects.exists()),
|
||||||
|
"books_menu": Book.objects.exists(),
|
||||||
|
"catalogs_menu": Catalog.objects.exists(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag('flatpages/flatpages_menu.html')
|
@register.inclusion_tag('flatpages/flatpages_menu.html')
|
||||||
|
@@ -14,7 +14,8 @@ from portal.views import (
|
|||||||
Scales,
|
Scales,
|
||||||
Types,
|
Types,
|
||||||
Books,
|
Books,
|
||||||
GetBook,
|
Catalogs,
|
||||||
|
GetBookCatalog,
|
||||||
SearchObjects,
|
SearchObjects,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ urlpatterns = [
|
|||||||
path(
|
path(
|
||||||
"roster/page/<int:page>",
|
"roster/page/<int:page>",
|
||||||
GetRoster.as_view(),
|
GetRoster.as_view(),
|
||||||
name="roster_pagination"
|
name="rosters_pagination"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"page/<str:flatpage>",
|
"page/<str:flatpage>",
|
||||||
@@ -33,12 +34,12 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"consists",
|
"consists",
|
||||||
Consists.as_view(template="consists.html"),
|
Consists.as_view(),
|
||||||
name="consists"
|
name="consists"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"consists/page/<int:page>",
|
"consists/page/<int:page>",
|
||||||
Consists.as_view(template="consists.html"),
|
Consists.as_view(),
|
||||||
name="consists_pagination"
|
name="consists_pagination"
|
||||||
),
|
),
|
||||||
path("consist/<uuid:uuid>", GetConsist.as_view(), name="consist"),
|
path("consist/<uuid:uuid>", GetConsist.as_view(), name="consist"),
|
||||||
@@ -49,55 +50,69 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"companies",
|
"companies",
|
||||||
Companies.as_view(template="companies.html"),
|
Companies.as_view(),
|
||||||
name="companies"
|
name="companies"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"companies/page/<int:page>",
|
"companies/page/<int:page>",
|
||||||
Companies.as_view(template="companies.html"),
|
Companies.as_view(),
|
||||||
name="companies_pagination",
|
name="companies_pagination",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"manufacturers/<str:category>",
|
"manufacturers/<str:category>",
|
||||||
Manufacturers.as_view(template="manufacturers.html"),
|
Manufacturers.as_view(template="pagination_manufacturers.html"),
|
||||||
name="manufacturers"
|
name="manufacturers"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"manufacturers/<str:category>/page/<int:page>",
|
"manufacturers/<str:category>/page/<int:page>",
|
||||||
Manufacturers.as_view(template="manufacturers.html"),
|
Manufacturers.as_view(template="pagination_manufacturers.html"),
|
||||||
name="manufacturers_pagination",
|
name="manufacturers_pagination",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"scales",
|
"scales",
|
||||||
Scales.as_view(template="scales.html"),
|
Scales.as_view(),
|
||||||
name="scales"
|
name="scales"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"scales/page/<int:page>",
|
"scales/page/<int:page>",
|
||||||
Scales.as_view(template="scales.html"),
|
Scales.as_view(),
|
||||||
name="scales_pagination"
|
name="scales_pagination"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"types",
|
"types",
|
||||||
Types.as_view(template="types.html"),
|
Types.as_view(),
|
||||||
name="types"
|
name="rolling_stock_types"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"types/page/<int:page>",
|
"types/page/<int:page>",
|
||||||
Types.as_view(template="types.html"),
|
Types.as_view(),
|
||||||
name="types_pagination"
|
name="rolling_stock_types_pagination"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"bookshelf/books",
|
"bookshelf/books",
|
||||||
Books.as_view(template="bookshelf/books.html"),
|
Books.as_view(),
|
||||||
name="books"
|
name="books"
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"bookshelf/books/page/<int:page>",
|
"bookshelf/books/page/<int:page>",
|
||||||
Books.as_view(template="bookshelf/books.html"),
|
Books.as_view(),
|
||||||
name="books_pagination"
|
name="books_pagination"
|
||||||
),
|
),
|
||||||
path("bookshelf/book/<uuid:uuid>", GetBook.as_view(), name="book"),
|
path(
|
||||||
|
"bookshelf/<str:selector>/<uuid:uuid>",
|
||||||
|
GetBookCatalog.as_view(),
|
||||||
|
name="bookshelf_item"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"bookshelf/catalogs",
|
||||||
|
Catalogs.as_view(),
|
||||||
|
name="catalogs"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"bookshelf/catalogs/page/<int:page>",
|
||||||
|
Catalogs.as_view(),
|
||||||
|
name="catalogs_pagination"
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"search",
|
"search",
|
||||||
SearchObjects.as_view(http_method_names=["post"]),
|
SearchObjects.as_view(http_method_names=["post"]),
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
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
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ from portal.utils import get_site_conf
|
|||||||
from portal.models import Flatpage
|
from portal.models import Flatpage
|
||||||
from roster.models import RollingStock
|
from roster.models import RollingStock
|
||||||
from consist.models import Consist
|
from consist.models import Consist
|
||||||
from bookshelf.models import Book
|
from bookshelf.models import Book, Catalog
|
||||||
from metadata.models import (
|
from metadata.models import (
|
||||||
Company,
|
Company,
|
||||||
Manufacturer,
|
Manufacturer,
|
||||||
@@ -61,8 +62,8 @@ class Render404(View):
|
|||||||
|
|
||||||
class GetData(View):
|
class GetData(View):
|
||||||
title = "Home"
|
title = "Home"
|
||||||
template = "roster.html"
|
template = "pagination.html"
|
||||||
item_type = "rolling_stock"
|
item_type = "roster"
|
||||||
filter = Q() # empty filter by default
|
filter = Q() # empty filter by default
|
||||||
|
|
||||||
def get_data(self, request):
|
def get_data(self, request):
|
||||||
@@ -97,8 +98,8 @@ class GetData(View):
|
|||||||
|
|
||||||
|
|
||||||
class GetRoster(GetData):
|
class GetRoster(GetData):
|
||||||
title = "Roster"
|
title = "The Roster"
|
||||||
item_type = "rolling_stock"
|
item_type = "roster"
|
||||||
|
|
||||||
def get_data(self, request):
|
def get_data(self, request):
|
||||||
return RollingStock.objects.get_published(request.user).order_by(
|
return RollingStock.objects.get_published(request.user).order_by(
|
||||||
@@ -148,15 +149,18 @@ 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 = []
|
||||||
rolling_stock = (
|
roster = (
|
||||||
RollingStock.objects.get_published(request.user)
|
RollingStock.objects.get_published(request.user)
|
||||||
.filter(query)
|
.filter(query)
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by(*get_order_by_field())
|
.order_by(*get_order_by_field())
|
||||||
)
|
)
|
||||||
for item in rolling_stock:
|
for item in roster:
|
||||||
data.append({"type": "rolling_stock", "item": item})
|
data.append({"type": "roster", "item": item})
|
||||||
|
|
||||||
if _filter is None:
|
if _filter is None:
|
||||||
consists = (
|
consists = (
|
||||||
Consist.objects.get_published(request.user)
|
Consist.objects.get_published(request.user)
|
||||||
@@ -175,7 +179,12 @@ class SearchObjects(View):
|
|||||||
.filter(title__icontains=search)
|
.filter(title__icontains=search)
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
for item in books:
|
catalogs = (
|
||||||
|
Catalog.objects.get_published(request.user)
|
||||||
|
.filter(manufacturer__name__icontains=search)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
for item in list(chain(books, catalogs)):
|
||||||
data.append({"type": "book", "item": item})
|
data.append({"type": "book", "item": item})
|
||||||
|
|
||||||
paginator = Paginator(data, get_items_per_page())
|
paginator = Paginator(data, get_items_per_page())
|
||||||
@@ -237,7 +246,7 @@ class GetManufacturerItem(View):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if search != "all":
|
if search != "all":
|
||||||
rolling_stock = get_list_or_404(
|
roster = get_list_or_404(
|
||||||
RollingStock.objects.get_published(request.user).order_by(
|
RollingStock.objects.get_published(request.user).order_by(
|
||||||
*get_order_by_field()
|
*get_order_by_field()
|
||||||
),
|
),
|
||||||
@@ -250,10 +259,10 @@ class GetManufacturerItem(View):
|
|||||||
manufacturer,
|
manufacturer,
|
||||||
# all returned records must have the same `item_number``;
|
# all returned records must have the same `item_number``;
|
||||||
# just pick it up the first result, otherwise `search`
|
# just pick it up the first result, otherwise `search`
|
||||||
rolling_stock[0].item_number if rolling_stock else search,
|
roster.first.item_number if roster else search,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
rolling_stock = (
|
roster = (
|
||||||
RollingStock.objects.get_published(request.user)
|
RollingStock.objects.get_published(request.user)
|
||||||
.order_by(*get_order_by_field())
|
.order_by(*get_order_by_field())
|
||||||
.filter(
|
.filter(
|
||||||
@@ -264,8 +273,8 @@ class GetManufacturerItem(View):
|
|||||||
title = "Manufacturer: {0}".format(manufacturer)
|
title = "Manufacturer: {0}".format(manufacturer)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for item in rolling_stock:
|
for item in roster:
|
||||||
data.append({"type": "rolling_stock", "item": item})
|
data.append({"type": "roster", "item": item})
|
||||||
|
|
||||||
paginator = Paginator(data, get_items_per_page())
|
paginator = Paginator(data, get_items_per_page())
|
||||||
data = paginator.get_page(page)
|
data = paginator.get_page(page)
|
||||||
@@ -308,7 +317,7 @@ class GetObjectsFiltered(View):
|
|||||||
else:
|
else:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
rolling_stock = (
|
roster = (
|
||||||
RollingStock.objects.get_published(request.user)
|
RollingStock.objects.get_published(request.user)
|
||||||
.filter(query)
|
.filter(query)
|
||||||
.distinct()
|
.distinct()
|
||||||
@@ -316,8 +325,8 @@ class GetObjectsFiltered(View):
|
|||||||
)
|
)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for item in rolling_stock:
|
for item in roster:
|
||||||
data.append({"type": "rolling_stock", "item": item})
|
data.append({"type": "roster", "item": item})
|
||||||
|
|
||||||
try: # Execute only if query_2nd is defined
|
try: # Execute only if query_2nd is defined
|
||||||
consists = (
|
consists = (
|
||||||
@@ -442,7 +451,7 @@ class GetConsist(View):
|
|||||||
raise Http404
|
raise Http404
|
||||||
data = [
|
data = [
|
||||||
{
|
{
|
||||||
"type": "rolling_stock",
|
"type": "roster",
|
||||||
"item": RollingStock.objects.get_published(request.user).get(
|
"item": RollingStock.objects.get_published(request.user).get(
|
||||||
uuid=r.rolling_stock_id
|
uuid=r.rolling_stock_id
|
||||||
),
|
),
|
||||||
@@ -517,10 +526,26 @@ class Books(GetData):
|
|||||||
return Book.objects.get_published(request.user).all()
|
return Book.objects.get_published(request.user).all()
|
||||||
|
|
||||||
|
|
||||||
class GetBook(View):
|
class Catalogs(GetData):
|
||||||
def get(self, request, uuid):
|
title = "Catalogs"
|
||||||
|
item_type = "catalog"
|
||||||
|
|
||||||
|
def get_data(self, request):
|
||||||
|
return Catalog.objects.get_published(request.user).all()
|
||||||
|
|
||||||
|
|
||||||
|
class GetBookCatalog(View):
|
||||||
|
def get_object(self, request, uuid, selector):
|
||||||
|
if selector == "book":
|
||||||
|
return Book.objects.get_published(request.user).get(uuid=uuid)
|
||||||
|
elif selector == "catalog":
|
||||||
|
return Catalog.objects.get_published(request.user).get(uuid=uuid)
|
||||||
|
else:
|
||||||
|
raise Http404
|
||||||
|
|
||||||
|
def get(self, request, uuid, selector):
|
||||||
try:
|
try:
|
||||||
book = Book.objects.get_published(request.user).get(uuid=uuid)
|
book = self.get_object(request, uuid, selector)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
@@ -532,6 +557,7 @@ class GetBook(View):
|
|||||||
"title": book,
|
"title": book,
|
||||||
"book_properties": book_properties,
|
"book_properties": book_properties,
|
||||||
"book": book,
|
"book": book,
|
||||||
|
"type": selector
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from ram.utils import git_suffix
|
from ram.utils import git_suffix
|
||||||
|
|
||||||
__version__ = "0.13.6"
|
__version__ = "0.14.0"
|
||||||
__version__ += git_suffix(__file__)
|
__version__ += git_suffix(__file__)
|
||||||
|
@@ -32,6 +32,7 @@ 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))
|
||||||
@@ -65,6 +66,7 @@ class Image(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
ordering = ["order"]
|
ordering = ["order"]
|
||||||
|
verbose_name_plural = "Images"
|
||||||
|
|
||||||
objects = PublicManager()
|
objects = PublicManager()
|
||||||
|
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
# 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"},
|
||||||
|
),
|
||||||
|
]
|
Reference in New Issue
Block a user