Source code for derrida.books.management.commands.export_zotero

'''
Manage command to export references cited in all Derrida works to a group
Zotero library. Each work is assumed to correspond to a collection in the
Zotero library.

Command assumes that the target Zotero library ID and a Zotero API key are
populated in local_settings.py.
'''
from collections import defaultdict
from itertools import islice

import progressbar
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.db.models import QuerySet
from pyzotero import zotero

from derrida.books.models import DerridaWork, Instance


[docs]class Command(BaseCommand): '''Export all references in all Derrida works to a Zotero library.''' help = __doc__ library = None #: normal verbosity level v_normal = 1 #: output verbosity verbosity = v_normal #: number of items to send to Zotero API per request; Zotero only allows 50 chunk_size = 50 # NOTE: options to validate (using pyzotero check_items) and run in # no-act mode might be useful
[docs] def handle(self, *args, **kwargs): # check for secrets if not getattr(settings, 'ZOTERO_API_KEY', None): raise CommandError('Zotero API key must be set.') if not getattr(settings, 'ZOTERO_LIBRARY_ID', None): raise CommandError('Zotero library ID must be set.') self.verbosity = kwargs.get('verbosity', self.v_normal) # initialize the library self.library = zotero.Zotero(settings.ZOTERO_LIBRARY_ID, 'group', settings.ZOTERO_API_KEY) # create new collections for any derrida works with a zotero id self.create_collections(DerridaWork.objects.filter(zotero_id='')) # create/update all items cited in a derrida work stats = self.create_items(Instance.objects.filter(cited_in__isnull=False)) summary = '\nExport complete. \n\ Created {created:,d}; updated {updated:,d}; unchanged {unchanged:,d}; failed {failed:,d}.'.format(**stats) self.stdout.write(summary)
[docs] def create_collections(self, works: QuerySet): ''' create zotero collections for the provided derrida works and store the generated zotero collection id on the work ''' new = works.count() if new > 0: self.stdout.write('Found {} new Derrida work{}.'.format( new, '' if new == 1 else 's')) res = self.library.create_collections([{'name': work.short_title} for work in works]) # zotero returns a dict with index (as string) as key and collection id as value for index, value in res['success'].items(): works[int(index)].zotero_id = value works[int(index)].save() else: if self.verbosity > self.v_normal: self.stdout.write('No collections to create.')
[docs] def create_items(self, instances: QuerySet): ''' ensure all instances cited in derrida works are represented as items: create new items where no zotero id exists and update those that already have an id ''' total = instances.count() stats = defaultdict(int) # nothing to do; bail out if not total: if self.verbosity > self.v_normal: self.stdout.write('No items to create.') return self.stdout.write('Exporting {} instances.'.format(total)) progbar = progressbar.ProgressBar(redirect_stdout=True, max_value=total) instances = instances.iterator() # store initial count to determine how many are added initial_count = self.library.count_items() count = 0 # iterate over the queryset in chunks, since Zotero API # only allows sending in sets of 50 chunk = list(islice(instances, self.chunk_size)) while chunk: # using create items with existing zotero id to update requires a # last modified; get last modified version of the library. # NOTE: must be done for each chunk last_mod = self.library.last_modified_version() # convert the instances to zotero items items = [instance.as_zotero_item(self.library) for instance in chunk] res = self.library.create_items(items, last_modified=last_mod) progbar.update(count) stats['updated'] += len(res['success']) stats['unchanged'] += len(res['unchanged']) stats['failed'] += len(res['failed']) # report any failures if res['failed']: for index, error in res['failed'].items(): self.stderr.write('\nError on %s: %s' % \ (chunk[int(index)], error['message'])) count += self.chunk_size # save newly generated zotero ids to the items in the database for index, value in res['success'].items(): chunk[int(index)].zotero_id = value chunk[int(index)].save() # get the next chunk of items chunk = list(islice(instances, self.chunk_size)) progbar.finish() # Determine number of items newly created based on library count. stats['created'] = self.library.count_items() - initial_count # TODO: success includes created; subtract created from updated? return stats