Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Ticket #316: Możliwość stworzenia rozdziału bez wydawnictwa nadrzędnego

## Problem

The publication importer (`importer_publikacji`) allowed creating chapters
(`Wydawnictwo_Zwarte` records with `charakter_formalny.charakter_ogolny == "roz"`)
without a parent publication (`wydawnictwo_nadrzedne`). This created orphan chapter
records — chapters that don't belong to any book — which violates the data model's
semantic constraint. The bug was triggered by BibTeX imports of `@inbook`/`@incollection`
entries, where the importer's source step (step 3) had no UI for selecting a parent
publication.

## Changes

### 1. `src/importer_publikacji/models.py`
- Added two nullable ForeignKey fields to `ImportSession`:
- `wydawnictwo_nadrzedne` → `bpp.Wydawnictwo_Zwarte`
- `wydawnictwo_nadrzedne_w_pbn` → `pbn_api.Publication`

### 2. `src/importer_publikacji/migrations/0005_importsession_wydawnictwo_nadrzedne.py`
- New migration adding both FK fields to `ImportSession`.

### 3. `src/importer_publikacji/forms.py`
- Added `wydawnictwo_nadrzedne` and `wydawnictwo_nadrzedne_w_pbn` `ModelChoiceField`
fields to `SourceForm` (both `required=False`, conditional validation in view).

### 4. `src/importer_publikacji/views.py`
- Added `_is_chapter()` helper to detect chapters by `charakter_ogolny`.
- Modified `_source_context()` to pass `is_chapter` flag and parent publication
objects for Select2 pre-population.
- Modified `SourceView.post()` to validate that chapters have exactly one parent
publication (either BPP or PBN, not both, not neither).
- Modified `_create_wydawnictwo_zwarte()` to set `wydawnictwo_nadrzedne` and/or
`wydawnictwo_nadrzedne_w_pbn` on the created record.

### 5. `src/importer_publikacji/templates/.../step_source.html`
- Added conditional UI block for chapters: shows two Select2 autocomplete fields
for parent publication selection (BPP book or PBN publication).
- Added JavaScript to initialize Select2 AJAX on both fields using existing
autocomplete URLs (`wydawnictwo-nadrzedne-autocomplete`,
`wydawnictwo-nadrzedne-w-pbn-autocomplete`).

### 6. `src/importer_publikacji/templates/.../step_review.html`
- Added display of parent publication in the review step summary table.

## How to Verify

1. Start the development server and open the publication importer.
2. Import a BibTeX `@inbook` or `@incollection` entry.
3. On step 2 (Verify), confirm the charakter formalny is set to a chapter type.
4. On step 3 (Source), verify:
- The parent publication section appears with "Rozdział wymaga wydawnictwa
nadrzędnego" callout.
- Two Select2 fields are shown: "Wydawnictwo nadrzędne (BPP)" and
"Wydawnictwo nadrzędne (PBN)".
- Attempting to proceed without selecting a parent shows validation error.
- Selecting both a BPP and PBN parent shows mutual exclusivity error.
- Selecting exactly one parent allows proceeding.
5. On step 5 (Review), verify the parent publication appears in the summary.
6. After creation, verify the `Wydawnictwo_Zwarte` record has `wydawnictwo_nadrzedne`
set correctly.
7. Import a regular book (non-chapter) and verify step 3 does NOT show the parent
publication fields.
12 changes: 12 additions & 0 deletions src/importer_publikacji/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
Jezyk,
Typ_KBN,
Wydawca,
Wydawnictwo_Zwarte,
Zrodlo,
)
from pbn_api.models import Publication as PBNPublication

from .providers import (
get_available_providers,
Expand Down Expand Up @@ -135,6 +137,16 @@ class SourceForm(forms.Form):
max_length=256,
required=False,
)
wydawnictwo_nadrzedne = forms.ModelChoiceField(
queryset=Wydawnictwo_Zwarte.objects.all(),
label="Wydawnictwo nadrzędne",
required=False,
)
wydawnictwo_nadrzedne_w_pbn = forms.ModelChoiceField(
queryset=PBNPublication.objects.all(),
label="Wydawnictwo nadrzędne w PBN",
required=False,
)


class AuthorMatchForm(forms.Form):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("bpp", "0001_initial"),
("pbn_api", "0001_initial"),
(
"importer_publikacji",
"0004_rename_user_to_created_by_add_modified_by",
),
]

operations = [
migrations.AddField(
model_name="importsession",
name="wydawnictwo_nadrzedne",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="bpp.wydawnictwo_zwarte",
verbose_name="wydawnictwo nadrzędne",
),
),
migrations.AddField(
model_name="importsession",
name="wydawnictwo_nadrzedne_w_pbn",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="pbn_api.publication",
verbose_name="wydawnictwo nadrzędne w PBN",
),
),
]
14 changes: 14 additions & 0 deletions src/importer_publikacji/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ class Status(models.TextChoices):
blank=True,
verbose_name="wydawca",
)
wydawnictwo_nadrzedne = models.ForeignKey(
"bpp.Wydawnictwo_Zwarte",
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="wydawnictwo nadrzędne",
)
wydawnictwo_nadrzedne_w_pbn = models.ForeignKey(
"pbn_api.Publication",
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="wydawnictwo nadrzędne w PBN",
)
jezyk = models.ForeignKey(
"bpp.Jezyk",
on_delete=models.SET_NULL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ <h6>Dane publikacji</h6>
<td>{{ session.wydawca }}</td>
</tr>
{% endif %}
{% if session.wydawnictwo_nadrzedne %}
<tr>
<th>Wydawnictwo nadrzędne</th>
<td>
{{ session.wydawnictwo_nadrzedne }}
</td>
</tr>
{% endif %}
{% if session.wydawnictwo_nadrzedne_w_pbn %}
<tr>
<th>
Wydawnictwo nadrzędne (PBN)
</th>
<td>
{{ session.wydawnictwo_nadrzedne_w_pbn }}
</td>
</tr>
{% endif %}
{% if data.volume %}
<tr>
<th>Tom</th>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,58 @@ <h4>
<p class="help-text">
Podaj wydawcę lub wpisz szczegóły wydawcy.
</p>

{% if is_chapter %}
<hr>
<div class="callout secondary small">
<strong>
Rozdział wymaga wydawnictwa
nadrzędnego.
</strong>
Wybierz istniejącą książkę z BPP
lub publikację z PBN (jedno z dwóch).
</div>
<div class="grid-x grid-margin-x">
<div class="cell medium-6">
<label
for="id_wydawnictwo_nadrzedne">
Wydawnictwo nadrzędne (BPP) *
</label>
<select
name="wydawnictwo_nadrzedne"
id="id_wydawnictwo_nadrzedne"
data-url="{% url 'bpp:wydawnictwo-nadrzedne-autocomplete' %}"
style="width:100%">
{% if wydawnictwo_nadrzedne_obj %}
<option
value="{{ wydawnictwo_nadrzedne_obj.pk }}"
selected>
{{ wydawnictwo_nadrzedne_obj }}
</option>
{% endif %}
</select>
</div>
<div class="cell medium-6">
<label
for="id_wydawnictwo_nadrzedne_w_pbn">
Wydawnictwo nadrzędne (PBN)
</label>
<select
name="wydawnictwo_nadrzedne_w_pbn"
id="id_wydawnictwo_nadrzedne_w_pbn"
data-url="{% url 'bpp:wydawnictwo-nadrzedne-w-pbn-autocomplete' %}"
style="width:100%">
{% if wydawnictwo_nadrzedne_w_pbn_obj %}
<option
value="{{ wydawnictwo_nadrzedne_w_pbn_obj.pk }}"
selected>
{{ wydawnictwo_nadrzedne_w_pbn_obj }}
</option>
{% endif %}
</select>
</div>
</div>
{% endif %}
{% else %}
{# Wydawnictwo ciągłe: źródło wymagane #}
<div class="grid-x grid-margin-x">
Expand Down Expand Up @@ -112,3 +164,39 @@ <h4>
<span class="fi-loop"></span> Przetwarzanie...
</div>
</div>

{% if is_chapter %}
<script>
$(document).ready(function() {
function initSourceSelect2(selector, placeholder) {
var $el = $(selector);
if (!$el.length) return;
$el.select2({
ajax: {
url: $el.data('url'),
dataType: 'json',
delay: 300,
data: function(params) {
return {q: params.term};
},
processResults: function(data) {
return data;
}
},
minimumInputLength: 2,
placeholder: placeholder,
allowClear: true,
language: 'pl'
});
}
initSourceSelect2(
'#id_wydawnictwo_nadrzedne',
'Szukaj książki...'
);
initSourceSelect2(
'#id_wydawnictwo_nadrzedne_w_pbn',
'Szukaj w PBN...'
);
});
</script>
{% endif %}
Loading
Loading