More data in catalogue
[redakcja.git] / src / catalogue / wikidata.py
1 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 from datetime import date
5 from django.db import models
6 from django.db.models.signals import m2m_changed
7 from django.utils.html import format_html
8 from django.utils.translation import gettext_lazy as _
9 from django.dispatch import receiver
10 from wikidata.client import Client
11 from wikidata.datavalue import DatavalueError
12
13
14 class WikidataMixin(models.Model):
15     wikidata = models.CharField(
16         max_length=255,
17         blank=True,
18         help_text=_('If you have a Wikidata ID, like "Q1337", enter it and save.'),
19     )
20
21     class Meta:
22         abstract = True
23
24     def wikidata_populate(self, client, entity, attname, wd):
25         model_field = self._meta.get_field(attname)
26         if isinstance(model_field, models.ManyToManyField):
27             if getattr(self, attname).all().exists():
28                 return
29         else:
30             if getattr(self, attname):
31                 return
32
33         wdvalue = None
34         if wd == "description":
35             wdvalue = entity.description.get("pl", str(entity.description))
36         elif wd == "label":
37             wdvalue = entity.label.get("pl", str(entity.label))
38         else:
39             try:
40                 wdvalue = entity.get(client.get(wd))
41             except DatavalueError:
42                 pass
43
44         self.set_field_from_wikidata(attname, wdvalue)
45         
46     def save(self, **kwargs):
47         super().save()
48         if self.wikidata and hasattr(self, "Wikidata"):
49             Wikidata = type(self).Wikidata
50             client = Client()
51             # Probably should getlist
52             entity = client.get(self.wikidata)
53             for attname in dir(Wikidata):
54                 if attname.startswith("_"):
55                     continue
56                 wd = getattr(Wikidata, attname)
57
58                 self.wikidata_populate(client, entity, attname, wd)
59             if hasattr(Wikidata, '_supplement'):
60                 for attname, wd in Wikidata._supplement(self):
61                     self.wikidata_populate(client, entity, attname, wd)
62
63
64         kwargs.update(force_insert=False, force_update=True)
65         super().save(**kwargs)
66
67     def set_field_from_wikidata(self, attname, wdvalue):
68         if not wdvalue:
69             return
70         # Find out what this model field is
71         model_field = self._meta.get_field(attname)
72         if isinstance(model_field, models.ForeignKey):
73             rel_model = model_field.related_model
74             if issubclass(rel_model, WikidataMixin):
75                 # welp, we can try and find by WD identifier.
76                 wdvalue, created = rel_model.objects.get_or_create(wikidata=wdvalue.id)
77                 setattr(self, attname, wdvalue)
78         elif isinstance(model_field, models.ManyToManyField):
79             rel_model = model_field.related_model
80             if issubclass(rel_model, WikidataMixin):
81                 wdvalue, created = rel_model.objects.get_or_create(wikidata=wdvalue.id)
82                 getattr(self, attname).set([wdvalue])
83         else:
84             # How to get original title?
85             if isinstance(wdvalue, date):
86                 if isinstance(model_field, models.IntegerField):
87                     wdvalue = wdvalue.year
88             elif not isinstance(wdvalue, str):
89                 wdvalue = wdvalue.label.get("pl", str(wdvalue.label))
90             setattr(self, attname, wdvalue)
91
92     def wikidata_link(self):
93         if self.wikidata:
94             return format_html(
95                 '<a href="https://www.wikidata.org/wiki/{wd}" target="_blank">{wd}</a>',
96                 wd=self.wikidata,
97             )
98         else:
99             return ""
100
101     wikidata_link.admin_order_field = "wikidata"
102
103
104 class WikidataAdminMixin:
105     def save_related(self, request, form, formsets, change):
106         super().save_related(request, form, formsets, change)
107         form.instance.save()