]> git.parisson.com Git - telemeta.git/commitdiff
fix various issues in CREM models, move unit tests into tests.py
authorolivier <>
Mon, 18 Jan 2010 20:54:19 +0000 (20:54 +0000)
committerolivier <>
Mon, 18 Jan 2010 20:54:19 +0000 (20:54 +0000)
telemeta/models/crem.py
telemeta/models/cremtests.py [deleted file]
telemeta/tests.py [new file with mode: 0644]

index a41a3c59d85c2250c27b9324ee90a66878e9d2b1..58c4ccb23109f2130afcf8d6b088b9f79c8c9222 100755 (executable)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (C) 2007 Samalyse SARL
+# Copyright (C) 2007-2010 Samalyse SARL
 
 # This software is a computer program whose purpose is to backup, analyse,
 # transcode and stream any audio content with its metadata over a web frontend.
 #          David LIPSZYC <davidlipszyc@gmail.com>
 
 from django.db import models
-import query
+import cremquery as query
+
+class MediaCore(object):
+    "Base class of all media objects"
+
+    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 to_list(self):  
+        "Return model fields as a list"
+        fields_list = []
+        for field in self._meta.fields:
+            fields_list.append({'name': field.name, 'value': getattr(self, field.name)})
+        return fields_list
+
+    def get_dom_element_name(cls):
+        "Convert the class name to a DOM element name"
+        clsname = cls.__name__
+        return clsname[0].lower() + clsname[1:]
+    get_dom_element_name = classmethod(get_dom_element_name)
+
+    def to_dom(self):
+        "Return the DOM representation of this media object"
+        impl = getDOMImplementation()
+        root = self.get_dom_element_name()
+        doc = impl.createDocument(None, root, None)
+        top = doc.documentElement
+        top.setAttribute("id", self.id)
+        fields = self.to_dict()
+        for name, value in fields.iteritems():
+            element = doc.createElement(name)
+            value = unicode(value)
+            element.appendChild(doc.createTextNode(value))
+            top.appendChild(element)
+        return doc
+    
+    def is_well_formed_id(cls, value):
+        "Check if the media id is well formed"
+        regex = re.compile(r"^" + media_id_regex + r"$")
+        if regex.match(value):
+            return True 
+        else:
+            return False
+    is_well_formed_id = classmethod(is_well_formed_id)
+
+class MetaCore:
+    app_label = 'telemeta'
 
 class MediaCollection(models.Model):
     "Describe a collection of items"
@@ -86,7 +136,7 @@ class MediaCollection(models.Model):
     a_informer_07_03      = models.CharField(max_length=250, default="")
     ad_conversion         = models.ForeignKey('AdConversion', related_name='collections',
                                               null=True)
-    public_access         = models.CharField(choices=PUBLIC_ACCESS_CHOICES, max_length=250, default="metadata")
+    public_access         = models.CharField(choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
 
     objects               = query.MediaCollectionManager()
 
@@ -101,7 +151,7 @@ class MediaCollection(models.Model):
         super(MediaCollection, self).save(force_insert, force_update)
         Revision(element_type='collection', element_id=self.id, user=user).touch()    
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'media_collections'
 
 class MediaItem(models.Model):
@@ -139,11 +189,11 @@ class MediaItem(models.Model):
     comment               = models.TextField(default="")
     filename              = models.CharField(max_length=250, default="")
     public_access         = models.CharField(choices=PUBLIC_ACCESS_CHOICES, 
-                                             max_length=250, default="metadata")
+                                             max_length=16, default="metadata")
 
     objects               = query.MediaItemManager()
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'media_items'
 
     def __unicode__(self):
@@ -166,7 +216,7 @@ class MediaPart(models.Model):
     start = models.FloatField()
     end   = models.FloatField()
     
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'media_parts'
 
     def __unicode__(self):
@@ -174,86 +224,86 @@ class MediaPart(models.Model):
 
 class PhysicalFormat(models.Model):
     "Collection physical format"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
     
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'physical_formats'
 
 class PublishingStatus(models.Model):
     "Collection publishing status"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'publishing_status'
 
 class AcquisitionMode(models.Model):
     "Mode of acquisition of the collection"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'acquisition_modes'
 
 class MetadataAuthor(models.Model):
     "Collection metadata author"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'metadata_authors'
 
 class MetadataWriter(models.Model):  
     "Collection metadata writer"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'metadata_writers'
 
 class LegalRight(models.Model):
     "Collection legal rights" 
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'legal_rights'
 
 class RecordingContext(models.Model):
     "Collection recording context"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'recording_contexts'
 
 class AdConversion(models.Model):
     "Collection digital to analog conversion status"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'ad_conversions'
 
 class VernacularStyle(models.Model):
     "Item vernacular style"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'vernacular_styles'
 
 class GenericStyle(models.Model):
     "Item generic style"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'generic_styles'
 
 class Instrument(models.Model):
     "Instrument used in the item"
     name    = models.CharField(max_length=250)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'instruments'
 
 class InstrumentAlias(models.Model):
     "Instrument other name"
     name = models.CharField(max_length=250)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'instrument_aliases'
 
 class InstrumentRelation(models.Model):
@@ -261,16 +311,18 @@ class InstrumentRelation(models.Model):
     instrument        = models.ForeignKey('Instrument', related_name="parent_relation")
     parent_instrument = models.ForeignKey('Instrument', related_name="child_relation")
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'instrument_relations'
+        unique_together = (('instrument', 'parent_instrument'),)
 
 class InstrumentAliasRelation(models.Model):
     "Instrument family other name"
     alias      = models.ForeignKey('InstrumentAlias', related_name="other_name")
     instrument = models.ForeignKey('InstrumentAlias', related_name="relation")
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'instrument_alias_relations'
+        unique_together = (('alias', 'instrument'),)
 
 class MediaItemPerformance(models.Model):
     "Item performance"
@@ -282,29 +334,29 @@ class MediaItemPerformance(models.Model):
     instruments_num = models.CharField(max_length=250, default="")
     musicians       = models.CharField(max_length=250, default="")
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'media_item_performances'
 
 class User(models.Model):
     "Telemeta user"
     LEVEL_CHOICES = (('user', 'user'), ('maintainer', 'maintainer'), ('admin', 'admin'))    
 
-    username   = models.CharField(primary_key=True, max_length=250)
+    username   = models.CharField(primary_key=True, max_length=64)
     level      = models.CharField(choices=LEVEL_CHOICES, max_length=250)
     first_name = models.CharField(max_length=250, default="")
     last_name  = models.CharField(max_length=250, default="")
     phone      = models.CharField(max_length=250, default="")
     email      = models.CharField(max_length=250, default="")
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'users'
 
 class Playlist(models.Model):
     "Item or collection playlist"
-    owner_username = models.ForeignKey('User', related_name="playlists") 
+    owner_username = models.ForeignKey('User', related_name="playlists", db_column="owner_username"
     name           = models.CharField(max_length=250)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'playlists'
 
 class PlaylistResource(models.Model):
@@ -315,21 +367,21 @@ class PlaylistResource(models.Model):
     resource_type         = models.CharField(choices=RESOURCE_TYPE_CHOICES, max_length=250)
     resource              = models.IntegerField()
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'playlist_resources'
 
 class Location(models.Model):
     "Item location"
     TYPE_CHOICES     = (('country', 'country'), ('continent', 'continent'), ('other', 'other'))
 
-    name             = models.CharField(primary_key=True, max_length=250)
-    type             = models.CharField(choices=TYPE_CHOICES, max_length=250)
+    name             = models.CharField(primary_key=True, max_length=150)
+    type             = models.CharField(choices=TYPE_CHOICES, max_length=16)
     complete_type    = models.ForeignKey('LocationType', related_name="types")
     current_name     = models.ForeignKey('self', related_name="past_names", 
                                          db_column="current_name", null=True) 
     is_authoritative = models.BooleanField(default=0)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'locations'
 
     def __unicode__(self):
@@ -337,38 +389,39 @@ class Location(models.Model):
 
 class LocationType(models.Model):
     "Location type of an item location"
-    id   = models.CharField(max_length=250, primary_key=True)
-    name = models.CharField(max_length=250)
+    id   = models.CharField(max_length=64, primary_key=True)
+    name = models.CharField(max_length=150)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'location_types'
 
 class LocationAlias(models.Model):
     "Location other name"
     location_name    = models.ForeignKey('Location', related_name="aliases",
-                                          db_column="location_name")
-    alias            = models.CharField(max_length=250)
+                                          db_column="location_name", max_length=150)
+    alias            = models.CharField(max_length=150)
     is_authoritative = models.BooleanField(default=0)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'location_aliases'
+        unique_together = (('location_name', 'alias'),)
     
 class LocationRelation(models.Model):
     "Location family"
     location_name        = models.ForeignKey('Location', related_name="parent_relations",
-                                              db_column="location_name")
+                                              db_column="location_name", max_length=150)
     parent_location_name = models.ForeignKey('Location', related_name="child_relations",
-                                              db_column="parent_location_name", null=True)
+                                              db_column="parent_location_name", null=True, max_length=150)
     is_authoritative     = models.BooleanField()
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'location_relations'
     
 class ContextKeyword(models.Model):
     "Keyword"
     value = models.CharField(max_length=250)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'context_keywords'
 
 class MediaItemKeyword(models.Model):
@@ -376,14 +429,15 @@ class MediaItemKeyword(models.Model):
     item    = models.ForeignKey('MediaItem')
     keyword = models.ForeignKey('ContextKeyword')
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'media_item_keywords'
+        unique_together = (('item', 'keyword'),)
 
 class Publisher(models.Model): 
     "Collection publisher"
-    value = models.CharField(max_length=250)
+    value = models.CharField(max_length=250, unique=True)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'publishers'
 
 class PublisherCollection(models.Model):
@@ -391,7 +445,7 @@ class PublisherCollection(models.Model):
     publisher = models.ForeignKey('Publisher', related_name="publisher_collections")
     value     = models.CharField(max_length=250)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'publisher_collections'
 
 class Revision(models.Model):
@@ -399,9 +453,9 @@ class Revision(models.Model):
     ELEMENT_TYPE_CHOICES = (('collection', 'collection'), ('item', 'item'), ('part', 'part'))
     CHANGE_TYPE_CHOICES  = (('import', 'import'), ('create', 'create'), ('update', 'update'), ('delete','delete'))
 
-    element_type         = models.CharField(choices=ELEMENT_TYPE_CHOICES, max_length=250)
+    element_type         = models.CharField(choices=ELEMENT_TYPE_CHOICES, max_length=16)
     element_id           = models.IntegerField()
-    change_type          = models.CharField(choices=CHANGE_TYPE_CHOICES, max_length=250)
+    change_type          = models.CharField(choices=CHANGE_TYPE_CHOICES, max_length=16)
     time                 = models.DateTimeField(auto_now_add=True)
     user                 = models.ForeignKey('User', db_column='username', related_name="revisions")
     
@@ -414,14 +468,14 @@ class Revision(models.Model):
             self.change_type = 'create'
         self.save()
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'revisions'
     
 class EthnicGroup(models.Model):
     "Item ethnic group"
     name = models.CharField(max_length=250)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'ethnic_groups'
 
     def __unicode__(self):
@@ -432,7 +486,7 @@ class EthnicGroupAlias(models.Model):
     ethnic_group = models.ForeignKey('EthnicGroup', related_name="aliases")
     name         = models.CharField(max_length=250)
 
-    class Meta:
+    class Meta(MetaCore):
         db_table = 'ethnic_group_aliases'
 
 
diff --git a/telemeta/models/cremtests.py b/telemeta/models/cremtests.py
deleted file mode 100644 (file)
index ce1ff1b..0000000
+++ /dev/null
@@ -1,311 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (C) 2007 Samalyse SARL
-
-# This software is a computer program whose purpose is to backup, analyse,
-# transcode and stream any audio content with its metadata over a web frontend.
-
-# This software is governed by the CeCILL  license under French law and
-# abiding by the rules of distribution of free software.  You can  use,
-# modify and/ or redistribute the software under the terms of the CeCILL
-# license as circulated by CEA, CNRS and INRIA at the following URL
-# "http://www.cecill.info".
-
-# As a counterpart to the access to the source code and  rights to copy,
-# modify and redistribute granted by the license, users are provided only
-# with a limited warranty  and the software's author,  the holder of the
-# economic rights,  and the successive licensors  have only  limited
-# liability.
-
-# In this respect, the user's attention is drawn to the risks associated
-# with loading,  using,  modifying and/or developing or reproducing the
-# software by the user in light of its specific status of free software,
-# that may mean  that it is complicated to manipulate,  and  that  also
-# therefore means  that it is reserved for developers  and  experienced
-# professionals having in-depth computer knowledge. Users are therefore
-# encouraged to load and test the software's suitability as regards their
-# requirements in conditions enabling the security of their systems and/or
-# data to be ensured and,  more generally, to use and operate it in the
-# same conditions as regards security.
-
-# The fact that you are presently reading this means that you have had
-# knowledge of the CeCILL license and that you accept its terms.
-#
-# Authors: Olivier Guilyardi <olivier@samalyse.com>
-#          David LIPSZYC <davidlipszyc@gmail.com>
-
-import unittest
-from models import MediaCollection, MediaItem, Location, EthnicGroup, LocationType, User, Revision
-from datetime import datetime, timedelta
-
-class CollectionItemTestCase(unittest.TestCase):
-    def setUp(self):
-        "Create a test database based on objects created in Django"
-   
-        User.objects.all().delete() 
-        self.david   = User.objects.create(username="david", level="user", first_name="david", last_name="aaa", phone="0156565656",
-                       email="david@a.com")
-        self.olivier = User.objects.create(username="olivier", level="admin", first_name="olivier", last_name="bbb", 
-                       phone="0155555555", email="olivier@a.com")    
-
-        LocationType.objects.all().delete()
-        self.country = LocationType.objects.create(id="country", name="country")
-        self.continent = LocationType.objects.create(id="continent", name="continent")
-        self.city = LocationType.objects.create(id="city", name="city")
-
-        Location.objects.all().delete()        
-        self.paris = Location.objects.create(name="Paris", type="other", complete_type=self.city, is_authoritative=0)
-        self.france = Location.objects.create(name="France", type="country", complete_type=self.country, is_authoritative=0)
-        self.europe = Location.objects.create(name="Europe", type="continent", complete_type=self.continent, is_authoritative=0)
-        self.belgique = Location.objects.create(name="Belgique", type="country", complete_type=self.country, is_authoritative=0)
-
-        EthnicGroup.objects.all().delete()
-        self.a = EthnicGroup.objects.create(name="a")
-        self.b = EthnicGroup.objects.create(name="b")
-        self.c = EthnicGroup.objects.create(name="c")
-        self.d = EthnicGroup.objects.create(name="d")
-
-        MediaCollection.objects.all().delete()
-        self.persepolis = MediaCollection(id=1, reference="A1", physical_format_id=1111, old_code="10", code="100",                                                          title="persepolis", alt_title="bjr", creator="Abraham LINCOLN", 
-                                          booklet_author="Maria BALTHAZAR", 
-                                          booklet_description="compilation de mots français", 
-                                          collector="Friedrich HEINZ", collector_is_creator=0, publisher_id=1442, 
-                                          year_published=2009, publisher_collection_id=1234, 
-                                          publisher_serial="123456", external_references="Larousse", 
-                                          acquisition_mode_id=1, comment="chants", metadata_author_id=1, 
-                                          metadata_writer_id=1, legal_rights_id=1, alt_ids="89", 
-                                          recorded_from_year=1970, recorded_to_year=1980, recording_context_id=1, 
-                                          approx_duration="5:00:00", doctype_code=1357, travail="travail", 
-                                          state="etat", cnrs_contributor="Jean PETIT", items_done="fiches", 
-                                          a_informer_07_03="a informer", ad_conversion_id=9, public_access="full")
-        
-        self.persepolis.save_by_user(self.david)
-
-        self.volonte = MediaCollection(id=2, reference="A2", physical_format_id=222, old_code="20", code="200", 
-                                       title="Volonté de puissance", alt_title="ar", creator="Friedrich NIETZSCHE", 
-                                       booklet_author="George POMPIDOU", booklet_description="notice numero 2", 
-                                       collector="Jean AMORA", collector_is_creator=0, publisher_id=2884, 
-                                       year_published=1999, publisher_collection_id=2345, 
-                                       publisher_serial="234567", external_references="dico", 
-                                       acquisition_mode_id=2, comment="commentaire 2", metadata_author_id=2, 
-                                       metadata_writer_id=2, legal_rights_id=2, alt_ids="78", 
-                                       recorded_from_year=1960, recorded_to_year=2000, recording_context_id=2, 
-                                       approx_duration="1:00:00", doctype_code=2468, travail="travail 2", 
-                                       state="etat 2", cnrs_contributor="Richard LIONHEART", items_done="fiches 2", 
-                                       a_informer_07_03="a informer 2", ad_conversion_id=8, 
-                                       public_access="metadata")
-
-        self.volonte.save_by_user(self.olivier)
-
-        self.nicolas = MediaCollection(id=3, reference="A3", physical_format_id=333, old_code="30", code="300", 
-                                       title="petit nicolas", alt_title="slt", creator="Georgette McKenic", 
-                                       booklet_author="Francesca DICORTO", booklet_description="notice 3", 
-                                       collector="Paul MAILLE", collector_is_creator=0, publisher_id=3773, 
-                                       year_published=1999, publisher_collection_id=7890, publisher_serial="8764", 
-                                       external_references="ref externes", acquisition_mode_id=3, 
-                                       comment="commentaire 3", metadata_author_id=3, metadata_writer_id=3, 
-                                       legal_rights_id=3, alt_ids="56", recorded_from_year=1967, 
-                                       recorded_to_year=1968, recording_context_id=3, approx_duration="0:00:00", 
-                                       doctype_code=5790, travail="travail 3", state="etat 3", 
-                                       cnrs_contributor="Gerard MICKAEL", items_done="fiches 3", 
-                                       a_informer_07_03="a informer 3", ad_conversion_id=8, public_access="none")
-                                   
-        self.nicolas.save_by_user(self.olivier)
-     
-        MediaItem.objects.all().delete()        
-        self.item_1 = MediaItem(id=1, collection=self.persepolis, track="1111", old_code="101", code="1010", 
-                                approx_duration="00:01:00", recorded_from_date="1971-01-12", 
-                                recorded_to_date="1971-02-24", location_name=self.paris, 
-                                location_comment="capital de la France", ethnic_group=self.a, 
-                                title="item 1", alt_title="I1", author="Mickael SHEPHERD", 
-                                context_comment="contexte ethno 1", external_references="ext ref 1", 
-                                moda_execut="moda exec 1",copied_from_item_id=99, 
-                                collector="Charles PREMIER", cultural_area="Ile de France", generic_style_id=1, 
-                                collector_selection="collec sel 1", creator_reference="ref du deposant 1", 
-                                comment="comment 1", filename="item 1.item", public_access="full") 
-
-        self.item_1.save_by_user(self.david)
-
-        self.item_2 = MediaItem(id=2, collection=self.volonte, track="2222", old_code="202", code="2020", 
-                                approx_duration="00:02:00", recorded_from_date="1981-01-12", 
-                                recorded_to_date="1991-02-24", location_name=self.france, 
-                                location_comment="loc comment 2", ethnic_group=self.a, title="item 2",
-                                alt_title="I2", author="Rick ROLL", context_comment="contexte ethno 2", 
-                                external_references="ext ref 2", moda_execut="moda exec 2", 
-                                copied_from_item_id=98, collector="Gerard LENORMAND", 
-                                cultural_area="Nord de la France", generic_style_id=1,
-                                collector_selection="collec sel 2", creator_reference="ref du deposant 2", 
-                                comment="comment 2", filename="item 2.item", public_access="metadata") 
-
-        self.item_2.save_by_user(self.david)
-
-        self.item_3 = MediaItem(id=3, collection=self.nicolas, track="3333", old_code="303", code="3030", 
-                                approx_duration="00:03:00", recorded_from_date="1968-01-12", 
-                                recorded_to_date="1968-02-24", location_name=self.belgique, 
-                                location_comment="en Europe", ethnic_group=self.b, title="item 3", 
-                                alt_title="I3", author="John SMITH", context_comment="contexte ethno 3", 
-                                external_references="ext ref 3", moda_execut="moda exec 3", copied_from_item_id=97, 
-                                collector="Paul CARLOS", cultural_area="Europe occidentale", generic_style_id=1,
-                                collector_selection="collec sel 3", creator_reference="ref du deposant 3", 
-                                comment="comment 3", filename="item 3.item", public_access="none")
-
-        self.item_3.save_by_user(self.olivier)
-
-        self.item_4 = MediaItem(id=4, collection=self.persepolis, track="4444", old_code="404", code="4040", 
-                                approx_duration="00:04:00", recorded_from_date="1972-01-12", 
-                                recorded_to_date="1972-02-24", location_name=self.europe, 
-                                location_comment="loc comm 4", ethnic_group=self.a, title="item 4", 
-                                alt_title="I4", author="Keanu REAVES", context_comment="contexte ethno 4", 
-                                external_references="ext ref 4", moda_execut="moda exec 4", copied_from_item_id=96, 
-                                collector="Christina BARCELONA", cultural_area="aire culturelle 4", 
-                                generic_style_id=1, collector_selection="collec sel 4", 
-                                creator_reference="ref du deposant 4", comment="comment 4", filename="item 4.item",
-                                public_access="none")
-
-        self.item_4.save_by_user(self.olivier)
-
-        self.item_5 = MediaItem(id=5, collection=self.volonte, track="5555", old_code="505", code="5050", 
-                                approx_duration="00:05:00", recorded_from_date="1978-01-12", 
-                                recorded_to_date="1978-02-24", location_name=self.belgique, 
-                                location_comment="loc comm 5", ethnic_group=self.a, title="item 5", 
-                                alt_title="I5", author="Simon PAUL", context_comment="contexte ethno 5", 
-                                external_references="ext ref 5", moda_execut="moda exec 5", copied_from_item_id=95, 
-                                collector="Javier BARDEM", cultural_area="aire culturelle 5", generic_style_id=1,
-                                collector_selection="collec sel 5", creator_reference="ref du deposant 5", 
-                                comment="comment 5", filename="item 5.item", public_access="metadata")
-
-        self.item_5.save_by_user(self.olivier)
-
-        self.item_6 = MediaItem(id=6, collection=self.persepolis, track="10000", old_code="1111111", 
-                                code="6060", approx_duration="10:03:00", recorded_from_date="1968-01-12", 
-                                recorded_to_date="1968-02-11", location_name=self.france, 
-                                location_comment="loc comment 10000", ethnic_group=self.b, 
-                                title="item 6", alt_title="I10000", author="Paul ANDERSON", 
-                                context_comment="contexte ethno 10000", external_references="ext ref 10000", 
-                                moda_execut="moda exec 10000", copied_from_item_id=11111, collector="Jim CARLSON", 
-                                cultural_area="cul area 10000", generic_style_id=1, 
-                                collector_selection="collec sel 10000", creator_reference="ref du deposant 10000", 
-                                comment="comment 10000", filename="item 10000.item", public_access="none")
-        
-        self.item_6.save_by_user(self.david)
-
-        self.collections = MediaCollection.objects.all()
-        self.items       = MediaItem.objects.all()
-
-    def testQuickSearchOnCollections(self):
-        "Test quick_search property of MediaCollection class"
-        result = self.collections.quick_search("persepolis")
-        self.assertEquals(len(result), 1)
-        self.assertEquals(result[0], self.persepolis)
-        self.assertEquals(self.collections.quick_search("nietzsche")[0], self.volonte)
-        result = self.collections.quick_search("nicolas")
-        self.assertEquals(result[0], self.nicolas)
-
-    def testQuickSearchOnItems(self):
-        "Test quick_search property of MediaItem class"
-        result = self.items.quick_search("item").order_by("title")
-        self.assertEquals(result[0], self.item_1)
-        self.assertEquals(result[1], self.item_2)
-        self.assertEquals(result[2], self.item_3)
-        self.assertEquals(result[3], self.item_4)
-        self.assertEquals(result[4], self.item_5)
-        self.assertEquals(result[5], self.item_6)
-
-    def testWordSearch(self):
-        "Test quick_search property of MediaCollection class, specificly quick_search on collection title"
-        result = self.collections.quick_search("volonté puissance")
-        self.assertEquals(result[0], self.volonte)
-        result = self.collections.quick_search("puissance volonté")
-        self.assertEquals(result[0], self.volonte)
-        result = self.collections.quick_search("volonte puissance")
-        self.assertEquals(result[0], self.volonte)
-        result = self.collections.quick_search("puissance volonte")
-        self.assertEquals(result[0], self.volonte)
-        
-    def testLocationSearch(self):
-        "Test by_country and by_continent properties of MediaCollection class"
-        self.assertEquals(self.collections.by_country("France")[0], self.persepolis)
-        self.assertEquals(self.collections.by_continent("Europe")[0], self.persepolis)
-        self.assertEquals(self.collections.by_country("Belgique").order_by("title")[0], self.nicolas)
-        self.assertEquals(self.collections.by_country("Belgique").order_by("title")[1], self.volonte)
-
-    def testRecordingYear(self): 
-        "Test by_recording_year property of MediaCollection class"
-        self.assertEquals(self.collections.by_recording_year(1970, 1980)[0], self.persepolis)
-        result = self.collections.by_recording_year(1975).order_by("title")
-        self.assertEquals(result[0], self.persepolis)
-        self.assertEquals(result[1], self.volonte)
-    
-    def testPublishYearOnCollection(self):
-        "Test by_publish_year property of MediaCollection class"
-        result=self.collections.by_publish_year(1999).order_by("title")
-        self.assertEquals(result[0], self.nicolas)
-        self.assertEquals(result[1], self.volonte)
-        
-    def testEthnicGroup(self):
-        "Test by_ethnic_group property of MediaCollection class"
-        result=self.collections.by_ethnic_group("a").order_by("title")
-        self.assertEquals(result[0], self.persepolis)
-        self.assertEquals(result[1], self.volonte)
-
-    def testRecordingDate(self):
-        "Test by_recording_date property of MediaItem class"
-        result = self.items.by_recording_date("1968-01-01", "1972-12-12").order_by("title")
-        self.assertEquals(result[0], self.item_1)
-        self.assertEquals(result[1], self.item_3)
-        self.assertEquals(result[2], self.item_4)
-        self.assertEquals(result[3], self.item_6)
-        result = self.items.by_recording_date("1968-02-06").order_by("title")
-        self.assertEquals(result[0], self.item_3)
-        self.assertEquals(result[1], self.item_6)
-
-    def testTitle(self):
-        "Test by_title property of MediaItem class"
-        result = self.items.by_title("item").order_by("title")
-        self.assertEquals(result[0], self.item_1)
-        self.assertEquals(result[1], self.item_2)
-        self.assertEquals(result[2], self.item_3)
-        self.assertEquals(result[3], self.item_4)
-        self.assertEquals(result[4], self.item_5)
-        self.assertEquals(result[5], self.item_6)
-        result = self.items.by_title("volonté").order_by("title")
-        self.assertEquals(result[0], self.item_2)
-        self.assertEquals(result[1], self.item_5)
-        result = self.items.by_title("puissance volonté").order_by("title")
-        self.assertEquals(result[0], self.item_2)
-        self.assertEquals(result[1], self.item_5)
-
-    def testPublishYearOnItem(self):
-        "Test by_publish_year property of MediaItem class"
-        result = self.items.by_publish_year(1999).order_by("title")
-        self.assertEquals(result[0], self.item_2)
-        self.assertEquals(result[1], self.item_3)
-        self.assertEquals(result[2], self.item_5)
-    
-    def testWordSearchCore(self):
-        "Test word_search property of CoreQuerySet class"
-        self.assertEquals(self.collections.word_search("title", "volonté")[0], self.volonte)
-        self.assertEquals(self.collections.word_search("code", "100")[0], self.persepolis)
-        self.assertEquals(self.items.word_search("code", "1010")[0], self.item_1)
-        result = self.items.word_search("comment", "comment").order_by("title")
-        self.assertEquals(result[0], self.item_1)
-        self.assertEquals(result[1], self.item_2)
-        self.assertEquals(result[2], self.item_3)
-        self.assertEquals(result[3], self.item_4)
-        self.assertEquals(result[4], self.item_5)
-        self.assertEquals(result[5], self.item_6)
-    
-    def testByChangeTimeOnCollection(self):
-        "Test by_change_time property of MediaCollection class"
-        now = datetime.now()
-        result = self.collections.by_change_time(now - timedelta(hours=1), now).order_by("title")
-        self.assertEquals(result[0], self.persepolis)
-
-    def testByChangeTimeOnItem(self):
-        "Test by_change_time property of MediaItem class"
-        now = datetime.now()
-        result = self.items.by_change_time(now - timedelta(hours=1), now).order_by("title")
-        self.assertEquals(result[0], self.item_1)
-
-    def testWithoutCollection(self):
-        "Test without_collection property of MediaItem class"
-        self.assertEquals(self.items.without_collection().count(), 0)
diff --git a/telemeta/tests.py b/telemeta/tests.py
new file mode 100644 (file)
index 0000000..ce1ff1b
--- /dev/null
@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007 Samalyse SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          David LIPSZYC <davidlipszyc@gmail.com>
+
+import unittest
+from models import MediaCollection, MediaItem, Location, EthnicGroup, LocationType, User, Revision
+from datetime import datetime, timedelta
+
+class CollectionItemTestCase(unittest.TestCase):
+    def setUp(self):
+        "Create a test database based on objects created in Django"
+   
+        User.objects.all().delete() 
+        self.david   = User.objects.create(username="david", level="user", first_name="david", last_name="aaa", phone="0156565656",
+                       email="david@a.com")
+        self.olivier = User.objects.create(username="olivier", level="admin", first_name="olivier", last_name="bbb", 
+                       phone="0155555555", email="olivier@a.com")    
+
+        LocationType.objects.all().delete()
+        self.country = LocationType.objects.create(id="country", name="country")
+        self.continent = LocationType.objects.create(id="continent", name="continent")
+        self.city = LocationType.objects.create(id="city", name="city")
+
+        Location.objects.all().delete()        
+        self.paris = Location.objects.create(name="Paris", type="other", complete_type=self.city, is_authoritative=0)
+        self.france = Location.objects.create(name="France", type="country", complete_type=self.country, is_authoritative=0)
+        self.europe = Location.objects.create(name="Europe", type="continent", complete_type=self.continent, is_authoritative=0)
+        self.belgique = Location.objects.create(name="Belgique", type="country", complete_type=self.country, is_authoritative=0)
+
+        EthnicGroup.objects.all().delete()
+        self.a = EthnicGroup.objects.create(name="a")
+        self.b = EthnicGroup.objects.create(name="b")
+        self.c = EthnicGroup.objects.create(name="c")
+        self.d = EthnicGroup.objects.create(name="d")
+
+        MediaCollection.objects.all().delete()
+        self.persepolis = MediaCollection(id=1, reference="A1", physical_format_id=1111, old_code="10", code="100",                                                          title="persepolis", alt_title="bjr", creator="Abraham LINCOLN", 
+                                          booklet_author="Maria BALTHAZAR", 
+                                          booklet_description="compilation de mots français", 
+                                          collector="Friedrich HEINZ", collector_is_creator=0, publisher_id=1442, 
+                                          year_published=2009, publisher_collection_id=1234, 
+                                          publisher_serial="123456", external_references="Larousse", 
+                                          acquisition_mode_id=1, comment="chants", metadata_author_id=1, 
+                                          metadata_writer_id=1, legal_rights_id=1, alt_ids="89", 
+                                          recorded_from_year=1970, recorded_to_year=1980, recording_context_id=1, 
+                                          approx_duration="5:00:00", doctype_code=1357, travail="travail", 
+                                          state="etat", cnrs_contributor="Jean PETIT", items_done="fiches", 
+                                          a_informer_07_03="a informer", ad_conversion_id=9, public_access="full")
+        
+        self.persepolis.save_by_user(self.david)
+
+        self.volonte = MediaCollection(id=2, reference="A2", physical_format_id=222, old_code="20", code="200", 
+                                       title="Volonté de puissance", alt_title="ar", creator="Friedrich NIETZSCHE", 
+                                       booklet_author="George POMPIDOU", booklet_description="notice numero 2", 
+                                       collector="Jean AMORA", collector_is_creator=0, publisher_id=2884, 
+                                       year_published=1999, publisher_collection_id=2345, 
+                                       publisher_serial="234567", external_references="dico", 
+                                       acquisition_mode_id=2, comment="commentaire 2", metadata_author_id=2, 
+                                       metadata_writer_id=2, legal_rights_id=2, alt_ids="78", 
+                                       recorded_from_year=1960, recorded_to_year=2000, recording_context_id=2, 
+                                       approx_duration="1:00:00", doctype_code=2468, travail="travail 2", 
+                                       state="etat 2", cnrs_contributor="Richard LIONHEART", items_done="fiches 2", 
+                                       a_informer_07_03="a informer 2", ad_conversion_id=8, 
+                                       public_access="metadata")
+
+        self.volonte.save_by_user(self.olivier)
+
+        self.nicolas = MediaCollection(id=3, reference="A3", physical_format_id=333, old_code="30", code="300", 
+                                       title="petit nicolas", alt_title="slt", creator="Georgette McKenic", 
+                                       booklet_author="Francesca DICORTO", booklet_description="notice 3", 
+                                       collector="Paul MAILLE", collector_is_creator=0, publisher_id=3773, 
+                                       year_published=1999, publisher_collection_id=7890, publisher_serial="8764", 
+                                       external_references="ref externes", acquisition_mode_id=3, 
+                                       comment="commentaire 3", metadata_author_id=3, metadata_writer_id=3, 
+                                       legal_rights_id=3, alt_ids="56", recorded_from_year=1967, 
+                                       recorded_to_year=1968, recording_context_id=3, approx_duration="0:00:00", 
+                                       doctype_code=5790, travail="travail 3", state="etat 3", 
+                                       cnrs_contributor="Gerard MICKAEL", items_done="fiches 3", 
+                                       a_informer_07_03="a informer 3", ad_conversion_id=8, public_access="none")
+                                   
+        self.nicolas.save_by_user(self.olivier)
+     
+        MediaItem.objects.all().delete()        
+        self.item_1 = MediaItem(id=1, collection=self.persepolis, track="1111", old_code="101", code="1010", 
+                                approx_duration="00:01:00", recorded_from_date="1971-01-12", 
+                                recorded_to_date="1971-02-24", location_name=self.paris, 
+                                location_comment="capital de la France", ethnic_group=self.a, 
+                                title="item 1", alt_title="I1", author="Mickael SHEPHERD", 
+                                context_comment="contexte ethno 1", external_references="ext ref 1", 
+                                moda_execut="moda exec 1",copied_from_item_id=99, 
+                                collector="Charles PREMIER", cultural_area="Ile de France", generic_style_id=1, 
+                                collector_selection="collec sel 1", creator_reference="ref du deposant 1", 
+                                comment="comment 1", filename="item 1.item", public_access="full") 
+
+        self.item_1.save_by_user(self.david)
+
+        self.item_2 = MediaItem(id=2, collection=self.volonte, track="2222", old_code="202", code="2020", 
+                                approx_duration="00:02:00", recorded_from_date="1981-01-12", 
+                                recorded_to_date="1991-02-24", location_name=self.france, 
+                                location_comment="loc comment 2", ethnic_group=self.a, title="item 2",
+                                alt_title="I2", author="Rick ROLL", context_comment="contexte ethno 2", 
+                                external_references="ext ref 2", moda_execut="moda exec 2", 
+                                copied_from_item_id=98, collector="Gerard LENORMAND", 
+                                cultural_area="Nord de la France", generic_style_id=1,
+                                collector_selection="collec sel 2", creator_reference="ref du deposant 2", 
+                                comment="comment 2", filename="item 2.item", public_access="metadata") 
+
+        self.item_2.save_by_user(self.david)
+
+        self.item_3 = MediaItem(id=3, collection=self.nicolas, track="3333", old_code="303", code="3030", 
+                                approx_duration="00:03:00", recorded_from_date="1968-01-12", 
+                                recorded_to_date="1968-02-24", location_name=self.belgique, 
+                                location_comment="en Europe", ethnic_group=self.b, title="item 3", 
+                                alt_title="I3", author="John SMITH", context_comment="contexte ethno 3", 
+                                external_references="ext ref 3", moda_execut="moda exec 3", copied_from_item_id=97, 
+                                collector="Paul CARLOS", cultural_area="Europe occidentale", generic_style_id=1,
+                                collector_selection="collec sel 3", creator_reference="ref du deposant 3", 
+                                comment="comment 3", filename="item 3.item", public_access="none")
+
+        self.item_3.save_by_user(self.olivier)
+
+        self.item_4 = MediaItem(id=4, collection=self.persepolis, track="4444", old_code="404", code="4040", 
+                                approx_duration="00:04:00", recorded_from_date="1972-01-12", 
+                                recorded_to_date="1972-02-24", location_name=self.europe, 
+                                location_comment="loc comm 4", ethnic_group=self.a, title="item 4", 
+                                alt_title="I4", author="Keanu REAVES", context_comment="contexte ethno 4", 
+                                external_references="ext ref 4", moda_execut="moda exec 4", copied_from_item_id=96, 
+                                collector="Christina BARCELONA", cultural_area="aire culturelle 4", 
+                                generic_style_id=1, collector_selection="collec sel 4", 
+                                creator_reference="ref du deposant 4", comment="comment 4", filename="item 4.item",
+                                public_access="none")
+
+        self.item_4.save_by_user(self.olivier)
+
+        self.item_5 = MediaItem(id=5, collection=self.volonte, track="5555", old_code="505", code="5050", 
+                                approx_duration="00:05:00", recorded_from_date="1978-01-12", 
+                                recorded_to_date="1978-02-24", location_name=self.belgique, 
+                                location_comment="loc comm 5", ethnic_group=self.a, title="item 5", 
+                                alt_title="I5", author="Simon PAUL", context_comment="contexte ethno 5", 
+                                external_references="ext ref 5", moda_execut="moda exec 5", copied_from_item_id=95, 
+                                collector="Javier BARDEM", cultural_area="aire culturelle 5", generic_style_id=1,
+                                collector_selection="collec sel 5", creator_reference="ref du deposant 5", 
+                                comment="comment 5", filename="item 5.item", public_access="metadata")
+
+        self.item_5.save_by_user(self.olivier)
+
+        self.item_6 = MediaItem(id=6, collection=self.persepolis, track="10000", old_code="1111111", 
+                                code="6060", approx_duration="10:03:00", recorded_from_date="1968-01-12", 
+                                recorded_to_date="1968-02-11", location_name=self.france, 
+                                location_comment="loc comment 10000", ethnic_group=self.b, 
+                                title="item 6", alt_title="I10000", author="Paul ANDERSON", 
+                                context_comment="contexte ethno 10000", external_references="ext ref 10000", 
+                                moda_execut="moda exec 10000", copied_from_item_id=11111, collector="Jim CARLSON", 
+                                cultural_area="cul area 10000", generic_style_id=1, 
+                                collector_selection="collec sel 10000", creator_reference="ref du deposant 10000", 
+                                comment="comment 10000", filename="item 10000.item", public_access="none")
+        
+        self.item_6.save_by_user(self.david)
+
+        self.collections = MediaCollection.objects.all()
+        self.items       = MediaItem.objects.all()
+
+    def testQuickSearchOnCollections(self):
+        "Test quick_search property of MediaCollection class"
+        result = self.collections.quick_search("persepolis")
+        self.assertEquals(len(result), 1)
+        self.assertEquals(result[0], self.persepolis)
+        self.assertEquals(self.collections.quick_search("nietzsche")[0], self.volonte)
+        result = self.collections.quick_search("nicolas")
+        self.assertEquals(result[0], self.nicolas)
+
+    def testQuickSearchOnItems(self):
+        "Test quick_search property of MediaItem class"
+        result = self.items.quick_search("item").order_by("title")
+        self.assertEquals(result[0], self.item_1)
+        self.assertEquals(result[1], self.item_2)
+        self.assertEquals(result[2], self.item_3)
+        self.assertEquals(result[3], self.item_4)
+        self.assertEquals(result[4], self.item_5)
+        self.assertEquals(result[5], self.item_6)
+
+    def testWordSearch(self):
+        "Test quick_search property of MediaCollection class, specificly quick_search on collection title"
+        result = self.collections.quick_search("volonté puissance")
+        self.assertEquals(result[0], self.volonte)
+        result = self.collections.quick_search("puissance volonté")
+        self.assertEquals(result[0], self.volonte)
+        result = self.collections.quick_search("volonte puissance")
+        self.assertEquals(result[0], self.volonte)
+        result = self.collections.quick_search("puissance volonte")
+        self.assertEquals(result[0], self.volonte)
+        
+    def testLocationSearch(self):
+        "Test by_country and by_continent properties of MediaCollection class"
+        self.assertEquals(self.collections.by_country("France")[0], self.persepolis)
+        self.assertEquals(self.collections.by_continent("Europe")[0], self.persepolis)
+        self.assertEquals(self.collections.by_country("Belgique").order_by("title")[0], self.nicolas)
+        self.assertEquals(self.collections.by_country("Belgique").order_by("title")[1], self.volonte)
+
+    def testRecordingYear(self): 
+        "Test by_recording_year property of MediaCollection class"
+        self.assertEquals(self.collections.by_recording_year(1970, 1980)[0], self.persepolis)
+        result = self.collections.by_recording_year(1975).order_by("title")
+        self.assertEquals(result[0], self.persepolis)
+        self.assertEquals(result[1], self.volonte)
+    
+    def testPublishYearOnCollection(self):
+        "Test by_publish_year property of MediaCollection class"
+        result=self.collections.by_publish_year(1999).order_by("title")
+        self.assertEquals(result[0], self.nicolas)
+        self.assertEquals(result[1], self.volonte)
+        
+    def testEthnicGroup(self):
+        "Test by_ethnic_group property of MediaCollection class"
+        result=self.collections.by_ethnic_group("a").order_by("title")
+        self.assertEquals(result[0], self.persepolis)
+        self.assertEquals(result[1], self.volonte)
+
+    def testRecordingDate(self):
+        "Test by_recording_date property of MediaItem class"
+        result = self.items.by_recording_date("1968-01-01", "1972-12-12").order_by("title")
+        self.assertEquals(result[0], self.item_1)
+        self.assertEquals(result[1], self.item_3)
+        self.assertEquals(result[2], self.item_4)
+        self.assertEquals(result[3], self.item_6)
+        result = self.items.by_recording_date("1968-02-06").order_by("title")
+        self.assertEquals(result[0], self.item_3)
+        self.assertEquals(result[1], self.item_6)
+
+    def testTitle(self):
+        "Test by_title property of MediaItem class"
+        result = self.items.by_title("item").order_by("title")
+        self.assertEquals(result[0], self.item_1)
+        self.assertEquals(result[1], self.item_2)
+        self.assertEquals(result[2], self.item_3)
+        self.assertEquals(result[3], self.item_4)
+        self.assertEquals(result[4], self.item_5)
+        self.assertEquals(result[5], self.item_6)
+        result = self.items.by_title("volonté").order_by("title")
+        self.assertEquals(result[0], self.item_2)
+        self.assertEquals(result[1], self.item_5)
+        result = self.items.by_title("puissance volonté").order_by("title")
+        self.assertEquals(result[0], self.item_2)
+        self.assertEquals(result[1], self.item_5)
+
+    def testPublishYearOnItem(self):
+        "Test by_publish_year property of MediaItem class"
+        result = self.items.by_publish_year(1999).order_by("title")
+        self.assertEquals(result[0], self.item_2)
+        self.assertEquals(result[1], self.item_3)
+        self.assertEquals(result[2], self.item_5)
+    
+    def testWordSearchCore(self):
+        "Test word_search property of CoreQuerySet class"
+        self.assertEquals(self.collections.word_search("title", "volonté")[0], self.volonte)
+        self.assertEquals(self.collections.word_search("code", "100")[0], self.persepolis)
+        self.assertEquals(self.items.word_search("code", "1010")[0], self.item_1)
+        result = self.items.word_search("comment", "comment").order_by("title")
+        self.assertEquals(result[0], self.item_1)
+        self.assertEquals(result[1], self.item_2)
+        self.assertEquals(result[2], self.item_3)
+        self.assertEquals(result[3], self.item_4)
+        self.assertEquals(result[4], self.item_5)
+        self.assertEquals(result[5], self.item_6)
+    
+    def testByChangeTimeOnCollection(self):
+        "Test by_change_time property of MediaCollection class"
+        now = datetime.now()
+        result = self.collections.by_change_time(now - timedelta(hours=1), now).order_by("title")
+        self.assertEquals(result[0], self.persepolis)
+
+    def testByChangeTimeOnItem(self):
+        "Test by_change_time property of MediaItem class"
+        now = datetime.now()
+        result = self.items.by_change_time(now - timedelta(hours=1), now).order_by("title")
+        self.assertEquals(result[0], self.item_1)
+
+    def testWithoutCollection(self):
+        "Test without_collection property of MediaItem class"
+        self.assertEquals(self.items.without_collection().count(), 0)