diff --git a/ram/bookshelf/migrations/0026_alter_basebook_language_alter_magazine_image_and_more.py b/ram/bookshelf/migrations/0026_alter_basebook_language_alter_magazine_image_and_more.py new file mode 100644 index 0000000..189c84b --- /dev/null +++ b/ram/bookshelf/migrations/0026_alter_basebook_language_alter_magazine_image_and_more.py @@ -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, + ), + ), + ] diff --git a/ram/bookshelf/models.py b/ram/bookshelf/models.py index 0aa0be8..58de694 100644 --- a/ram/bookshelf/models.py +++ b/ram/bookshelf/models.py @@ -43,8 +43,8 @@ class BaseBook(BaseModel): ISBN = models.CharField(max_length=17, blank=True) # 13 + dashes language = models.CharField( max_length=7, - choices=settings.LANGUAGES, - default='en' + choices=sorted(settings.LANGUAGES, key=lambda s: s[1]), + default="en", ) number_of_pages = 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): book = models.ForeignKey( 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 image = models.ImageField( blank=True, - upload_to=book_image_upload, + upload_to=magazine_image_upload, storage=DeduplicatedStorage, ) language = models.CharField( max_length=7, - choices=settings.LANGUAGES, + choices=sorted(settings.LANGUAGES, key=lambda s: s[1]), default='en' ) tags = models.ManyToManyField( @@ -192,8 +201,8 @@ class Magazine(BaseModel): def get_absolute_url(self): return reverse( - "bookshelf_item", - kwargs={"selector": "magazine", "uuid": self.uuid} + "magazine", + kwargs={"uuid": self.uuid} ) @@ -224,3 +233,16 @@ class MagazineIssue(BaseBook): def preview(self): 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 + } + ) diff --git a/ram/portal/templates/bookshelf/book.html b/ram/portal/templates/bookshelf/book.html index 778fcd4..34c66d1 100644 --- a/ram/portal/templates/bookshelf/book.html +++ b/ram/portal/templates/bookshelf/book.html @@ -89,7 +89,30 @@ Publisher - {{ book.publisher }} + + {{ book.publisher.country }} {{ book.publisher }} + {% if book.publisher.website %} {% endif %} + + + {% elif type == "magazineissue" %} + + Magazine + {{ book.magazine }} + + + Publisher + + {{ book.publisher.country }} {{ book.publisher }} + {% if book.publisher.website %} {% endif %} + + + + Issue + {{ book.issue_number }} + + + Date + {{ book.publication_year|default:"-" }} / {{ book.publication_month|default:"-" }} {% endif %} @@ -104,10 +127,12 @@ Number of pages {{ book.number_of_pages|default:"-" }} + {% if type == "boook" or type == "catalog" %} Publication year {{ book.publication_year|default:"-" }} + {% endif %} {% if book.description %} Description diff --git a/ram/portal/templates/bookshelf/bookshelf_menu.html b/ram/portal/templates/bookshelf/bookshelf_menu.html index 7321045..a10e207 100644 --- a/ram/portal/templates/bookshelf/bookshelf_menu.html +++ b/ram/portal/templates/bookshelf/bookshelf_menu.html @@ -10,6 +10,9 @@ {% if catalogs_menu %}
  • Catalogs
  • {% endif %} + {% if magazines_menu %} +
  • Magazines
  • + {% endif %} {% endif %} diff --git a/ram/portal/templates/cards.html b/ram/portal/templates/cards.html index d80659b..fc64027 100644 --- a/ram/portal/templates/cards.html +++ b/ram/portal/templates/cards.html @@ -18,6 +18,8 @@ {% include "cards/consist.html" %} {% elif d.type == "manufacturer" %} {% include "cards/manufacturer.html" %} + {% elif d.type == "magazine" or d.type == "magazineissue" %} + {% include "cards/magazine.html" %} {% elif d.type == "book" or d.type == "catalog" %} {% include "cards/book.html" %} {% endif %} diff --git a/ram/portal/templates/cards/book.html b/ram/portal/templates/cards/book.html index 6205aee..5110276 100644 --- a/ram/portal/templates/cards/book.html +++ b/ram/portal/templates/cards/book.html @@ -24,8 +24,7 @@ - {% if d.type == "catalog" %}Catalog - {% elif d.type == "book" %}Book{% endif %} + {{ d.type | capfirst }}
    {% if not d.item.published %} Unpublished @@ -53,7 +52,7 @@ Publisher - {{ d.item.publisher }} + {{ d.item.publisher.country }} {{ d.item.publisher }} {% endif %} diff --git a/ram/portal/templates/cards/consist.html b/ram/portal/templates/cards/consist.html index 5d22611..e3da341 100644 --- a/ram/portal/templates/cards/consist.html +++ b/ram/portal/templates/cards/consist.html @@ -63,7 +63,7 @@
    Show all data - {% if request.user.is_staff %}Edit{% endif %} + {% if request.user.is_staff %}Edit{% endif %}
    diff --git a/ram/portal/templates/cards/magazine.html b/ram/portal/templates/cards/magazine.html new file mode 100644 index 0000000..8e90c29 --- /dev/null +++ b/ram/portal/templates/cards/magazine.html @@ -0,0 +1,97 @@ +{% load static %} +
    +
    + {% if d.type == "magazine" %} + + {% if d.item.image and d.type == "magazine" %} + {{ d.item }} + {% elif d.item.issue.first.image.exists %} + {% with d.item.issue.first as i %} + {{ d.item }} + {% endwith %} + {% else %} + + {{ d.item }} + {% endif %} + + {% elif d.type == "magazineissue" %} + + {% if d.item.image.exists %} + {{ d.item }} + {% else %} + + {{ d.item }} + {% endif %} + + {% endif %} + +
    +

    + {{ d.item }} + +

    + {% if d.item.tags.all %} +

    Tags: + {% for t in d.item.tags.all %} + {{ t.name }}{# new line is required #} + {% endfor %} +

    + {% endif %} + + + + + + + + {% if d.type == "magazineissue" %} + + + + + {% endif %} + + + + + {% if d.type == "magazineissue" %} + + + + + + + + + + + + + {% endif %} + + + + + +
    + {{ d.type | capfirst }} +
    + {% if not d.item.published %} + Unpublished + {% endif %} +
    +
    Magazine{{ d.item.magazine }}
    Publisher + {{ d.item.publisher.country }} {{ d.item.publisher }} + {% if d.item.publisher.website %} {% endif %} +
    Issue{{ d.item.issue_number }}
    Date{{ d.item.publication_year|default:"-" }} / {{ d.item.publication_month|default:"-" }}
    Pages{{ d.item.number_of_pages|default:"-" }}
    Language{{ d.item.get_language_display }}
    +
    + {% if d.type == "magazine" %} + Show {{ d.item.issues }} issue{{ d.item.issues|pluralize }} + {% else %} + Show all data + {% endif %} + {% if request.user.is_staff %}Edit{% endif %} +
    +
    +
    +
    diff --git a/ram/portal/templates/cards/manufacturer.html b/ram/portal/templates/cards/manufacturer.html index c7e41e0..fbcfd3f 100644 --- a/ram/portal/templates/cards/manufacturer.html +++ b/ram/portal/templates/cards/manufacturer.html @@ -31,7 +31,7 @@
    {% with items=d.item.num_items %} - Show {{ items }} item{{ items | pluralize}} + Show {{ items }} item{{ items|pluralize }} {% if request.user.is_staff %}Edit{% endif %} {% endwith %}
    diff --git a/ram/portal/templates/magazine.html b/ram/portal/templates/magazine.html new file mode 100644 index 0000000..05f7630 --- /dev/null +++ b/ram/portal/templates/magazine.html @@ -0,0 +1,120 @@ +{% extends "cards.html" %} + {% block header %} + {{ block.super }} + {% if magazine.tags.all %} +

    Tags: + {% for t in magazine.tags.all %} + {{ t.name }}{# new line is required #} + {% endfor %} +

    + {% if not magazine.published %} + Unpublished | + {% endif %} + Updated {{ magazine.updated_time | date:"M d, Y H:i" }} + {% endif %} + {% endblock %} + {% block carousel %} + {% if magazine.image %} +
    + +
    + {% endif %} + {% endblock %} + {% block pagination %} + {% if data.has_other_pages %} + + {% endif %} + {% endblock %} + {% block extra_content %} +
    +
    +
    + + + +
    + {% if request.user.is_staff %}Edit{% endif %} +
    +
    +
    +
    + {% endblock %} diff --git a/ram/portal/templatetags/show_menu.py b/ram/portal/templatetags/show_menu.py index 35421dd..4ae0fd9 100644 --- a/ram/portal/templatetags/show_menu.py +++ b/ram/portal/templatetags/show_menu.py @@ -1,6 +1,6 @@ from django import template from portal.models import Flatpage -from bookshelf.models import Book, Catalog +from bookshelf.models import Book, Catalog, Magazine register = template.Library() @@ -8,10 +8,14 @@ register = template.Library() @register.inclusion_tag('bookshelf/bookshelf_menu.html') def show_bookshelf_menu(): # FIXME: Filter out unpublished books and catalogs? + books = Book.objects.exists() + catalogs = Catalog.objects.exists() + magazines = Magazine.objects.exists() return { - "bookshelf_menu": (Book.objects.exists() or Catalog.objects.exists()), - "books_menu": Book.objects.exists(), - "catalogs_menu": Catalog.objects.exists(), + "bookshelf_menu": (books or catalogs or magazines), + "books_menu": books, + "catalogs_menu": catalogs, + "magazines_menu": magazines, } diff --git a/ram/portal/urls.py b/ram/portal/urls.py index b12943e..d517a55 100644 --- a/ram/portal/urls.py +++ b/ram/portal/urls.py @@ -15,6 +15,9 @@ from portal.views import ( Types, Books, Catalogs, + Magazines, + GetMagazine, + GetMagazineIssue, GetBookCatalog, SearchObjects, ) @@ -98,6 +101,31 @@ urlpatterns = [ Books.as_view(), name="books_pagination" ), + path( + "bookshelf/magazine/", + GetMagazine.as_view(), + name="magazine" + ), + path( + "bookshelf/magazine//page/", + GetMagazine.as_view(), + name="magazine_pagination", + ), + path( + "bookshelf/magazine//issue/", + GetMagazineIssue.as_view(), + name="issue", + ), + path( + "bookshelf/magazines", + Magazines.as_view(), + name="magazines" + ), + path( + "bookshelf/magazines/page/", + Magazines.as_view(), + name="magazines_pagination" + ), path( "bookshelf//", GetBookCatalog.as_view(), diff --git a/ram/portal/views.py b/ram/portal/views.py index 7b90bc2..8141761 100644 --- a/ram/portal/views.py +++ b/ram/portal/views.py @@ -16,7 +16,7 @@ from portal.utils import get_site_conf from portal.models import Flatpage from roster.models import RollingStock from consist.models import Consist -from bookshelf.models import Book, Catalog +from bookshelf.models import Book, Catalog, Magazine, MagazineIssue from metadata.models import ( Company, Manufacturer, @@ -73,7 +73,8 @@ class GetData(View): .filter(self.filter) ) - def get(self, request, page=1): + def get(self, request, filter=Q(), page=1): + self.filter = filter data = [] for item in self.get_data(request): data.append({"type": self.item_type, "item": item}) @@ -491,7 +492,8 @@ class Manufacturers(GetData): def get_data(self, request): return ( - Manufacturer.objects.filter(self.filter).annotate( + Manufacturer.objects.filter(self.filter) + .annotate( num_rollingstock=( Count( "rollingstock", @@ -592,9 +594,7 @@ class Scales(GetData): num_consists=Count( "consist", filter=Q( - consist__in=Consist.objects.get_published( - request.user - ) + consist__in=Consist.objects.get_published(request.user) ), distinct=True, ), @@ -637,6 +637,85 @@ class Catalogs(GetData): 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): def get_object(self, request, uuid, selector): if selector == "book": diff --git a/ram/ram/__init__.py b/ram/ram/__init__.py index 4e27f29..01e66b5 100644 --- a/ram/ram/__init__.py +++ b/ram/ram/__init__.py @@ -1,4 +1,4 @@ from ram.utils import git_suffix -__version__ = "0.18.00" +__version__ = "0.18.1" __version__ += git_suffix(__file__)