无法在 Django 应用程序中创建图像字段

Can't create image field in Django app

我正在开发一个 Django 应用程序,其核心功能是基于存储相册。我正在使用 Django 1.8.1 和 Python 3.

以下是我目前创建的模型:

import uuid
from django.db import models
from django.conf import settings
from django.utils import timezone

# Create your models here.
class Album(models.Model):
    """ 
    Model for an album
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField(max_length=200)
    description = models.TextField()
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True)

    def __str__(self):
        return self.title


class Photo(models.Model):
    """ 
    Model for a photo
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    album = models.ForeignKey(Album)
    datetime = models.DateTimeField(default=timezone.now, blank=True)
    image = models.ImageField(upload_to="images", blank=True, null=True)

测试如下:

from django.test import TestCase
from django.contrib.auth.models import User
from django.utils import timezone
from django.core.files import File
from albums.models import Album, Photo
from unittest import mock
import factory.django

class UserFactory(factory.django.DjangoModelFactory):
    """ 
    Factory for users
    """
    class Meta:
        model = User
        django_get_or_create = ('first_name', 'last_name', 'username','email', 'password',)

    first_name = 'Roger'
    last_name = 'Smith'
    username = 'user'
    email = 'user@example.com'
    password = 'password'


class AlbumFactory(factory.django.DjangoModelFactory):
    """ 
    Factory for Albums
    """
    class Meta:
        model = Album
        django_get_or_create = ['title', 'description', 'owner']

    title = 'My brilliant album'
    description = 'An album of extreme awesomeness'


class PhotoFactory(factory.django.DjangoModelFactory):
    """ 
    Factory for Photos
    """
    class Meta:
        model = Photo
        django_get_or_create = ['album', 'datetime', 'image']

    datetime = timezone.now()
    image = File(open("albums/tests/sample_image.jpg"))

class AlbumTest(TestCase):
    """ 
    Test album model
    """
    def test_create_album(self):
        # Create user
        user = UserFactory()

        # Create album
        album = AlbumFactory(owner=user)

        # Check we can find it
        all_albums = Album.objects.all()
        self.assertEqual(len(all_albums), 1)
        only_album = all_albums[0]
        self.assertEqual(only_album, album)

        # Check attributes
        self.assertEqual(only_album.title, 'My brilliant album')
        self.assertEqual(only_album.description, 'An album of extreme awesomeness')
        self.assertEqual(only_album.owner, user)


class PhotoTest(TestCase):
    """
    Test photo model
    """
    def test_create_photo(self):
        # Create user
        user = UserFactory()

        # Create album
        album = AlbumFactory(owner=user)

        # Create photo
        photo = PhotoFactory(album=album)

        # Check we can find it
        all_photos = Photo.objects.all()
        self.assertEqual(len(all_photos), 1)
        only_photo = all_photos[0]
        self.assertEqual(only_photo, photo)

        # Check attributes
        self.assertEqual(only_photo.album.pk, album.pk)
        self.assertEqual(only_photo.datetime, photo.datetime)

现在,我正在使用 Factory Boy 创建测试中的对象。当我尝试使用 Factory Boy 创建 Photo 对象时,出现以下错误:

Python 3.4.0 (default, Apr 11 2014, 13:05:11) 
Type "copyright", "credits" or "license" for more information.

IPython 3.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: from albums.models import *

In [2]: from albums.tests import *

In [3]: user = UserFactory()

In [4]: album = AlbumFactory(owner=user)

In [5]: photo = PhotoFactory(album=album)
---------------------------------------------------------------------------
DoesNotExist                              Traceback (most recent call last)
/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/query.py in get_or_create(self, defaults, **kwargs)
    404         try:
--> 405             return self.get(**lookup), False
    406         except self.model.DoesNotExist:

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/query.py in get(self, *args, **kwargs)
    333                 "%s matching query does not exist." %
--> 334                 self.model._meta.object_name
    335             )

DoesNotExist: Photo matching query does not exist.

During handling of the above exception, another exception occurred:

UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-5-4b467adb13ef> in <module>()
----> 1 photo = PhotoFactory(album=album)

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/factory/base.py in __call__(cls, **kwargs)
     79             return cls.build(**kwargs)
     80         elif cls._meta.strategy == CREATE_STRATEGY:
---> 81             return cls.create(**kwargs)
     82         elif cls._meta.strategy == STUB_STRATEGY:
     83             return cls.stub(**kwargs)

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/factory/base.py in create(cls, **kwargs)
    557         """Create an instance of the associated class, with overriden attrs."""
    558         attrs = cls.attributes(create=True, extra=kwargs)
--> 559         return cls._generate(True, attrs)
    560
    561     @classmethod

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/factory/base.py in _generate(cls, create, attrs)
    482
    483         # Generate the object
--> 484         obj = cls._prepare(create, **attrs)
    485
    486         # Handle post-generation attributes

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/factory/base.py in _prepare(cls, create, **kwargs)
    457         )
    458         if create:
--> 459             return cls._create(model_class, *args, **kwargs)
    460         else:
    461             return cls._build(model_class, *args, **kwargs)
/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/factory/django.py in _create(cls, model_class, *args, **kwargs)
    145
    146         if cls._meta.django_get_or_create:
--> 147             return cls._get_or_create(model_class, *args, **kwargs)
    148
    149         return manager.create(*args, **kwargs)

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/factory/django.py in _get_or_create(cls, model_class, *args, **kwargs)
    136         key_fields['defaults'] = kwargs
    137
--> 138         obj, _created = manager.get_or_create(*args, **key_fields)
    139         return obj
    140

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/manager.py in manager_method(self, *args, **kwargs)
    125         def create_method(name, method):
    126             def manager_method(self, *args, **kwargs):
--> 127                 return getattr(self.get_queryset(), name)(*args, **kwargs)
    128             manager_method.__name__ = method.__name__
    129             manager_method.__doc__ = method.__doc__

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/query.py in get_or_create(self, defaults, **kwargs)
    405             return self.get(**lookup), False
    406         except self.model.DoesNotExist:
--> 407             return self._create_object_from_params(lookup, params)
    408
    409     def update_or_create(self, defaults=None, **kwargs):

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/query.py in _create_object_from_params(self, lookup, params)
    437         try:
    438             with transaction.atomic(using=self.db):
--> 439                 obj = self.create(**params)
    440             return obj, True
    441         except IntegrityError:

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/query.py in create(self, **kwargs)
    346         obj = self.model(**kwargs)
    347         self._for_write = True
--> 348         obj.save(force_insert=True, using=self.db)
    349         return obj
    350

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/base.py in save(self, force_insert, force_update, using, update_fields)
    708
    709         self.save_base(using=using, force_insert=force_insert,
--> 710                        force_update=force_update, update_fields=update_fields)
    711     save.alters_data = True
    712

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/base.py in save_base(self, raw, force_insert, force_update, using, update_fields)
    736             if not raw:
    737                 self._save_parents(cls, using, update_fields)
--> 738             updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
    739         # Store the database on which the object was saved
    740         self._state.db = using

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/base.py in _save_table(self, raw, cls, force_insert, force_update, using, update_fields)
    820
    821             update_pk = bool(meta.has_auto_field and not pk_set)
--> 822             result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
    823             if update_pk:
    824                 setattr(self, meta.pk.attname, result)

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/base.py in _do_insert(self, manager, using, fields, update_pk, raw)
    859         """
    860         return manager._insert([self], fields=fields, return_id=update_pk,
--> 861                                using=using, raw=raw)
    862
    863     def delete(self, using=None):

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/manager.py in manager_method(self, *args, **kwargs)
    125         def create_method(name, method):
    126             def manager_method(self, *args, **kwargs):
--> 127                 return getattr(self.get_queryset(), name)(*args, **kwargs)
    128             manager_method.__name__ = method.__name__
    129             manager_method.__doc__ = method.__doc__

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/query.py in _insert(self, objs, fields, return_id, raw, using)
    918         query = sql.InsertQuery(self.model)
    919         query.insert_values(fields, objs, raw=raw)
--> 920         return query.get_compiler(using=using).execute_sql(return_id)
    921     _insert.alters_data = True
    922     _insert.queryset_only = False

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/sql/compiler.py in execute_sql(self, return_id)
    968         self.return_id = return_id
    969         with self.connection.cursor() as cursor:
--> 970             for sql, params in self.as_sql():
    971                 cursor.execute(sql, params)
    972             if not (return_id and cursor):

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/sql/compiler.py in as_sql(self)
    926                     ) for f in fields
    927                 ]
--> 928                 for obj in self.query.objs
    929             ]
    930         else:

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/sql/compiler.py in <listcomp>(.0)
    926                     ) for f in fields
    927                 ]
--> 928                 for obj in self.query.objs
    929             ]
    930         else:

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/sql/compiler.py in <listcomp>(.0)
    924                         getattr(obj, f.attname) if self.query.raw else f.pre_save(obj, True),
    925                         connection=self.connection
--> 926                     ) for f in fields
    927                 ]
    928                 for obj in self.query.objs

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/fields/files.py in pre_save(self, model_instance, add)
    313         if file and not file._committed:
    314             # Commit the file to storage prior to saving the model
--> 315             file.save(file.name, file, save=False)
    316         return file
    317

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/db/models/fields/files.py in save(self, name, content, save)
     92         args, varargs, varkw, defaults = getargspec(self.storage.save)
     93         if 'max_length' in args:
---> 94             self.name = self.storage.save(name, content, max_length=self.field.max_length)
     95         else:
     96             warnings.warn(

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/core/files/storage.py in save(self, name, content, max_length)
     62             name = self.get_available_name(name)
     63
---> 64         name = self._save(name, content)
     65
     66         # Store filenames with forward slashes, even on Windows

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/core/files/storage.py in _save(self, name, content)
    251                     try:
    252                         locks.lock(fd, locks.LOCK_EX)
--> 253                         for chunk in content.chunks():
    254                             if _file is None:
    255                                 mode = 'wb' if isinstance(chunk, bytes) else 'wt'

/home/matthew/Projects/myproject/venv/lib/python3.4/site-packages/django/core/files/base.py in chunks(self, chunk_size)
     83
     84         while True:
---> 85             data = self.read(chunk_size)
     86             if not data:
     87                 break

/usr/lib/python3.4/codecs.py in decode(self, input, final)
    311         # decode input (taking the buffer into account)
    312         data = self.buffer + input
--> 313         (result, consumed) = self._buffer_decode(data, self.errors, final)
    314         # keep undecoded input until the next call
    315         self.buffer = data[consumed:]

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte

如果我尝试手动定义图像,也会发生同样的事情。据我所知,图像的路径是正确的。另外,我在 Python 2.7 上对 Django 1.7 项目 运行 使用了类似的东西,没有问题,所以它可能是 Python 3.

特有的东西

谁能看出我哪里出错了?我可以使用 Django 管理界面创建新的照片对象,所以我认为问题不在于模型,而在于我在测试中如何与它交互。

最后找到方法,使用SimpleUploadedFile:

from django.core.files.uploadedfile import SimpleUploadedFile


class PhotoFactory(factory.django.DjangoModelFactory):
    """ 
    Factory for Photos
    """
    class Meta:
        model = Photo
        django_get_or_create = ['album', 'datetime', 'image']

    datetime = timezone.now()
    image = SimpleUploadedFile(name='sample_image.jpg', content=open("albums/tests/sample_image.jpg", 'rb').read(), content_type='image/jpeg')