]> git.parisson.com Git - telemeta.git/commitdiff
- add dublin core modelization and new to_dublincore() model methods
authorolivier <>
Wed, 16 May 2007 14:47:42 +0000 (14:47 +0000)
committerolivier <>
Wed, 16 May 2007 14:47:42 +0000 (14:47 +0000)
- improved dublin core mapping
- add dublin core HTML-based views of collections and items
- fixed URL handling with non aplhanumeric record IDs
- fix #21: the web view now properly handle export streams
- turn the model list() method into the tolist template filter
- new submenu template block + css fixes

telemeta/dublincore.py [new file with mode: 0644]
telemeta/htdocs/css/telemeta.css
telemeta/models.py
telemeta/templates/base.html
telemeta/templates/collection_detail.html
telemeta/templates/collection_detail_dc.html [new file with mode: 0644]
telemeta/templates/mediaitem_detail.html
telemeta/templates/mediaitem_detail_dc.html [new file with mode: 0644]
telemeta/templatetags/telemeta_utils.py
telemeta/urls.py
telemeta/views/web.py

diff --git a/telemeta/dublincore.py b/telemeta/dublincore.py
new file mode 100644 (file)
index 0000000..2f87195
--- /dev/null
@@ -0,0 +1,37 @@
+# Copyright (C) 2007 Samalyse SARL
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://svn.parisson.org/telemeta/TelemetaLicense.
+#
+# Author: Olivier Guilyardi <olivier@samalyse.com>
+
+class Resource(object):
+    "Represent a Dublin Core resource"
+    def __init__(self, *args):
+        self.elements = args  
+
+    def flatten(self):
+        """Convert the resource to a dictionary with element names as keys.
+
+        Warnings: 
+        - refinements are lost during this conversion,
+        - if there are several occurences of the same element, only the first is 
+        used, others are lost.
+        - all values are converted to strings
+        """
+        result = {}
+        for element in self.elements:
+            if not result.has_key(element.name):
+                result[element.name] = element.value
+        return result
+
+class Element(object):
+    "Represent a Dublin Core element"
+    def __init__(self, name, field=None, value=None, refinement=None):
+        self.name = name
+        self.value = value
+        self.refinement = refinement
+        self.field = field
+        
index 04dc30e23d26ed6c168a69797f719fe4eb5a608f..b2e398295e02fb5c123c06c5aa8df265081e3994 100644 (file)
@@ -3,49 +3,42 @@ body {
     font-size: 80%;
 }
 
+a:link, a:visited {
+    border-bottom:1px dotted #BBBBBB;
+    color:#BB0000;
+    text-decoration:none;
+}
+
+a:link:hover, a:visited:hover {
+    background-color:#EEEEEE;
+    color:#555555;
+}
+
 #header {
-    font-size: 140%;
     border-bottom: solid 1px black;
-    font-weight: bold;
-    padding-bottom: 0.8em;
+    padding-bottom: 0.2em;
     padding-top: 0.2em;
 }
 #header a {
-    color: black;
     text-decoration: none;
 }
 #header a img {
     border: none;
 }
 
-/*
-label {
-    width: 20ex;
-    display: block;
-    float: left;
-}
-input {
-    margin-bottom: 1ex;
-}
-*/
 
 #menu {
     text-align: right;
     clear: right;
 }
 
-a:link, a:visited {
-    border-bottom:1px dotted #BBBBBB;
-    color:#BB0000;
-    text-decoration:none;
-}
-
-a:link:hover, a:visited:hover {
-    background-color:#EEEEEE;
-    color:#555555;
+#submenu {
+    clear: right;
+    float: right;
+    padding-top: 1em;
 }
-
-#menu a {
+#submenu a {
+    color: #000066;
 }
 
 #quick_search {
@@ -165,3 +158,9 @@ label.disabled { color: #d7d7d7 }
  padding: 0.1em;
  background: none;
 }
+
+/* HTML dublin core display */
+
+table.dublincore {
+    width: auto;
+}
index eb1b2c58e4e15a558c1f08b1e3135e5d52b42c2e..067dc2ce00daaf63abcb955453fb993537a7ad3e 100644 (file)
@@ -12,23 +12,33 @@ from django.db import models
 from django.db.models import Q
 from telemeta.core import *
 from django.core.exceptions import ObjectDoesNotExist
+from telemeta import dublincore as dc
 
 class MediaModel(Component):
     pass
 
 class MediaCore:
-    def list(self):
-        fields_list = []
-        for field in self._meta.fields:
-            fields_list.append({'name': field.verbose_name, 'value': getattr(self, field.name)})
-        return fields_list
-
-    def to_dict(self):        
+    def to_dict(self):  
+        "Return model fields as a dict of name/value pairs"
         fields_dict = {}
         for field in self._meta.fields:
             fields_dict[field.name] = getattr(self, field.name)
         return fields_dict
 
+#    def dc_elements(self):        
+#        """Return model fields mapped to Dublin Core elements, as a dict of 
+#        the form: {dc_element_name: [value1, value2, ....], ...}
+#        """
+#        fields_dict = {}
+#        for field in self._meta.fields:
+#            if (hasattr(field, 'dc_element')):
+#                if fields_dict.has_key(field.dc_element):
+#                    fields_dict[field.dc_element].append(getattr(self, field.name))
+#                else:
+#                    fields_dict[field.dc_element] = [getattr(self, field.name)]
+#                    
+#        return fields_dict
+
 class PhysicalFormat(models.Model):
     value = models.CharField(maxlength=250)
     is_dictionary = True
@@ -53,28 +63,19 @@ class MediaCollection(models.Model, MediaCore):
     physical_format = models.CharField(maxlength=250, blank=True)
     id = models.CharField(maxlength=250, primary_key=True, 
         verbose_name='identifier')
-    id.dc_element = 'identifier'
     title = models.CharField(maxlength=250)
-    title.dc_element = 'title'
     native_title = models.CharField(maxlength=250, blank=True)
-    native_title.dc_element = 'title'
     physical_items_num = models.CharField(maxlength=250, blank=True) 
     publishing_status = models.CharField(maxlength=250, blank=True)
     is_original = models.CharField(maxlength=250)
     is_full_copy = models.CharField(maxlength=250)
-    copied_from = models.ForeignKey('self', blank=True)
-    #copied_from[0].dc_element = 'relation'
+    copied_from = models.ForeignKey('self', null=True, blank=True)
     creator = models.CharField(maxlength=250)
-    creator.dc_element = 'creator'
     booklet_writer = models.CharField(maxlength=250, blank=True)
-    booklet_writer.dc_element = 'contributor'
     booklet_description = models.TextField(blank=True)
     collector = models.CharField(maxlength=250, blank=True)
-    collector.dc_element = 'contributor'
     publisher = models.CharField(maxlength=250, blank=True)
-    publisher.dc_element = 'publisher'
     date_published = models.CharField(maxlength=250, blank=True)
-    date_published.dc_element = 'date'
     publisher_collection = models.CharField(maxlength=250, blank=True)
     publisher_serial_id = models.CharField(maxlength=250, blank=True)
     ref_biblio = models.TextField(blank=True)
@@ -83,9 +84,7 @@ class MediaCollection(models.Model, MediaCore):
     record_author = models.CharField(maxlength=250, blank=True)
     record_writer = models.CharField(maxlength=250, blank=True)
     rights = models.CharField(maxlength=250, blank=True)
-    rights.dc_element = 'rights'
     annee_enr = models.CharField(maxlength=250, blank=True)
-    annee_enr.dc_element = 'date'
     terrain_ou_autre = models.CharField(maxlength=250, blank=True)
     duree_approx = models.CharField(maxlength=250, blank=True)
     tri_dibm = models.CharField(maxlength=250, blank=True)
@@ -97,24 +96,38 @@ class MediaCollection(models.Model, MediaCore):
     numerisation = models.CharField(maxlength=250, blank=True)
     champ36 = models.CharField(maxlength=250, blank=True)
      
-#    date = models.DateField()
-#    contributor = models.CharField(maxlength=250, blank=True)
-#    coverage = models.CharField(maxlength=250, blank=True)
-#    creator = models.CharField(maxlength=250, blank=True)
-#    description = models.CharField(maxlength=250, blank=True)
-#    format = models.CharField(maxlength=250, blank=True)
-#    identifier = models.CharField(maxlength=250, blank=True)
-#    language = models.CharField(maxlength=250, blank=True)
-#    publisher = models.CharField(maxlength=250, blank=True)
-#    rights = models.CharField(maxlength=250, blank=True)
-#    source = models.CharField(maxlength=250, blank=True)
-#    subject = models.CharField(maxlength=250, blank=True)
-#    physical_format = models.ForeignKey(PhysicalFormat, null=True, blank=True)
-
     objects = MediaCollectionManager()
 
+    def to_dublincore(self):
+        if (self.date_published):
+            date = self.date_published
+        else:
+            date = self.annee_enr
+
+        if (self.copied_from):
+            copied_from = self.copied_from.id
+        else:
+            copied_from = ''
+
+        resource = dc.Resource(
+            dc.Element('identifier','id', self.id),
+            dc.Element('type', value='Collection'),
+            dc.Element('title', 'title', self.title),
+            dc.Element('title', 'native_title', self.native_title),
+            dc.Element('creator', 'creator', self.creator),
+            dc.Element('relation', 'copied_from', copied_from, 
+                'isVersionOf'),
+            dc.Element('contributor', 'booklet_writer', self.booklet_writer),
+            dc.Element('contributor', 'collector', self.collector),
+            dc.Element('publisher', 'publisher', self.publisher),
+            dc.Element('date', value=date),
+            dc.Element('rights', 'rights', self.rights),
+        )
+        return resource
+
     def __str__(self):
-        return self.title
+        #return self.title
+        return self.id
 
     class Meta:
         ordering = ['title']
@@ -145,7 +158,7 @@ class MediaItem(models.Model, MediaCore):
     region_village = models.CharField(maxlength=250, blank=True)
     ethnie_grsocial = models.CharField(maxlength=250, blank=True)
     titre_support = models.CharField(maxlength=250, blank=True)
-    _title = models.CharField(maxlength=250, db_column='title', blank=True)
+    _title = models.CharField(maxlength=250, db_column='title')
     transcrip_trad = models.CharField(maxlength=250, blank=True)
     auteur = models.CharField(maxlength=250, blank=True)
     form_genr_style = models.CharField(maxlength=250, blank=True)
@@ -164,25 +177,8 @@ class MediaItem(models.Model, MediaCore):
     repere_bande = models.CharField(maxlength=250, blank=True)
     nroband_nropiec = models.CharField(maxlength=250, blank=True)
     continent = models.CharField(maxlength=250, blank=True)
-    file = models.FileField(upload_to='items/%Y/%m/%d')
-
-#    collection.dublin_core = 'relation'
-#    identifier = models.CharField(maxlength=250)
-#    title = models.CharField(maxlength=250)
-#    creator = models.CharField(maxlength=250)
-#    date = models.DateField()
-#    file = models.FileField(upload_to='items/%Y/%m/%d')
-#    subject = models.CharField(maxlength=250, blank=True)
-#    description = models.TextField(maxlength=250, blank=True)
-#    contributor = models.CharField(maxlength=250, blank=True)
-#    coverage = models.CharField(maxlength=250, blank=True)
-#    format = models.CharField(maxlength=25, blank=True)
-#    language = models.CharField(maxlength=250, blank=True)
-#    publisher = models.CharField(maxlength=250, blank=True)
-#    rights = models.CharField(maxlength=250, blank=True)
-#    source = models.CharField(maxlength=250, blank=True)
-#    duration = models.FloatField(max_digits=11, decimal_places=3, null=True, blank=True)
-#
+    file = models.FileField(upload_to='items/%Y/%m/%d', blank=True)
+
     objects = MediaItemManager()
 
     def _get_title(self):
@@ -198,6 +194,22 @@ class MediaItem(models.Model, MediaCore):
         return title
     title = property(_get_title)        
 
+    def to_dublincore(self):
+        if self.auteur:
+            creator = self.auteur
+        else: 
+            creator = self.collection.creator
+
+        resource = dc.Resource(
+            dc.Element('identifier','id', self.id),
+            dc.Element('type', value='Sound'),
+            dc.Element('relation', 'collection', self.collection.id, 'isPartOf'),
+            dc.Element('title', 'title', self.title),
+            dc.Element('creator', value=creator),
+            dc.Element('publisher', value=self.collection.publisher),
+        )
+        return resource
+
     def __str__(self):
         return self.title
 
index 7c53b07b79028c60066849ff420019008187e81c..5f3c6626b034be45aa8fb01b60e8f2aa001ded4f 100644 (file)
@@ -14,7 +14,6 @@
 -->
 <div id="header">
 <a href="/"><img src="/images/logo.png"></a>
-</div>
 
 <div id="quick_search">
 <form action="{% url telemeta-quicksearch %}" method="GET">
 <a href="{% url telemeta-items %}">Items</a> |
 <a href="{% url telemeta-admin %}">Admin</a>
 </div>
+</div>
+<div id="submenu">
+{% block submenu %}{% endblock %}
+</div>
 
 <div id="content">
 {% block content %}{% endblock %}
index 1a2a9289a652491fdfb633ff464240fc38bdf010..f552688338eb53c18f86236d0ff39d4a88916c8f 100644 (file)
@@ -1,10 +1,16 @@
 {% extends "base.html" %}
+{% load telemeta_utils %}
+
+{% block submenu %}
+    <a href="{% url telemeta-collection-dublincore object.id|urlencode %}">
+        Dublin Core</a>
+{% endblock %}
 
 {% block content %}
 {% if object %}
     <h3>Collection: {{ object.title }}</h3>
     <ul>
-    {% for field in object.list %}
+    {% for field in object.to_dict|tolist %}
         {% ifnotequal field.name "title" %}
 
         <li><b>{{ field.name|capfirst }}</b> : {{ field.value }}</li>
@@ -17,7 +23,7 @@
     <ul>
     {% for item in object.items.all %}
         <li><b>{{ item.creator }}</b> - {{ item.title }}
-            <a href="{% url telemeta-item-detail item.id %}">View</a>
+            <a href="{% url telemeta-item-detail item.id|urlencode %}">View</a>
             <a href="#">Edit</a>
             </li>
     {% endfor %}
diff --git a/telemeta/templates/collection_detail_dc.html b/telemeta/templates/collection_detail_dc.html
new file mode 100644 (file)
index 0000000..f005e32
--- /dev/null
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+{% load telemeta_utils %}
+
+{% block submenu %}
+    <a href="{% url telemeta-collection-detail object.id|urlencode %}">
+        Normal View</a>
+{% endblock %}
+
+{% block content %}
+{% if object %}
+    <h3>Collection: {{ object.title }}</h3>
+    <table class="dublincore listing">
+    <caption>Dublin Core Expression</caption>
+    <thead>
+        <tr><th>Element</th><th>Refinement</th><th>Value</th></tr>
+    </thead>
+    <tbody>
+        {% for element in object.to_dublincore.elements %}
+            {% if element.value %}
+            <tr><th>{{ element.name }}</th>
+            <td>{{ element.refinement|default:"&nbsp;" }}</td>
+            <td>{{ element.value }}</td>
+            {% endif %}
+        {% endfor %}
+        {% for item in object.items.all %}
+            <tr><th>relation</th><td>hasPart</td><td>
+            <a href="{% url telemeta-item-dublincore item.id|urlencode %}">
+                {{ item.id }}</a>
+            </td>
+        {% endfor %}
+    </tbody>
+    </table>
+{% else %}
+    <p>No such collection</p>
+{% endif %}
+{% endblock %}
index 1aa0c7df8971b6073e4a4486d9ff483152ede89d..c483853a0f18dd895520f5fc827edfd123a5d3c1 100644 (file)
@@ -1,9 +1,15 @@
 {% extends "base.html" %}
+{% load telemeta_utils %}
+
+{% block submenu %}
+    <a href="{% url telemeta-item-dublincore item.id|urlencode %}">
+        Dublin Core</a>
+{% endblock %}
 
 {% block content %}
 {% if item %}
     <div class="item_visualization">
-      <img src="{% url telemeta-item-visualize item.id,visualizer_id %}">
+      <img src="{% url telemeta-item-visualize item.id|urlencode,visualizer_id %}">
       <form method="GET">
         <select name="visualizer_id" onchange="this.form.submit()">
           {% for v in visualizers %}
     </div>
     <h3>Item: {{ item.title }}</h3>
     <ul>
-    {% for field in item.list %}
+    {% for field in item.to_dict|tolist %}
         {% ifnotequal field.name "id" %}
         {% ifnotequal field.name "title" %}
         {% ifnotequal field.name "file" %}
 
         <li><b>{{ field.name }}</b> : 
             {% ifequal field.name "collection" %}
-            <a href="{% url telemeta-collection-detail field.value.id %}">
+            <a href="{% url telemeta-collection-detail field.value.id|urlencode %}">
                 {{ field.value }}</a>
             {% else %}
             {{ field.value }}
diff --git a/telemeta/templates/mediaitem_detail_dc.html b/telemeta/templates/mediaitem_detail_dc.html
new file mode 100644 (file)
index 0000000..ff96fb8
--- /dev/null
@@ -0,0 +1,39 @@
+{% extends "base.html" %}
+{% load telemeta_utils %}
+
+{% block submenu %}
+    <a href="{% url telemeta-item-detail item.id|urlencode %}">
+        Normal View</a>
+{% endblock %}
+
+{% block content %}
+{% if item %}
+    <h3>Item: {{ item.title }}</h3>
+    <table class="dublincore listing">
+    <caption>Dublin Core Expression</caption>
+    <thead>
+        <tr><th>Element</th><th>Refinement</th><th>Value</th></tr>
+    </thead>
+    <tbody>
+        {% for element in item.to_dublincore.elements %}
+            {% if element.value %}
+                <tr><th>{{ element.name }}</th>
+                <td>{{ element.refinement|default:"&nbsp;" }}</td>
+                <td>
+                {% ifequal element.field "collection" %}
+                    <a href="{% url telemeta-collection-dublincore item.collection.id|urlencode %}">
+                        {{ element.value }}</a>
+                {% else %}
+                    {{ element.value }}
+                {% endifequal %}
+                </td>
+                </tr>
+            {% endif %}
+        {% endfor %}
+    </tbody>
+    </table>
+{% else %}
+    <p>No such item</p>
+{% endif %}
+{% endblock %}
+
index 59a03b717e551f86655840b40879f2265baaa8ad..e52bb21363f6fa4ae1caafd7e9ba0eb7590909a4 100644 (file)
@@ -8,4 +8,12 @@ register = template.Library()
 #    "Escapes a value for use in a URL (converts slashes)"
 #    return value.replace('/', '--')
 
+@register.filter
+def tolist(dict):
+    "Converts a simple dict into a list"
+    list = []
+    for k, v in dict.iteritems():
+        list.append({'name': k, 'value': v})
+    return list        
+
 
index 3779677e0159c2b9bc7f3fc7b5f21252b7c03328..582cbc92584b6b8aa9fc808046632f4066828227 100644 (file)
@@ -27,12 +27,18 @@ urlpatterns = patterns('',
     url(r'^items/$', 'django.views.generic.list_detail.object_list', 
         dict(all_items, paginate_by=20, template_name="mediaitem_list.html"),
         name="telemeta-items"),
-    url(r'^items/(?P<item_id>[0-9A-Z._:-]+)/$', web_view.item_detail, 
+    url(r'^items/(?P<item_id>[0-9A-Z._:%?-]+)/$', web_view.item_detail, 
         name="telemeta-item-detail"),
-    url(r'^items/(?P<item_id>[0-9A-Z._:-]+)/download/(?P<format>[0-9A-Z]+)/$', 
+    url(r'^items/(?P<item_id>[0-9A-Z._:%?-]+)/dc/$', web_view.item_detail, 
+        {'template': 'mediaitem_detail_dc.html'},
+        name="telemeta-item-dublincore"),
+    url(r'^items/(?P<item_id>[0-9A-Z._:%?-]+)/dc/xml/$', web_view.item_detail, 
+        {'format': 'dublin_core_xml'},
+        name="telemeta-item-dublincore-xml"),
+    url(r'^items/(?P<item_id>[0-9A-Z._:%?-]+)/download/(?P<format>[0-9A-Z]+)/$', 
         web_view.item_export,
         name="telemeta-item-export"),
-    url(r'^items/(?P<item_id>[0-9A-Z._:-]+)/visualize/(?P<visualizer_id>[0-9a-z]+)/$', 
+    url(r'^items/(?P<item_id>[0-9A-Z._:%?-]+)/visualize/(?P<visualizer_id>[0-9a-z]+)/$', 
         web_view.item_visualize,
         name="telemeta-item-visualize"),
 
@@ -44,10 +50,14 @@ urlpatterns = patterns('',
     url(r'^collections/?page=(?P<page>[0-9]+)$', 
         'django.views.generic.list_detail.object_list',
         dict(all_collections, paginate_by=20)),
-    url(r'^collections/(?P<object_id>[0-9A-Z._-]+)/$', 
+    url(r'^collections/(?P<object_id>[0-9A-Z._%?-]+)/$', 
         'django.views.generic.list_detail.object_detail',
         dict(all_collections, template_name="collection_detail.html"),
         name="telemeta-collection-detail"),
+    url(r'^collections/(?P<object_id>[0-9A-Z._%?-]+)/dc/$', 
+        'django.views.generic.list_detail.object_detail',
+        dict(all_collections, template_name="collection_detail_dc.html"),
+        name="telemeta-collection-dublincore"),
 
     # search
     url(r'^search/$', web_view.quick_search, name="telemeta-quicksearch"),
index c2cf6e6d9d53ce741d164423a719920a534f6816..71984daaf4199e9da02e4c7efe11c5acdb8101ff 100644 (file)
@@ -35,7 +35,7 @@ class WebView(Component):
         context = Context({})
         return HttpResponse(template.render(context))
 
-    def item_detail(self, request, item_id):
+    def item_detail(self, request, item_id, template='mediaitem_detail.html'):
         """Show the details of a given item"""
         item = MediaItem.objects.get(pk=item_id)
         formats = []
@@ -49,7 +49,8 @@ class WebView(Component):
             visualizer_id = request.REQUEST['visualizer_id']
         else:
             visualizer_id = 'waveform'
-        return render_to_response('mediaitem_detail.html', 
+
+        return render_to_response(template, 
                     {'item': item, 'export_formats': formats, 
                     'visualizers': visualizers, 'visualizer_id': visualizer_id})
                     
@@ -67,22 +68,6 @@ class WebView(Component):
         response = HttpResponse(stream, mimetype = 'image/png')
         return response
 
-
-    def __file_stream(self, filepath):
-        """Generator for streaming a file from the disk. 
-        
-        This method shouldn't be needed anymore when bug #8 get fixed
-        """
-
-        buffer_size = 0xFFFF
-        f = open(filepath, 'rb')
-        chunk = f.read(buffer_size)
-        yield chunk
-        while chunk:
-            chunk = f.read(buffer_size)
-            yield chunk
-        f.close()            
-
     def item_export(self, request, item_id, format):                    
         """Export a given media item in the specified format (OGG, FLAC, ...)"""
         for exporter in self.exporters:
@@ -99,16 +84,10 @@ class WebView(Component):
         item = MediaItem.objects.get(pk=item_id)
 
         infile = settings.MEDIA_ROOT + "/" + item.file
-        metadata = item.to_dict()
-        metadata['collection'] = str(metadata['collection'])
-        metadata['Collection'] = metadata['collection']
-        metadata['Artist'] = metadata['creator']
-        metadata['Title'] = metadata['title']
-
-        stream = exporter.process(item.id, infile, metadata, [])
+        metadata = item.to_dublincore().flatten()
+        stream = exporter.process(item.id, infile, metadata)
 
-        response = HttpResponse(self.__file_stream(outfile),mimetype=mime_type)
-        #response = HttpResponse(stream, mimetype = mime_type)
+        response = HttpResponse(stream, mimetype = mime_type)
         response['Content-Disposition'] = 'attachment; filename="download.' + \
                     exporter.get_file_extension() + '"'
         return response