From 8c260d3f2788128e9cc9fdb72e2d8ac877e54c17 Mon Sep 17 00:00:00 2001 From: olivier <> Date: Mon, 6 Apr 2009 16:10:30 +0000 Subject: [PATCH] #67: couple OAI-PMH subsystem with models and web view --- telemeta/interop/oaidatasource.py | 67 +++++++++++++++++++++++++++++++ telemeta/models/dublincore.py | 8 ++++ telemeta/models/media.py | 14 ++++++- telemeta/models/query.py | 21 ++++++++++ telemeta/urls.py | 3 ++ telemeta/web/base.py | 11 +++++ 6 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 telemeta/interop/oaidatasource.py diff --git a/telemeta/interop/oaidatasource.py b/telemeta/interop/oaidatasource.py new file mode 100644 index 00000000..be9bd139 --- /dev/null +++ b/telemeta/interop/oaidatasource.py @@ -0,0 +1,67 @@ +from telemeta.models import MediaCollection, MediaItem, Revision +from datetime import datetime + +class TelemetaOAIDataSource(object): + """Telemeta OAI datasource adapter. This class implements the oai.IDataSource interface.""" + + def get_earliest_time(self): + """Return the change time of the oldest record(s) as a datetime object""" + try: + rev = Revision.objects.order_by('time')[0] + return rev.time + except IndexError: + return datetime.now() + + def prepare_record(self, type, record): + ctime = record.get_revision().time + dc = [] + _dc = record.to_dublincore().to_list() + for k, v in _dc: + if k == 'identifier': + dc.append((k, type + ':' + v)) + else: + dc.append((k, v)) + return (dc, ctime) + + def get_record(self, id): + """Return a specific record""" + type, id = id.split(':') + if (type == 'collection'): + record = MediaCollection.objects.get(id=id) + elif (type == 'item'): + record = MediaItem.objects.get(id=id) + else: + raise Exception("No such record type: %s" % type) + + return self.prepare_record(type, record) + + + def count_records(self, from_time = None, until_time = None): + """Must return the number of records between (optional) from and until change time.""" + nitems = MediaItem.objects.by_change_time(from_time, until_time).count() + ncolls = MediaCollection.objects.by_change_time(from_time, until_time).count() + return nitems + ncolls + + def list_records(self, offset, limit, from_time = None, until_time = None): + """Return a list of records""" + + result = [] + + query = MediaItem.objects.by_change_time(from_time, until_time) + nitems = query.count() + if (offset < nitems): + set = query[offset:offset + limit] + for record in set: + result.append(self.prepare_record('item', record)) + limit -= len(set) + offset = 0 + else: + offset -= nitems + + if limit > 0: + query = MediaCollection.objects.by_change_time(from_time, until_time) + set = query[offset:offset + limit] + for record in set: + result.append(self.prepare_record('collection', record)) + + return result diff --git a/telemeta/models/dublincore.py b/telemeta/models/dublincore.py index e3062d34..ef00030d 100644 --- a/telemeta/models/dublincore.py +++ b/telemeta/models/dublincore.py @@ -30,6 +30,14 @@ class Resource(object): result[element.name] = unicode(element.value) return result + def to_list(self): + """Convert the resource to unqualified dublin core, as a list of the form: + [(key, value), ...]""" + result = [] + for element in self.elements: + result.append((element.name, unicode(element.value))) + return result + class Element(object): "Represent a Dublin Core element" diff --git a/telemeta/models/media.py b/telemeta/models/media.py index 2b50c355..1a818cdb 100644 --- a/telemeta/models/media.py +++ b/telemeta/models/media.py @@ -191,6 +191,9 @@ class MediaCollection(Model, MediaCore): super(MediaCollection, self).save(force_insert, force_update) Revision(element_type='collection', element_id=self.id).touch() + def get_revision(self): + return Revision.objects.filter(element_type='collection', element_id=self.id).order_by('-time')[0] + class Meta: app_label = 'telemeta' ordering = ['title'] @@ -280,6 +283,9 @@ class MediaItem(Model, MediaCore): return duration + def get_revision(self): + return Revision.objects.filter(element_type='item', element_id=self.id).order_by('-time')[0] + def __unicode__(self): return self.title @@ -326,6 +332,9 @@ class MediaPart(Model, MediaCore): super(MediaPart, self).save(force_insert, force_update) Revision(element_type='part', element_id=self.id).touch() + def get_revision(self): + return Revision.objects.filter(element_type='part', element_id=self.id).order_by('-time')[0] + class Meta: app_label = 'telemeta' ordering = ['title'] @@ -336,8 +345,9 @@ class Revision(Model): element_type = CharField(max_length=16, choices=(('collection', 'collection'), ('item', 'item'), ('part', 'part'))) - element_id = CharField(max_length=250) - change_type = CharField(max_length=8, choices= (('create', 'create'), + element_id = CharField(max_length=250, db_index=True) + change_type = CharField(max_length=8, choices= (('import', 'import'), + ('create', 'create'), ('update', 'update'), ('delete', 'delete'))) time = DateTimeField(auto_now_add=True) diff --git a/telemeta/models/query.py b/telemeta/models/query.py index fa589d74..397b9098 100644 --- a/telemeta/models/query.py +++ b/telemeta/models/query.py @@ -30,6 +30,15 @@ class CoreQuerySet(QuerySet): kwargs = {field + '__iregex': regex} return self.filter(**kwargs) + def _by_change_time(self, type, from_time = None, until_time = None): + where = ["element_type = '%s'" % type] + if from_time: + where.append("time >= '%s'" % from_time.strftime('%Y-%m-%d %H:%M:%S')) + if until_time: + where.append("time <= '%s'" % until_time.strftime('%Y-%m-%d %H:%M:%S')) + return self.extra( + where = ["id IN (SELECT DISTINCT element_id FROM telemeta_revision WHERE %s)" % " AND ".join(where)]); + class CoreManager(Manager): "Base class for all models managers" @@ -64,6 +73,9 @@ class MediaCollectionQuerySet(CoreQuerySet): def by_ethnic_group(self, group): return self.filter(items__ethnie_grsocial=group).distinct() + def by_change_time(self, from_time = None, until_time = None): + return self._by_change_time('collection', from_time, until_time) + class MediaCollectionManager(CoreManager): "Manage collection queries" @@ -88,6 +100,9 @@ class MediaCollectionManager(CoreManager): def by_ethnic_group(self, *args, **kwargs): return self.get_query_set().by_ethnic_group(*args, **kwargs) + def by_change_time(self, *args, **kwargs): + return self.get_query_set().by_change_time(*args, **kwargs) + def stat_continents(self, order_by='num'): "Return the number of collections by continents and countries as a tree" from django.db import connection @@ -170,6 +185,9 @@ class MediaItemQuerySet(CoreQuerySet): def by_publish_date(self, pattern): return self.filter(collection__date_published__icontains=pattern) + + def by_change_time(self, from_time = None, until_time = None): + return self._by_change_time('item', from_time, until_time) class MediaItemManager(CoreManager): "Manage media items queries" @@ -192,6 +210,9 @@ class MediaItemManager(CoreManager): def by_publish_date(self, *args, **kwargs): return self.get_query_set().by_publish_date(*args, **kwargs) + def by_change_time(self, *args, **kwargs): + return self.get_query_set().by_change_time(*args, **kwargs) + def list_ethnic_groups(self): "Return a list of all ethnic groups" diff --git a/telemeta/urls.py b/telemeta/urls.py index d54fa435..7972430d 100644 --- a/telemeta/urls.py +++ b/telemeta/urls.py @@ -135,4 +135,7 @@ urlpatterns = patterns('', url(r'^timeside/(?P.*)$', 'django.views.static.serve', {'document_root': htdocs+'/timeside'}, name="telemeta-timeside"), + + # OAI-PMH Data Provider + url(r'^oai/.*$', web_view.handle_oai_request, name="telemeta-oai") ) diff --git a/telemeta/web/base.py b/telemeta/web/base.py index 1371a6dd..f0b0bf53 100644 --- a/telemeta/web/base.py +++ b/telemeta/web/base.py @@ -27,6 +27,8 @@ from telemeta.export import * from telemeta.visualization import * from telemeta.analysis import * from telemeta.analysis.vamp import * +import telemeta.interop.oai as oai +from telemeta.interop.oaidatasource import TelemetaOAIDataSource class WebView(Component): """Provide web UI methods""" @@ -323,3 +325,12 @@ class WebView(Component): template_name='telemeta/geo_country_collections.html', paginate_by=20, extra_context={'country': country, 'continent': continent}) + def handle_oai_request(self, request): + url = request.META['HTTP_HOST'] + request.path + datasource = TelemetaOAIDataSource() + provider = oai.DataProvider(datasource, "Telemeta", url, "admin@telemeta.org") + args = request.GET.copy() + args.update(request.POST) + return HttpResponse(provider.handle(args), mimetype='text/xml') + + -- 2.39.5