Improve sorting and extend search to magazines

This commit is contained in:
2025-12-21 22:56:45 +01:00
parent c539255bf9
commit 5a71dc36fa
8 changed files with 126 additions and 61 deletions

View File

@@ -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",
]
},
),
]

View File

@@ -5,6 +5,7 @@ from django.db import models
from django.conf import settings from django.conf import settings
from django.urls import reverse from django.urls import reverse
from django.utils.dates import MONTHS from django.utils.dates import MONTHS
from django.db.models.functions import Lower
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django_countries.fields import CountryField from django_countries.fields import CountryField
@@ -59,36 +60,24 @@ class BaseBook(BaseModel):
blank=True, blank=True,
) )
purchase_date = models.DateField(null=True, blank=True) purchase_date = models.DateField(null=True, blank=True)
tags = models.ManyToManyField( tags = models.ManyToManyField(Tag, related_name="bookshelf", blank=True)
Tag, related_name="bookshelf", blank=True
)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
shutil.rmtree( shutil.rmtree(
os.path.join( os.path.join(
settings.MEDIA_ROOT, "images", "books", str(self.uuid) settings.MEDIA_ROOT, "images", "books", str(self.uuid)
), ),
ignore_errors=True ignore_errors=True,
) )
super(BaseBook, self).delete(*args, **kwargs) super(BaseBook, self).delete(*args, **kwargs)
def book_image_upload(instance, filename): def book_image_upload(instance, filename):
return os.path.join( return os.path.join("images", "books", str(instance.book.uuid), filename)
"images",
"books",
str(instance.book.uuid),
filename
)
def magazine_image_upload(instance, filename): def magazine_image_upload(instance, filename):
return os.path.join( return os.path.join("images", "magazines", str(instance.uuid), filename)
"images",
"magazines",
str(instance.uuid),
filename
)
class BaseBookImage(Image): class BaseBookImage(Image):
@@ -132,8 +121,7 @@ class Book(BaseBook):
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse(
"bookshelf_item", "bookshelf_item", kwargs={"selector": "book", "uuid": self.uuid}
kwargs={"selector": "book", "uuid": self.uuid}
) )
@@ -158,12 +146,12 @@ class Catalog(BaseBook):
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse(
"bookshelf_item", "bookshelf_item", kwargs={"selector": "catalog", "uuid": self.uuid}
kwargs={"selector": "catalog", "uuid": self.uuid}
) )
def get_scales(self): def get_scales(self):
return "/".join([s.scale for s in self.scales.all()]) return "/".join([s.scale for s in self.scales.all()])
get_scales.short_description = "Scales" get_scales.short_description = "Scales"
@@ -180,32 +168,27 @@ class Magazine(BaseModel):
language = models.CharField( language = models.CharField(
max_length=7, max_length=7,
choices=sorted(settings.LANGUAGES, key=lambda s: s[1]), choices=sorted(settings.LANGUAGES, key=lambda s: s[1]),
default='en' default="en",
)
tags = models.ManyToManyField(
Tag, related_name="magazine", blank=True
) )
tags = models.ManyToManyField(Tag, related_name="magazine", blank=True)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
shutil.rmtree( shutil.rmtree(
os.path.join( os.path.join(
settings.MEDIA_ROOT, "images", "magazines", str(self.uuid) settings.MEDIA_ROOT, "images", "magazines", str(self.uuid)
), ),
ignore_errors=True ignore_errors=True,
) )
super(Magazine, self).delete(*args, **kwargs) super(Magazine, self).delete(*args, **kwargs)
class Meta: class Meta:
ordering = ["name"] ordering = [Lower("name")]
def __str__(self): def __str__(self):
return self.name return self.name
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse("magazine", kwargs={"uuid": self.uuid})
"magazine",
kwargs={"uuid": self.uuid}
)
def website_short(self): def website_short(self):
if self.website: if self.website:
@@ -218,14 +201,17 @@ class MagazineIssue(BaseBook):
) )
issue_number = models.CharField(max_length=100) issue_number = models.CharField(max_length=100)
publication_month = models.SmallIntegerField( publication_month = models.SmallIntegerField(
null=True, null=True, blank=True, choices=MONTHS.items()
blank=True,
choices=MONTHS.items()
) )
class Meta: class Meta:
unique_together = ("magazine", "issue_number") unique_together = ("magazine", "issue_number")
ordering = ["magazine", "issue_number"] ordering = [
"magazine",
"publication_year",
"publication_month",
"issue_number",
]
def __str__(self): def __str__(self):
return f"{self.magazine.name} - {self.issue_number}" return f"{self.magazine.name} - {self.issue_number}"
@@ -246,9 +232,5 @@ class MagazineIssue(BaseBook):
def get_absolute_url(self): def get_absolute_url(self):
return reverse( return reverse(
"issue", "issue", kwargs={"uuid": self.uuid, "magazine": self.magazine.uuid}
kwargs={
"uuid": self.uuid,
"magazine": self.magazine.uuid
}
) )

View File

@@ -13,13 +13,13 @@
<strong>{{ d.item }}</strong> <strong>{{ d.item }}</strong>
<a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a> <a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a>
</p> </p>
{% if d.item.tags.all %}
<p class="card-text"><small>Tags:</small> <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"> {% 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 #} {{ t.name }}</a>{# new line is required #}
{% empty %}
<span class="badge rounded-pill bg-secondary"><i class="bi bi-ban"></i></span>
{% endfor %} {% endfor %}
</p> </p>
{% endif %}
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>

View File

@@ -14,13 +14,13 @@
<strong>{{ d.item }}</strong> <strong>{{ d.item }}</strong>
<a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a> <a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a>
</p> </p>
{% if d.item.tags.all %}
<p class="card-text"><small>Tags:</small> <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"> {% 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 #} {{ t.name }}</a>{# new line is required #}
{% empty %}
<span class="badge rounded-pill bg-secondary"><i class="bi bi-ban"></i></span>
{% endfor %} {% endfor %}
</p> </p>
{% endif %}
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>

View File

@@ -31,13 +31,13 @@
<strong>{{ d.item }}</strong> <strong>{{ d.item }}</strong>
<a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a> <a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a>
</p> </p>
{% if d.item.tags.all %}
<p class="card-text"><small>Tags:</small> <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"> {% 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 #} {{ t.name }}</a>{# new line is required #}
{% empty %}
<span class="badge rounded-pill bg-secondary"><i class="bi bi-ban"></i></span>
{% endfor %} {% endfor %}
</p> </p>
{% endif %}
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>

View File

@@ -14,13 +14,13 @@
<strong>{{ d.item }}</strong> <strong>{{ d.item }}</strong>
<a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a> <a class="stretched-link" href="{{ d.item.get_absolute_url }}"></a>
</p> </p>
{% if d.item.tags.all %}
<p class="card-text"><small>Tags:</small> <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"> {% 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 #} {{ t.name }}</a>{# new line is required #}
{% empty %}
<span class="badge rounded-pill bg-secondary"><i class="bi bi-ban"></i></span>
{% endfor %} {% endfor %}
</p> </p>
{% endif %}
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>

View File

@@ -8,6 +8,7 @@ from django.views import View
from django.http import Http404, HttpResponseBadRequest from django.http import Http404, HttpResponseBadRequest
from django.db.utils import OperationalError, ProgrammingError from django.db.utils import OperationalError, ProgrammingError
from django.db.models import F, Q, Count 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.shortcuts import render, get_object_or_404, get_list_or_404
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator from django.core.paginator import Paginator
@@ -76,11 +77,13 @@ class GetData(View):
def get(self, request, page=1): def get(self, request, page=1):
data = [] data = []
for item in self.get_data(request): for item in self.get_data(request):
data.append({ data.append(
{
"type": self.item_type, "type": self.item_type,
"label": self.item_type.capitalize(), "label": self.item_type.capitalize(),
"item": item "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)
@@ -180,16 +183,50 @@ class SearchObjects(View):
data.append({"type": "consist", "item": item}) data.append({"type": "consist", "item": item})
books = ( books = (
Book.objects.get_published(request.user) Book.objects.get_published(request.user)
.filter(title__icontains=search) .filter(
Q(
Q(title__icontains=search)
| Q(description__icontains=search)
)
)
.distinct() .distinct()
) )
catalogs = ( catalogs = (
Catalog.objects.get_published(request.user) Catalog.objects.get_published(request.user)
.filter(manufacturer__name__icontains=search) .filter(
Q(
Q(manufacturer__name__icontains=search)
| Q(description__icontains=search)
)
)
.distinct() .distinct()
) )
for item in list(chain(books, catalogs)): 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()) paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page) data = paginator.get_page(page)
@@ -347,15 +384,32 @@ class GetObjectsFiltered(View):
.filter(query_2nd) .filter(query_2nd)
.distinct() .distinct()
) )
for item in books:
data.append({"type": "book", "item": item})
catalogs = ( catalogs = (
Catalog.objects.get_published(request.user) Catalog.objects.get_published(request.user)
.filter(query_2nd) .filter(query_2nd)
.distinct() .distinct()
) )
for item in catalogs: for item in list(chain(books, catalogs)):
data.append({"type": "catalog", "item": item}) 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: except NameError:
pass pass
@@ -647,7 +701,7 @@ class Magazines(GetData):
def get_data(self, request): def get_data(self, request):
return ( return (
Magazine.objects.get_published(request.user) Magazine.objects.get_published(request.user)
.all() .order_by(Lower("name"))
.annotate( .annotate(
issues=Count( issues=Count(
"issue", "issue",

View File

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