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:

    + +
    + +

    Role:

    +

    Bio:

    +
    +
    + + +

    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 %} +

    + + {{ book.full_title }} + +

    +

    + {% for contributor in book.contributor_set.all %} + {{ contributor }}{% if not forloop.last %}, {% endif %} + {% endfor %} +

    +
    + {% for format in book.format_set.all %} + +   + Download {{ format.title }} + + {% if format.is_epub and format.display_read_link %} + + Read this Book + + {% endif %} + {% endfor %} + {% if book.remote_url %} + + {{ book.remote_book_label }} + + + {% endif %} + {% if book.purchase_url %} + + {% if book.category %}{{ book.category.buy_button_text }}{% else %} + Buy this Book{% endif %} +   + + {% endif %} +
    + {% 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 @@ + 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 %} +
    +

    Chapters

    + +
    + {% endif %} +
    + +
    + {% if book.bookpreprint_set.exists %} +
    +

    Linked Items

    + +
    + {% 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.cover %} + {{ book.title }} book cover + {% else %} + Default book cover + {% endif %} + +

    + +  Back to {{ book.full_title }} + +

    +
    + +
    + {% 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

    +
    + + + + + + + + + {% for preprint in available_preprints %} + + + + + {% endfor %} + +
    TitleAction
    {{ preprint.title }} +
    + {% csrf_token %} + +
    +
    +
    +
    +
    +{% 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 %}

    -
    -
    -
    - {% csrf_token %} - {% include "admin/elements/forms/errors.html" %} - {{ form|foundation }} -
    -
    - {% if chapter.filename %}

    Current file: {{ chapter.filename }}

    {% endif %} -
    -
    - -
    +
    +

    {% if chapter %}Edit Chapter{% else %}Add Chapter{% endif %}

    +  Back +
    +
    +
    + {% csrf_token %} + {% include "admin/elements/forms/errors.html" %} + {{ form|foundation }} +
    +
    + {% if chapter.filename %}

    Current file: {{ chapter.filename }} +

    {% endif %}
    +
    + +
    +
    +
    +
    + {% if chapter %} +
    +
    +
    +

    Delete Chapter

    +
    +
    +

    Delete this chapter using the button below. Please not you cannot recover this chapter once you delete it.

    +
    + {% csrf_token %} + +
    +
    + {% 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 -
    -
    -
    - {% csrf_token %} - {{ form|foundation }} - -
    -
    +
    +
    +
    +
    +

    {% if contributor %}Editing {{ contributor }}{% else %}Adding Contributor + to {{ book }}{% endif %}

    +  Back
    +
    +
    + {% csrf_token %} + {{ form|foundation }} + +
    +
    +
    +
    + {% if contributor %} +
    +
    +
    +

    Delete Contributor

    +
    +

    Delete this format using the button below. Please not you cannot recover this format once you delete it.

    +
    + {% csrf_token %} + +
    +
    + {% 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" %} -
    - {% csrf_token %} - {{ form|foundation }} -
    -

    {% if format.filename %}Current File:  {{ format.filename }}{% endif %}

    -
    - -
    +
    +
    +
    +
    +

    {% if contributor %}Editing {{ format }}{% else %}Adding Format to + {{ book }}{% endif %}

    +  Back +
    +
    + {% include "elements/forms/errors.html" %} +
    + {% csrf_token %} + {{ form|foundation }} +
    +

    {% if format.filename %}Current File: +  {{ format.filename }} + {% endif %} +

    + +
    +
    + {% if format %} +
    +
    +
    +

    Delete Format

    +
    +

    Delete this format using the button below. Please not you cannot recover this format once you delete it.

    +
    + {% csrf_token %} + +
    +
    +
    + {% 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 %}


     Back to Listing

    {% endif %} -
    -
    - {% if book.is_open_access %} - {% include "books/oa_header.html" %} - {% else %} -

    {% if books %}{% endif %}{{ book.full_title }}{% if books %}{% endif %}

    - {% endif %} - -
    {% for contributor in book.contributor_set.all %}{% if not forloop.first and not forloop.last %}, - {% elif forloop.last and not forloop.first %} & {% endif %}{{ contributor }}{% endfor %}
    - - {% if book.remote_url and book.remote_book_label %} - {{ book.remote_book_label }} - {% endif %} - {% for format in book.format_set.all %} -   Download {{ format.title }} - {% if format.is_epub %}  Read this Book{% endif %} - {% endfor %} - - {% if book.purchase_url %} - - {% if book.category %}{{ book.category.buy_button_text }}{% else %}Buy this Book{% endif %} - - - {% endif %} - - {% if book.category %} - - - - - - - {% endif %} - - - - - - -
    Category
    {{ book.category.name }}
    Description
    - {{ book.description|safe }} - {% if books %} -

    - View more detail -

    - {% endif %} -
    - - {% with chapters=book.chapter_set.all %} - {% if not books %} - {% include "books/OLH/detail.html" %} - {% endif %} - {% endwith %} - -
    - - {% 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 -
      - - - - - {% if book.pages %} - - {% endif %} - {% if book.isbn %} - {% endif %} - {% if book.doi %} - {% endif %} - {% if chapters %} - {% endif %} - - - - - {% if book.pages %} - - {% endif %} - {% if book.isbn %} - {% endif %} - {% if book.doi %} - {% endif %} - {% if chapters %} - {% endif %} - - {% if book.license_information %} - - - - - - - {% endif %} - - - - - - -
      PublishedPublished ByPagesISBNDOI{% if book.category %}{{ book.category.chapter_name_plural|capfirst }}{% else %}Chapters{% endif %}
      {{ book.date_published }}{{ book.publisher_name }}{{ book.pages }}{{ book.isbn }}{{ book.doi }}{{ chapters.count }}
      License Information
      {{ book.license_information }}
      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 }} + +
      + + + + + + + + +
      +
      +
    • + {% 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