Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f0f5b68
refactor(deduplikator): zmień nazwę IgnoredAuthor → IgnoredScientist
mpasternak May 1, 2026
49d2c80
feat(deduplikator): nowy model IgnoredAuthor (FK→Autor) dla trybu gen…
mpasternak May 1, 2026
698984d
feat(deduplikator): pola phase, scan_mode, status PARTIAL_COMPLETED, …
mpasternak May 1, 2026
f3b377e
feat(deduplikator): utils.cluster — union-find dla klastrów autorów
mpasternak May 1, 2026
b1f6170
feat(deduplikator): utils.main_selection — hierarchia wyboru głównego
mpasternak May 1, 2026
552865f
feat(deduplikator): utils.meta — pre-load wszystkich autorów do pamięci
mpasternak May 1, 2026
8e5710c
feat(deduplikator): utils.analysis_meta — scoring par bez SQL
mpasternak May 1, 2026
97ced1e
feat(deduplikator): utils.search_general — generator par dla trybu ge…
mpasternak May 1, 2026
8726db3
feat(deduplikator): _run_general_phase w tasks.py — algorytm fazy gen…
mpasternak May 1, 2026
6eece6a
feat(deduplikator): scan_for_duplicates dwufazowo (PBN + general) z P…
mpasternak May 1, 2026
ebcec98
feat(deduplikator): mode filter w widoku + get_latest_usable_scan (PA…
mpasternak May 1, 2026
d32be38
feat(deduplikator): scal_autorow_view backwards-compat + split ignore…
mpasternak May 1, 2026
085b0e6
feat(deduplikator): UI — radio mode, badges, banner PARTIAL_COMPLETED…
mpasternak May 1, 2026
245935a
feat(deduplikator): kolumna Tryb w XLSX export
mpasternak May 1, 2026
73339e6
docs(newsfragment): tryb ogólny deduplikatora autorów
mpasternak May 1, 2026
0e1fcac
fix(deduplikator): naprawy z final review (PARTIAL_COMPLETED, perf, e…
mpasternak May 1, 2026
b457774
Merge branch 'dev' into feature/deduplikator-autorow-general
mpasternak May 2, 2026
1783b96
Merge branch 'dev' into feature/deduplikator-autorow-general
mpasternak May 2, 2026
1117c18
Merge branch 'dev' into feature/deduplikator-autorow-general
mpasternak May 2, 2026
b879a7c
Merge branch 'dev' into feature/deduplikator-autorow-general
mpasternak May 2, 2026
89e2ec3
Merge branch 'dev' into feature/deduplikator-autorow-general
mpasternak May 2, 2026
acc8b08
fix(deduplikator_autorow): UI poprawki — layout, bold publikacji, naw…
mpasternak May 2, 2026
3cbbe01
feat(autocomplete): log Autor creation via autocomplete as Django adm…
mpasternak May 2, 2026
a371624
fix(deduplikator_autorow): UI poprawki cz. 2 — CSS loading, layout, p…
mpasternak May 2, 2026
12ff646
feat(deduplikator_autorow): hard rejection rozłącznych imion, ORCID w…
mpasternak May 2, 2026
0cd3e8d
Merge branch 'dev' into feature/deduplikator-autorow-general
mpasternak May 3, 2026
afc1c57
refactor(deduplikator_autorow): split views.py (1177L) into views/ pa…
mpasternak May 3, 2026
a0a6b6a
Potential fix for pull request finding 'CodeQL / Information exposure…
mpasternak May 3, 2026
0e2f7bc
Potential fix for pull request finding 'CodeQL / Information exposure…
mpasternak May 3, 2026
461731a
Potential fix for pull request finding 'CodeQL / Information exposure…
mpasternak May 3, 2026
998ed8e
Potential fix for pull request finding 'CodeQL / Information exposure…
mpasternak May 3, 2026
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ dump.rdb
.worktrees/
.claude/

.grunt-build-stamp

# Local TODO / audit notes (not committed)
TODO-*.txt
TODO-*.md
Expand Down
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,22 @@ management system built with Django. Python >=3.10,<3.15.
- Public frontend (Foundation CSS): monochrome Foundation-Icons
(`<span class="fi-icon"/>`)
- Django admin (`templates/admin/`): use emoji (no Foundation Icons)
- **Django template comments `{# ... #}` są jedno-liniowe — KAZDA LINIA
MUSI mieć własne otwarcie `{#` i zamknięcie `#}` na tej samej linii.**
Po `\n` w środku komentarza parser przestaje go widzieć i tekst wycieka
do wyrenderowanego HTML-u. Powtarzający się błąd. Reguła:
- ❌ ZABRONIONE wieloliniowe komentarze typu:
```django
{# linia 1
linia 2 #}
```
- ✅ ZAWSZE każda linia z osobnym `{# ... #}`:
```django
{# linia 1 #}
{# linia 2 #}
```
- Alternatywa dla bloków: `{% comment %}...{% endcomment %}` (też OK,
ale per-line `{# #}` jest preferowane przez użytkownika).

## Python and Django Execution

Expand Down
22 changes: 13 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ clean-pycache: ## Usuń __pycache__, *.pyc oraz .eggs/.cache
rm -rf .eggs .cache

clean: clean-pycache ## Szersze czyszczenie: egg-info, logi, build, dist, staticroot/CACHE, .tox
rm -f .grunt-build-stamp
find . -type d -name \*egg-info -print0 | xargs -0 rm -rf
find . -name \*~ -print0 | xargs -0 rm -f
find . -name \*.prof -print0 | xargs -0 rm -f
rm -rf prof/
find . -name \*\\.log -print0 | xargs -0 rm -f
find . -name \*\\.log -print0 | xargs -0 rm -f
find . -name \#\* -print0 | xargs -0 rm -f
find . -name \#\* -not -path './node_modules/*' -print0 | xargs -0 rm -rf
rm -rf build dist/*django_bpp*whl dist/*bpp_iplweb*whl *.log dist
rm -rf src/django_bpp/staticroot/CACHE
rm -rf .tox
Expand All @@ -149,11 +149,13 @@ distclean: clean ## Pełne czyszczenie: + node_modules, staticroot, media, dist,
grunt-build: ## Uruchom `grunt build` (SCSS → CSS, bundling JS)
grunt build

# CSS output files (targets)
CSS_TARGETS := src/bpp/static/scss/app-blue.css src/bpp/static/scss/app-green.css src/bpp/static/scss/app-orange.css
# grunt build kompiluje WSZYSTKIE SCSS → CSS za jednym odpaleniem.
# Pattern rule $(CSS_TARGETS): $(SCSS_SOURCES) odpalałby grunt N razy
# (raz per out-of-date target). Zamiast tego: jeden stamp file zależy od
# wszystkich SCSS + node_modules; grunt dotyka stampu po zakończeniu.

# SCSS source files
SCSS_SOURCES := $(wildcard src/bpp/static/scss/*.scss)
SCSS_SOURCES := $(wildcard src/bpp/static/scss/*.scss) \
$(wildcard src/*/static/*/scss/*.scss)

# Node modules dependency
NODE_MODULES := node_modules/.installed
Expand All @@ -166,14 +168,16 @@ $(NODE_MODULES): package.json yarn.lock
export PUPPETEER_SKIP_CHROME_DOWNLOAD=true PUPPETEER_SKIP_CHROME_HEADLESS_SHELL_DOWNLOAD=true && $(YARN_CMD) install --no-progress --emoji false -s
touch $(NODE_MODULES)

$(CSS_TARGETS): $(SCSS_SOURCES) $(NODE_MODULES)
CSS_STAMP := .grunt-build-stamp

$(CSS_STAMP): $(SCSS_SOURCES) $(NODE_MODULES)
grunt build
@touch $(CSS_STAMP)

$(MO_FILES): $(PO_FILES)
# cd src && django-admin compilemessages
uv run python src/manage.py compilemessages --locale=pl --ignore=site-packages

assets: $(CSS_TARGETS) $(MO_FILES) ## Zbuduj frontend (CSS + .mo); uruchamia `yarn install` jeśli trzeba
assets: $(CSS_STAMP) $(MO_FILES) ## Zbuduj frontend (CSS + .mo); uruchamia `yarn install` jeśli trzeba

yarn: $(NODE_MODULES) ## Zainstaluj zależności Node.js (yarn install)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Deduplikator autorów: nowy tryb "ogólny" znajdujący duplikaty wśród
autorów spoza listy pracowników instytucji w PBN. Jeden przycisk
"Skanuj duplikaty" uruchamia obie fazy (PBN + ogólna) sekwencyjnie.
Widok pozwala filtrować wyniki radio-button-em (PBN/Ogólny/Oba),
eksport XLSX zawiera kolumnę "Tryb". Anulowanie fazy ogólnej skutkuje
statusem "Częściowo zakończone" — wyniki PBN pozostają dostępne.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Deduplikator autorów: gruntowna przebudowa UI. Tytuł i pozycje
menu uproszczone z "Deduplikator autorów PBN" na "Deduplikator
autorów" (bez znacznika BETA), wpis dodany dodatkowo do podmenu
"Operacje". Tryb skanowania (PBN/ogólny) prezentowany jest jako
kolorowy badge przy "Główny rekord autora", filtr "Pokaż wyniki"
zmieniony z radio-buttonów na poziomy button-group.

Przyciski na karcie każdego potencjalnego duplikatu pogrupowane
w trzy logiczne sekcje: Podgląd (otwórz wyd. ciągłe/zwarte,
redagowanie, stronę główną, PBN), Decyzja ("Nie jest duplikatem
głównego autora", usuń autora bez publikacji), Scalanie (cztery
warianty scalania). Przyciski "Scal + ustaw dyscyplinę" oraz
"Scal + ustaw subdyscyplinę" są ukryte, gdy główny autor nie ma
żadnej dyscypliny.

Powody podobieństwa renderowane są jako kolorowe chipy z ikonami
Foundation, z tonami match/info/weak/warn dobranymi do siły
przesłanki. Procent pewności jest sklampowany do zakresu 0–100%
(wcześniej widoczne były wartości typu 140% wynikające z surowego
score).

Naprawione: oznaczenie autora jako nie-duplikat (przycisk
"Nie jest duplikatem głównego autora") wykonuje się teraz przez
AJAX z fadeOut karty, zamiast przeładowywać widok i przeskakiwać
do kolejnego głównego autora. Naprawiono też "Scal wszystkie",
który dla kandydatów z trybu ogólnego zwracał błąd 400 (JS
wysyłał ``main_scientist_id`` zamiast ``main_autor_id``); brakujące
parametry trafiają teraz dodatkowo do Rollbara.
4 changes: 2 additions & 2 deletions src/bpp/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
from bpp.models.struktura import Jednostka_Wydzial
from bpp.models.system import Charakter_PBN
from bpp.models.wydawca import Poziom_Wydawcy, Wydawca
from deduplikator_autorow.models import IgnoredAuthor, LogScalania, NotADuplicate
from deduplikator_autorow.models import IgnoredScientist, LogScalania, NotADuplicate
from dynamic_columns.models import ModelAdmin, ModelAdminColumn
from ewaluacja_common.models import Rodzaj_Autora
from ewaluacja_liczba_n.models import IloscUdzialowDlaAutoraZaRok, LiczbaNDlaUczelni
Expand Down Expand Up @@ -189,7 +189,7 @@
RozbieznosciZrodelView,
NotADuplicate,
LogScalania,
IgnoredAuthor,
IgnoredScientist,
],
"indeks autorów": [Autor, Autor_Jednostka],
"administracja": [
Expand Down
27 changes: 24 additions & 3 deletions src/bpp/tests/test_autocomplete/test_autocomplete_authors.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
)



def test_dyscyplina_naukowa_przypisanie_autocomplete(
app, autor_jan_kowalski, dyscyplina1, dyscyplina2, rok
):
Expand Down Expand Up @@ -75,7 +74,6 @@ def test_dyscyplina_naukowa_przypisanie_autocomplete(
assert res.json["results"][0]["text"] == "memetyka stosowana"



def test_dyscyplina_naukowa_przypisanie_autocomplete_brak_autora(
app,
):
Expand All @@ -90,7 +88,6 @@ def test_dyscyplina_naukowa_przypisanie_autocomplete_brak_autora(
assert res.json["results"][0]["text"] == "Podaj autora"



def test_dyscyplina_naukowa_przypisanie_autocomplete_brak_drugiej(
app, autor_jan_kowalski, dyscyplina1, dyscyplina2, rok
):
Expand Down Expand Up @@ -133,6 +130,30 @@ def autocomplete(s):
assert Autor.objects.first().imiona == "Baz Quux"


@pytest.mark.django_db
def test_AutorAutocomplete_create_object_creates_log_entry(rf, admin_user, db):
from django.contrib.admin.models import ADDITION, LogEntry
from django.contrib.contenttypes.models import ContentType

autor_count_before = Autor.objects.count()

ac = AutorAutocomplete()
ac.request = rf.post("/", data={"text": "Kowalski Jan"})
ac.request.user = admin_user

obj = ac.create_object("Kowalski Jan")

assert obj.pk != -1
assert Autor.objects.count() == autor_count_before + 1

ct = ContentType.objects.get_for_model(Autor)
log = LogEntry.objects.get(
content_type=ct, object_id=str(obj.pk), action_flag=ADDITION
)
assert log.user == admin_user
assert "autocomplete" in log.change_message


@pytest.mark.django_db
def test_Status_KorektyAutocomplete(statusy_korekt):
"""Test status korekty autocomplete filtering."""
Expand Down
19 changes: 18 additions & 1 deletion src/bpp/views/autocomplete/authors.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,27 @@ class AutorAutocomplete(GroupRequiredMixin, AutorAutocompleteBase):

def create_object(self, text):
try:
return Autor.objects.create_from_string(text)
obj = Autor.objects.create_from_string(text)
except ValueError:
return self.err

from django.contrib.admin.models import ADDITION, LogEntry
from django.contrib.contenttypes.models import ContentType

try:
LogEntry.objects.create(
user_id=self.request.user.pk,
content_type_id=ContentType.objects.get_for_model(Autor).pk,
object_id=str(obj.pk),
object_repr=str(obj)[:200],
action_flag=ADDITION,
change_message="Utworzono z formularza autocomplete",
)
except (AttributeError, TypeError):
pass

return obj


class PublicAutorAutocomplete(AutorAutocompleteBase):
"""Public autocomplete for authors (no create, no PBN/MNISW markers)."""
Expand Down
41 changes: 39 additions & 2 deletions src/deduplikator_autorow/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
DuplicateCandidate,
DuplicateScanRun,
IgnoredAuthor,
IgnoredScientist,
LogScalania,
NotADuplicate,
)
Expand Down Expand Up @@ -76,8 +77,8 @@ def get_author_last_name(self, obj):
get_author_last_name.admin_order_field = "scientist_pk"


@admin.register(IgnoredAuthor)
class IgnoredAuthorAdmin(DynamicAdminFilterMixin, admin.ModelAdmin):
@admin.register(IgnoredScientist)
class IgnoredScientistAdmin(DynamicAdminFilterMixin, admin.ModelAdmin):
list_display = [
"get_scientist_display",
"get_autor_display",
Expand Down Expand Up @@ -133,6 +134,42 @@ def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)


@admin.register(IgnoredAuthor)
class IgnoredAuthorAdmin(DynamicAdminFilterMixin, admin.ModelAdmin):
list_display = [
"get_autor_display",
"reason",
"created_by",
"created_on",
]

list_filter = ["created_on", "created_by"]

search_fields = [
"autor__nazwisko",
"autor__imiona",
"reason",
"created_by__username",
]

readonly_fields = ["created_on"]
date_hierarchy = "created_on"
ordering = ["-created_on"]

def get_autor_display(self, obj):
if obj.autor:
url = reverse("admin:bpp_autor_change", args=[obj.autor.pk])
return mark_safe(f'<a href="{url}">{obj.autor}</a>')
return "-"

get_autor_display.short_description = "Autor (BPP)"

def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
super().save_model(request, obj, form, change)


@admin.register(LogScalania)
class LogScalaniaAdmin(DynamicAdminFilterMixin, admin.ModelAdmin):
list_display = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("deduplikator_autorow", "0008_add_priority_field"),
]

operations = [
migrations.RenameModel(
old_name="IgnoredAuthor",
new_name="IgnoredScientist",
),
migrations.AlterModelOptions(
name="ignoredscientist",
options={
"ordering": ["-created_on"],
"verbose_name": "Ignorowany Scientist (PBN)",
"verbose_name_plural": "Ignorowani Scientist (PBN)",
},
),
]
Loading
Loading