diff --git a/admin.py b/admin.py
index 877c359..1b924bd 100644
--- a/admin.py
+++ b/admin.py
@@ -1,35 +1,84 @@
from django.contrib import admin
-from plugins.books.models import *
+import plugins.books.models as models
+@admin.register(models.BookSetting)
class BookSettingAdmin(admin.ModelAdmin):
list_display = ('book_page_title',)
+@admin.register(models.Chapter)
class ChapterAdmin(admin.ModelAdmin):
- """Displays objects in the Django admin interface."""
list_display = ('title', 'number', 'book', 'pages', 'doi')
list_filter = ('book',)
- search_fields = ('title',)
+ search_fields = ('title', 'book__title', 'doi')
raw_id_fields = ('book',)
filter_horizontal = ('contributors',)
+@admin.register(models.BookAccess)
class BookAccessAdmin(admin.ModelAdmin):
list_display = ('book', 'chapter', 'type', 'format', 'country', 'accessed')
+ list_filter = ('book', 'type', 'format', 'country')
+ search_fields = ('book__title', 'chapter__title')
+
+
+@admin.register(models.Book)
+class BookAdmin(admin.ModelAdmin):
+ list_display = ('title', 'publisher_name', 'date_published', 'is_open_access')
+ list_filter = ('is_open_access', 'peer_reviewed', 'date_published', 'category')
+ search_fields = ('title', 'subtitle', 'publisher_name', 'isbn', 'doi')
+ raw_id_fields = ('category',)
+ filter_horizontal = ('keywords', 'publisher_notes', 'linked_repository_objects')
+
+
+@admin.register(models.Contributor)
+class ContributorAdmin(admin.ModelAdmin):
+ list_display = ('last_name', 'first_name', 'book', 'affiliation', 'sequence')
+ list_filter = ('book',)
+ search_fields = ('first_name', 'last_name', 'affiliation', 'email')
+ raw_id_fields = ('book',)
+
+
+@admin.register(models.Format)
+class FormatAdmin(admin.ModelAdmin):
+ list_display = ('title', 'book', 'sequence')
+ list_filter = ('book',)
+ search_fields = ('title', 'filename')
+ raw_id_fields = ('book',)
+
+
+@admin.register(models.Category)
+class CategoryAdmin(admin.ModelAdmin):
+ list_display = ('name', 'slug', 'display_title')
+ search_fields = ('name', 'slug')
+
+
+@admin.register(models.BookPreprint)
+class BookPreprintAdmin(admin.ModelAdmin):
+ list_display = ('book', 'preprint', 'order')
list_filter = ('book',)
- search_fields = ('book__title',)
+ raw_id_fields = ('book', 'preprint')
+
+
+@admin.register(models.KeywordBook)
+class KeywordBookAdmin(admin.ModelAdmin):
+ list_display = ('keyword', 'book', 'order')
+ list_filter = ('book',)
+ search_fields = ('keyword__word',)
+ raw_id_fields = ('keyword', 'book')
+
+@admin.register(models.KeywordChapter)
+class KeywordChapterAdmin(admin.ModelAdmin):
+ list_display = ('keyword', 'chapter', 'order')
+ list_filter = ('chapter',)
+ search_fields = ('keyword__word',)
+ raw_id_fields = ('keyword', 'chapter')
-admin_list = [
- (Book, ),
- (Contributor,),
- (Format,),
- (BookAccess, BookAccessAdmin),
- (Chapter, ChapterAdmin),
- (Category,),
- (BookSetting, BookSettingAdmin)
-]
-[admin.site.register(*t) for t in admin_list]
+@admin.register(models.PublisherNote)
+class PublisherNoteAdmin(admin.ModelAdmin):
+ list_display = ('note',)
+ search_fields = ('note',)
diff --git a/forms.py b/forms.py
index 293b740..2efbcad 100644
--- a/forms.py
+++ b/forms.py
@@ -6,6 +6,7 @@
from django_summernote.widgets import SummernoteWidget
from plugins.books import models, files
+from repository import models as repository_models
class DateInput(forms.DateInput):
@@ -44,7 +45,7 @@ class BookForm(forms.ModelForm):
class Meta:
model = models.Book
- exclude = ('keywords', 'publisher_notes')
+ exclude = ('keywords', 'publisher_notes', 'linked_repository_objects')
widgets = {
'description': SummernoteWidget(),
'date_published': DateInput(),
@@ -68,6 +69,11 @@ class FormatForm(forms.ModelForm):
file = forms.FileField()
+ def __init__(self, *args, **kwargs):
+ super(FormatForm, self).__init__(*args, **kwargs)
+ if self.instance and self.instance.pk and self.instance.filename:
+ self.fields['file'].required = False
+
class Meta:
model = models.Format
exclude = ('book', 'filename')
@@ -75,8 +81,10 @@ class Meta:
def save(self, commit=True, *args, **kwargs):
save_format = super(FormatForm, self).save(commit=False)
file = self.cleaned_data["file"]
- filename = files.save_file_to_disk(file, save_format)
- save_format.filename = filename
+
+ if file:
+ filename = files.save_file_to_disk(file, save_format)
+ save_format.filename = filename
if commit:
save_format.save()
@@ -165,3 +173,17 @@ def save(self, commit=True):
save_category.save()
return save_category
+
+
+class PreprintSelectionForm(forms.Form):
+ preprint_id = forms.ModelChoiceField(
+ queryset=repository_models.Preprint.objects.none(),
+ label="Select a Preprint",
+ required=True,
+ )
+
+ def __init__(self, *args, **kwargs):
+ available_preprints = kwargs.pop('available_preprints', None)
+ super().__init__(*args, **kwargs)
+ if available_preprints is not None:
+ self.fields['preprint_id'].queryset = available_preprints
\ No newline at end of file
diff --git a/hooks.py b/hooks.py
index 7417427..c6a2c0c 100644
--- a/hooks.py
+++ b/hooks.py
@@ -1,11 +1,35 @@
-from django.core.urlresolvers import reverse
+from django.urls import reverse
from django.template.loader import render_to_string
+from django.utils.safestring import mark_safe
from utils.function_cache import cache
+
@cache(600)
def nav_hook(context):
return '
Books '.format(
url=reverse('books_admin')
)
+
+
+def linked_books(context):
+ request = context.get("request")
+ preprint = context.get("preprint")
+
+ if not preprint or not hasattr(preprint, "get_linked_books"):
+ return ""
+
+ theme = getattr(preprint.repository, "theme", "OLH")
+ template_path = f"books/{theme}/repository_linked_books.html"
+
+ books = preprint.get_linked_books()
+
+ return mark_safe(render_to_string(
+ template_path,
+ {
+ "preprint": preprint,
+ "linked_books": books,
+ },
+ request=request,
+ ))
\ No newline at end of file
diff --git a/management/__init__.py b/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/management/commands/__init__.py b/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/management/commands/generate_random_books.py b/management/commands/generate_random_books.py
new file mode 100644
index 0000000..858a7f9
--- /dev/null
+++ b/management/commands/generate_random_books.py
@@ -0,0 +1,145 @@
+import random
+import string
+import os
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+from django.conf import settings
+from faker import Faker
+import svgwrite
+
+from plugins.books import models as book_models
+
+fake = Faker()
+
+# Define the SVG cover creation path
+COVERS_DIR = os.path.join(settings.MEDIA_ROOT, 'cover_images')
+
+
+class Command(BaseCommand):
+ help = 'Generate a specified number of random Book instances with contributors and chapters.'
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'num_books',
+ type=int,
+ help='The number of books to generate.',
+ )
+
+ def handle(self, *args, **kwargs):
+ num_books = kwargs['num_books']
+ categories = book_models.Category.objects.all()
+ if not categories:
+ self.stdout.write(self.style.ERROR(
+ "No categories available. Please add categories first."))
+ return
+
+ # Ensure the cover images directory exists
+ os.makedirs(COVERS_DIR, exist_ok=True)
+
+ books_created = []
+ for _ in range(num_books):
+ category = random.choice(categories)
+ title = fake.sentence(nb_words=3).title()
+ subtitle = fake.sentence(nb_words=5).title() if random.choice(
+ [True, False]) else None
+ publisher_name = fake.company()
+ publisher_loc = fake.city()
+
+ # Generate SVG cover
+ cover_filename = f"{title.replace(' ', '_')}_{random.randint(1000, 9999)}.svg"
+ cover_path = os.path.join(COVERS_DIR, cover_filename)
+ self.create_svg_cover(title, cover_path)
+
+ book = book_models.Book.objects.create(
+ title=title,
+ subtitle=subtitle,
+ category=category,
+ description=fake.text(max_nb_chars=200),
+ pages=random.randint(100, 500),
+ is_edited_volume=random.choice([True, False]),
+ is_open_access=random.choice([True, False]),
+ date_published=fake.date_this_century(),
+ publisher_name=publisher_name,
+ publisher_loc=publisher_loc,
+ doi=f'10.{random.randint(1000, 9999)}/{random.randint(1000000, 9999999)}',
+ isbn=''.join(random.choices(string.digits, k=13)),
+ purchase_url=fake.url(),
+ license_information=fake.sentence(nb_words=10),
+ cover=f'cover_images/{cover_filename}',
+ )
+
+ # Add random contributors
+ num_contributors = random.randint(2, 10)
+ self.create_contributors(book, num_contributors)
+
+ # Add random chapters
+ num_chapters = random.randint(5, 15)
+ self.create_chapters(book, num_chapters)
+
+ books_created.append(book)
+
+ self.stdout.write(
+ self.style.SUCCESS(
+ f'Successfully created {len(books_created)} books with contributors and chapters.')
+ )
+
+ def create_svg_cover(self, title, filepath):
+ # Create an SVG drawing with a black background and the title in white
+ svg = svgwrite.Drawing(filepath, size=("200px", "300px"))
+ svg.add(svg.rect(insert=(0, 0), size=("100%", "100%"), fill="black"))
+
+ # Add title text to the SVG, centered
+ svg.add(
+ svg.text(
+ title,
+ insert=("50%", "50%"),
+ text_anchor="middle",
+ alignment_baseline="middle",
+ fill="white",
+ font_size="20px",
+ font_family="Arial",
+ )
+ )
+ svg.save()
+
+ def create_contributors(self, book, num_contributors):
+ for sequence in range(1, num_contributors + 1):
+ first_name = fake.first_name()
+ last_name = fake.last_name()
+ middle_name = fake.first_name() if random.choice(
+ [True, False]) else None
+
+ book_models.Contributor.objects.create(
+ book=book,
+ first_name=first_name,
+ middle_name=middle_name,
+ last_name=last_name,
+ affiliation=fake.company(),
+ email=fake.email(),
+ sequence=sequence,
+ )
+
+ def create_chapters(self, book, num_chapters):
+ for sequence in range(1, num_chapters + 1):
+ title = fake.sentence(nb_words=5).title()
+ description = fake.text(max_nb_chars=200)
+ doi = f'10.{random.randint(1000, 9999)}/{random.randint(100000, 999999)}'
+ pages = random.randint(5, 20)
+
+ chapter = book_models.Chapter.objects.create(
+ book=book,
+ title=title,
+ description=description,
+ pages=pages,
+ doi=doi,
+ number=str(sequence),
+ date_published=fake.date_this_century(),
+ sequence=sequence,
+ filename=f"{title.replace(' ', '_')}.pdf",
+ )
+
+ # Add contributors to each chapter
+ num_contributors = random.randint(1, 3)
+ chapter_contributors = book_models.Contributor.objects.filter(
+ book=book).order_by('?')[:num_contributors]
+ chapter.contributors.add(*chapter_contributors)
diff --git a/migrations/0021_alter_bookaccess_chapter_alter_bookaccess_country_and_more.py b/migrations/0021_alter_bookaccess_chapter_alter_bookaccess_country_and_more.py
new file mode 100644
index 0000000..f9fcc2c
--- /dev/null
+++ b/migrations/0021_alter_bookaccess_chapter_alter_bookaccess_country_and_more.py
@@ -0,0 +1,48 @@
+# Generated by Django 4.2.15 on 2024-11-06 14:56
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0099_alter_accountrole_options'),
+ ('repository', '0045_historicalrepository_display_public_metrics_and_more'),
+ ('books', '0020_auto_20220823_0931'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='bookaccess',
+ name='chapter',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='books.chapter'),
+ ),
+ migrations.AlterField(
+ model_name='bookaccess',
+ name='country',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.country'),
+ ),
+ migrations.AlterField(
+ model_name='bookaccess',
+ name='format',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='books.format'),
+ ),
+ migrations.CreateModel(
+ name='BookPreprint',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('order', models.IntegerField(default=0)),
+ ('book', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='books.book')),
+ ('preprint', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='repository.preprint')),
+ ],
+ options={
+ 'ordering': ['order'],
+ },
+ ),
+ migrations.AddField(
+ model_name='book',
+ name='linked_repository_objects',
+ field=models.ManyToManyField(blank=True, help_text='Repository objects linked to this book.', through='books.BookPreprint', to='repository.preprint'),
+ ),
+ ]
diff --git a/migrations/0022_book_peer_reviewed_booksetting_display_review_status.py b/migrations/0022_book_peer_reviewed_booksetting_display_review_status.py
new file mode 100644
index 0000000..28ff323
--- /dev/null
+++ b/migrations/0022_book_peer_reviewed_booksetting_display_review_status.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.15 on 2024-11-13 15:16
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('books', '0021_alter_bookaccess_chapter_alter_bookaccess_country_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='book',
+ name='peer_reviewed',
+ field=models.BooleanField(default=False, help_text='Mark if this book has been peer reviewed.'),
+ ),
+ migrations.AddField(
+ model_name='booksetting',
+ name='display_review_status',
+ field=models.BooleanField(default=False, help_text="Mark this to display a book's review status."),
+ ),
+ ]
diff --git a/migrations/0023_book_edition.py b/migrations/0023_book_edition.py
new file mode 100644
index 0000000..4da6e2c
--- /dev/null
+++ b/migrations/0023_book_edition.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.20 on 2025-07-22 12:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('books', '0022_book_peer_reviewed_booksetting_display_review_status'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='book',
+ name='edition',
+ field=models.CharField(blank=True, null=True),
+ ),
+ ]
diff --git a/migrations/0024_format_display_read_link.py b/migrations/0024_format_display_read_link.py
new file mode 100644
index 0000000..1c50fb7
--- /dev/null
+++ b/migrations/0024_format_display_read_link.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.20 on 2025-07-22 14:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('books', '0023_book_edition'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='format',
+ name='display_read_link',
+ field=models.BooleanField(default=True, help_text='For EPUB only, check if you want a Read this Book link'),
+ ),
+ ]
diff --git a/models.py b/models.py
index b4ea3b5..4727b48 100644
--- a/models.py
+++ b/models.py
@@ -8,11 +8,10 @@
from user_agents import parse as parse_ua_string
from django.db import models
-from django.conf import settings
-from django.core.files.storage import FileSystemStorage
from django.core.files.images import get_image_dimensions
from django.utils import timezone
from django.core.exceptions import ValidationError
+from django.urls import reverse
from core import models as core_models
from core.file_system import JanewayFileSystemStorage
@@ -20,6 +19,7 @@
from metrics.logic import get_iso_country_code
from utils.shared import get_ip_address
from plugins.books import files
+from press import models as press_models
fs = JanewayFileSystemStorage()
@@ -40,6 +40,10 @@ class BookSetting(models.Model):
max_length=255,
default="Published Books",
)
+ display_review_status = models.BooleanField(
+ default=False,
+ help_text="Mark this to display a book's review status."
+ )
def save(self, *args, **kwargs):
if not self.pk and BookSetting.objects.exists():
@@ -142,12 +146,25 @@ class Book(models.Model):
related_name="book",
help_text='Free-text public publisher notes regarding this book',
)
-
custom_how_to_cite = models.TextField(
blank=True, null=True,
help_text="Custom 'how to cite' text. To be used only if the block"
" generated by Janeway is not suitable.",
)
+ linked_repository_objects = models.ManyToManyField(
+ 'repository.Preprint',
+ help_text='Repository objects linked to this book.',
+ blank=True,
+ through='BookPreprint'
+ )
+ peer_reviewed = models.BooleanField(
+ default=False,
+ help_text='Mark if this book has been peer reviewed.',
+ )
+ edition = models.CharField(
+ blank=True,
+ null=True,
+ )
def __str__(self):
return self.title
@@ -165,18 +182,19 @@ def citation(self):
def contributors_citation(self):
contributors = self.contributor_set.all()
-
- if contributors.count() == 1:
- return '{contributor} '.format(
- contributor=contributors[0].citation_name()
- )
- elif contributors.count() == 2:
- return '{contributor_one} & {contributor_two} '.format(
- contributor_one=contributors[0].citation_name(),
- contributor_two=contributors[1].citation_name(),
- )
- else:
- return '{contributor} et al. '.format(contributor=contributors[0])
+ if contributors:
+ if contributors.count() == 1:
+ return '{contributor} '.format(
+ contributor=contributors[0].citation_name()
+ )
+ elif contributors.count() == 2:
+ return '{contributor_one} & {contributor_two} '.format(
+ contributor_one=contributors[0].citation_name(),
+ contributor_two=contributors[1].citation_name(),
+ )
+ else:
+ return '{contributor} et al. '.format(contributor=contributors[0])
+ return ''
def full_title(self):
if self.prefix and self.subtitle:
@@ -249,6 +267,28 @@ def remote_book_label(self):
urlparse(self.remote_url).netloc
)
+ @property
+ def press(self):
+ press = press_models.Press.objects.all().first()
+ return press
+
+ def url(self):
+ # Cross Repo/Press URLs are not renderable so we need to
+ # generate this manually. Bad practice but it gets the job done.
+ return "//{press_domain}/plugins/books/{book_id}".format(
+ press_domain=self.press.domain,
+ book_id=self.pk,
+ )
+
+
+class BookPreprint(models.Model):
+ book = models.ForeignKey(Book, on_delete=models.CASCADE)
+ preprint = models.ForeignKey('repository.Preprint', on_delete=models.CASCADE)
+ order = models.IntegerField(default=0)
+
+ class Meta:
+ ordering = ['order']
+
class Contributor(models.Model):
book = models.ForeignKey(
@@ -292,6 +332,10 @@ class Format(models.Model):
title = models.CharField(max_length=100)
filename = models.CharField(max_length=100)
sequence = models.PositiveIntegerField(default=10)
+ display_read_link = models.BooleanField(
+ default=True,
+ help_text="For EPUB only, check if you want a Read this Book link",
+ )
class Meta:
ordering = ('sequence',)
@@ -514,17 +558,19 @@ def citation(self):
def contributors_citation(self):
contributors = self.contributors.all()
- if contributors.count() == 1:
- return '{contributor} '.format(
- contributor=contributors[0].citation_name()
- )
- elif contributors.count() == 2:
- return '{contributor_one} & {contributor_two} '.format(
- contributor_one=contributors[0].citation_name(),
- contributor_two=contributors[1].citation_name(),
- )
- else:
- return '{contributor} et al. '.format(contributor=contributors[0])
+ if contributors:
+ if contributors.count() == 1:
+ return '{contributor} '.format(
+ contributor=contributors[0].citation_name()
+ )
+ elif contributors.count() == 2:
+ return '{contributor_one} & {contributor_two} '.format(
+ contributor_one=contributors[0].citation_name(),
+ contributor_two=contributors[1].citation_name(),
+ )
+ else:
+ return '{contributor} et al. '.format(contributor=contributors[0])
+ return ''
class KeywordBook(models.Model):
diff --git a/partial_views.py b/partial_views.py
new file mode 100644
index 0000000..ce02e07
--- /dev/null
+++ b/partial_views.py
@@ -0,0 +1,85 @@
+from django.shortcuts import render, get_object_or_404
+from plugins.books import models
+from django.contrib.admin.views.decorators import staff_member_required
+from django.views.decorators.http import require_POST
+
+from pprint import pprint
+
+
+@require_POST
+@staff_member_required
+def move_preprint(request, book_id, book_preprint_id, direction):
+ book = get_object_or_404(models.Book, pk=book_id)
+ book_preprint = get_object_or_404(models.BookPreprint, id=book_preprint_id,
+ book=book)
+
+ linked_preprints = models.BookPreprint.objects.filter(book=book).order_by(
+ 'order')
+ for index, preprint in enumerate(linked_preprints):
+ if preprint.order != index:
+ preprint.order = index
+ preprint.save()
+ book_preprint.refresh_from_db()
+
+ current_order = book_preprint.order
+
+ if direction == 'up':
+ # Move up: find the previous item and swap orders
+ previous_item = models.BookPreprint.objects.filter(
+ book=book,
+ order=current_order - 1,
+ ).first()
+ if previous_item:
+ previous_item.order += 1
+ previous_item.save()
+ book_preprint.order -= 1
+ book_preprint.save()
+
+ elif direction == 'down':
+ # Move down: find the next item and swap orders
+ next_item = models.BookPreprint.objects.filter(
+ book=book,
+ order=current_order + 1,
+ ).first()
+ if next_item:
+ next_item.order -= 1
+ next_item.save()
+ book_preprint.order += 1
+ book_preprint.save()
+
+ # Fetch the updated list of linked preprints
+ linked_preprints = models.BookPreprint.objects.filter(book=book).order_by(
+ 'order')
+ return render(
+ request,
+ 'books/partials/linked_preprints.html',
+ {'book': book, 'linked_preprints': linked_preprints},
+ )
+
+
+@require_POST
+@staff_member_required
+def remove_preprint(request, book_id, book_preprint_id):
+ book = get_object_or_404(models.Book, pk=book_id)
+ book_preprint = get_object_or_404(
+ models.BookPreprint,
+ id=book_preprint_id,
+ book=book,
+ )
+
+ book_preprint.delete()
+
+ linked_preprints = models.BookPreprint.objects.filter(
+ book=book
+ ).order_by('order')
+
+ template = 'books/partials/linked_preprints.html'
+ context = {
+ 'book': book,
+ 'linked_preprints': linked_preprints,
+ }
+ return render(
+ request,
+ template,
+ context,
+ )
\ No newline at end of file
diff --git a/plugin_settings.py b/plugin_settings.py
index 932d6ec..88e261b 100644
--- a/plugin_settings.py
+++ b/plugin_settings.py
@@ -25,6 +25,8 @@ def install():
def hook_registry():
return {
- 'press_admin_nav_block': {'module': 'plugins.books.hooks', 'function': 'nav_hook'}
+ 'press_admin_nav_block': {'module': 'plugins.books.hooks', 'function': 'nav_hook'},
+ 'preprint_sidebar': {'module': 'plugins.books.hooks',
+ 'function': 'linked_books'}
}
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..9efe26c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+Faker==18.9.0
+svgwrite==1.4.1
\ No newline at end of file
diff --git a/static/books/books.css b/static/books/books.css
new file mode 100644
index 0000000..a72cae4
--- /dev/null
+++ b/static/books/books.css
@@ -0,0 +1,184 @@
+.olh-book {
+
+.book {
+ background-color: #f7f7f7;
+ border-radius: 0.75rem;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ margin-top: 2rem;
+ overflow: hidden;
+}
+
+.book-cover {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.book-info {
+ padding: 2rem;
+}
+
+.category {
+ color: #e6194B;
+ font-size: 0.875rem;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.book-title {
+ color: #1a202c;
+ font-size: 1.5rem;
+ font-weight: 700;
+ margin-top: 0.25rem;
+}
+
+.book-title a {
+ color: #1a202c;
+}
+
+.authors {
+ color: #4a5568;
+ margin-top: 0.5rem;
+ margin-bottom: 0;
+}
+
+.read-button {
+ background-color: #e6194B;
+ color: white;
+ border-radius: 0.375rem;
+ display: inline-flex;
+ align-items: center;
+ margin-top: 1rem;
+}
+
+.read-button:hover {
+ background-color: #c81032;
+}
+
+.section {
+ padding: 1.2rem;
+ border-top: 1px solid #e2e8f0;
+}
+
+.section-title {
+ color: #e6194B;
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+}
+
+.section-content {
+ color: #4a5568;
+}
+
+.label-text {
+ color: #900C3F;
+ font-weight: 600;
+}
+
+.associated-files {
+ margin-top: 1rem;
+}
+
+.associated-files li {
+ margin-bottom: 0.5rem;
+}
+
+.associated-files a {
+ color: #3182ce;
+ display: flex;
+ align-items: center;
+}
+
+.associated-files a:hover {
+ color: #2c5282;
+}
+
+.file-icon {
+ margin-right: 0.5rem;
+}
+
+.description-preview {
+ margin-top: 1rem;
+ max-height: 200px;
+ overflow: hidden;
+}
+
+.expand-description, .collapse-description {
+ display: inline-block;
+ margin-top: 0.5rem;
+ color: #1779ba;
+ text-decoration: underline;
+}
+
+.full-description {
+ margin-top: 1rem;
+}
+
+/* Styles for Chapters and Linked Preprints with fixed two columns and vertical scroll */
+.chapter-list,
+.preprint-list {
+ display: flex;
+ flex-wrap: wrap; /* Allow items to wrap within two columns */
+ overflow-x: hidden; /* Prevent horizontal scrolling */
+ list-style-type: none; /* Remove default list styling */
+ padding: 0; /* Remove padding for clean alignment */
+}
+
+/* Each item takes up 50% width to create two columns */
+.chapter-item,
+.preprint-item {
+ width: 50%; /* Ensure exactly two columns */
+ padding: 0.2rem 0; /* Optional spacing for each item */
+ box-sizing: border-box; /* Include padding in width */
+}
+
+.chapter-item p,
+.preprint-item p {
+ margin-bottom: 0;
+}
+
+/* Scrollbar styling (optional for better UX) */
+.chapter-list::-webkit-scrollbar,
+.preprint-list::-webkit-scrollbar {
+ width: 8px;
+}
+
+.chapter-list::-webkit-scrollbar-thumb,
+.preprint-list::-webkit-scrollbar-thumb {
+ background-color: #c81032; /* Scrollbar thumb color */
+ border-radius: 4px;
+}
+
+.chapter-list::-webkit-scrollbar-track,
+.preprint-list::-webkit-scrollbar-track {
+ background-color: #f1f1f1; /* Scrollbar track color */
+}
+
+/* Section titles */
+.section-title {
+ color: #e6194B;
+ font-size: 1.25rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+}
+
+.label-text {
+ color: #900C3F;
+ font-weight: 600;
+}
+
+/* Responsive adjustment for smaller screens */
+@media (max-width: 768px) {
+ .chapter-item,
+ .preprint-item {
+ width: 100%; /* Use single column on small screens */
+ }
+ .chapter-list,
+ .preprint-list {
+ max-height: 200px; /* Adjust height for smaller screens */
+ }
+}
+
+
+}
\ No newline at end of file
diff --git a/static/books/onix.xsl b/static/books/onix.xsl
new file mode 100644
index 0000000..575d084
--- /dev/null
+++ b/static/books/onix.xsl
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+ ONIX Book Export
+
+
+
+ ONIX Book Export
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ISBN:
+
+
+
+
Contributors:
+
+
+
+
+
+
Description:
+
+
+
+
Publishing Details:
+
Publisher:
+
Location:
+
Date Published:
+
+
+
diff --git a/templates/books/OLH/book.html b/templates/books/OLH/book.html
new file mode 100644
index 0000000..99fd217
--- /dev/null
+++ b/templates/books/OLH/book.html
@@ -0,0 +1,23 @@
+{% extends "core/base.html" %}
+{% load static %}
+
+{% block title %}{{ book.title }}{% endblock %}
+
+{% block css %}
+
+{% endblock css %}
+
+{% block body %}
+
+
+ {% include "books/OLH/breadcrumb.html" %}
+
+ {% include "books/OLH/book_detail.html" with book=book %}
+
+
+
+{% endblock %}
+
+{% block js %}
+ {% include "books/OLH/read_more.js" %}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/books/OLH/book_detail.html b/templates/books/OLH/book_detail.html
new file mode 100644
index 0000000..b26cbd5
--- /dev/null
+++ b/templates/books/OLH/book_detail.html
@@ -0,0 +1,88 @@
+{% load static %}
+
+
+
+
+
+ {% if book.category %}
+
{{ book.category.name }}
+ {% endif %}
+
+
+ {% for contributor in book.contributor_set.all %}
+ {{ contributor }}{% if not forloop.last %}, {% endif %}
+ {% endfor %}
+
+
+ {% if book.keyword_set.exists %}
+
+
Keywords
+
{% for keyword in book.keyword_set.all %}
+ {{ keyword.word }}{% endfor %}
+
+ {% endif %}
+
+
Description
+
+ {{ book.description|safe }}
+
+
Read
+ more
+
+ {% if index %}
+
+ View detail
+
+ {% endif %}
+
+
+
+ {% if not books %}
+ {% include "books/OLH/detail.html" %}
+ {% endif %}
+
+
+
diff --git a/templates/books/OLH/breadcrumb.html b/templates/books/OLH/breadcrumb.html
new file mode 100644
index 0000000..78d01ce
--- /dev/null
+++ b/templates/books/OLH/breadcrumb.html
@@ -0,0 +1,23 @@
+
+
+ Book List
+
+ {% if book %}
+ {% if book.category %}
+
+
+ {{ book.category.name }}
+
+
+ {% endif %}
+
+
+ {{ book.full_title }}
+
+ {% else %}
+ {% if category %}
+ {{ category.name }}
+ {% endif %}
+ {% endif %}
+
+
diff --git a/templates/books/OLH/detail.html b/templates/books/OLH/detail.html
new file mode 100644
index 0000000..23a7f39
--- /dev/null
+++ b/templates/books/OLH/detail.html
@@ -0,0 +1,109 @@
+
+
+
Publication Details
+
Published:
+ {{ book.date_published|date:"F j, Y" }}
+ {% if book.edition %}
+
Edition: {{ book.edition }}
+
+ {% endif %}
+
Publisher: {{ book.publisher_name }}
+
+
Location: {{ book.publisher_loc }}
+ {% if book.doi %}
+
DOI: https://doi.org/{{ book.doi }}
+ {% endif %}
+ {% if book.isbn %}
+
ISBN: {{ book.isbn }}
+ {% endif %}
+ {% if book.peer_reviewed %}
+
Peer Review: Yes
+ {% endif %}
+ {% if book_settings.display_review_status %}
+
+
+ Peer Reviewed:
+ {% if book.peer_reviewed %}Yes{% else %}No{% endif %}
+
+ {% endif %}
+ {% if book.format_set.exists %}
+ {% if book.metrics.views %}
+
+
+ Views:
+ {{ book.metrics.views }}
+
+ {% endif %}
+ {% if book.metrics.downloads %}
+
+
+ Downloads:
+ {{ book.metrics.downloads }}
+
+ {% endif %}
+ {% endif %}
+ {% if book.license_information %}
+
License Information: {{ book.license_information|safe }}
+
+ {% endif %}
+
+
+
+ {% if book.chapter_set.exists %}
+
+ {% endif %}
+
+
+
+ {% if book.bookpreprint_set.exists %}
+
+ {% endif %}
+
+
+
+
+
+
Citation
+
{{ book.citation|safe }}
+
+
+ {% if book.publisher_notes.exists %}
+
+
Publisher Notes
+ {% for note in book.publisher_notes.all %}
+
{{ note.note|safe }}
+ {% endfor %}
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/templates/books/OLH/index.html b/templates/books/OLH/index.html
new file mode 100644
index 0000000..35b95bd
--- /dev/null
+++ b/templates/books/OLH/index.html
@@ -0,0 +1,41 @@
+{% extends "core/base.html" %}
+{% load static %}
+
+{% block title %}{% if not category %}Published Books{% else %}
+ {{ category.name }}{% endif %}{% endblock %}
+
+{% block css %}
+
+{% endblock css %}
+
+{% block body %}
+
+
+
+ {% include "books/OLH/breadcrumb.html" %}
+ {% if not category %}
+
{{ book_settings.book_page_title }}
+ {% else %}
+ {% if category.display_title %}
+
Category: {{ category.name }}
+ {% endif %}
+ {{ category.description|safe }}
+
< Back to All
+ {% endif %}
+
+ {% for book in books %}
+
+ {% include "books/OLH/book_detail.html" with book=book index=True %}
+
+ {% empty %}
+
There are no published books to display.
+ {% endfor %}
+
+
+
+
+{% endblock body %}
+
+{% block js %}
+ {% include "books/OLH/read_more.js" %}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/books/OLH/read_more.js b/templates/books/OLH/read_more.js
new file mode 100644
index 0000000..643d508
--- /dev/null
+++ b/templates/books/OLH/read_more.js
@@ -0,0 +1,46 @@
+
diff --git a/templates/books/OLH/repository_linked_books.html b/templates/books/OLH/repository_linked_books.html
new file mode 100644
index 0000000..a5ec710
--- /dev/null
+++ b/templates/books/OLH/repository_linked_books.html
@@ -0,0 +1,12 @@
+{% load fqdn %}
+
+{% if linked_books %}
+ Linked Books
+
+{% endif %}
\ No newline at end of file
diff --git a/templates/books/OLH/view_chapter.html b/templates/books/OLH/view_chapter.html
new file mode 100644
index 0000000..f1cf1f0
--- /dev/null
+++ b/templates/books/OLH/view_chapter.html
@@ -0,0 +1,124 @@
+{% extends "core/base.html" %}
+{% load static %}
+
+{% block title %}
+ {% if chapter.number %}[{{ chapter.number }}] {% endif %}{{ chapter.title }}
+{% endblock %}
+
+{% block css %}
+
+{% endblock %}
+
+{% block body %}
+
+
+ {% include "books/OLH/breadcrumb.html" %}
+
+
+
+
+
+
+ {% if book.category %}
+
{{ book.category.name }}
+ {% endif %}
+
+
+ {% if chapter.number %}{{ chapter.number }}. {% endif %}{{ chapter.title }}
+
+
+ {% if chapter.contributors.exists %}
+
+ {% for contributor in chapter.contributors.all %}
+ {{ contributor }}{% if not forloop.last %}, {% endif %}
+ {% endfor %}
+
+ {% endif %}
+
+
+ This {% if book.category %}{{ book.category.chapter_name }}{% else %}
+ chapter{% endif %}
+ is part of: {{ book.citation|safe }}
+
+
+
+
+ {% if chapter.description %}
+
+
Description
+
+ {{ chapter.description|safe }}
+
+
Read
+ more
+
+ {% endif %}
+
+
+
Publication Details
+
Published:
+ {{ book.date_published|date:"F j, Y" }}
+
Publisher: {{ book.publisher_name }}
+
+ {% if chapter.pages %}
+
Pages: {{ chapter.pages }}
+ {% endif %}
+ {% if chapter.doi %}
+
DOI: {{ chapter.doi }}
+ {% endif %}
+ {% if chapter.license_information or book.license_information %}
+
License Information:
+ {% if chapter.license_information %}
+ {{ chapter.license_information|safe }}
+ {% else %}
+ {{ book.license_information|safe }}
+ {% endif %}
+
+ {% endif %}
+
+
+
+
Citation
+
{{ chapter.citation|safe }}
+
+
+ {% if chapter.publisher_notes.exists %}
+
+
Publisher Notes
+ {% for note in chapter.publisher_notes.all %}
+
{{ note.note|safe }}
+ {% endfor %}
+
+ {% endif %}
+
+
+
+
+
+
+{% endblock %}
+
+{% block js %}
+ {% include "books/OLH/read_more.js" %}
+{% endblock %}
diff --git a/templates/books/book_preprint_manager.html b/templates/books/book_preprint_manager.html
new file mode 100644
index 0000000..1d3aa90
--- /dev/null
+++ b/templates/books/book_preprint_manager.html
@@ -0,0 +1,114 @@
+{% extends "admin/core/base.html" %}
+
+{% block title %}Manage Preprints for {{ book.title }}{% endblock %}
+{% block admin-header %}Manage Preprints for {{ book.title }}{% endblock %}
+
+{% block breadcrumbs %}
+ {{ block.super }}
+ Books Admin
+ {{ book.title }}
+ Manage Preprints for {{ book.title }}
+{% endblock breadcrumbs %}
+
+{% block css %}
+
+
+
+{% endblock %}
+
+{% block body %}
+
+
+
+
+
+
+
Linked Preprints
+
+
+
+ {% include "books/partials/linked_preprints.html" %}
+
+
+
+
+
+
Available Preprints
+
+
+
+
+ Title
+ Action
+
+
+
+ {% for preprint in available_preprints %}
+
+ {{ preprint.title }}
+
+
+
+
+ {% endfor %}
+
+
+
+
+
+{% endblock %}
+
+{% block js %}
+
+
+
+ {% include "elements/datatables.html" with target="#available-preprints #linked-preprints" %}
+
+
+{% endblock js %}
\ No newline at end of file
diff --git a/templates/books/chapter.html b/templates/books/chapter.html
index a9e4941..087e38b 100644
--- a/templates/books/chapter.html
+++ b/templates/books/chapter.html
@@ -5,31 +5,54 @@
{% block admin-header %}Books Admin{% endblock %}
{% block breadcrumbs %}
- {{ block.super }}
- Books Admin
- {{ book.title }}
- {% if chapter %}Edit Chapter{% else %}Add Chapter{% endif %}
+ {{ block.super }}
+ Books Admin
+ {{ book.title }}
+ {% if chapter %}Edit Chapter{% else %}Add Chapter{% endif %}
{% endblock %}
{% block body %}
+
+
-
-
-
{% if chapter %}Edit Chapter{% else %}Add Chapter{% endif %}
-
-
-
+
+
{% if chapter %}Edit Chapter{% else %}Add Chapter{% endif %}
+
Back
+
+
+
+
+ {% if chapter %}
+
+
+
+
Delete Chapter
+
+
+
Delete this chapter using the button below. Please not you cannot recover this chapter once you delete it.
+
+
+ {% endif %}
+
+
{% endblock %}
diff --git a/templates/books/edit_book.html b/templates/books/edit_book.html
index eefa429..24c65eb 100644
--- a/templates/books/edit_book.html
+++ b/templates/books/edit_book.html
@@ -80,6 +80,14 @@
Chapters
{% endif %}
+ {% if book %}
+
+
Linked Preprints
+
+
+ {% endif %}
diff --git a/templates/books/edit_contributor.html b/templates/books/edit_contributor.html
index a54c597..543912b 100644
--- a/templates/books/edit_contributor.html
+++ b/templates/books/edit_contributor.html
@@ -5,26 +5,44 @@
{% block admin-header %}Books Admin{% endblock %}
{% block breadcrumbs %}
- {{ block.super }}
- Books Admin
- {{ book.title }}
- Edit Contributor
+ {{ block.super }}
+ Books Admin
+ {{ book.title }}
+ Edit Contributor
{% endblock %}
{% block body %}
-
-
-
-
{% if contributor %}Editing {{ contributor }}{% else %}Adding Contributor to {{ book }}{% endif %}
-
Back
-
-
-
-
+
+
+
+
+
{% if contributor %}Editing {{ contributor }}{% else %}Adding Contributor
+ to {{ book }}{% endif %}
+
Back
+
+
+
+
+
+ {% if contributor %}
+
+
+
+
Delete Contributor
+
+
Delete this format using the button below. Please not you cannot recover this format once you delete it.
+
+
+ {% endif %}
+
{% endblock %}
diff --git a/templates/books/edit_format.html b/templates/books/edit_format.html
index 73fb3df..e1c6d54 100644
--- a/templates/books/edit_format.html
+++ b/templates/books/edit_format.html
@@ -5,30 +5,53 @@
{% block admin-header %}Books Admin{% endblock %}
{% block breadcrumbs %}
- {{ block.super }}
-
Books Admin
-
{{ book.title }}
-
Edit Format
+ {{ block.super }}
+
Books Admin
+
{{ book.title }}
+
Edit Format
{% endblock %}
{% block body %}
-
-
-
-
{% if contributor %}Editing {{ format }}{% else %}Adding Format to {{ book }}{% endif %}
-
Back
-
-
- {% include "elements/forms/errors.html" %}
-
+
+
+
+
+
{% if contributor %}Editing {{ format }}{% else %}Adding Format to
+ {{ book }}{% endif %}
+
Back
+
+
+ {% include "elements/forms/errors.html" %}
+
+
+ {% if format %}
+
+
+
+
Delete Format
+
+
Delete this format using the button below. Please not you cannot recover this format once you delete it.
+
+
+
+ {% endif %}
+
+
{% endblock %}
diff --git a/templates/books/olh/book.html b/templates/books/olh/book.html
deleted file mode 100644
index be5b12d..0000000
--- a/templates/books/olh/book.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{% extends "core/base.html" %}
-
-{% block title %}{{ book.title }}{% endblock %}
-
-{% block body %}
-
-
-
-
- {% include "books/OLH/book_detail.html" with book=book %}
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/templates/books/olh/book_detail.html b/templates/books/olh/book_detail.html
deleted file mode 100644
index 0709e26..0000000
--- a/templates/books/olh/book_detail.html
+++ /dev/null
@@ -1,78 +0,0 @@
-{% load static %}
-{% load i18n %}
-
-
-
-
-
- {% if not books and book.format_set.exists %}
-
-
Book Metrics
-
{{ book.metrics.downloads }}
-
Downloads
- {% if book.metrics.views %}
-
{{ book.metrics.views }}
-
EPUB Views
- {% endif %}
-
- {% endif %}
-
-
diff --git a/templates/books/olh/detail.html b/templates/books/olh/detail.html
deleted file mode 100644
index 63691b8..0000000
--- a/templates/books/olh/detail.html
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
- Details
-
-
-
- Published
- Published By
- {% if book.pages %}
- Pages
- {% endif %}
- {% if book.isbn %}
- ISBN {% endif %}
- {% if book.doi %}
- DOI {% endif %}
- {% if chapters %}
- {% if book.category %}{{ book.category.chapter_name_plural|capfirst }}{% else %}Chapters{% endif %} {% endif %}
-
-
- {{ book.date_published }}
- {{ book.publisher_name }}
- {% if book.pages %}
- {{ book.pages }}
- {% endif %}
- {% if book.isbn %}
- {{ book.isbn }} {% endif %}
- {% if book.doi %}
- {{ book.doi }} {% endif %}
- {% if chapters %}
- {{ chapters.count }} {% endif %}
-
- {% if book.license_information %}
-
- License Information
-
-
- {{ book.license_information }}
-
- {% endif %}
-
- Citation
-
-
- {{ book.citation|safe }}
-
-
-
-
-
- {% if chapters %}
-
- {% if book.category %}{{ book.category.chapter_name_plural|capfirst }}{% else %}Chapters{% endif %}
-
-
{{ book.full_title }} has the following {% if book.category %}{{ book.category.chapter_name_plural }}{% else %}Chapters{% endif %}:
-
-
-
-
- {% endif %}
-
- {% if book.publisher_notes.exists %}
-
- Publisher Notes
-
-
- {{ book.full_title }} has the following notes
-
- {% for note in book.publisher_notes.all %}
- {{ note.note |safe }}
- {% endfor %}
-
-
-
-
- {% endif %}
-
diff --git a/templates/books/olh/index.html b/templates/books/olh/index.html
deleted file mode 100644
index 3f2166b..0000000
--- a/templates/books/olh/index.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{% extends "core/base.html" %}
-
-{% block title %}{% if not category %}Published Books{% else %}{{ category.name }}{% endif %}{% endblock %}
-
-{% block body %}
-
-
- {% if not category %}
-
{{ book_settings.book_page_title }}
- {% else %}
- {% if category.display_title %}
-
Category: {{ category.name }}
- {% endif %}
- {{ category.description|safe }}
-
< Back to All
- {% endif %}
-
- {% for book in books %}
-
- {% include "books/OLH/book_detail.html" with book=book %}
-
-
- {% empty %}
-
There are no published books to display.
- {% endfor %}
-
-
-
-
-{% endblock body %}
\ No newline at end of file
diff --git a/templates/books/onix.xml b/templates/books/onix.xml
index bdc650a..3fea4ca 100644
--- a/templates/books/onix.xml
+++ b/templates/books/onix.xml
@@ -1,4 +1,5 @@
+
@@ -8,76 +9,88 @@
{{ request.press.domain }}
- {% now "Ymd" %}
- Export from Janeway
+ {% now "Ymd" %}
+ Export from Janeway
+
{% for book in books %}
{{ request.press.name|slugify }}.{% if book.doi %}{{ book.doi }}{% else %}{{ book.pk }}{% endif %}
- 03
+ 03
+
{% if book.isbn %}
-
- 15
- {{ book.isbn }}
-
+
+ 15
+ {{ book.isbn }}
+
{% endif %}
+
-
- 1
- 01
+
+ 1
+ 01
{{ book.title }}
-
+
+
{% for contributor in book.contributor_set.all %}
{{ forloop.counter }}
- A01
+ A01
{{ contributor.first_name }}{% if contributor.middle_name %} {{ contributor.middle_initial }}{% endif %}
{{ contributor.last_name }}
- {{ contributor.affiliation|safe }}
+ {{ contributor.affiliation|safe }}
{% endfor %}
+
+ {% if book.pages %}
- 00
+ 00
{{ book.pages }}
- 03
+ 03
+ {% endif %}
+
- 03
- 00
+ 03
+ 00
{{ book.description|safe }}
+
+ {% if book.cover %}
- 01
- 00
- 03
+ 01
+ 00
+ 03
- 02
+ 02
- 01
+ 01
{{ book.cover_onix_code }}
- 02
+ 02
{{ book.cover_height }}
- 03
+ 03
{{ book.cover_width }}
- {{ request.press_base_url }}{{ book.cover.url|urlencode }}
+ {{ request.press_base_url }}{{ book.cover.url|urlencode }}
+ {% endif %}
+
{{ request.press.name }}
- 01
+ 01
{{ book.publisher_name }}
@@ -85,12 +98,12 @@
{{ request.press_base_url }}
{{ book.publisher_loc }}
- 04
+ 04
- 01
+ 01
{{ book.date_published|date:"Ymd" }}
{% endfor %}
-
\ No newline at end of file
+
diff --git a/templates/books/partials/linked_preprints.html b/templates/books/partials/linked_preprints.html
new file mode 100644
index 0000000..a69b6ff
--- /dev/null
+++ b/templates/books/partials/linked_preprints.html
@@ -0,0 +1,44 @@
+
+ {% csrf_token %}
+ {% for book_preprint in linked_preprints %}
+
+
+
[{{ book_preprint.order }}] {{ book_preprint.preprint.title }}
+
+
+
+
+ Up
+
+
+
+
+ Down
+
+
+
+
+ Remove
+
+
+
+
+ {% empty %}
+ No preprints linked to this book.
+ {% endfor %}
+
diff --git a/urls.py b/urls.py
index 5e4cbfc..b7f84e8 100644
--- a/urls.py
+++ b/urls.py
@@ -1,6 +1,6 @@
from django.urls import re_path
-from plugins.books import views
+from plugins.books import views, partial_views
urlpatterns = [
re_path(r'^$', views.index, name='books_index'),
@@ -43,4 +43,21 @@
re_path(r'^onix/export/$', views.export_onix_xml, name='books_export_onix_xml'),
re_path(r'^onix/export/(?P
\d+)/$', views.export_onix_xml, name='books_export_onix_xml_book'),
+ re_path(
+ r'^admin/(?P\d+)/manage-preprints/$',
+ views.book_preprint_management_view,
+ name='book_preprint_management',
+ ),
+
+ # Partial URLs for HTMX
+ re_path(
+ r'^books/(?P\d+)/preprint/(?P\d+)/move/(?Pup|down)/$',
+ partial_views.move_preprint,
+ name='books_move_preprint',
+ ),
+ re_path(
+ r'^books/(?P\d+)/preprint/(?P\d+)/remove/$',
+ partial_views.remove_preprint,
+ name='books_remove_preprint',
+ ),
]
\ No newline at end of file
diff --git a/views.py b/views.py
index bac0b56..aeadcf0 100644
--- a/views.py
+++ b/views.py
@@ -6,16 +6,17 @@
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib import messages
from django.http import Http404
+from django.utils import timezone
from plugins.books import models, forms, files, logic
from core import files as core_files
-from utils import setting_handler
+from repository import models as repository_models
def index(request, category_slug=None):
category = None
books = models.Book.objects.filter(
- date_published__isnull=False,
+ date_published__lte=timezone.now()
).order_by(
'-date_published',
)
@@ -47,6 +48,7 @@ def view_book(request, book_id):
template = 'books/{}/book.html'.format(request.press.theme)
context = {
'book': book,
+ 'book_settings': models.BookSetting.objects.first(),
}
return render(request, template, context)
@@ -160,6 +162,15 @@ def edit_contributor(request, book_id, contributor_id=None):
form = forms.ContributorForm(instance=contributor, book=book)
if request.POST:
+ if contributor and "delete" in request.POST:
+ contributor.delete()
+ messages.success(request, 'Contributor deleted.')
+ return redirect(
+ reverse(
+ 'books_edit_book',
+ kwargs={'book_id': book.pk},
+ )
+ )
form = forms.ContributorForm(request.POST, instance=contributor, book=book)
if form.is_valid():
@@ -182,14 +193,31 @@ def edit_contributor(request, book_id, contributor_id=None):
@staff_member_required
def edit_format(request, book_id, format_id=None):
book_format = None
- book = get_object_or_404(models.Book, pk=book_id)
+ book = get_object_or_404(
+ models.Book,
+ pk=book_id,
+ )
if format_id:
- book_format = get_object_or_404(models.Format, pk=format_id, book=book)
+ book_format = get_object_or_404(
+ models.Format,
+ pk=format_id,
+ book=book,
+ )
form = forms.FormatForm(instance=book_format)
if request.POST:
+ if book_format and "delete" in request.POST:
+ book_format.delete()
+ messages.success(request, 'Format deleted.')
+ return redirect(
+ reverse(
+ 'books_edit_book',
+ kwargs={'book_id': book.pk},
+ )
+ )
+
form = forms.FormatForm(request.POST, request.FILES, instance=book_format)
if form.is_valid():
form_format = form.save(commit=False)
@@ -267,19 +295,26 @@ def import_books_process(request, uuid):
return redirect(reverse('books_import_preview', kwargs={'uuid': uuid}))
-@staff_member_required
-def export_onix_xml(request, book_id=None):
- books = models.Book.objects.all()
-
- if book_id:
- books = models.Book.objects.filter(pk=book_id)
+def export_onix_xml(
+ request,
+ book_id=None,
+):
+ # Get the books based on the optional book_id parameter
+ books = models.Book.objects.all() if book_id is None else models.Book.objects.filter(pk=book_id)
+ # Use an ONIX-compliant XML template
template = 'books/onix.xml'
context = {
'books': books,
+ 'chapters': models.Chapter.objects.filter(book__in=books),
+ 'contributors': models.Contributor.objects.filter(book__in=books),
}
- return render(request, template, context)
+ xml_content = render(request, template, context).content
+ return HttpResponse(
+ xml_content,
+ content_type='application/xml',
+ )
@staff_member_required
@@ -360,6 +395,16 @@ def books_chapter(request, book_id, chapter_id=None):
)
if request.POST:
+ if chapter and "delete" in request.POST:
+ chapter.delete()
+ messages.success(request, 'Chapter deleted.')
+ return redirect(
+ reverse(
+ 'books_edit_book',
+ kwargs={'book_id': book.pk},
+ )
+ )
+
form = forms.ChapterForm(
request.POST,
request.FILES,
@@ -403,6 +448,8 @@ def view_chapter(request, book_id, chapter_id):
chapter = get_object_or_404(models.Chapter, pk=chapter_id)
template = 'books/view_chapter.html'
+ if request.press.theme == 'OLH':
+ template = 'books/OLH/view_chapter.html'
context = {
'book': book,
'chapter': chapter,
@@ -410,6 +457,7 @@ def view_chapter(request, book_id, chapter_id):
return render(request, template, context)
+
@staff_member_required
def categories(request, category_id=None):
"""
@@ -463,7 +511,6 @@ def categories(request, category_id=None):
)
)
-
template = 'books/categories.html'
context = {
'categories': all_categories,
@@ -471,3 +518,69 @@ def categories(request, category_id=None):
}
return render(request, template, context)
+
+
+@staff_member_required
+def book_preprint_management_view(
+ request,
+ book_id,
+):
+ """Manage linked preprints for a given book."""
+ book = get_object_or_404(
+ models.Book,
+ id=book_id,
+ )
+
+ # Fetch linked preprints through the BookPreprint model
+ linked_preprints = models.BookPreprint.objects.filter(
+ book=book,
+ ).order_by('order')
+
+ # Fetch available preprints that are not already linked
+ available_preprints = repository_models.Preprint.objects.exclude(
+ id__in=linked_preprints.values_list('preprint_id', flat=True),
+ )
+
+ # Initialize the form for adding a new preprint
+ form = forms.PreprintSelectionForm(
+ request.POST or None,
+ available_preprints=available_preprints,
+ )
+
+ if request.method == 'POST' and 'preprint_id' in request.POST:
+ # Handle linking a new preprint
+ if form.is_valid():
+ preprint = form.cleaned_data['preprint_id']
+
+ # Create a new BookPreprint entry with the next available order
+ max_order = models.BookPreprint.objects.filter(
+ book=book,
+ ).count()
+
+ models.BookPreprint.objects.create(
+ book=book,
+ preprint=preprint,
+ order=max_order,
+ )
+
+ messages.success(
+ request,
+ 'Preprint linked to book.',
+ )
+ return redirect(
+ 'book_preprint_management',
+ book_id=book.id,
+ )
+
+ context = {
+ 'book': book,
+ 'linked_preprints': linked_preprints,
+ 'available_preprints': available_preprints,
+ 'form': form,
+ }
+
+ return render(
+ request,
+ 'books/book_preprint_manager.html',
+ context,
+ )
\ No newline at end of file