diff --git a/src/bpp/management/commands/rebuild_cache.py b/src/bpp/management/commands/rebuild_cache.py index fb1d1fcc5..918ee8f7f 100644 --- a/src/bpp/management/commands/rebuild_cache.py +++ b/src/bpp/management/commands/rebuild_cache.py @@ -4,21 +4,28 @@ from django.conf import settings from django.core.management import BaseCommand -from bpp.models import Wydawnictwo_Zwarte, Wydawnictwo_Ciagle, rebuild_ciagle, rebuild_zwarte, Patent -from bpp.util import partition_count, no_threads - +from bpp.models import ( + Wydawnictwo_Ciagle, + Wydawnictwo_Zwarte, + rebuild_ciagle, + rebuild_zwarte, +) +from bpp.util import no_threads, partition_count def subprocess_setup(*args): from django.db import connection + connection.connect() + class Command(BaseCommand): - help = 'Odbudowuje cache' + help = "Odbudowuje cache" def handle(self, *args, **options): if not settings.TESTING: from django import db + db.connections.close_all() pool_size = no_threads(0.5) @@ -29,4 +36,3 @@ def handle(self, *args, **options): pc = pool.apply(partition_count, args=(Wydawnictwo_Zwarte.objects, pool_size)) pool.starmap(rebuild_zwarte, pc) - diff --git a/src/bpp/migrations/0195_cc0.py b/src/bpp/migrations/0195_cc0.py new file mode 100644 index 000000000..b6414d85e --- /dev/null +++ b/src/bpp/migrations/0195_cc0.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.10 on 2020-02-17 21:34 + +from django.db import migrations + + +def forwards_func(apps, schema_editor): + Licencja_OpenAccess = apps.get_model("bpp", "Licencja_OpenAccess") + db_alias = schema_editor.connection.alias + Licencja_OpenAccess.objects.using(db_alias).get_or_create( + skrot="CC-ZERO", + nazwa="Creative Commons - Universal - Przekazanie do Domeny Publicznej (CC0 1.0)", + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("bpp", "0194_auto_20200213_2148"), + ] + + operations = [migrations.RunPython(forwards_func)] diff --git a/src/bpp/models/cache.py b/src/bpp/models/cache.py index b4d3a1495..d246f731f 100644 --- a/src/bpp/models/cache.py +++ b/src/bpp/models/cache.py @@ -16,31 +16,55 @@ from django.db.models import Func, ForeignKey, CASCADE from django.db.models.deletion import DO_NOTHING from django.db.models.lookups import In -from django.db.models.signals import post_save, post_delete, pre_save, \ - pre_delete +from django.db.models.signals import post_save, post_delete, pre_save, pre_delete from django.utils import six from django.utils.functional import cached_property -from bpp.models import Patent, \ - Praca_Doktorska, Praca_Habilitacyjna, \ - Typ_Odpowiedzialnosci, Wydawnictwo_Zwarte, \ - Wydawnictwo_Ciagle, Wydawnictwo_Ciagle_Autor, Wydawnictwo_Zwarte_Autor, \ - Patent_Autor, Zrodlo, Dyscyplina_Naukowa, Autor, Jednostka -from bpp.models.abstract import ModelPunktowanyBaza, \ - ModelZRokiem, ModelZeSzczegolami, ModelRecenzowany, \ - ModelZeZnakamiWydawniczymi, ModelZOpenAccess, ModelZKonferencja, \ - ModelTypowany, ModelZCharakterem +from bpp.models import ( + Patent, + Praca_Doktorska, + Praca_Habilitacyjna, + Typ_Odpowiedzialnosci, + Wydawnictwo_Zwarte, + Wydawnictwo_Ciagle, + Wydawnictwo_Ciagle_Autor, + Wydawnictwo_Zwarte_Autor, + Patent_Autor, + Zrodlo, + Dyscyplina_Naukowa, + Autor, + Jednostka, +) +from bpp.models.abstract import ( + ModelPunktowanyBaza, + ModelZRokiem, + ModelZeSzczegolami, + ModelRecenzowany, + ModelZeZnakamiWydawniczymi, + ModelZOpenAccess, + ModelZKonferencja, + ModelTypowany, + ModelZCharakterem, +) from bpp.models.system import Charakter_Formalny, Jezyk from bpp.models.util import ModelZOpisemBibliograficznym from bpp.util import FulltextSearchMixin # zmiana CACHED_MODELS powoduje zmiane opisu bibliograficznego wszystkich rekordow -CACHED_MODELS = [Wydawnictwo_Ciagle, Wydawnictwo_Zwarte, Praca_Doktorska, - Praca_Habilitacyjna, Patent] +CACHED_MODELS = [ + Wydawnictwo_Ciagle, + Wydawnictwo_Zwarte, + Praca_Doktorska, + Praca_Habilitacyjna, + Patent, +] # zmiana DEPENDEND_REKORD_MODELS powoduje zmiane opisu bibliograficznego rekordu z pola z ich .rekord -DEPENDENT_REKORD_MODELS = [Wydawnictwo_Ciagle_Autor, Wydawnictwo_Zwarte_Autor, - Patent_Autor] +DEPENDENT_REKORD_MODELS = [ + Wydawnictwo_Ciagle_Autor, + Wydawnictwo_Zwarte_Autor, + Patent_Autor, +] # Other dependent -- czyli skasowanie tych obiektów pociąga za sobą odbudowanie tabeli cache OTHER_DEPENDENT_MODELS = [Typ_Odpowiedzialnosci, Jezyk, Charakter_Formalny, Zrodlo] @@ -49,13 +73,15 @@ def defer_zaktualizuj_opis(instance, *args, **kw): """Obiekt typu Wydawnictwo_..., Patent, Praca_... został zapisany. Zaktualizuj jego opis bibliograficzny.""" - flds = kw.get('update_fields', []) + flds = kw.get("update_fields", []) if flds: flds = list(flds) - for elem in ['opis_bibliograficzny_cache', - 'opis_bibliograficzny_autorzy_cache', - 'opis_bibliograficzny_zapisani_autorzy_cache']: + for elem in [ + "opis_bibliograficzny_cache", + "opis_bibliograficzny_autorzy_cache", + "opis_bibliograficzny_zapisani_autorzy_cache", + ]: try: flds.remove(elem) except ValueError: @@ -105,7 +131,9 @@ def zrodlo_pre_save(instance, *args, **kw): return if old.skrot != instance.skrot: # sprawdz skrot, bo on idzie do opisu - instance._BPP_CHANGED_FIELDS = ['skrot', ] + instance._BPP_CHANGED_FIELDS = [ + "skrot", + ] def zrodlo_post_save(instance, *args, **kw): @@ -114,16 +142,19 @@ def zrodlo_post_save(instance, *args, **kw): changed = getattr(instance, "_BPP_CHANGED_FIELDS", []) if not changed: - changed = kw.get('update_fields') or [] + changed = kw.get("update_fields") or [] - if 'skrot' in changed: + if "skrot" in changed: from bpp.tasks import zaktualizuj_zrodlo + zaktualizuj_zrodlo.delay(instance.pk) def zrodlo_pre_delete(instance, *args, **kw): # TODO: moze byc memory-consuming, lepiej byloby to wrzucic do bazy danych - moze? - instance._PRACE = list(Rekord.objects.filter(zrodlo__id=instance.pk).values_list("id", flat=True)) + instance._PRACE = list( + Rekord.objects.filter(zrodlo__id=instance.pk).values_list("id", flat=True) + ) def zrodlo_post_delete(instance, *args, **kw): @@ -134,8 +165,6 @@ def zrodlo_post_delete(instance, *args, **kw): continue defer_zaktualizuj_opis(rekord.original) - pass - _CACHE_ENABLED = False @@ -151,7 +180,8 @@ class AlreadyDisabledException(Exception): def enable(): global _CACHE_ENABLED - if _CACHE_ENABLED: raise AlreadyEnabledException() + if _CACHE_ENABLED: + raise AlreadyEnabledException() pre_save.connect(zrodlo_pre_save, sender=Zrodlo) post_save.connect(zrodlo_post_save, sender=Zrodlo) @@ -203,11 +233,11 @@ def from_db_value(self, value, expression, connection, context): class TupleInLookup(In): def get_prep_lookup(self): values = super(TupleInLookup, self).get_prep_lookup() - if hasattr(self.rhs, '_prepare'): + if hasattr(self.rhs, "_prepare"): return values prepared_values = [] for value in values: - if hasattr(value, 'resolve_expression'): + if hasattr(value, "resolve_expression"): prepared_values.append(value) else: prepared_values.append(tuple(value)) @@ -220,17 +250,14 @@ def filter_rekord(self, rekord): class AutorzyBase(models.Model): - id = TupleField( - models.IntegerField(), - size=2, - primary_key=True) + id = TupleField(models.IntegerField(), size=2, primary_key=True) - autor = models.ForeignKey('Autor', DO_NOTHING) - jednostka = models.ForeignKey('Jednostka', DO_NOTHING) + autor = models.ForeignKey("Autor", DO_NOTHING) + jednostka = models.ForeignKey("Jednostka", DO_NOTHING) kolejnosc = models.IntegerField() - typ_odpowiedzialnosci = models.ForeignKey('Typ_Odpowiedzialnosci', DO_NOTHING) + typ_odpowiedzialnosci = models.ForeignKey("Typ_Odpowiedzialnosci", DO_NOTHING) zapisany_jako = models.TextField() - dyscyplina_naukowa = models.ForeignKey('Dyscyplina_Naukowa', DO_NOTHING) + dyscyplina_naukowa = models.ForeignKey("Dyscyplina_Naukowa", DO_NOTHING) afiliuje = models.BooleanField() zatrudniony = models.BooleanField() @@ -243,45 +270,41 @@ class Meta: class Autorzy(AutorzyBase): rekord = models.ForeignKey( - 'bpp.Rekord', + "bpp.Rekord", related_name="autorzy", # tak na prawdę w bazie danych jest constraint dla ON_DELETE, ale # dajemy to tutaj, żeby django się nie awanturowało i nie próbowało # tego ręcznie kasować - on_delete=DO_NOTHING + on_delete=DO_NOTHING, ) class Meta: managed = False - db_table = 'bpp_autorzy_mat' + db_table = "bpp_autorzy_mat" class AutorzyView(AutorzyBase): rekord = models.ForeignKey( - 'bpp.RekordView', + "bpp.RekordView", related_name="autorzy", # tak na prawdę w bazie danych jest constraint dla ON_DELETE, ale # dajemy to tutaj, żeby django się nie awanturowało i nie próbowało # tego ręcznie kasować - on_delete=DO_NOTHING + on_delete=DO_NOTHING, ) class Meta: managed = False - db_table = 'bpp_autorzy' + db_table = "bpp_autorzy" class ZewnetrzneBazyDanychView(models.Model): rekord = models.ForeignKey( - 'bpp.Rekord', - related_name='zewnetrzne_bazy', - on_delete=DO_NOTHING) - - baza = models.ForeignKey( - 'bpp.Zewnetrzna_Baza_Danych', - on_delete=DO_NOTHING + "bpp.Rekord", related_name="zewnetrzne_bazy", on_delete=DO_NOTHING ) + baza = models.ForeignKey("bpp.Zewnetrzna_Baza_Danych", on_delete=DO_NOTHING) + info = models.TextField() class Meta: @@ -290,13 +313,10 @@ class Meta: class RekordManager(FulltextSearchMixin, models.Manager): - fts_field = 'search_index' + fts_field = "search_index" def get_for_model(self, model): - pk = ( - ContentType.objects.get_for_model(model).pk, - model.pk - ) + pk = (ContentType.objects.get_for_model(model).pk, model.pk) return self.get(pk=pk) def prace_autora(self, autor): @@ -308,39 +328,43 @@ def prace_autora_z_afiliowanych_jednostek(self, autor): do jednostek skupiających pracowników, gdzie autor jest zaznaczony jako afiliowany. """ - return self.prace_autora(autor).filter( - autorzy__autor=autor, - autorzy__jednostka__skupia_pracownikow=True, - autorzy__afiliuje=True - ).distinct() + return ( + self.prace_autora(autor) + .filter( + autorzy__autor=autor, + autorzy__jednostka__skupia_pracownikow=True, + autorzy__afiliuje=True, + ) + .distinct() + ) def prace_autor_i_typ(self, autor, skrot): - return self.prace_autora(autor).filter( - autorzy__typ_odpowiedzialnosci_id=Typ_Odpowiedzialnosci.objects.get(skrot=skrot).pk - ).distinct() + return ( + self.prace_autora(autor) + .filter( + autorzy__typ_odpowiedzialnosci_id=Typ_Odpowiedzialnosci.objects.get( + skrot=skrot + ).pk + ) + .distinct() + ) def prace_jednostki(self, jednostka): - return self.filter( - autorzy__jednostka=jednostka - ).distinct() + return self.filter(autorzy__jednostka=jednostka).distinct() def prace_wydzialu(self, wydzial): - return self.filter( - autorzy__jednostka__wydzial=wydzial - ).distinct() + return self.filter(autorzy__jednostka__wydzial=wydzial).distinct() def redaktorzy_z_jednostki(self, jednostka): return self.filter( autorzy__jednostka=jednostka, autorzy__typ_odpowiedzialnosci_id=Typ_Odpowiedzialnosci.objects.get( - skrot='red.').pk + skrot="red." + ).pk, ).distinct() def get_original(self, model): - return self.get(pk=[ - ContentType.objects.get_for_model(model).pk, - model.pk - ]) + return self.get(pk=[ContentType.objects.get_for_model(model).pk, model.pk]) @transaction.atomic def full_refresh(self): @@ -375,16 +399,20 @@ def full_refresh(self): @six.python_2_unicode_compatible -class RekordBase(ModelPunktowanyBaza, ModelZOpisemBibliograficznym, - ModelZRokiem, ModelZeSzczegolami, ModelRecenzowany, - ModelZeZnakamiWydawniczymi, ModelZOpenAccess, - ModelTypowany, ModelZCharakterem, - ModelZKonferencja, - models.Model): - id = TupleField( - models.IntegerField(), - size=2, - primary_key=True) +class RekordBase( + ModelPunktowanyBaza, + ModelZOpisemBibliograficznym, + ModelZRokiem, + ModelZeSzczegolami, + ModelRecenzowany, + ModelZeZnakamiWydawniczymi, + ModelZOpenAccess, + ModelTypowany, + ModelZCharakterem, + ModelZKonferencja, + models.Model, +): + id = TupleField(models.IntegerField(), size=2, primary_key=True) tekst_przed_pierwszym_autorem = None tekst_po_ostatnim_autorze = None @@ -427,7 +455,7 @@ class RekordBase(ModelPunktowanyBaza, ModelZOpisemBibliograficznym, "typ_odpowiedzialnosci": "autorzy__typ_odpowiedzialnosci__skrot", "autor": "autorzy__autor_id", "jednostka": "autorzy__jednostka__pk", - "wydzial": "autorzy__jednostka__wydzial__pk" + "wydzial": "autorzy__jednostka__wydzial__pk", } class Meta: @@ -454,12 +482,20 @@ def js_safe_pk(self): @cached_property def ma_punktacje_sloty(self): - return Cache_Punktacja_Autora.objects.filter(rekord_id=[self.id[0], self.id[1]]).exists() or \ - Cache_Punktacja_Dyscypliny.objects.filter(rekord_id=[self.id[0], self.id[1]]).exists() + return ( + Cache_Punktacja_Autora.objects.filter( + rekord_id=[self.id[0], self.id[1]] + ).exists() + or Cache_Punktacja_Dyscypliny.objects.filter( + rekord_id=[self.id[0], self.id[1]] + ).exists() + ) @cached_property def punktacja_dyscypliny(self): - return Cache_Punktacja_Dyscypliny.objects.filter(rekord_id=[self.id[0], self.id[1]]) + return Cache_Punktacja_Dyscypliny.objects.filter( + rekord_id=[self.id[0], self.id[1]] + ) @cached_property def punktacja_autora(self): @@ -469,14 +505,14 @@ def punktacja_autora(self): class Rekord(RekordBase): class Meta: managed = False - ordering = ['tytul_oryginalny_sort'] - db_table = 'bpp_rekord_mat' + ordering = ["tytul_oryginalny_sort"] + db_table = "bpp_rekord_mat" class RekordView(RekordBase): class Meta: managed = False - db_table = 'bpp_rekord' + db_table = "bpp_rekord" def with_cache(fun): @@ -500,7 +536,9 @@ def _wrapped(*args, **kw): if not enable_failure: disable() else: - raise Exception("Enable failure, trace enable function, there was a bug there...") + raise Exception( + "Enable failure, trace enable function, there was a bug there..." + ) return _wrapped @@ -510,13 +548,12 @@ def add(self, obj): from bpp.tasks import aktualizuj_cache obj, created = self.get_or_create( - created_on=Func(function='NOW'), + created_on=Func(function="NOW"), object_id=obj.pk, - content_type=ContentType.objects.get_for_model(obj._meta.model)) + content_type=ContentType.objects.get_for_model(obj._meta.model), + ) if created: - transaction.on_commit( - lambda: aktualizuj_cache.delay() - ) + transaction.on_commit(lambda: aktualizuj_cache.delay()) return obj @@ -539,12 +576,12 @@ class CacheQueue(models.Model): content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() - rekord = GenericForeignKey('content_type', 'object_id') + rekord = GenericForeignKey("content_type", "object_id") objects = CacheManager() class Meta: - ordering = ('-created_on',) + ordering = ("-created_on",) class Cache_Punktacja_Dyscypliny(models.Model): @@ -555,7 +592,7 @@ class Cache_Punktacja_Dyscypliny(models.Model): slot = models.DecimalField(max_digits=20, decimal_places=4) class Meta: - ordering = ('dyscyplina__nazwa',) + ordering = ("dyscyplina__nazwa",) class Cache_Punktacja_Autora_Base(models.Model): @@ -566,7 +603,7 @@ class Cache_Punktacja_Autora_Base(models.Model): slot = models.DecimalField(max_digits=20, decimal_places=4) class Meta: - ordering = ('autor__nazwisko', 'dyscyplina__nazwa') + ordering = ("autor__nazwisko", "dyscyplina__nazwa") abstract = True @@ -574,19 +611,19 @@ class Cache_Punktacja_Autora(Cache_Punktacja_Autora_Base): rekord_id = TupleField(models.IntegerField(), size=2, db_index=True) class Meta: - ordering = ('autor__nazwisko', 'dyscyplina__nazwa') + ordering = ("autor__nazwisko", "dyscyplina__nazwa") class Cache_Punktacja_Autora_Query(Cache_Punktacja_Autora_Base): - rekord = ForeignKey('bpp.Rekord', DO_NOTHING) + rekord = ForeignKey("bpp.Rekord", DO_NOTHING) class Meta: - db_table = 'bpp_cache_punktacja_autora' + db_table = "bpp_cache_punktacja_autora" managed = False class Cache_Punktacja_Autora_Sum(Cache_Punktacja_Autora_Base): - rekord = ForeignKey('bpp.Rekord', DO_NOTHING) + rekord = ForeignKey("bpp.Rekord", DO_NOTHING) autor = ForeignKey(Autor, DO_NOTHING) jednostka = ForeignKey(Jednostka, DO_NOTHING) dyscyplina = ForeignKey(Dyscyplina_Naukowa, DO_NOTHING) @@ -595,13 +632,17 @@ class Cache_Punktacja_Autora_Sum(Cache_Punktacja_Autora_Base): pkdautslotsum = models.FloatField() class Meta: - db_table = 'bpp_temporary_cpaq' + db_table = "bpp_temporary_cpaq" managed = False - ordering = ('autor', 'dyscyplina', '-pkdautslot',) + ordering = ( + "autor", + "dyscyplina", + "-pkdautslot", + ) class Cache_Punktacja_Autora_Sum_Ponizej(Cache_Punktacja_Autora_Base): - rekord = ForeignKey('bpp.Rekord', DO_NOTHING) + rekord = ForeignKey("bpp.Rekord", DO_NOTHING) autor = ForeignKey(Autor, DO_NOTHING) jednostka = ForeignKey(Jednostka, DO_NOTHING) dyscyplina = ForeignKey(Dyscyplina_Naukowa, DO_NOTHING) @@ -610,9 +651,13 @@ class Cache_Punktacja_Autora_Sum_Ponizej(Cache_Punktacja_Autora_Base): pkdautslotsum = models.FloatField() class Meta: - db_table = 'bpp_temporary_cpaq_2' + db_table = "bpp_temporary_cpaq_2" managed = False - ordering = ('autor', 'dyscyplina', 'pkdautslot',) + ordering = ( + "autor", + "dyscyplina", + "pkdautslot", + ) class Cache_Punktacja_Autora_Sum_Group_Ponizej(models.Model): @@ -623,9 +668,12 @@ class Cache_Punktacja_Autora_Sum_Group_Ponizej(models.Model): pkdautslotsum = models.FloatField() class Meta: - db_table = 'bpp_temporary_cpasg_2' + db_table = "bpp_temporary_cpasg_2" managed = False - ordering = ('autor', 'dyscyplina',) + ordering = ( + "autor", + "dyscyplina", + ) class Cache_Punktacja_Autora_Sum_Gruop(models.Model): @@ -636,15 +684,19 @@ class Cache_Punktacja_Autora_Sum_Gruop(models.Model): pkdautslotsum = models.FloatField() class Meta: - db_table = 'bpp_temporary_cpasg' + db_table = "bpp_temporary_cpasg" managed = False - ordering = ('autor', 'dyscyplina',) + ordering = ( + "autor", + "dyscyplina", + ) # # Rebuilder # + @transaction.atomic def rebuild(klass, offset=None, limit=None, extra_flds=None, extra_tables=None): if extra_flds is None: @@ -653,30 +705,33 @@ def rebuild(klass, offset=None, limit=None, extra_flds=None, extra_tables=None): if extra_tables is None: extra_tables = () - ids = klass.objects.all().values_list('pk')[offset:limit] - - query = klass.objects.filter(pk__in=ids). \ - select_for_update(nowait=True, of=('self',)). \ - select_related("charakter_formalny", "typ_kbn", *extra_tables). \ - only("tytul_oryginalny", - "tytul", - "informacje", - "charakter_formalny__skrot", - "charakter_formalny__charakter_sloty", - "szczegoly", - "uwagi", - "doi", - "tekst_przed_pierwszym_autorem", - "tekst_po_ostatnim_autorze", - - "opis_bibliograficzny_cache", - "opis_bibliograficzny_autorzy_cache", - "opis_bibliograficzny_zapisani_autorzy_cache", - - "typ_kbn__nazwa", - "typ_kbn__skrot", - "rok", - "punkty_kbn", *extra_flds) + ids = klass.objects.all().values_list("pk").order_by("pk")[offset:limit] + + query = ( + klass.objects.filter(pk__in=ids) + .select_for_update(nowait=True, of=("self",)) + .select_related("charakter_formalny", "typ_kbn", *extra_tables) + .only( + "tytul_oryginalny", + "tytul", + "informacje", + "charakter_formalny__skrot", + "charakter_formalny__charakter_sloty", + "szczegoly", + "uwagi", + "doi", + "tekst_przed_pierwszym_autorem", + "tekst_po_ostatnim_autorze", + "opis_bibliograficzny_cache", + "opis_bibliograficzny_autorzy_cache", + "opis_bibliograficzny_zapisani_autorzy_cache", + "typ_kbn__nazwa", + "typ_kbn__skrot", + "rok", + "punkty_kbn", + *extra_flds + ) + ) from bpp.tasks import aktualizuj_cache_rekordu @@ -696,12 +751,19 @@ def rebuild(klass, offset=None, limit=None, extra_flds=None, extra_tables=None): def rebuild_zwarte(offset=None, limit=None): return rebuild( - Wydawnictwo_Zwarte, offset=offset, limit=limit, - extra_tables=['wydawca', ], - extra_flds=['miejsce_i_rok', 'wydawca__nazwa', 'wydawca_opis', 'isbn']) + Wydawnictwo_Zwarte, + offset=offset, + limit=limit, + extra_tables=["wydawca",], + extra_flds=["miejsce_i_rok", "wydawca__nazwa", "wydawca_opis", "isbn"], + ) def rebuild_ciagle(offset=None, limit=None): - return rebuild(Wydawnictwo_Ciagle, offset=offset, limit=limit, - extra_tables=['zrodlo'], - extra_flds=['zrodlo__nazwa', 'zrodlo__skrot']) + return rebuild( + Wydawnictwo_Ciagle, + offset=offset, + limit=limit, + extra_tables=["zrodlo"], + extra_flds=["zrodlo__nazwa", "zrodlo__skrot"], + ) diff --git a/src/import_dbf/management/commands/integruj_dbf.py b/src/import_dbf/management/commands/integruj_dbf.py index fe5e3d502..c776438d9 100644 --- a/src/import_dbf/management/commands/integruj_dbf.py +++ b/src/import_dbf/management/commands/integruj_dbf.py @@ -1,36 +1,38 @@ # -*- encoding: utf-8 -*- +import argparse +import logging import multiprocessing from math import floor import django - -django.setup() from django.core.management import BaseCommand from bpp.models import Konferencja -from import_dbf.models import Bib, B_A +from bpp.util import partition_count +from import_dbf.models import B_A, Bib from import_dbf.util import ( - integruj_wydzialy, - integruj_jednostki, - integruj_uczelnia, + ekstrakcja_konferencji, integruj_autorow, - integruj_publikacje, + integruj_b_a, integruj_charaktery, + integruj_funkcje_autorow, + integruj_jednostki, integruj_jezyki, integruj_kbn, - integruj_zrodla, - integruj_b_a, - wyswietl_prace_bez_dopasowania, - usun_podwojne_przypisania_b_a, + integruj_publikacje, integruj_tytuly_autorow, - integruj_funkcje_autorow, + integruj_uczelnia, + integruj_wydzialy, + integruj_zrodla, mapuj_elementy_publikacji, - ekstrakcja_konferencji, przypisz_jednostki, sprawdz_zamapowanie_autorow, + usun_podwojne_przypisania_b_a, + wyswietl_prace_bez_dopasowania, + wzbogacaj_charaktery, ) -from bpp.util import partition_count -import logging + +django.setup() class Command(BaseCommand): @@ -48,6 +50,10 @@ def add_arguments(self, parser): parser.add_argument("--enable-autor", action="store_true") parser.add_argument("--enable-publikacja", action="store_true") parser.add_argument("--enable-charakter-kbn-jezyk", action="store_true") + parser.add_argument( + "--charaktery-enrichment-xls", nargs="?", type=argparse.FileType("rb") + ) + parser.add_argument("--enable-zrodlo", action="store_true") parser.add_argument("--enable-b-a", action="store_true") parser.add_argument("--enable-przypisz-jednostki", action="store_true") @@ -94,6 +100,11 @@ def handle( if enable_all or options["enable_charakter_kbn_jezyk"]: pool.apply(integruj_charaktery) + + fp = options.get("charaktery_enrichment_xls").name + if fp: + pool.apply(wzbogacaj_charaktery, fp) + pool.apply(integruj_kbn) pool.apply(integruj_jezyki) diff --git a/src/import_dbf/sql/alter-schema.sql b/src/import_dbf/sql/alter-schema.sql index ed7d72f8c..93846f9b1 100644 --- a/src/import_dbf/sql/alter-schema.sql +++ b/src/import_dbf/sql/alter-schema.sql @@ -51,7 +51,8 @@ CREATE UNIQUE INDEX import_dbf_aut_idt ON import_dbf_aut(idt_aut); create index import_dbf_aut_nazwisko on import_dbf_aut(nazwisko); ALTER TABLE import_dbf_aut ALTER COLUMN exp_id SET DATA TYPE INT USING exp_id::integer; -update import_dbf_aut set pbn_id = NULL where pbn_id IN ('', '1981-02-01', '2004-09-15', '3988552 Za', '3996174 Z', '3996174 Z'); +update import_dbf_aut set pbn_id = NULL where pbn_id = ''; +-- IN ('', '1981-02-01', '2004-09-15', '3988552 Za', '3996174 Z', '3996174 Z'); ALTER TABLE import_dbf_aut ALTER COLUMN pbn_id SET DATA TYPE INT USING pbn_id::integer; ALTER TABLE import_dbf_jez ADD COLUMN bpp_id INTEGER; diff --git a/src/import_dbf/tests/test_chf.xlsx b/src/import_dbf/tests/test_chf.xlsx new file mode 100644 index 000000000..8d31d9ed9 Binary files /dev/null and b/src/import_dbf/tests/test_chf.xlsx differ diff --git a/src/import_dbf/tests/test_util.py b/src/import_dbf/tests/test_util.py index 3652b3533..8c3fa9856 100644 --- a/src/import_dbf/tests/test_util.py +++ b/src/import_dbf/tests/test_util.py @@ -2,11 +2,17 @@ import pytest -from import_dbf.util import addslashes, exp_parse_str, exp_add_spacing, dbf2sql +from import_dbf.util import ( + addslashes, + dbf2sql, + exp_add_spacing, + exp_parse_str, + xls2dict, +) def test_util_addslashes(): - assert addslashes(None) == None + assert addslashes(None) is None assert addslashes(1) == 1 assert addslashes("foo'") == "foo''" @@ -18,54 +24,70 @@ def test_util_import_dbf(): @pytest.mark.parametrize( "input, output", - - [("#204$ #a$ Zachowania zdrowotne dzieci w wieku szkolnym - badania wstępne #b$ #c$ #d$ #e$ s.184-191 " - "#f$ ryc., tab., bibliogr., streszcz.", - {'id': 204, - 'a': 'Zachowania zdrowotne dzieci w wieku szkolnym - badania wstępne', - 'b': '', - 'c': '', - 'd': '', - 'e': 's.184-191', - 'f': 'ryc., tab., bibliogr., streszcz.', } - ), - - ("#102$ #a$ Autologous hematopoietic stem cell transplant for progressive diffuse systemic sclerosis: procedural " - "success and clinical outcome in 5-year follow-up #b$ #c$ #d$", - {'id': 102, - 'a': 'Autologous hematopoietic stem cell transplant for progressive diffuse systemic sclerosis: procedural success and clinical outcome in 5-year follow-up', - 'b': '', - 'c': '', - 'd': ''} - ), - - ('#150$ #a$ Badania nad wpływem selenu na integralność chromatyny plemnikowej w przypadkach mężczyzn z ' - 'upośledzoną płodnością #b$ #c$ #d$', - {'id': 150, - 'a': 'Badania nad wpływem selenu na integralność chromatyny plemnikowej w przypadkach mężczyzn z upośledzoną płodnością', - 'b': '', - 'c': '', - 'd': ''} - ), - - ('#150$ #a$Neurologia #c$Howard L. Weiner, Lawrence P. Levitt ', - {'id': 150, - 'a': 'Neurologia', - 'c': 'Howard L. Weiner, Lawrence P. Levitt'}), - - ( - '#103$#a$ Abstracts of the 5-th World Congress on Heart Failure - Mechanisms and Managment. Washinfton, USA, 11-14 May, 1997', - {'id': 103, - 'a': 'Abstracts of the 5-th World Congress on Heart Failure - Mechanisms and Managment. Washinfton, USA, 11-14 May, 1997'}), - - ] + [ + ( + "#204$ #a$ Zachowania zdrowotne dzieci w wieku szkolnym - badania wstępne #b$ #c$ #d$ #e$ s.184-191 " + "#f$ ryc., tab., bibliogr., streszcz.", + { + "id": 204, + "a": "Zachowania zdrowotne dzieci w wieku szkolnym - badania wstępne", + "b": "", + "c": "", + "d": "", + "e": "s.184-191", + "f": "ryc., tab., bibliogr., streszcz.", + }, + ), + ( + "#102$ #a$ Autologous hematopoietic stem cell transplant for progressive" + " diffuse systemic sclerosis: procedural " + "success and clinical outcome in 5-year follow-up #b$ #c$ #d$", + { + "id": 102, + "a": "Autologous hematopoietic stem cell transplant for progressive diffuse systemic sclerosis: " + "procedural success and clinical outcome in 5-year follow-up", + "b": "", + "c": "", + "d": "", + }, + ), + ( + "#150$ #a$ Badania nad wpływem selenu na integralność chromatyny plemnikowej w przypadkach mężczyzn z " + "upośledzoną płodnością #b$ #c$ #d$", + { + "id": 150, + "a": "Badania nad wpływem selenu na integralność chromatyny plemnikowej w przypadkach mężczyzn " + "z upośledzoną płodnością", + "b": "", + "c": "", + "d": "", + }, + ), + ( + "#150$ #a$Neurologia #c$Howard L. Weiner, Lawrence P. Levitt ", + {"id": 150, "a": "Neurologia", "c": "Howard L. Weiner, Lawrence P. Levitt"}, + ), + ( + "#103$#a$ Abstracts of the 5-th World Congress on Heart Failure - Mechanisms and Managment. " + "Washinfton, USA, 11-14 May, 1997", + { + "id": 103, + "a": "Abstracts of the 5-th World Congress on Heart Failure - Mechanisms and Managment. " + "Washinfton, USA, 11-14 May, 1997", + }, + ), + ], ) def test_util_exp_parse_str(input, output): assert exp_parse_str(input) == output def test_util_exp_parse_str_raises(): - bad = '#105$ Electrophysiological estimation of the peripheral nerves conduction parameters and the autonomic nervous system function in the course of amyotrophic lateral sclerosis #b$' + bad = ( + "#105$ Electrophysiological estimation of the peripheral nerves conduction " + "parameters and the autonomic nervous system function in the course" + " of amyotrophic lateral sclerosis #b$" + ) with pytest.raises(ValueError): exp_parse_str(bad) @@ -73,3 +95,7 @@ def test_util_exp_parse_str_raises(): def test_util_exp_add_spacing(): assert exp_add_spacing("te.st.this.(act.)") == "te. st. this. (act.)" + +def test_xls2dict(): + ret = list(xls2dict(os.path.join(os.path.dirname(__file__), "test_chf.xlsx"))) + assert ret[0]["skrot"] == "PAA" diff --git a/src/import_dbf/tests/~$test_chf.xlsx b/src/import_dbf/tests/~$test_chf.xlsx new file mode 100644 index 000000000..0481c82a2 Binary files /dev/null and b/src/import_dbf/tests/~$test_chf.xlsx differ diff --git a/src/import_dbf/util.py b/src/import_dbf/util.py index 169242f3c..dd2546430 100644 --- a/src/import_dbf/util.py +++ b/src/import_dbf/util.py @@ -1,7 +1,8 @@ import os +import sys from collections import defaultdict -import sys +import xlrd from dbfread import DBF from django.contrib.contenttypes.models import ContentType from django.db import IntegrityError, transaction @@ -10,14 +11,16 @@ from bpp import models as bpp from bpp.models import ( Status_Korekty, - wez_zakres_stron, - parse_informacje, Zewnetrzna_Baza_Danych, cache, + const, + parse_informacje, + wez_zakres_stron, ) from bpp.system import User from bpp.util import pbar from import_dbf import models as dbf + from .codecs import custom_search_function # noqa custom_search_function # noqa @@ -374,6 +377,41 @@ def integruj_charaktery(): rec.save() +def wzbogacaj_charaktery(fp): + for elem in xls2dict(fp): + ch = bpp.Charakter_Formalny.objects.get(skrot=elem["skrot"]) + save = False + + if elem["charakter_pbn"]: + ch.charakter_pbn = bpp.Charakter_PBN.objects.get(opis=elem["charakter_pbn"]) + save = True + + if elem["rodzaj_dla_pbn"]: + if elem["rodzaj_dla_pbn"] == "Książka": + ch.rodzaj_pbn = const.RODZAJ_PBN_KSIAZKA + elif elem["rodzaj_dla_pbn"] == "Artykuł": + ch.rodzaj_pbn = const.RODZAJ_PBN_ARTYKUL + elif elem["rodzaj_dla_pbn"] == "Rozdział": + ch.rodzaj_pbn = const.RODZAJ_PBN_ROZDZIAL + else: + raise ValueError("Niezdefiniowany rodzaj: %r" % elem["rodzaj_dla_pbn"]) + save = True + + if elem["charakter_dla_slotów"]: + if elem["charakter_dla_slotów"] == "Książka": + ch.charakter_sloty = const.CHARAKTER_SLOTY_KSIAZKA + elif elem["charakter_dla_slotów"] == "Rozdział": + ch.charakter_sloty = const.CHARAKTER_SLOTY_ROZDZIAL + else: + raise ValueError( + "Niezdefiniowany rozdaj: %r" % elem["charakter_dla_slotow"] + ) + save = True + + if save: + ch.save() + + def integruj_kbn(): for rec in dbf.Kbn.objects.all(): try: @@ -1045,7 +1083,7 @@ def integruj_publikacje(offset=None, limit=None): if elem.get("b"): s = dbf.Usi.objects.get(idt_usi=elem["b"][1:]) if s.nazwa == "CC": - s.nazwa = "CC-BY" + s.nazwa = "CC-ZERO" try: o = bpp.Licencja_OpenAccess.objects.get( @@ -1521,3 +1559,19 @@ def przypisz_jednostki(): bpp.Autor_Jednostka.objects.get_or_create( autor_id=elem[0], jednostka_id=elem[1] ) + + +def xls2dict(fp): + """Wczytuje plik XLS do słownika. Tylko pierwszy skoroszyt. + Pierwszy wiersz w pliku XLS to nazwy kolumn, które będą użyte + w zwracanych słownikach.""" + + book = xlrd.open_workbook(fp) + first_sheet = book.sheet_by_index(0) + headers = [x.lower().replace(" ", "_") for x in first_sheet.row_values(0)] + + for row_idx in range(1, first_sheet.nrows): + ret = {} + for col_idx, header in zip(range(0, first_sheet.ncols), headers): + ret[header] = first_sheet.cell(row_idx, col_idx).value + yield ret