Stabilize the magazine repository app

This commit is contained in:
2025-12-08 22:48:14 +01:00
parent 5e6366e10c
commit 7f456890dd
6 changed files with 261 additions and 48 deletions

View File

@@ -76,7 +76,7 @@ class BookAdmin(SortableAdminBase, admin.ModelAdmin):
autocomplete_fields = ("authors", "publisher", "shop") autocomplete_fields = ("authors", "publisher", "shop")
readonly_fields = ("invoices", "creation_time", "updated_time") readonly_fields = ("invoices", "creation_time", "updated_time")
search_fields = ("title", "publisher__name", "authors__last_name") search_fields = ("title", "publisher__name", "authors__last_name")
list_filter = ("publisher__name", "authors") list_filter = ("publisher__name", "authors", "published")
fieldsets = ( fieldsets = (
( (
@@ -239,7 +239,12 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
autocomplete_fields = ("manufacturer",) autocomplete_fields = ("manufacturer",)
readonly_fields = ("invoices", "creation_time", "updated_time") readonly_fields = ("invoices", "creation_time", "updated_time")
search_fields = ("manufacturer__name", "years", "scales__scale") search_fields = ("manufacturer__name", "years", "scales__scale")
list_filter = ("manufacturer__name", "publication_year", "scales__scale") list_filter = (
"manufacturer__name",
"publication_year",
"scales__scale",
"published",
)
fieldsets = ( fieldsets = (
( (
@@ -358,8 +363,8 @@ class CatalogAdmin(SortableAdminBase, admin.ModelAdmin):
actions = [publish, unpublish, download_csv] actions = [publish, unpublish, download_csv]
@admin.register(Issue) @admin.register(MagazineIssue)
class MagazineIssueAdmin(admin.ModelAdmin): class MagazineIssueAdmin(SortableAdminBase, admin.ModelAdmin):
inlines = ( inlines = (
BookPropertyInline, BookPropertyInline,
BookImageInline, BookImageInline,
@@ -370,10 +375,8 @@ class MagazineIssueAdmin(admin.ModelAdmin):
"issue_number", "issue_number",
"published", "published",
) )
# autocomplete_fields = ("publisher",) autocomplete_fields = ("shop",)
# readonly_fields = ("creation_time", "updated_time") readonly_fields = ("magazine", "creation_time", "updated_time")
# search_fields = ("title", "publisher__name")
# list_filter = ("publisher__name", "language")
def get_model_perms(self, request): def get_model_perms(self, request):
""" """
@@ -381,14 +384,106 @@ class MagazineIssueAdmin(admin.ModelAdmin):
""" """
return {} return {}
fieldsets = (
(
None,
{
"fields": (
"published",
"magazine",
"issue_number",
"publication_year",
"publication_month",
"ISBN",
"language",
"number_of_pages",
"description",
"tags",
)
},
),
(
"Purchase data",
{
"classes": ("collapse",),
"fields": (
"shop",
"purchase_date",
"price",
),
},
),
(
"Notes",
{"classes": ("collapse",), "fields": ("notes",)},
),
(
"Audit",
{
"classes": ("collapse",),
"fields": (
"creation_time",
"updated_time",
),
},
),
)
actions = [publish, unpublish] actions = [publish, unpublish]
class MagazineIssueInline(admin.StackedInline):
model = MagazineIssue
min_num = 0
extra = 0
autocomplete_fields = ("shop",)
show_change_link = True
fieldsets = (
(
None,
{
"fields": (
"published",
"issue_number",
"publication_year",
"publication_month",
)
},
),
(
"Additional info",
{
"classes": ("collapse",),
"fields": (
"language",
"number_of_pages",
"ISBN",
"tags",
),
},
),
(
"Purchase data",
{
"classes": ("collapse",),
"fields": (
"shop",
"purchase_date",
"price",
),
},
),
)
class Media:
js = ('admin/js/magazine_issue_defaults.js',)
@admin.register(Magazine) @admin.register(Magazine)
class MagazineAdmin(admin.ModelAdmin): class MagazineAdmin(SortableAdminBase, admin.ModelAdmin):
inlines = ( inlines = (
MagazineIssueInline, MagazineIssueInline,
) )
list_display = ( list_display = (
"__str__", "__str__",
"publisher", "publisher",
@@ -397,6 +492,37 @@ class MagazineAdmin(admin.ModelAdmin):
autocomplete_fields = ("publisher",) autocomplete_fields = ("publisher",)
readonly_fields = ("creation_time", "updated_time") readonly_fields = ("creation_time", "updated_time")
search_fields = ("name", "publisher__name") search_fields = ("name", "publisher__name")
list_filter = ("publisher__name", "language") list_filter = ("publisher__name", "published")
fieldsets = (
(
None,
{
"fields": (
"published",
"name",
"publisher",
"ISBN",
"language",
"description",
"image",
"tags",
)
},
),
(
"Notes",
{"classes": ("collapse",), "fields": ("notes",)},
),
(
"Audit",
{
"classes": ("collapse",),
"fields": (
"creation_time",
"updated_time",
),
},
),
)
actions = [publish, unpublish] actions = [publish, unpublish]

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.8 on 2025-11-13 23:01 # Generated by Django 6.0 on 2025-12-08 17:47
import bookshelf.models import bookshelf.models
import django.db.models.deletion import django.db.models.deletion
@@ -11,32 +11,11 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("bookshelf", "0023_delete_basebookdocument"), ("bookshelf", "0024_alter_basebook_language"),
("metadata", "0025_alter_company_options_alter_manufacturer_options_and_more"), ("metadata", "0025_alter_company_options_alter_manufacturer_options_and_more"),
] ]
operations = [ operations = [
migrations.CreateModel(
name="Issue",
fields=[
(
"basebook_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="bookshelf.basebook",
),
),
("issue_number", models.CharField(max_length=100)),
],
options={
"abstract": False,
},
bases=("bookshelf.basebook",),
),
migrations.CreateModel( migrations.CreateModel(
name="Magazine", name="Magazine",
fields=[ fields=[
@@ -55,12 +34,13 @@ class Migration(migrations.Migration):
("updated_time", models.DateTimeField(auto_now=True)), ("updated_time", models.DateTimeField(auto_now=True)),
("published", models.BooleanField(default=True)), ("published", models.BooleanField(default=True)),
("name", models.CharField(max_length=200)), ("name", models.CharField(max_length=200)),
("ISBN", models.CharField(blank=True, max_length=17)),
( (
"image", "image",
models.ImageField( models.ImageField(
blank=True, blank=True,
storage=ram.utils.DeduplicatedStorage, storage=ram.utils.DeduplicatedStorage,
upload_to=bookshelf.models.magazine_image_upload, upload_to=bookshelf.models.book_image_upload,
), ),
), ),
( (
@@ -108,6 +88,7 @@ class Migration(migrations.Migration):
("hi", "Hindi"), ("hi", "Hindi"),
("hr", "Croatian"), ("hr", "Croatian"),
("hsb", "Upper Sorbian"), ("hsb", "Upper Sorbian"),
("ht", "Haitian Creole"),
("hu", "Hungarian"), ("hu", "Hungarian"),
("hy", "Armenian"), ("hy", "Armenian"),
("ia", "Interlingua"), ("ia", "Interlingua"),
@@ -193,34 +174,51 @@ class Migration(migrations.Migration):
name="MagazineIssue", name="MagazineIssue",
fields=[ fields=[
( (
"id", "basebook_ptr",
models.BigAutoField( models.OneToOneField(
auto_created=True, auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True, primary_key=True,
serialize=False, serialize=False,
verbose_name="ID", to="bookshelf.basebook",
), ),
), ),
("issue_number", models.CharField(max_length=100)),
( (
"issue", "publication_month",
models.ForeignKey( models.SmallIntegerField(
on_delete=django.db.models.deletion.CASCADE, blank=True,
related_name="magazine_issue", choices=[
to="bookshelf.issue", (1, "January"),
(2, "February"),
(3, "March"),
(4, "April"),
(5, "May"),
(6, "June"),
(7, "July"),
(8, "August"),
(9, "September"),
(10, "October"),
(11, "November"),
(12, "December"),
],
null=True,
), ),
), ),
( (
"magazine", "magazine",
models.ForeignKey( models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name="magazine_issue", related_name="issue",
to="bookshelf.magazine", to="bookshelf.magazine",
), ),
), ),
], ],
options={ options={
"ordering": ["magazine", "issue"], "ordering": ["magazine", "issue_number"],
"unique_together": {("magazine", "issue")}, "unique_together": {("magazine", "issue_number")},
}, },
bases=("bookshelf.basebook",),
), ),
] ]

View File

@@ -4,6 +4,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.core.exceptions import ValidationError
from django_countries.fields import CountryField from django_countries.fields import CountryField
from ram.utils import DeduplicatedStorage from ram.utils import DeduplicatedStorage
@@ -212,4 +213,11 @@ class MagazineIssue(BaseBook):
ordering = ["magazine", "issue_number"] ordering = ["magazine", "issue_number"]
def __str__(self): def __str__(self):
return f"{self.magazine.name} - {self.issue.issue_number}" return f"{self.magazine.name} - {self.issue_number}"
def clean(self):
if self.magazine.published is False and self.published is True:
raise ValidationError(
"Cannot set an issue as published if the magazine is not "
"published."
)

View File

@@ -0,0 +1,16 @@
document.addEventListener('formset:added', function(event) {
const newForm = event.target; // the new inline form element
const defaultLanguage = document.querySelector('#id_language').value;
const defaultStatus = document.querySelector('#id_published').checked;
const languageInput = newForm.querySelector('select[name$="language"]');
const statusInput = newForm.querySelector('input[name$="published"]');
if (languageInput) {
languageInput.value = defaultLanguage;
}
if (statusInput) {
statusInput.checked = defaultStatus;
}
});

View File

@@ -0,0 +1,65 @@
# Generated by Django 6.0 on 2025-12-08 17:47
import django.db.models.deletion
import ram.utils
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookshelf", "0025_magazine_magazineissue"),
(
"repository",
"0003_alter_bookdocument_file_alter_catalogdocument_file_and_more",
),
]
operations = [
migrations.CreateModel(
name="MagazineIssueDocument",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("description", models.CharField(blank=True, max_length=128)),
(
"file",
models.FileField(
storage=ram.utils.DeduplicatedStorage, upload_to="files/"
),
),
("creation_time", models.DateTimeField(auto_now_add=True)),
("updated_time", models.DateTimeField(auto_now=True)),
(
"private",
models.BooleanField(
default=False,
help_text="Document will be visible only to logged users",
),
),
(
"issue",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="document",
to="bookshelf.magazineissue",
),
),
],
options={
"verbose_name_plural": "Magazines documents",
"constraints": [
models.UniqueConstraint(
fields=("issue", "file"), name="unique_issue_file"
)
],
},
),
]

View File

@@ -5,7 +5,7 @@ from tinymce import models as tinymce
from ram.models import PrivateDocument from ram.models import PrivateDocument
from metadata.models import Decoder, Shop, Tag from metadata.models import Decoder, Shop, Tag
from roster.models import RollingStock from roster.models import RollingStock
from bookshelf.models import Book, Catalog, Issue from bookshelf.models import Book, Catalog, MagazineIssue
class GenericDocument(PrivateDocument): class GenericDocument(PrivateDocument):
@@ -78,7 +78,7 @@ class CatalogDocument(PrivateDocument):
class MagazineIssueDocument(PrivateDocument): class MagazineIssueDocument(PrivateDocument):
issue = models.ForeignKey( issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="document" MagazineIssue, on_delete=models.CASCADE, related_name="document"
) )
class Meta: class Meta: