3ec843a7f45bceed29610ab0ce2534a128ac77f7
[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.conf import settings
6 from django.db import models
7 from django.db.models.signals import m2m_changed
8 from django.utils.html import format_html
9 from django.utils.translation import gettext_lazy as _
10 from django.dispatch import receiver
11 from wikidata.client import Client
12 from wikidata.datavalue import DatavalueError
13 from modeltranslation.translator import translator
14 from modeltranslation.settings import AVAILABLE_LANGUAGES
15
16
17 class WikidataModel(models.Model):
18     wikidata = models.CharField(
19         max_length=255,
20         blank=True,
21         help_text=_('If you have a Wikidata ID, like "Q1337", enter it and save.'),
22     )
23
24     class Meta:
25         abstract = True
26         
27     def wikidata_populate_field(self, client, entity, attname, wd, save, lang):
28         model_field = self._meta.get_field(attname)
29         if isinstance(model_field, models.ManyToManyField):
30             if getattr(self, attname).all().exists():
31                 return
32         else:
33             if getattr(self, attname):
34                 return
35
36         wdvalue = None
37         if wd == "description":
38             wdvalue = entity.description.get(lang, str(entity.description))
39         elif wd == "label":
40             wdvalue = entity.label.get(lang, str(entity.label))
41         else:
42             try:
43                 # TODO: lang?
44                 wdvalue = entity.get(client.get(wd))
45             except DatavalueError:
46                 pass
47
48         self.set_field_from_wikidata(attname, wdvalue, save=save)
49
50     def wikidata_populate(self, save=True):
51         Wikidata = type(self).Wikidata
52         client = Client()
53         # Probably should getlist
54         entity = client.get(self.wikidata)
55         for attname in dir(Wikidata):
56             if attname.startswith("_"):
57                 continue
58             wd = getattr(Wikidata, attname)
59
60             self.wikidata_populate_attribute(client, entity, attname, wd, save=save)
61         if hasattr(Wikidata, '_supplement'):
62             for attname, wd in Wikidata._supplement(self):
63                 self.wikidata_populate_attribute(client, entity, attname, wd, save=save)
64
65     def wikidata_fields_for_attribute(self, attname):
66         field = getattr(type(self), attname)
67         if type(self) in translator._registry:
68             try:
69                 opts = translator.get_options_for_model(type(self))
70             except:
71                 pass
72             else:
73                 if attname in opts.fields:
74                     tfields = opts.fields[attname]
75                     for tf in tfields:
76                         yield tf.name, tf.language
77                     return
78
79         yield attname, settings.LANGUAGE_CODE
80
81     def wikidata_populate_attribute(self, client, entity, attname, wd, save):
82         for fieldname, lang in self.wikidata_fields_for_attribute(attname):
83             self.wikidata_populate_field(client, entity, fieldname, wd, save, lang)
84                 
85     def save(self, **kwargs):
86         am_new = self.pk is None
87
88         super().save()
89         if am_new and self.wikidata and hasattr(self, "Wikidata"):
90             self.wikidata_populate()
91
92         kwargs.update(force_insert=False, force_update=True)
93         super().save(**kwargs)
94
95     def set_field_from_wikidata(self, attname, wdvalue, save, language='pl'):
96         if not wdvalue:
97             return
98         # Find out what this model field is
99         model_field = self._meta.get_field(attname)
100         if isinstance(model_field, models.ForeignKey):
101             rel_model = model_field.related_model
102             if issubclass(rel_model, WikidataModel):
103                 label = wdvalue.label.get(language, str(wdvalue.label))
104                 try:
105                     wdvalue = rel_model.objects.get(wikidata=wdvalue.id)
106                 except rel_model.DoesNotExist:
107                     wdvalue = rel_model(wikidata=wdvalue.id)
108                     if save:
109                         wdvalue.save()
110                 wdvalue._wikidata_label = label
111                 setattr(self, attname, wdvalue)
112         elif isinstance(model_field, models.ManyToManyField):
113             rel_model = model_field.related_model
114             if issubclass(rel_model, WikidataModel):
115                 label = wdvalue.label.get(language, str(wdvalue.label))
116                 try:
117                     wdvalue = rel_model.objects.get(wikidata=wdvalue.id)
118                 except rel_model.DoesNotExist:
119                     wdvalue = rel_model(wikidata=wdvalue.id)
120                     if save:
121                         wdvalue.save()
122                 wdvalue._wikidata_label = label
123                 getattr(self, attname).set([wdvalue])
124         else:
125             # How to get original title?
126             if isinstance(wdvalue, date):
127                 if isinstance(model_field, models.IntegerField):
128                     wdvalue = wdvalue.year
129             elif not isinstance(wdvalue, str):
130                 wdvalue = wdvalue.label.get(language, str(wdvalue.label))
131             setattr(self, attname, wdvalue)
132
133     def wikidata_link(self):
134         if self.wikidata:
135             return format_html(
136                 '<a href="https://www.wikidata.org/wiki/{wd}" target="_blank">{wd}</a>',
137                 wd=self.wikidata,
138             )
139         else:
140             return ""
141
142     wikidata_link.admin_order_field = "wikidata"
143
144
145 class WikidataAdminMixin:
146     class Media:
147         css = {"screen": ("catalogue/wikidata_admin.css",)}
148         js = ("catalogue/wikidata_admin.js",)
149
150     def save_related(self, request, form, formsets, change):
151         super().save_related(request, form, formsets, change)
152         form.instance.save()