httplib2 # oauth2 dependency
python-slugify
python-docx==0.8.10
+Wikidata==0.6.1
librarian==1.8.1
from django.contrib import admin
from . import models
+from .wikidata import WikidataAdminMixin
+class AuthorAdmin(WikidataAdminMixin, admin.ModelAdmin):
+ list_display = "first_name", "last_name", "notes"
+ search_fields = ["first_name", "last_name", "wikidata"]
+ prepopulated_fields = {"slug": ("first_name", "last_name")}
-class AuthorAdmin(admin.ModelAdmin):
- search_fields = ['name']
admin.site.register(models.Author, AuthorAdmin)
-class BookAdmin(admin.ModelAdmin):
- raw_id_fields = ['authors']
- autocomplete_fields = ['translators']
+class BookAdmin(WikidataAdminMixin, admin.ModelAdmin):
+ list_display = "title", "notes"
+ autocomplete_fields = ["authors", "translators"]
+ prepopulated_fields = {"slug": ("title",)}
-admin.site.register(models.Book, BookAdmin)
+admin.site.register(models.Book, BookAdmin)
--- /dev/null
+class WIKIDATA:
+ AUTHOR = "P50"
+ LANGUAGE = "P407"
+ DATE_OF_DEATH = "P570"
+ LAST_NAME = "P734"
+ GIVEN_NAME = "P735"
+ TRANSLATOR = "P655"
+ BASED_ON = "P629"
+ TITLE = "P1476"
--- /dev/null
+# Generated by Django 3.0.4 on 2020-04-10 17:41
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0007_auto_20200322_2326'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='author',
+ old_name='name',
+ new_name='last_name',
+ ),
+ migrations.RemoveField(
+ model_name='book',
+ name='translator',
+ ),
+ migrations.RemoveField(
+ model_name='book',
+ name='uri',
+ ),
+ migrations.AddField(
+ model_name='author',
+ name='first_name',
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ migrations.AddField(
+ model_name='author',
+ name='notes',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='author',
+ name='priority',
+ field=models.PositiveSmallIntegerField(choices=[(0, 'Low'), (1, 'Medium'), (2, 'High')], default=0),
+ ),
+ migrations.AddField(
+ model_name='author',
+ name='slug',
+ field=models.SlugField(blank=True, null=True, unique=True),
+ ),
+ migrations.AddField(
+ model_name='author',
+ name='wikidata',
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ migrations.AddField(
+ model_name='book',
+ name='slug',
+ field=models.SlugField(blank=True, max_length=255, null=True, unique=True),
+ ),
+ migrations.AddField(
+ model_name='book',
+ name='translators',
+ field=models.ManyToManyField(blank=True, related_name='translated_book_set', related_query_name='translated_book', to='catalogue.Author'),
+ ),
+ migrations.AddField(
+ model_name='book',
+ name='wikidata',
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ migrations.AlterField(
+ model_name='book',
+ name='authors',
+ field=models.ManyToManyField(blank=True, to='catalogue.Author'),
+ ),
+ ]
--- /dev/null
+# Generated by Django 3.0.4 on 2020-04-11 11:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('catalogue', '0008_auto_20200410_1741'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='author',
+ name='last_name',
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ migrations.AlterField(
+ model_name='author',
+ name='wikidata',
+ field=models.CharField(blank=True, help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255, null=True, unique=True),
+ ),
+ migrations.AlterField(
+ model_name='book',
+ name='language',
+ field=models.CharField(blank=True, max_length=3),
+ ),
+ migrations.AlterField(
+ model_name='book',
+ name='title',
+ field=models.CharField(blank=True, max_length=255),
+ ),
+ migrations.AlterField(
+ model_name='book',
+ name='wikidata',
+ field=models.CharField(blank=True, help_text='If you have a Wikidata ID, like "Q1337", enter it and save.', max_length=255, null=True, unique=True),
+ ),
+ ]
from django.db import models
from django.utils.translation import gettext_lazy as _
+from .constants import WIKIDATA
+from .wikidata import WikidataMixin
-class Author(models.Model):
- name = models.CharField(max_length=255)
+class Author(WikidataMixin, models.Model):
+ slug = models.SlugField(null=True, blank=True, unique=True)
+ first_name = models.CharField(max_length=255, blank=True)
+ last_name = models.CharField(max_length=255, blank=True)
year_of_death = models.SmallIntegerField(null=True, blank=True)
- status = models.PositiveSmallIntegerField(null=True, blank=True, choices=[
- (1, _('Alive')),
- (2, _('Dead')),
- (3, _('Long dead')),
- (4, _('Unknown')),
- ])
+ status = models.PositiveSmallIntegerField(
+ null=True,
+ blank=True,
+ choices=[
+ (1, _("Alive")),
+ (2, _("Dead")),
+ (3, _("Long dead")),
+ (4, _("Unknown")),
+ ],
+ )
+ notes = models.TextField(blank=True)
+ priority = models.PositiveSmallIntegerField(
+ default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))]
+ )
- def __str__(self):
- return self.name
+ class Wikidata:
+ first_name = WIKIDATA.GIVEN_NAME
+ last_name = WIKIDATA.LAST_NAME
+ year_of_death = WIKIDATA.DATE_OF_DEATH
+ notes = "description"
+ def __str__(self):
+ return f"{self.first_name} {self.last_name}"
-class Book(models.Model):
- uri = models.CharField(max_length=255)
+class Book(WikidataMixin, models.Model):
+ slug = models.SlugField(max_length=255, blank=True, null=True, unique=True)
authors = models.ManyToManyField(Author, blank=True)
- translators = models.ManyToManyField(Author, related_name='translated_book_set', related_query_name='translated_book', blank=True)
- title = models.CharField(max_length=255)
- language = models.CharField(max_length=3)
- based_on = models.ForeignKey('self', models.PROTECT, related_name='translation', null=True, blank=True)
-
+ translators = models.ManyToManyField(
+ Author,
+ related_name="translated_book_set",
+ related_query_name="translated_book",
+ blank=True,
+ )
+ title = models.CharField(max_length=255, blank=True)
+ language = models.CharField(max_length=3, blank=True)
+ based_on = models.ForeignKey(
+ "self", models.PROTECT, related_name="translation", null=True, blank=True
+ )
scans_source = models.CharField(max_length=255, blank=True)
text_source = models.CharField(max_length=255, blank=True)
notes = models.TextField(blank=True)
- priority = models.PositiveSmallIntegerField(default=0, choices=[
- (0, _('Low')),
- (1, _('Medium')),
- (2, _('High')),
- ])
+ priority = models.PositiveSmallIntegerField(
+ default=0, choices=[(0, _("Low")), (1, _("Medium")), (2, _("High"))]
+ )
+
+ class Wikidata:
+ authors = WIKIDATA.AUTHOR
+ translators = WIKIDATA.TRANSLATOR
+ title = WIKIDATA.TITLE
+ language = WIKIDATA.LANGUAGE
+ based_on = WIKIDATA.BASED_ON
+ notes = "description"
def __str__(self):
return self.title
--- /dev/null
+from datetime import date
+from django.db import models
+from django.db.models.signals import m2m_changed
+from django.utils.translation import gettext_lazy as _
+from django.dispatch import receiver
+from wikidata.client import Client
+from wikidata.datavalue import DatavalueError
+
+
+class WikidataMixin(models.Model):
+ wikidata = models.CharField(
+ max_length=255,
+ null=True,
+ blank=True,
+ unique=True,
+ help_text=_('If you have a Wikidata ID, like "Q1337", enter it and save.'),
+ )
+
+ class Meta:
+ abstract = True
+
+ def save(self, **kwargs):
+ super().save()
+ if self.wikidata and hasattr(self, "Wikidata"):
+ Wikidata = type(self).Wikidata
+ client = Client()
+ # Probably should getlist
+ entity = client.get(self.wikidata)
+ for attname in dir(Wikidata):
+ if attname.startswith("_"):
+ continue
+ wd = getattr(Wikidata, attname)
+
+ model_field = self._meta.get_field(attname)
+ if isinstance(model_field, models.ManyToManyField):
+ if getattr(self, attname).all().exists():
+ continue
+ else:
+ if getattr(self, attname):
+ continue
+
+ wdvalue = None
+ if wd == "description":
+ wdvalue = entity.description.get("pl", str(entity.description))
+ elif wd == "label":
+ wdvalue = entity.label.get("pl", str(entity.label))
+ else:
+ try:
+ wdvalue = entity.get(client.get(wd))
+ except DatavalueError:
+ pass
+
+ self.set_field_from_wikidata(attname, wdvalue)
+
+ kwargs.update(force_insert=False, force_update=True)
+ super().save(**kwargs)
+
+ def set_field_from_wikidata(self, attname, wdvalue):
+ if not wdvalue:
+ return
+ # Find out what this model field is
+ model_field = self._meta.get_field(attname)
+ if isinstance(model_field, models.ForeignKey):
+ rel_model = model_field.related_model
+ if issubclass(rel_model, WikidataMixin):
+ # welp, we can try and find by WD identifier.
+ wdvalue, created = rel_model.objects.get_or_create(wikidata=wdvalue.id)
+ setattr(self, attname, wdvalue)
+ elif isinstance(model_field, models.ManyToManyField):
+ rel_model = model_field.related_model
+ if issubclass(rel_model, WikidataMixin):
+ wdvalue, created = rel_model.objects.get_or_create(wikidata=wdvalue.id)
+ getattr(self, attname).set([wdvalue])
+ else:
+ # How to get original title?
+ if isinstance(wdvalue, date):
+ if isinstance(model_field, models.IntegerField):
+ wdvalue = wdvalue.year
+ elif not isinstance(wdvalue, str):
+ wdvalue = wdvalue.label.get("pl", str(wdvalue.label))
+ setattr(self, attname, wdvalue)
+
+
+class WikidataAdminMixin:
+ def save_related(self, request, form, formsets, change):
+ super().save_related(request, form, formsets, change)
+ form.instance.save()