From 5a71dc36fadf570735ef7cd108d903a25d043b30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniele=20Vigan=C3=B2?=
Date: Sun, 21 Dec 2025 22:56:45 +0100
Subject: [PATCH] Improve sorting and extend search to magazines
---
...ine_options_alter_magazineissue_options.py | 29 +++++++
ram/bookshelf/models.py | 60 +++++---------
ram/portal/templates/cards/book.html | 4 +-
ram/portal/templates/cards/consist.html | 4 +-
ram/portal/templates/cards/magazine.html | 4 +-
ram/portal/templates/cards/roster.html | 4 +-
ram/portal/views.py | 80 ++++++++++++++++---
ram/ram/__init__.py | 2 +-
8 files changed, 126 insertions(+), 61 deletions(-)
create mode 100644 ram/bookshelf/migrations/0028_alter_magazine_options_alter_magazineissue_options.py
diff --git a/ram/bookshelf/migrations/0028_alter_magazine_options_alter_magazineissue_options.py b/ram/bookshelf/migrations/0028_alter_magazine_options_alter_magazineissue_options.py
new file mode 100644
index 0000000..688329b
--- /dev/null
+++ b/ram/bookshelf/migrations/0028_alter_magazine_options_alter_magazineissue_options.py
@@ -0,0 +1,29 @@
+# Generated by Django 6.0 on 2025-12-21 21:56
+
+import django.db.models.functions.text
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("bookshelf", "0027_magazine_website"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="magazine",
+ options={"ordering": [django.db.models.functions.text.Lower("name")]},
+ ),
+ migrations.AlterModelOptions(
+ name="magazineissue",
+ options={
+ "ordering": [
+ "magazine",
+ "publication_year",
+ "publication_month",
+ "issue_number",
+ ]
+ },
+ ),
+ ]
diff --git a/ram/bookshelf/models.py b/ram/bookshelf/models.py
index df556ce..f3b5f36 100644
--- a/ram/bookshelf/models.py
+++ b/ram/bookshelf/models.py
@@ -5,6 +5,7 @@ from django.db import models
from django.conf import settings
from django.urls import reverse
from django.utils.dates import MONTHS
+from django.db.models.functions import Lower
from django.core.exceptions import ValidationError
from django_countries.fields import CountryField
@@ -59,36 +60,24 @@ class BaseBook(BaseModel):
blank=True,
)
purchase_date = models.DateField(null=True, blank=True)
- tags = models.ManyToManyField(
- Tag, related_name="bookshelf", blank=True
- )
+ tags = models.ManyToManyField(Tag, related_name="bookshelf", blank=True)
def delete(self, *args, **kwargs):
shutil.rmtree(
os.path.join(
settings.MEDIA_ROOT, "images", "books", str(self.uuid)
),
- ignore_errors=True
+ ignore_errors=True,
)
super(BaseBook, self).delete(*args, **kwargs)
def book_image_upload(instance, filename):
- return os.path.join(
- "images",
- "books",
- str(instance.book.uuid),
- filename
- )
+ return os.path.join("images", "books", str(instance.book.uuid), filename)
def magazine_image_upload(instance, filename):
- return os.path.join(
- "images",
- "magazines",
- str(instance.uuid),
- filename
- )
+ return os.path.join("images", "magazines", str(instance.uuid), filename)
class BaseBookImage(Image):
@@ -132,8 +121,7 @@ class Book(BaseBook):
def get_absolute_url(self):
return reverse(
- "bookshelf_item",
- kwargs={"selector": "book", "uuid": self.uuid}
+ "bookshelf_item", kwargs={"selector": "book", "uuid": self.uuid}
)
@@ -158,12 +146,12 @@ class Catalog(BaseBook):
def get_absolute_url(self):
return reverse(
- "bookshelf_item",
- kwargs={"selector": "catalog", "uuid": self.uuid}
+ "bookshelf_item", kwargs={"selector": "catalog", "uuid": self.uuid}
)
def get_scales(self):
return "/".join([s.scale for s in self.scales.all()])
+
get_scales.short_description = "Scales"
@@ -180,32 +168,27 @@ class Magazine(BaseModel):
language = models.CharField(
max_length=7,
choices=sorted(settings.LANGUAGES, key=lambda s: s[1]),
- default='en'
- )
- tags = models.ManyToManyField(
- Tag, related_name="magazine", blank=True
+ default="en",
)
+ tags = models.ManyToManyField(Tag, related_name="magazine", blank=True)
def delete(self, *args, **kwargs):
shutil.rmtree(
os.path.join(
settings.MEDIA_ROOT, "images", "magazines", str(self.uuid)
),
- ignore_errors=True
+ ignore_errors=True,
)
super(Magazine, self).delete(*args, **kwargs)
class Meta:
- ordering = ["name"]
+ ordering = [Lower("name")]
def __str__(self):
return self.name
def get_absolute_url(self):
- return reverse(
- "magazine",
- kwargs={"uuid": self.uuid}
- )
+ return reverse("magazine", kwargs={"uuid": self.uuid})
def website_short(self):
if self.website:
@@ -218,14 +201,17 @@ class MagazineIssue(BaseBook):
)
issue_number = models.CharField(max_length=100)
publication_month = models.SmallIntegerField(
- null=True,
- blank=True,
- choices=MONTHS.items()
+ null=True, blank=True, choices=MONTHS.items()
)
class Meta:
unique_together = ("magazine", "issue_number")
- ordering = ["magazine", "issue_number"]
+ ordering = [
+ "magazine",
+ "publication_year",
+ "publication_month",
+ "issue_number",
+ ]
def __str__(self):
return f"{self.magazine.name} - {self.issue_number}"
@@ -246,9 +232,5 @@ class MagazineIssue(BaseBook):
def get_absolute_url(self):
return reverse(
- "issue",
- kwargs={
- "uuid": self.uuid,
- "magazine": self.magazine.uuid
- }
+ "issue", kwargs={"uuid": self.uuid, "magazine": self.magazine.uuid}
)
diff --git a/ram/portal/templates/cards/book.html b/ram/portal/templates/cards/book.html
index b425ba1..1421c61 100644
--- a/ram/portal/templates/cards/book.html
+++ b/ram/portal/templates/cards/book.html
@@ -13,13 +13,13 @@
{{ d.item }}
- {% if d.item.tags.all %}
Tags:
{% for t in d.item.tags.all %}
{{ t.name }}{# new line is required #}
+ {% empty %}
+
{% endfor %}
- {% endif %}
diff --git a/ram/portal/templates/cards/consist.html b/ram/portal/templates/cards/consist.html
index e3da341..1fc8ce6 100644
--- a/ram/portal/templates/cards/consist.html
+++ b/ram/portal/templates/cards/consist.html
@@ -14,13 +14,13 @@
{{ d.item }}
- {% if d.item.tags.all %}
Tags:
{% for t in d.item.tags.all %}
{{ t.name }}{# new line is required #}
+ {% empty %}
+
{% endfor %}
- {% endif %}
diff --git a/ram/portal/templates/cards/magazine.html b/ram/portal/templates/cards/magazine.html
index c86a5a6..25832b5 100644
--- a/ram/portal/templates/cards/magazine.html
+++ b/ram/portal/templates/cards/magazine.html
@@ -31,13 +31,13 @@
{{ d.item }}
- {% if d.item.tags.all %}
Tags:
{% for t in d.item.tags.all %}
{{ t.name }}{# new line is required #}
+ {% empty %}
+
{% endfor %}
- {% endif %}
diff --git a/ram/portal/templates/cards/roster.html b/ram/portal/templates/cards/roster.html
index 4519c05..0d79921 100644
--- a/ram/portal/templates/cards/roster.html
+++ b/ram/portal/templates/cards/roster.html
@@ -14,13 +14,13 @@
{{ d.item }}
- {% if d.item.tags.all %}
Tags:
{% for t in d.item.tags.all %}
{{ t.name }}{# new line is required #}
+ {% empty %}
+
{% endfor %}
- {% endif %}
diff --git a/ram/portal/views.py b/ram/portal/views.py
index 588f212..5e059a5 100644
--- a/ram/portal/views.py
+++ b/ram/portal/views.py
@@ -8,6 +8,7 @@ from django.views import View
from django.http import Http404, HttpResponseBadRequest
from django.db.utils import OperationalError, ProgrammingError
from django.db.models import F, Q, Count
+from django.db.models.functions import Lower
from django.shortcuts import render, get_object_or_404, get_list_or_404
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
@@ -76,11 +77,13 @@ class GetData(View):
def get(self, request, page=1):
data = []
for item in self.get_data(request):
- data.append({
- "type": self.item_type,
- "label": self.item_type.capitalize(),
- "item": item
- })
+ data.append(
+ {
+ "type": self.item_type,
+ "label": self.item_type.capitalize(),
+ "item": item,
+ }
+ )
paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page)
@@ -180,16 +183,50 @@ class SearchObjects(View):
data.append({"type": "consist", "item": item})
books = (
Book.objects.get_published(request.user)
- .filter(title__icontains=search)
+ .filter(
+ Q(
+ Q(title__icontains=search)
+ | Q(description__icontains=search)
+ )
+ )
.distinct()
)
catalogs = (
Catalog.objects.get_published(request.user)
- .filter(manufacturer__name__icontains=search)
+ .filter(
+ Q(
+ Q(manufacturer__name__icontains=search)
+ | Q(description__icontains=search)
+ )
+ )
.distinct()
)
for item in list(chain(books, catalogs)):
- data.append({"type": "book", "item": item})
+ data.append(
+ {
+ "type": "book",
+ "label": item._meta.object_name,
+ "item": item,
+ }
+ )
+ magazine_issues = (
+ MagazineIssue.objects.get_published(request.user)
+ .filter(
+ Q(
+ Q(magazine__name__icontains=search)
+ | Q(description__icontains=search)
+ )
+ )
+ .distinct()
+ )
+ for item in magazine_issues:
+ data.append(
+ {
+ "type": "book",
+ "label": "Magazine Issue",
+ "item": item,
+ }
+ )
paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page)
@@ -347,15 +384,32 @@ class GetObjectsFiltered(View):
.filter(query_2nd)
.distinct()
)
- for item in books:
- data.append({"type": "book", "item": item})
catalogs = (
Catalog.objects.get_published(request.user)
.filter(query_2nd)
.distinct()
)
- for item in catalogs:
- data.append({"type": "catalog", "item": item})
+ for item in list(chain(books, catalogs)):
+ data.append(
+ {
+ "type": "book",
+ "label": item._meta.object_name,
+ "item": item,
+ }
+ )
+ magazine_issues = (
+ MagazineIssue.objects.get_published(request.user)
+ .filter(query_2nd)
+ .distinct()
+ )
+ for item in magazine_issues:
+ data.append(
+ {
+ "type": "book",
+ "label": "Magazine Issue",
+ "item": item,
+ }
+ )
except NameError:
pass
@@ -647,7 +701,7 @@ class Magazines(GetData):
def get_data(self, request):
return (
Magazine.objects.get_published(request.user)
- .all()
+ .order_by(Lower("name"))
.annotate(
issues=Count(
"issue",
diff --git a/ram/ram/__init__.py b/ram/ram/__init__.py
index 0f17a7e..35e25c3 100644
--- a/ram/ram/__init__.py
+++ b/ram/ram/__init__.py
@@ -1,4 +1,4 @@
from ram.utils import git_suffix
-__version__ = "0.18.5"
+__version__ = "0.18.6"
__version__ += git_suffix(__file__)