Add a new migration which removes/fixes the old migration
[mediagoblin.git] / mediagoblin / db / mixin.py
index 92834a72b4e6a6331ae02a374742135d093813f6..1f2e7ec30262d762deab08704059cc15b02bd6f2 100644 (file)
@@ -28,34 +28,42 @@ real objects.
 """
 
 import uuid
+import re
+from datetime import datetime
 
 from werkzeug.utils import cached_property
 
 from mediagoblin import mg_globals
-from mediagoblin.auth import lib as auth_lib
-from mediagoblin.media_types import get_media_managers, FileTypeNotSupported
+from mediagoblin.media_types import FileTypeNotSupported
 from mediagoblin.tools import common, licenses
+from mediagoblin.tools.pluginapi import hook_handle
 from mediagoblin.tools.text import cleaned_markdown_conversion
 from mediagoblin.tools.url import slugify
 
 
 class UserMixin(object):
-    def check_login(self, password):
-        """
-        See if a user can login with this password
-        """
-        return auth_lib.bcrypt_check_password(
-            password, self.pw_hash)
-
     @property
     def bio_html(self):
         return cleaned_markdown_conversion(self.bio)
 
+    def url_for_self(self, urlgen, **kwargs):
+        """Generate a URL for this User's home page."""
+        return urlgen('mediagoblin.user_pages.user_home',
+                      user=self.username, **kwargs)
 
-class MediaEntryMixin(object):
+
+class GenerateSlugMixin(object):
+    """
+    Mixin to add a generate_slug method to objects.
+
+    Depends on:
+     - self.slug
+     - self.title
+     - self.check_slug_used(new_slug)
+    """
     def generate_slug(self):
         """
-        Generate a unique slug for this MediaEntry.
+        Generate a unique slug for this object.
 
         This one does not *force* slugs, but usually it will probably result
         in a niceish one.
@@ -76,19 +84,15 @@ class MediaEntryMixin(object):
            generated bits until it's unique.  That'll be a little bit of junk,
            but at least it has the basis of a nice slug.
         """
-        # import this here due to a cyclic import issue
-        # (db.models -> db.mixin -> db.util -> db.models)
-        from mediagoblin.db.util import check_media_slug_used
-
         #Is already a slug assigned? Check if it is valid
         if self.slug:
             self.slug = slugify(self.slug)
-  
+
         # otherwise, try to use the title.
         elif self.title:
             # assign slug based on title
             self.slug = slugify(self.title)
-     
+
         # We don't want any empty string slugs
         if self.slug == u"":
             self.slug = None
@@ -98,26 +102,34 @@ class MediaEntryMixin(object):
         # so just return... we're not going to force one.
         if not self.slug:
             return  # giving up!
-  
+
         # Otherwise, let's see if this is unique.
-        if check_media_slug_used(self.uploader, self.slug, self.id):
+        if self.check_slug_used(self.slug):
             # It looks like it's being used... lame.
-        
+
             # Can we just append the object's id to the end?
             if self.id:
                 slug_with_id = u"%s-%s" % (self.slug, self.id)
-                if not check_media_slug_used(self.uploader,
-                                             slug_with_id, self.id):
+                if not self.check_slug_used(slug_with_id):
                     self.slug = slug_with_id
                     return  # success!
-        
+
             # okay, still no success;
             # let's whack junk on there till it's unique.
             self.slug += '-' + uuid.uuid4().hex[:4]
             # keep going if necessary!
-            while check_media_slug_used(self.uploader, self.slug, self.id):
+            while self.check_slug_used(self.slug):
                 self.slug += uuid.uuid4().hex[:4]
 
+
+class MediaEntryMixin(GenerateSlugMixin):
+    def check_slug_used(self, slug):
+        # import this here due to a cyclic import issue
+        # (db.models -> db.mixin -> db.util -> db.models)
+        from mediagoblin.db.util import check_media_slug_used
+
+        return check_media_slug_used(self.uploader, slug, self.id)
+
     @property
     def description_html(self):
         """
@@ -129,7 +141,7 @@ class MediaEntryMixin(object):
     def get_display_media(self):
         """Find the best media for display.
 
-        We try checking self.media_data.fetching_order if it exists to
+        We try checking self.media_manager.fetching_order if it exists to
         pull down the order.
 
         Returns:
@@ -137,7 +149,7 @@ class MediaEntryMixin(object):
           or, if not found, None.
 
         """
-        fetch_order = self.media_manager.get("media_fetch_order")
+        fetch_order = self.media_manager.media_fetch_order
 
         # No fetching order found?  well, give up!
         if not fetch_order:
@@ -190,20 +202,31 @@ class MediaEntryMixin(object):
             thumb_url = mg_globals.app.staticdirector(manager[u'default_thumb'])
         return thumb_url
 
+    @property
+    def original_url(self):
+        """ Returns the URL for the original image
+        will return self.thumb_url if original url doesn't exist"""
+        if u"original" not in self.media_files:
+            return self.thumb_url
+        
+        return mg_globals.app.public_store.file_url(
+            self.media_files[u"original"]
+            )
+
     @cached_property
     def media_manager(self):
         """Returns the MEDIA_MANAGER of the media's media_type
 
         Raises FileTypeNotSupported in case no such manager is enabled
         """
-        # TODO, we should be able to make this a simple lookup rather
-        # than iterating through all media managers.
-        for media_type, manager in get_media_managers():
-            if media_type == self.media_type:
-                return manager
+        manager = hook_handle(('media_manager', self.media_type))
+        if manager:
+            return manager(self)
+
         # Not found?  Then raise an error
         raise FileTypeNotSupported(
-            "MediaManager not in enabled types.  Check media_types in config?")
+            "MediaManager not in enabled types. Check media_type plugins are"
+            " enabled in config?")
 
     def get_fail_exception(self):
         """
@@ -217,15 +240,60 @@ class MediaEntryMixin(object):
         return licenses.get_license_by_url(self.license or "")
 
     def exif_display_iter(self):
-        from mediagoblin.tools.exif import USEFUL_TAGS
+        if not self.media_data:
+            return
+        exif_all = self.media_data.get("exif_all")
+
+        for key in exif_all:
+            label = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', key)
+            yield label.replace('EXIF', '').replace('Image', ''), exif_all[key]
 
+    def exif_display_data_short(self):
+        """Display a very short practical version of exif info"""
         if not self.media_data:
             return
+
         exif_all = self.media_data.get("exif_all")
 
-        for key in USEFUL_TAGS:
-            if key in exif_all:
-                yield key, exif_all[key]
+        exif_short = {}
+
+        if 'Image DateTimeOriginal' in exif_all:
+            # format date taken
+            takendate = datetime.strptime(
+                exif_all['Image DateTimeOriginal']['printable'],
+                '%Y:%m:%d %H:%M:%S').date()
+            taken = takendate.strftime('%B %d %Y')
+
+            exif_short.update({'Date Taken': taken})
+
+        aperture = None
+        if 'EXIF FNumber' in exif_all:
+            fnum = str(exif_all['EXIF FNumber']['printable']).split('/')
+
+            # calculate aperture
+            if len(fnum) == 2:
+                aperture = "f/%.1f" % (float(fnum[0])/float(fnum[1]))
+            elif fnum[0] != 'None':
+                aperture = "f/%s" % (fnum[0])
+
+        if aperture:
+            exif_short.update({'Aperture': aperture})
+
+        short_keys = [
+            ('Camera', 'Image Model', None),
+            ('Exposure', 'EXIF ExposureTime', lambda x: '%s sec' % x),
+            ('ISO Speed', 'EXIF ISOSpeedRatings', None),
+            ('Focal Length', 'EXIF FocalLength', lambda x: '%s mm' % x)]
+
+        for label, key, fmt_func in short_keys:
+            try:
+                val = fmt_func(exif_all[key]['printable']) if fmt_func \
+                        else exif_all[key]['printable']
+                exif_short.update({label: val})
+            except KeyError:
+                pass
+
+        return exif_short
 
 
 class MediaCommentMixin(object):
@@ -237,23 +305,28 @@ class MediaCommentMixin(object):
         """
         return cleaned_markdown_conversion(self.content)
 
+    def __unicode__(self):
+        return u'<{klass} #{id} {author} "{comment}">'.format(
+            klass=self.__class__.__name__,
+            id=self.id,
+            author=self.get_author,
+            comment=self.content)
 
-class CollectionMixin(object):
-    def generate_slug(self):
+    def __repr__(self):
+        return '<{klass} #{id} {author} "{comment}">'.format(
+            klass=self.__class__.__name__,
+            id=self.id,
+            author=self.get_author,
+            comment=self.content)
+
+
+class CollectionMixin(GenerateSlugMixin):
+    def check_slug_used(self, slug):
         # import this here due to a cyclic import issue
         # (db.models -> db.mixin -> db.util -> db.models)
         from mediagoblin.db.util import check_collection_slug_used
 
-        self.slug = slugify(self.title)
-
-        duplicate = check_collection_slug_used(mg_globals.database,
-            self.creator, self.slug, self.id)
-
-        if duplicate:
-            if self.id is not None:
-                self.slug = u"%s-%s" % (self.id, self.slug)
-            else:
-                self.slug = None
+        return check_collection_slug_used(self.creator, slug, self.id)
 
     @property
     def description_html(self):