Magazine UI (#54)

* Work in progress to implement magazines and issues UI

* Fully implement UI for magazines
This commit is contained in:
2025-12-10 22:58:39 +01:00
committed by GitHub
parent 6b10051bc4
commit 39b0a9378b
14 changed files with 646 additions and 23 deletions

View File

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

View File

@@ -43,8 +43,8 @@ class BaseBook(BaseModel):
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,
choices=settings.LANGUAGES, choices=sorted(settings.LANGUAGES, key=lambda s: s[1]),
default='en' default="en",
) )
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)
@@ -81,6 +81,15 @@ def book_image_upload(instance, filename):
) )
def magazine_image_upload(instance, filename):
return os.path.join(
"images",
"magazines",
str(instance.uuid),
filename
)
class BaseBookImage(Image): class BaseBookImage(Image):
book = models.ForeignKey( book = models.ForeignKey(
BaseBook, on_delete=models.CASCADE, related_name="image" BaseBook, on_delete=models.CASCADE, related_name="image"
@@ -163,12 +172,12 @@ class Magazine(BaseModel):
ISBN = models.CharField(max_length=17, blank=True) # 13 + dashes ISBN = models.CharField(max_length=17, blank=True) # 13 + dashes
image = models.ImageField( image = models.ImageField(
blank=True, blank=True,
upload_to=book_image_upload, upload_to=magazine_image_upload,
storage=DeduplicatedStorage, storage=DeduplicatedStorage,
) )
language = models.CharField( language = models.CharField(
max_length=7, max_length=7,
choices=settings.LANGUAGES, choices=sorted(settings.LANGUAGES, key=lambda s: s[1]),
default='en' default='en'
) )
tags = models.ManyToManyField( tags = models.ManyToManyField(
@@ -192,8 +201,8 @@ class Magazine(BaseModel):
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse(
"bookshelf_item", "magazine",
kwargs={"selector": "magazine", "uuid": self.uuid} kwargs={"uuid": self.uuid}
) )
@@ -224,3 +233,16 @@ class MagazineIssue(BaseBook):
def preview(self): def preview(self):
return self.image.first().image_thumbnail(100) return self.image.first().image_thumbnail(100)
@property
def publisher(self):
return self.magazine.publisher
def get_absolute_url(self):
return reverse(
"issue",
kwargs={
"uuid": self.uuid,
"magazine": self.magazine.uuid
}
)

View File

@@ -89,7 +89,30 @@
</tr> </tr>
<tr> <tr>
<th class="w-33" scope="row">Publisher</th> <th class="w-33" scope="row">Publisher</th>
<td>{{ book.publisher }}</td> <td>
<img src="{{ book.publisher.country.flag }}" alt="{{ book.publisher.country }}"> {{ book.publisher }}
{% if book.publisher.website %} <a href="{{ book.publisher.website }}" target="_blank"><i class="bi bi-box-arrow-up-right"></i></a>{% endif %}
</td>
</tr>
{% elif type == "magazineissue" %}
<tr>
<th class="w-33" scope="row">Magazine</th>
<td><a href="{% url 'magazine' book.magazine.pk %}">{{ book.magazine }}</a></td>
</tr>
<tr>
<th class="w-33" scope="row">Publisher</th>
<td>
<img src="{{ book.publisher.country.flag }}" alt="{{ book.publisher.country }}"> {{ book.publisher }}
{% if book.publisher.website %} <a href="{{ book.publisher.website }}" target="_blank"><i class="bi bi-box-arrow-up-right"></i></a>{% endif %}
</td>
</tr>
<tr>
<th class="w-33" scope="row">Issue</th>
<td>{{ book.issue_number }}</td>
</tr>
<tr>
<th class="w-33" scope="row">Date</th>
<td>{{ book.publication_year|default:"-" }} / {{ book.publication_month|default:"-" }}</td>
</tr> </tr>
{% endif %} {% endif %}
<tr> <tr>
@@ -104,10 +127,12 @@
<th scope="row">Number of pages</th> <th scope="row">Number of pages</th>
<td>{{ book.number_of_pages|default:"-" }}</td> <td>{{ book.number_of_pages|default:"-" }}</td>
</tr> </tr>
{% if type == "boook" or type == "catalog" %}
<tr> <tr>
<th scope="row">Publication year</th> <th scope="row">Publication year</th>
<td>{{ book.publication_year|default:"-" }}</td> <td>{{ book.publication_year|default:"-" }}</td>
</tr> </tr>
{% endif %}
{% if book.description %} {% if book.description %}
<tr> <tr>
<th class="w-33" scope="row">Description</th> <th class="w-33" scope="row">Description</th>

View File

@@ -10,6 +10,9 @@
{% if catalogs_menu %} {% if catalogs_menu %}
<li><a class="dropdown-item" href="{% url 'catalogs' %}">Catalogs</a></li> <li><a class="dropdown-item" href="{% url 'catalogs' %}">Catalogs</a></li>
{% endif %} {% endif %}
{% if magazines_menu %}
<li><a class="dropdown-item" href="{% url 'magazines' %}">Magazines</a></li>
{% endif %}
</ul> </ul>
</li> </li>
{% endif %} {% endif %}

View File

@@ -18,6 +18,8 @@
{% 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 == "magazine" or d.type == "magazineissue" %}
{% include "cards/magazine.html" %}
{% elif d.type == "book" or d.type == "catalog" %} {% elif d.type == "book" or d.type == "catalog" %}
{% include "cards/book.html" %} {% include "cards/book.html" %}
{% endif %} {% endif %}

View File

@@ -24,8 +24,7 @@
<thead> <thead>
<tr> <tr>
<th colspan="2" scope="row"> <th colspan="2" scope="row">
{% if d.type == "catalog" %}Catalog {{ d.type | capfirst }}
{% elif d.type == "book" %}Book{% endif %}
<div class="float-end"> <div class="float-end">
{% if not d.item.published %} {% if not d.item.published %}
<span class="badge text-bg-warning">Unpublished</span> <span class="badge text-bg-warning">Unpublished</span>
@@ -53,7 +52,7 @@
</tr> </tr>
<tr> <tr>
<th class="w-33" scope="row">Publisher</th> <th class="w-33" scope="row">Publisher</th>
<td>{{ d.item.publisher }}</td> <td><img src="{{ d.item.publisher.country.flag }}" alt="{{ d.item.publisher.country }}"> {{ d.item.publisher }}</td>
</tr> </tr>
{% endif %} {% endif %}
<tr> <tr>

View File

@@ -0,0 +1,97 @@
{% load static %}
<div class="col">
<div class="card shadow-sm">
{% if d.type == "magazine" %}
<a href="{{ d.item.get_absolute_url }}">
{% if d.item.image and d.type == "magazine" %}
<img class="card-img-top" src="{{ d.item.image.url }}" alt="{{ d.item }}">
{% elif d.item.issue.first.image.exists %}
{% with d.item.issue.first as i %}
<img class="card-img-top" src="{{ i.image.first.image.url }}" alt="{{ d.item }}">
{% endwith %}
{% else %}
<!-- Do not show the "Coming soon" image when running in a single card column mode (e.g. on mobile) -->
<img class="card-img-top d-none d-sm-block" src="{% static DEFAULT_CARD_IMAGE %}" alt="{{ d.item }}">
{% endif %}
</a>
{% elif d.type == "magazineissue" %}
<a href="{{ d.item.get_absolute_url }}">
{% if d.item.image.exists %}
<img class="card-img-top" src="{{ d.item.image.first.image.url }}" alt="{{ d.item }}">
{% else %}
<!-- Do not show the "Coming soon" image when running in a single card column mode (e.g. on mobile) -->
<img class="card-img-top d-none d-sm-block" src="{% static DEFAULT_CARD_IMAGE %}" alt="{{ d.item }}">
{% endif %}
</a>
{% endif %}
</a>
<div class="card-body">
<p class="card-text" style="position: relative;">
<strong>{{ d.item }}</strong>
<a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a>
</p>
{% if d.item.tags.all %}
<p class="card-text"><small>Tags:</small>
{% for t in d.item.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</a>{# new line is required #}
{% endfor %}
</p>
{% endif %}
<table class="table table-striped">
<thead>
<tr>
<th colspan="2" scope="row">
{{ d.type | capfirst }}
<div class="float-end">
{% if not d.item.published %}
<span class="badge text-bg-warning">Unpublished</span>
{% endif %}
</div>
</th>
</tr>
</thead>
<tbody class="table-group-divider">
{% if d.type == "magazineissue" %}
<tr>
<th class="w-33" scope="row">Magazine</th>
<td>{{ d.item.magazine }}</td>
</tr>
{% endif %}
<tr>
<th class="w-33" scope="row">Publisher</th>
<td>
<img src="{{ d.item.publisher.country.flag }}" alt="{{ d.item.publisher.country }}"> {{ d.item.publisher }}
{% if d.item.publisher.website %} <a href="{{ d.item.publisher.website }}" target="_blank"><i class="bi bi-box-arrow-up-right"></i></a>{% endif %}
</td>
</tr>
{% if d.type == "magazineissue" %}
<tr>
<th class="w-33" scope="row">Issue</th>
<td>{{ d.item.issue_number }}</td>
</tr>
<tr>
<th class="w-33" scope="row">Date</th>
<td>{{ d.item.publication_year|default:"-" }} / {{ d.item.publication_month|default:"-" }}</td>
</tr>
<tr>
<th class="w-33" scope="row">Pages</th>
<td>{{ d.item.number_of_pages|default:"-" }}</td>
</tr>
{% endif %}
<tr>
<th class="w-33" scope="row">Language</th>
<td>{{ d.item.get_language_display }}</td>
</tr>
</tbody>
</table>
<div class="d-grid gap-2 mb-1 d-md-block">
{% if d.type == "magazine" %}
<a class="btn btn-sm btn-outline-primary{% if d.item.issues == 0 %} disabled{% endif %}" href="{{ d.item.get_absolute_url }}">Show {{ d.item.issues }} issue{{ d.item.issues|pluralize }}</a>
{% else %}
<a class="btn btn-sm btn-outline-primary" href="{{ d.item.get_absolute_url }}">Show all data</a>
{% endif %}
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:bookshelf_magazineissue_change' d.item.pk %}">Edit</a>{% endif %}
</div>
</div>
</div>
</div>

View File

@@ -31,7 +31,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">
{% with items=d.item.num_items %} {% with items=d.item.num_items %}
<a class="btn btn-sm btn-outline-primary{% if items == 0 %} disabled{% endif %}" href="{% url 'filtered' _filter="manufacturer" search=d.item.slug %}">Show {{ items }} item{{ items | pluralize}}</a> <a class="btn btn-sm btn-outline-primary{% if items == 0 %} disabled{% endif %}" href="{% url 'filtered' _filter="manufacturer" search=d.item.slug %}">Show {{ items }} item{{ items|pluralize }}</a>
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:metadata_manufacturer_change' d.item.pk %}">Edit</a>{% endif %} {% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:metadata_manufacturer_change' d.item.pk %}">Edit</a>{% endif %}
{% endwith %} {% endwith %}
</div> </div>

View File

@@ -0,0 +1,120 @@
{% extends "cards.html" %}
{% block header %}
{{ block.super }}
{% if magazine.tags.all %}
<p><small>Tags:</small>
{% for t in magazine.tags.all %}<a href="{% url 'filtered' _filter="tag" search=t.slug %}" class="badge rounded-pill bg-primary">
{{ t.name }}</a>{# new line is required #}
{% endfor %}
</p>
{% if not magazine.published %}
<span class="badge text-bg-warning">Unpublished</span> |
{% endif %}
<small class="text-body-secondary">Updated {{ magazine.updated_time | date:"M d, Y H:i" }}</small>
{% endif %}
{% endblock %}
{% block carousel %}
{% if magazine.image %}
<div class="row pb-4">
<div id="carouselControls" class="carousel carousel-dark slide" data-bs-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="{{ magazine.image.url }}" class="d-block w-100 rounded img-thumbnail" alt="magazine cover">
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block pagination %}
{% if data.has_other_pages %}
<nav aria-label="Page navigation">
<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 'magazine_pagination' uuid=magazine.uuid 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 'magazine_pagination' uuid=magazine.uuid page=i %}#main-content">{{ i }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% if data.has_next %}
<li class="page-item">
<a class="page-link" href="{% url 'magazine_pagination' uuid=magazine.uuid 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 %}
{% block extra_content %}
<section class="py-4 text-start container">
<div class="row">
<div class="mx-auto">
<nav class="nav nav-tabs d-none d-lg-flex flex-row mb-2" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-summary-tab" data-bs-toggle="tab" data-bs-target="#nav-summary" type="button" role="tab" aria-controls="nav-summary" aria-selected="true">Summary</button>
</nav>
<select class="form-select d-lg-none mb-2" id="tabSelector" aria-label="Tab selector">
<option value="nav-summary" selected>Summary</option>
</select>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane show active" id="nav-summary" role="tabpanel" aria-labelledby="nav-summary-tab">
<table class="table table-striped">
<thead>
<tr>
<th colspan="2" scope="row">
Magazine
</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tr>
<th class="w-33" scope="row">Name</th>
<td>{{ magazine }} </td>
</tr>
<tr>
<th class="w-33" scope="row">Publisher</th>
<td>
<img src="{{ magazine.publisher.country.flag }}" alt="{{ magazine.publisher.country }}"> {{ magazine.publisher }}
{% if magazine.publisher.website %} <a href="{{ magazine.publisher.website }}" target="_blank"><i class="bi bi-box-arrow-up-right"></i></a>{% endif %}
</td>
</tr>
<tr>
<th scope="row">ISBN</th>
<td>{{ magazine.ISBN | default:"-" }}</td>
</tr>
{% if magazine.description %}
<tr>
<th scope="row">Description</th>
<td>{{ magazine.description | safe }}</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
{% if request.user.is_staff %}<a class="btn btn-sm btn-outline-danger" href="{% url 'admin:bookshelf_magazine_change' magazine.pk %}">Edit</a>{% endif %}
</div>
</div>
</div>
</section>
{% endblock %}

View File

@@ -1,6 +1,6 @@
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, Catalog, Magazine
register = template.Library() register = template.Library()
@@ -8,10 +8,14 @@ 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? # FIXME: Filter out unpublished books and catalogs?
books = Book.objects.exists()
catalogs = Catalog.objects.exists()
magazines = Magazine.objects.exists()
return { return {
"bookshelf_menu": (Book.objects.exists() or Catalog.objects.exists()), "bookshelf_menu": (books or catalogs or magazines),
"books_menu": Book.objects.exists(), "books_menu": books,
"catalogs_menu": Catalog.objects.exists(), "catalogs_menu": catalogs,
"magazines_menu": magazines,
} }

View File

@@ -15,6 +15,9 @@ from portal.views import (
Types, Types,
Books, Books,
Catalogs, Catalogs,
Magazines,
GetMagazine,
GetMagazineIssue,
GetBookCatalog, GetBookCatalog,
SearchObjects, SearchObjects,
) )
@@ -98,6 +101,31 @@ urlpatterns = [
Books.as_view(), Books.as_view(),
name="books_pagination" name="books_pagination"
), ),
path(
"bookshelf/magazine/<uuid:uuid>",
GetMagazine.as_view(),
name="magazine"
),
path(
"bookshelf/magazine/<uuid:uuid>/page/<int:page>",
GetMagazine.as_view(),
name="magazine_pagination",
),
path(
"bookshelf/magazine/<uuid:magazine>/issue/<uuid:uuid>",
GetMagazineIssue.as_view(),
name="issue",
),
path(
"bookshelf/magazines",
Magazines.as_view(),
name="magazines"
),
path(
"bookshelf/magazines/page/<int:page>",
Magazines.as_view(),
name="magazines_pagination"
),
path( path(
"bookshelf/<str:selector>/<uuid:uuid>", "bookshelf/<str:selector>/<uuid:uuid>",
GetBookCatalog.as_view(), GetBookCatalog.as_view(),

View File

@@ -16,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, Catalog from bookshelf.models import Book, Catalog, Magazine, MagazineIssue
from metadata.models import ( from metadata.models import (
Company, Company,
Manufacturer, Manufacturer,
@@ -73,7 +73,8 @@ class GetData(View):
.filter(self.filter) .filter(self.filter)
) )
def get(self, request, page=1): def get(self, request, filter=Q(), page=1):
self.filter = filter
data = [] data = []
for item in self.get_data(request): for item in self.get_data(request):
data.append({"type": self.item_type, "item": item}) data.append({"type": self.item_type, "item": item})
@@ -491,7 +492,8 @@ class Manufacturers(GetData):
def get_data(self, request): def get_data(self, request):
return ( return (
Manufacturer.objects.filter(self.filter).annotate( Manufacturer.objects.filter(self.filter)
.annotate(
num_rollingstock=( num_rollingstock=(
Count( Count(
"rollingstock", "rollingstock",
@@ -592,9 +594,7 @@ class Scales(GetData):
num_consists=Count( num_consists=Count(
"consist", "consist",
filter=Q( filter=Q(
consist__in=Consist.objects.get_published( consist__in=Consist.objects.get_published(request.user)
request.user
)
), ),
distinct=True, distinct=True,
), ),
@@ -637,6 +637,85 @@ class Catalogs(GetData):
return Catalog.objects.get_published(request.user).all() return Catalog.objects.get_published(request.user).all()
class Magazines(GetData):
title = "Magazines"
item_type = "magazine"
def get_data(self, request):
return (
Magazine.objects.get_published(request.user)
.all()
.annotate(
issues=Count(
"issue",
filter=Q(
issue__in=(
MagazineIssue.objects.get_published(request.user)
)
),
)
)
)
class GetMagazine(View):
def get(self, request, uuid, page=1):
try:
magazine = Magazine.objects.get_published(request.user).get(
uuid=uuid
)
except ObjectDoesNotExist:
raise Http404
data = [
{
"type": "magazineissue",
"item": i,
}
for i in magazine.issue.get_published(request.user).all()
]
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,
"magazine.html",
{
"title": magazine,
"magazine": magazine,
"data": data,
"matches": paginator.count,
"page_range": page_range,
},
)
class GetMagazineIssue(View):
def get(self, request, uuid, magazine, page=1):
try:
issue = MagazineIssue.objects.get_published(request.user).get(
uuid=uuid,
magazine__uuid=magazine,
)
except ObjectDoesNotExist:
raise Http404
properties = issue.property.get_public(request.user)
documents = issue.document.get_public(request.user)
return render(
request,
"bookshelf/book.html",
{
"title": issue,
"book": issue,
"documents": documents,
"properties": properties,
"type": "magazineissue",
},
)
class GetBookCatalog(View): class GetBookCatalog(View):
def get_object(self, request, uuid, selector): def get_object(self, request, uuid, selector):
if selector == "book": if selector == "book":

View File

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