Commits

Andy Mikhailenko committed f7e9010

Added basic validation of images in ImageField. Added documentation.

  • Participants
  • Parent commits dd64581

Comments (0)

Files changed (1)

File doqu/fields/files.py

 from .base import Field
 
 
-__all__ = ['FileField', 'ImageField']
+__all__ = ['FileField', 'ImageField',
+           'FileWrapper', 'ImageWrapper']
 
 
 class FileWrapper(object):
+    """A thin wrapper around the `file` object. In most cases you'll only want
+    to know about :attr:`FileWrapper.file`.
+    """
     def __init__(self, base_path, stream=None, path=None, saved=False):
         assert stream or path
         self.path = os.path.split(stream.name)[1] if stream else path
+        self.saved = saved
 
         if hasattr(base_path, '__call__'):
             self.base_path = base_path()
 
         self._stream = stream or open(self.full_path, 'rb')
 
-        self.saved = saved
-
     def __repr__(self):
         return '<{cls}: {path}>'.format(
             cls=self.__class__.__name__,
 
     @cached_property
     def file(self):
+        """The actual file-like object.
+        """
+        # TODO: validate (at least check extension)
         return self._stream
 
     @property
     def full_path(self):
+        """Full path to the file. If the file was not saved yet (e.g. changed
+        or just added), returns `None`.
+        """
+        if not self.saved:
+            return None
         return os.path.join(self.base_path, self.path)
 
     def save(self):
+        """If the `saved` flag is present (i.e. if the object was fetched from
+        the database and nothing was changed), does nothing. If not, saves the
+        stream to a *new* unique path generated with the original path in mind.
+        """
         if self.saved:
             return
         assert self._stream
 
     @cached_property
     def file(self):
+        """An `Image` instance that represents current file.
+        """
         if Image is None:
             raise ImportError('PIL is not installed.')
-        return Image.open(self._stream)
+        image = Image.open(self._stream)
+        image.verify()  # TODO: wrap exception
+        # workaround: verify() breaks the image (image.fh becomes None)
+        self._stream.seek(0)
+        image = Image.open(self._stream)
+        return image
 
 
 class FileField(Field):
     Usage::
 
         class Doc(Document):
-            attachment = FileField()
+            attachment = FileField(base_path=MEDIA_ROOT+'attachments/')
 
         d = Doc()
         d.attachment = open('foo.txt')
         dd = Doc.objects(db)[0]
         print dd.attachment.file.read()
 
+    :param base_path:
+
+        A string or callable: the directory where the files should be stored.
+
     """
     skip_type_conversion = True
     file_wrapper_class = FileWrapper
 
 
 class ImageField(FileField):
+    """A :class:`FileField` that provides extended support for images. The
+    :attr:`ImageField.file` is an :class:`ImageWrapper` instance.
+
+    Usage::
+
+        class Photo(Document):
+            summary = Field(unicode)
+            image = ImageField(base_path='photos/')
+
+        p = Photo(summary='Fido', image=open('fido.jpg'))
+        p.save(db)
+
+        # playing with image
+        print "The photo is {0}×{1}px".format(*p.image.size)
+        p.image.rotate(90)
+        p.image.save()
+
+    """
     file_wrapper_class = ImageWrapper