Compare commits

..

9 Commits

19 changed files with 73 additions and 209 deletions

View File

@@ -475,7 +475,6 @@ class MagazineAdmin(SortableAdminBase, admin.ModelAdmin):
"fields": (
"published",
"name",
"website",
"publisher",
"ISBN",
"language",

View File

@@ -1,18 +0,0 @@
# Generated by Django 6.0 on 2025-12-12 14:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0026_alter_basebook_language_alter_magazine_image_and_more"),
]
operations = [
migrations.AddField(
model_name="magazine",
name="website",
field=models.URLField(blank=True),
),
]

View File

@@ -1,29 +0,0 @@
# 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

@@ -1,11 +1,9 @@
import os
import shutil
from urllib.parse import urlparse
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
@@ -60,24 +58,36 @@ 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):
@@ -121,7 +131,8 @@ 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}
)
@@ -146,19 +157,18 @@ 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"
class Magazine(BaseModel):
name = models.CharField(max_length=200)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
website = models.URLField(blank=True)
ISBN = models.CharField(max_length=17, blank=True) # 13 + dashes
image = models.ImageField(
blank=True,
@@ -168,31 +178,32 @@ class Magazine(BaseModel):
language = models.CharField(
max_length=7,
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):
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 = [Lower("name")]
ordering = ["name"]
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("magazine", kwargs={"uuid": self.uuid})
def website_short(self):
if self.website:
return urlparse(self.website).netloc.replace("www.", "")
return reverse(
"magazine",
kwargs={"uuid": self.uuid}
)
class MagazineIssue(BaseBook):
@@ -201,17 +212,14 @@ 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",
"publication_year",
"publication_month",
"issue_number",
]
ordering = ["magazine", "issue_number"]
def __str__(self):
return f"{self.magazine.name} - {self.issue_number}"
@@ -232,5 +240,9 @@ 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
}
)

View File

@@ -49,5 +49,3 @@ class CatalogSerializer(serializers.ModelSerializer):
"price",
)
read_only_fields = ("creation_time", "updated_time")
# FIXME: add Magazine and MagazineIssue serializers

View File

@@ -38,5 +38,3 @@ class CatalogGet(RetrieveAPIView):
def get_queryset(self):
return Book.objects.get_published(self.request.user)
# FIXME: add Magazine and MagazineIssue views

View File

@@ -1,5 +1,4 @@
import os
from urllib.parse import urlparse
from django.db import models
from django.urls import reverse
from django.conf import settings
@@ -58,10 +57,6 @@ class Manufacturer(models.Model):
},
)
def website_short(self):
if self.website:
return urlparse(self.website).netloc.replace("www.", "")
def logo_thumbnail(self):
return get_image_preview(self.logo.url)

View File

@@ -61,7 +61,8 @@
<thead>
<tr>
<th colspan="2" scope="row">
{{ label|capfirst }}
{% if type == "catalog" %}Catalog
{% elif type == "book" %}Book{% endif %}
</th>
</tr>
</thead>
@@ -69,9 +70,7 @@
{% if type == "catalog" %}
<tr>
<th class="w-33" scope="row">Manufacturer</th>
<td>
<a href="{% url 'filtered' _filter="manufacturer" search=book.manufacturer.slug %}">{{ book.manufacturer }}{% if book.manufacturer.website %}</a> <a href="{{ book.manufacturer.website }}" target="_blank"><i class="bi bi-box-arrow-up-right"></i></a>{% endif %}
</td>
<td>{{ book.manufacturer }}</td>
</tr>
<tr>
<th class="w-33" scope="row">Scales</th>
@@ -98,10 +97,7 @@
{% elif type == "magazineissue" %}
<tr>
<th class="w-33" scope="row">Magazine</th>
<td>
<a href="{% url 'magazine' book.magazine.pk %}">{{ book.magazine }}</a>
{% if book.magazine.website %} <a href="{{ book.magazine.website }}" target="_blank"><i class="bi bi-box-arrow-up-right"></i></a>{% endif %}
</td>
<td><a href="{% url 'magazine' book.magazine.pk %}">{{ book.magazine }}</a></td>
</tr>
<tr>
<th class="w-33" scope="row">Publisher</th>
@@ -116,7 +112,7 @@
</tr>
<tr>
<th class="w-33" scope="row">Date</th>
<td>{{ book.publication_year|default:"-" }} / {{ book.get_publication_month_display|default:"-" }}</td>
<td>{{ book.publication_year|default:"-" }} / {{ book.publication_month|default:"-" }}</td>
</tr>
{% endif %}
<tr>

View File

@@ -13,18 +13,18 @@
<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 #}
{% empty %}
<span class="badge rounded-pill bg-secondary"><i class="bi bi-ban"></i></span>
{% endfor %}
</p>
{% endif %}
<table class="table table-striped">
<thead>
<tr>
<th colspan="2" scope="row">
{{ d.label|capfirst }}
{{ d.type | capfirst }}
<div class="float-end">
{% if not d.item.published %}
<span class="badge text-bg-warning">Unpublished</span>
@@ -37,9 +37,7 @@
{% if d.type == "catalog" %}
<tr>
<th class="w-33" scope="row">Manufacturer</th>
<td>
<a href="{% url 'filtered' _filter="manufacturer" search=d.item.manufacturer.slug %}">{{ d.item.manufacturer }}{% if d.item.manufacturer.website %}</a> <a href="{{ d.item.manufacturer.website }}" target="_blank"><i class="bi bi-box-arrow-up-right"></i></a>{% endif %}
</td>
<td>{{ d.item.manufacturer }}</td>
</tr>
<tr>
<th class="w-33" scope="row">Scales</th>

View File

@@ -14,13 +14,13 @@
<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 #}
{% empty %}
<span class="badge rounded-pill bg-secondary"><i class="bi bi-ban"></i></span>
{% endfor %}
</p>
{% endif %}
<table class="table table-striped">
<thead>
<tr>

View File

@@ -1,5 +1,4 @@
{% load static %}
{% load dynamic_url %}
<div class="col">
<div class="card shadow-sm">
{% if d.type == "magazine" %}
@@ -31,19 +30,18 @@
<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 #}
{% empty %}
<span class="badge rounded-pill bg-secondary"><i class="bi bi-ban"></i></span>
{% endfor %}
</p>
{% endif %}
<table class="table table-striped">
<thead>
<tr>
<th colspan="2" scope="row">
{{ d.label|capfirst }}
{{ d.type | capfirst }}
<div class="float-end">
{% if not d.item.published %}
<span class="badge text-bg-warning">Unpublished</span>
@@ -58,11 +56,6 @@
<th class="w-33" scope="row">Magazine</th>
<td>{{ d.item.magazine }}</td>
</tr>
{% else %}
<tr>
<th class="w-33" scope="row">Website</th>
<td>{% if d.item.website %}<a href="{{ d.item.website }}" target="_blank">{{ d.item.website_short }}</td>{% else %}-{% endif %}</td>
</tr>
{% endif %}
<tr>
<th class="w-33" scope="row">Publisher</th>
@@ -78,7 +71,7 @@
</tr>
<tr>
<th class="w-33" scope="row">Date</th>
<td>{{ d.item.publication_year|default:"-" }} / {{ d.item.get_publication_month_display|default:"-" }}</td>
<td>{{ d.item.publication_year|default:"-" }} / {{ d.item.publication_month|default:"-" }}</td>
</tr>
<tr>
<th class="w-33" scope="row">Pages</th>
@@ -97,7 +90,7 @@
{% 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="{% dynamic_admin_url 'bookshelf' d.type d.item.pk %}">Edit</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>

View File

@@ -17,10 +17,12 @@
<td><img class="logo" src="{{ d.item.logo.url }}" alt="{{ d.item.name }} logo"></td>
</tr>
{% endif %}
{% if d.item.website %}
<tr>
<th class="w-33" scope="row">Website</th>
<td>{% if d.item.website %}<a href="{{ d.item.website }}" target="_blank">{{ d.item.website_short }}</td>{% else %}-{% endif %}</td>
<td><a href="{{ d.item.website }}" target="_blank"><i class="bi bi-box-arrow-up-right"></i></a></td>
</tr>
{% endif %}
<tr>
<th class="w-33" scope="row">Category</th>
<td>{{ d.item.category | title }}</td>

View File

@@ -14,13 +14,13 @@
<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 #}
{% empty %}
<span class="badge rounded-pill bg-secondary"><i class="bi bi-ban"></i></span>
{% endfor %}
</p>
{% endif %}
<table class="table table-striped">
<thead>
<tr>

View File

@@ -88,7 +88,7 @@
<tbody class="table-group-divider">
<tr>
<th class="w-33" scope="row">Name</th>
<td>{{ magazine }}</td>
<td>{{ magazine }} </td>
</tr>
<tr>
<th class="w-33" scope="row">Publisher</th>
@@ -97,14 +97,6 @@
{% 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 class="w-33" scope="row">Website</th>
<td>{% if magazine.website %}<a href="{{ magazine.website }}" target="_blank">{{ magazine.website_short }}</td>{% else %}-{% endif %}</td>
</tr>
<tr>
<th class="w-33" scope="row">Language</th>
<td>{{ magazine.get_language_display }}</td>
</tr>
<tr>
<th scope="row">ISBN</th>
<td>{{ magazine.ISBN | default:"-" }}</td>

View File

@@ -8,7 +8,6 @@ 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
@@ -74,16 +73,11 @@ 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,
"label": self.item_type.capitalize(),
"item": item,
}
)
data.append({"type": self.item_type, "item": item})
paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page)
@@ -183,50 +177,16 @@ class SearchObjects(View):
data.append({"type": "consist", "item": item})
books = (
Book.objects.get_published(request.user)
.filter(
Q(
Q(title__icontains=search)
| Q(description__icontains=search)
)
)
.filter(title__icontains=search)
.distinct()
)
catalogs = (
Catalog.objects.get_published(request.user)
.filter(
Q(
Q(manufacturer__name__icontains=search)
| Q(description__icontains=search)
)
)
.filter(manufacturer__name__icontains=search)
.distinct()
)
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(
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,
}
)
data.append({"type": "book", "item": item})
paginator = Paginator(data, get_items_per_page())
data = paginator.get_page(page)
@@ -384,32 +344,15 @@ 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 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,
}
)
for item in catalogs:
data.append({"type": "catalog", "item": item})
except NameError:
pass
@@ -701,7 +644,7 @@ class Magazines(GetData):
def get_data(self, request):
return (
Magazine.objects.get_published(request.user)
.order_by(Lower("name"))
.all()
.annotate(
issues=Count(
"issue",
@@ -726,7 +669,6 @@ class GetMagazine(View):
data = [
{
"type": "magazineissue",
"label": "Magazine issue",
"item": i,
}
for i in magazine.issue.get_published(request.user).all()
@@ -770,7 +712,6 @@ class GetMagazineIssue(View):
"documents": documents,
"properties": properties,
"type": "magazineissue",
"label": "Magazine issue",
},
)
@@ -801,7 +742,6 @@ class GetBookCatalog(View):
"documents": documents,
"properties": properties,
"type": selector,
"label": selector.capitalize(),
},
)

View File

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

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,12 +0,0 @@
#!/bin/bash
mkdir -p output
for img in input/*.{jpg,png}; do
[ -e "$img" ] || continue # skip if no files
name=$(basename "${img%.*}").jpg
magick convert background.png \
\( "$img" -resize x820 \) \
-gravity center -composite \
-quality 85 -sampling-factor 4:4:4 \
"output/$name"
done