从 Django shell 中将照片上传到 Photologue 的自定义命令?
Custom command to upload photo to Photologue from within Django shell?
我已成功使用 Photologue 来展示 galleries of regularly-created data plot images。当然,既然已经建立了能力,就会创建大量的数据图,需要共享它们!
使用 Django shell 中的 manage.py
编写上传图像并将它们添加到画廊的过程脚本;但是,作为 Django 的业余爱好者,我遇到了一些困难。
这是我目前开发的自定义命令addphoto.py
:
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from photologue.models import Photo, Gallery
import os
from datetime import datetime
import pytz
class Command(BaseCommand):
help = 'Adds a photo to Photologue.'
def add_arguments(self, parser):
parser.add_argument('imagefile', type=str)
parser.add_argument('--title', type=str)
parser.add_argument('--date_added', type=str, help="datetime string in 'YYYY-mm-dd HH:MM:SS' format [UTC]")
parser.add_argument('--gallery', type=str)
def handle(self, *args, **options):
imagefile = options['imagefile']
if options['title']:
title = options['title']
else:
base = os.path.basename(imagefile)
title = os.path.splitext(base)[0]
if options['date_added']:
date_added = datetime.strptime(options['date_added'],'%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)
else:
date_added = timezone.now()
p = Photo(image=imagefile, title=title, date_added=date_added)
p.save()
不幸的是,当使用 --traceback
执行时,结果如下:
./manage.py addphoto '../dataplots/monitoring/test.png' --traceback
Failed to read EXIF DateTimeOriginal
Traceback (most recent call last):
File "/home/user/mysite/photologue/models.py", line 494, in save
exif_date = self.EXIF(self.image.file).get('EXIF DateTimeOriginal', None)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/db/models/fields/files.py", line 51, in _get_file
self._file = self.storage.open(self.name, 'rb')
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 38, in open
return self._open(name, mode)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 300, in _open
return File(open(self.path(name), mode))
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 405, in path
return safe_join(self.location, name)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/utils/_os.py", line 78, in safe_join
'component ({})'.format(final_path, base_path))
django.core.exceptions.SuspiciousFileOperation: The joined path (/home/user/mysite/dataplots/monitoring/test.png) is located outside of the base path component (/home/user/mysite/media)
Traceback (most recent call last):
File "./manage.py", line 22, in <module>
execute_from_command_line(sys.argv)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/management/__init__.py", line 363, in execute_from_command_line
utility.execute()
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/management/__init__.py", line 355, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/management/base.py", line 283, in run_from_argv
self.execute(*args, **cmd_options)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/management/base.py", line 330, in execute
output = self.handle(*args, **options)
File "/home/user/mysite/photologue/management/commands/addphoto.py", line 36, in handle
p.save()
File "/home/user/mysite/photologue/models.py", line 553, in save
super(Photo, self).save(*args, **kwargs)
File "/home/user/mysite/photologue/models.py", line 504, in save
self.pre_cache()
File "/home/user/mysite/photologue/models.py", line 472, in pre_cache
self.create_size(photosize)
File "/home/user/mysite/photologue/models.py", line 411, in create_size
if self.size_exists(photosize):
File "/home/user/mysite/photologue/models.py", line 364, in size_exists
if self.image.storage.exists(func()):
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 392, in exists
return os.path.exists(self.path(name))
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 405, in path
return safe_join(self.location, name)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/utils/_os.py", line 78, in safe_join
'component ({})'.format(final_path, base_path))
django.core.exceptions.SuspiciousFileOperation: The joined path (/home/user/mysite/dataplots/monitoring/cache/test_thumbnail.png) is located outside of the base path component (/home/user/mysite/media)
显然,图像文件的副本没有放在 media/
目录中。此外,虽然 image
、title
和 date_added
列填充在网站数据库的 photologue_photos
table 中,但 slug
列是没有。
如何将文件上传到MEDIA_ROOT
目录?
以下是 Photologue models.py
文件中 Photo
和 ImageModel
模型的相关片段,供参考:
class Photo(ImageModel):
title = models.CharField(_('title'),
max_length=250,
unique=True)
slug = models.SlugField(_('slug'),
unique=True,
max_length=250,
help_text=_('A "slug" is a unique URL-friendly title for an object.'))
caption = models.TextField(_('caption'),
blank=True)
date_added = models.DateTimeField(_('date added'),
default=now)
is_public = models.BooleanField(_('is public'),
default=True,
help_text=_('Public photographs will be displayed in the default views.'))
sites = models.ManyToManyField(Site, verbose_name=_(u'sites'),
blank=True)
objects = PhotoQuerySet.as_manager()
def save(self, *args, **kwargs):
if self.slug is None:
self.slug = slugify(self.title)
super(Photo, self).save(*args, **kwargs)
class ImageModel(models.Model):
image = models.ImageField(_('image'),
max_length=IMAGE_FIELD_MAX_LENGTH,
upload_to=get_storage_path)
date_taken = models.DateTimeField(_('date taken'),
null=True,
blank=True,
help_text=_('Date image was taken; is obtained from the image EXIF data.'))
view_count = models.PositiveIntegerField(_('view count'),
default=0,
editable=False)
crop_from = models.CharField(_('crop from'),
blank=True,
max_length=10,
default='center',
choices=CROP_ANCHOR_CHOICES)
effect = models.ForeignKey('photologue.PhotoEffect',
null=True,
blank=True,
related_name="%(class)s_related",
verbose_name=_('effect'))
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(ImageModel, self).__init__(*args, **kwargs)
self._old_image = self.image
def save(self, *args, **kwargs):
image_has_changed = False
if self._get_pk_val() and (self._old_image != self.image):
image_has_changed = True
# If we have changed the image, we need to clear from the cache all instances of the old
# image; clear_cache() works on the current (new) image, and in turn calls several other methods.
# Changing them all to act on the old image was a lot of changes, so instead we temporarily swap old
# and new images.
new_image = self.image
self.image = self._old_image
self.clear_cache()
self.image = new_image # Back to the new image.
self._old_image.storage.delete(self._old_image.name) # Delete (old) base image.
if self.date_taken is None or image_has_changed:
# Attempt to get the date the photo was taken from the EXIF data.
try:
exif_date = self.EXIF(self.image.file).get('EXIF DateTimeOriginal', None)
if exif_date is not None:
d, t = exif_date.values.split()
year, month, day = d.split(':')
hour, minute, second = t.split(':')
self.date_taken = datetime(int(year), int(month), int(day),
int(hour), int(minute), int(second))
except:
logger.error('Failed to read EXIF DateTimeOriginal', exc_info=True)
super(ImageModel, self).save(*args, **kwargs)
self.pre_cache()
这里是 get_storage_path
函数,根据要求:
# Look for user function to define file paths
PHOTOLOGUE_PATH = getattr(settings, 'PHOTOLOGUE_PATH', None)
if PHOTOLOGUE_PATH is not None:
if callable(PHOTOLOGUE_PATH):
get_storage_path = PHOTOLOGUE_PATH
else:
parts = PHOTOLOGUE_PATH.split('.')
module_name = '.'.join(parts[:-1])
module = import_module(module_name)
get_storage_path = getattr(module, parts[-1])
else:
def get_storage_path(instance, filename):
fn = unicodedata.normalize('NFKD', force_text(filename)).encode('ascii', 'ignore').decode('ascii')
return os.path.join(PHOTOLOGUE_DIR, 'photos', fn)
关于您问题的一部分:保存 Photo
时,slug
列为空。
它 应该 在保存 Photo
时自动填充 - 正如您对上面的 Photologue 源代码的复制和粘贴 if self.slug is None: self.slug = slugify(self.title)
清楚.
这表明 Photologue 源代码实际上不是从您的管理命令中调用的 - 您可以通过向 Photologue 代码的本地副本添加一些快速调试代码来检查这一点,例如save()
方法中的 print() 语句,并检查它是否 是 是 运行.
Edit/Update 2: 我想我知道为什么你的 slug 字段是空的。我认为问题在于 self.slug
不是 None
,即使该字段不包含字符串或包含空字符串(请参阅 this answer)。所以尝试将 if self.slug is None
更新为:
class Photo(ImageModel):
...
def save(self, *args, **kwargs):
# test if slug is not truthy, including empty string or None-ness
if not self.slug:
self.slug = slugify(self.title)
super(Photo, self).save(*args, **kwargs)
Edit/Update 1: 查看 this answer. It is from Django 1.4 (ancient, I know), but it should solve your problem. If you copy or move the files you are adding to MEDIA_ROOT
before creating the Photo
instances, then you should be good to go. Here is an answer 显示如何在 python 中复制文件。我建议您将自定义命令更改为:
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from photologue.models import Photo, Gallery
# so you can access settings.MEDIA_ROOT
from django.conf import settings
# so you can copy file to MEDIA_ROOT if need be
from shutil import copyfile
import os
from datetime import datetime
import pytz
class Command(BaseCommand):
help = 'Adds a photo to Photologue.'
def add_arguments(self, parser):
parser.add_argument('imagefile', type=str)
# where the imagefile is currently located, assumes MEDIA_ROOT
parser.add_argument('--media_source', type=str)
parser.add_argument('--title', type=str)
parser.add_argument('--date_added', type=str, help="datetime string in 'YYYY-mm-dd HH:MM:SS' format [UTC]")
parser.add_argument('--gallery', type=str)
def handle(self, *args, **options):
# the path of the file relative to media_source
imagefile = options['imagefile']
# if file is not in media root, copy it to there
if options['media_source']:
media_source = os.path.realpath(options['media_source'])
media_target = os.path.realpath(settings.MEDIA_ROOT)
if media_source != media_target:
copyfile(imagefile, os.path.join(media_target, imagefile)
# note: if media_source was not provided, assume file is already in MEDIA_ROOT
if options['title']:
title = options['title']
else:
base = os.path.basename(imagefile)
title = os.path.splitext(base)[0]
if options['date_added']:
date_added = datetime.strptime(options['date_added'],'%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)
else:
date_added = timezone.now()
p = Photo(image=imagefile, title=title, date_added=date_added)
p.save()
现在你的 imagefile
是相对于你的媒体源 (../dataplots
) 的,被复制到 MEDIA_ROOT
,一切都应该按计划进行。这是您的命令的样子
manage.py addphoto 'monitoring/test.png' --media_source='../dataplots'
应该将您的数据图复制到 MEDIA_ROOT
,然后按预期创建 Photo
实体。
原答案:
你能 post 下一行中的 get_storage_path
是什么吗:
class ImageModel(models.Model):
image = models.ImageField(_('image'),
max_length=IMAGE_FIELD_MAX_LENGTH,
upload_to=get_storage_path) # <-- here
在我知道那是什么之前,这个答案会有些不完整,但我想我明白了你的问题。看你的命令是运行:
./manage.py addphoto '../dataplots/monitoring/test.png' --traceback
你的 imagefile
参数是 ../dataplots/monitoring/test.png
。如果 get_storage_path
return 是相同的路径,路径的开头是 ../
,那么您将指定一个不在 MEDIA_ROOT
目录中的上传路径,但在其父目录中。我认为它正在尝试上传到 MEDIA_ROOT/../dataplots/monitoring/test.png
,如回溯中的第一个 SuspiciousFileOperation 所示:
# note: linebreaks and indentation added for readability
django.core.exceptions.SuspiciousFileOperation:
The joined path (/home/user/mysite/dataplots/monitoring/test.png)
is located outside of the base path component (/home/user/mysite/media)
所以它正在尝试上传到 MEDIA_ROOT/imagefile
,但是 imagefile
以 ../
开头,这是不允许的。
如果确实是这个问题(很难说直到你 post 代码 get_storage_path
),那么有很多方法可以解决这个问题。也许最快的解决方法就是将 dataplots
目录移动到与 manage.py
:
相同的目录中
mv ../dataplots ./dataplots
这应该可以直接解决您的问题,因为您不再需要 ../
,但您可能不希望项目目录中包含所有这些数据图,因此这是一个快速但效果不佳的解决方案。潜在的问题是源文件的路径和你上传源文件的路径不应该相等。我认为您应该更改命令参数以包含 image_source
和 image_destination
,或者您可以包含 source_media_root
和 imagefile
,其中 ../
是 source_media_root
,并且 imagefile
是相对于 source_media_root
和 MEDIA_ROOT
中所需的目标位置...有很多解决方案,但我不能完全提供正确的代码一个,直到我知道 get_storage_path
是什么(我假设它是一个函数 return 或可以 return imagefile
参数的一种或另一种方式)。
这是我最终创建的自定义 addphoto.py
命令的工作版本。
图像文件需要在MEDIA_ROOT/photologue/photos
以内以便于导入。使用 ./manage.py addphoto 'photologue/photos/test.png'
执行命令。请注意,有一个 --gallery
选项可将图像添加到图库,前提是图库的 slug
.
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from photologue.models import Photo, Gallery
import os
from datetime import datetime
import pytz
class Command(BaseCommand):
help = 'Adds a photo to Photologue.'
def add_arguments(self, parser):
parser.add_argument('imagefile',
type=str)
parser.add_argument('--title',
type=str)
parser.add_argument('--date_added',
type=str,
help="datetime string in 'YYYY-mm-dd HH:MM:SS' format [UTC]")
parser.add_argument('--gallery',
type=str)
def handle(self, *args, **options):
imagefile = options['imagefile']
base = os.path.basename(imagefile)
if options['title']:
title = options['title']
else:
title = os.path.splitext(base)[0]
if options['date_added']:
date_added = datetime.strptime(options['date_added'],'%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)
else:
date_added = timezone.now()
try:
p = Photo(image=imagefile, title=title, slug=title, date_added=date_added)
except:
raise CommandError('Photo "%s" could not be added' % base)
p.save()
self.stdout.write(self.style.SUCCESS('Successfully added photo "%s"' % p))
if options['gallery']:
try:
g = Gallery.objects.get(slug=options['gallery'])
except:
raise CommandError('Gallery "%s" does not exist' % options['gallery'])
p.galleries.add(g.pk)
p.save()
self.stdout.write(self.style.SUCCESS('Successfully added photo to gallery "%s"' % g))
我已成功使用 Photologue 来展示 galleries of regularly-created data plot images。当然,既然已经建立了能力,就会创建大量的数据图,需要共享它们!
使用 Django shell 中的 manage.py
编写上传图像并将它们添加到画廊的过程脚本;但是,作为 Django 的业余爱好者,我遇到了一些困难。
这是我目前开发的自定义命令addphoto.py
:
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from photologue.models import Photo, Gallery
import os
from datetime import datetime
import pytz
class Command(BaseCommand):
help = 'Adds a photo to Photologue.'
def add_arguments(self, parser):
parser.add_argument('imagefile', type=str)
parser.add_argument('--title', type=str)
parser.add_argument('--date_added', type=str, help="datetime string in 'YYYY-mm-dd HH:MM:SS' format [UTC]")
parser.add_argument('--gallery', type=str)
def handle(self, *args, **options):
imagefile = options['imagefile']
if options['title']:
title = options['title']
else:
base = os.path.basename(imagefile)
title = os.path.splitext(base)[0]
if options['date_added']:
date_added = datetime.strptime(options['date_added'],'%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)
else:
date_added = timezone.now()
p = Photo(image=imagefile, title=title, date_added=date_added)
p.save()
不幸的是,当使用 --traceback
执行时,结果如下:
./manage.py addphoto '../dataplots/monitoring/test.png' --traceback
Failed to read EXIF DateTimeOriginal
Traceback (most recent call last):
File "/home/user/mysite/photologue/models.py", line 494, in save
exif_date = self.EXIF(self.image.file).get('EXIF DateTimeOriginal', None)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/db/models/fields/files.py", line 51, in _get_file
self._file = self.storage.open(self.name, 'rb')
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 38, in open
return self._open(name, mode)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 300, in _open
return File(open(self.path(name), mode))
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 405, in path
return safe_join(self.location, name)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/utils/_os.py", line 78, in safe_join
'component ({})'.format(final_path, base_path))
django.core.exceptions.SuspiciousFileOperation: The joined path (/home/user/mysite/dataplots/monitoring/test.png) is located outside of the base path component (/home/user/mysite/media)
Traceback (most recent call last):
File "./manage.py", line 22, in <module>
execute_from_command_line(sys.argv)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/management/__init__.py", line 363, in execute_from_command_line
utility.execute()
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/management/__init__.py", line 355, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/management/base.py", line 283, in run_from_argv
self.execute(*args, **cmd_options)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/management/base.py", line 330, in execute
output = self.handle(*args, **options)
File "/home/user/mysite/photologue/management/commands/addphoto.py", line 36, in handle
p.save()
File "/home/user/mysite/photologue/models.py", line 553, in save
super(Photo, self).save(*args, **kwargs)
File "/home/user/mysite/photologue/models.py", line 504, in save
self.pre_cache()
File "/home/user/mysite/photologue/models.py", line 472, in pre_cache
self.create_size(photosize)
File "/home/user/mysite/photologue/models.py", line 411, in create_size
if self.size_exists(photosize):
File "/home/user/mysite/photologue/models.py", line 364, in size_exists
if self.image.storage.exists(func()):
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 392, in exists
return os.path.exists(self.path(name))
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/core/files/storage.py", line 405, in path
return safe_join(self.location, name)
File "/home/user/mysite/venv/lib/python3.5/site-packages/django/utils/_os.py", line 78, in safe_join
'component ({})'.format(final_path, base_path))
django.core.exceptions.SuspiciousFileOperation: The joined path (/home/user/mysite/dataplots/monitoring/cache/test_thumbnail.png) is located outside of the base path component (/home/user/mysite/media)
显然,图像文件的副本没有放在 media/
目录中。此外,虽然 image
、title
和 date_added
列填充在网站数据库的 photologue_photos
table 中,但 slug
列是没有。
如何将文件上传到MEDIA_ROOT
目录?
以下是 Photologue models.py
文件中 Photo
和 ImageModel
模型的相关片段,供参考:
class Photo(ImageModel):
title = models.CharField(_('title'),
max_length=250,
unique=True)
slug = models.SlugField(_('slug'),
unique=True,
max_length=250,
help_text=_('A "slug" is a unique URL-friendly title for an object.'))
caption = models.TextField(_('caption'),
blank=True)
date_added = models.DateTimeField(_('date added'),
default=now)
is_public = models.BooleanField(_('is public'),
default=True,
help_text=_('Public photographs will be displayed in the default views.'))
sites = models.ManyToManyField(Site, verbose_name=_(u'sites'),
blank=True)
objects = PhotoQuerySet.as_manager()
def save(self, *args, **kwargs):
if self.slug is None:
self.slug = slugify(self.title)
super(Photo, self).save(*args, **kwargs)
class ImageModel(models.Model):
image = models.ImageField(_('image'),
max_length=IMAGE_FIELD_MAX_LENGTH,
upload_to=get_storage_path)
date_taken = models.DateTimeField(_('date taken'),
null=True,
blank=True,
help_text=_('Date image was taken; is obtained from the image EXIF data.'))
view_count = models.PositiveIntegerField(_('view count'),
default=0,
editable=False)
crop_from = models.CharField(_('crop from'),
blank=True,
max_length=10,
default='center',
choices=CROP_ANCHOR_CHOICES)
effect = models.ForeignKey('photologue.PhotoEffect',
null=True,
blank=True,
related_name="%(class)s_related",
verbose_name=_('effect'))
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(ImageModel, self).__init__(*args, **kwargs)
self._old_image = self.image
def save(self, *args, **kwargs):
image_has_changed = False
if self._get_pk_val() and (self._old_image != self.image):
image_has_changed = True
# If we have changed the image, we need to clear from the cache all instances of the old
# image; clear_cache() works on the current (new) image, and in turn calls several other methods.
# Changing them all to act on the old image was a lot of changes, so instead we temporarily swap old
# and new images.
new_image = self.image
self.image = self._old_image
self.clear_cache()
self.image = new_image # Back to the new image.
self._old_image.storage.delete(self._old_image.name) # Delete (old) base image.
if self.date_taken is None or image_has_changed:
# Attempt to get the date the photo was taken from the EXIF data.
try:
exif_date = self.EXIF(self.image.file).get('EXIF DateTimeOriginal', None)
if exif_date is not None:
d, t = exif_date.values.split()
year, month, day = d.split(':')
hour, minute, second = t.split(':')
self.date_taken = datetime(int(year), int(month), int(day),
int(hour), int(minute), int(second))
except:
logger.error('Failed to read EXIF DateTimeOriginal', exc_info=True)
super(ImageModel, self).save(*args, **kwargs)
self.pre_cache()
这里是 get_storage_path
函数,根据要求:
# Look for user function to define file paths
PHOTOLOGUE_PATH = getattr(settings, 'PHOTOLOGUE_PATH', None)
if PHOTOLOGUE_PATH is not None:
if callable(PHOTOLOGUE_PATH):
get_storage_path = PHOTOLOGUE_PATH
else:
parts = PHOTOLOGUE_PATH.split('.')
module_name = '.'.join(parts[:-1])
module = import_module(module_name)
get_storage_path = getattr(module, parts[-1])
else:
def get_storage_path(instance, filename):
fn = unicodedata.normalize('NFKD', force_text(filename)).encode('ascii', 'ignore').decode('ascii')
return os.path.join(PHOTOLOGUE_DIR, 'photos', fn)
关于您问题的一部分:保存 Photo
时,slug
列为空。
它 应该 在保存 Photo
时自动填充 - 正如您对上面的 Photologue 源代码的复制和粘贴 if self.slug is None: self.slug = slugify(self.title)
清楚.
这表明 Photologue 源代码实际上不是从您的管理命令中调用的 - 您可以通过向 Photologue 代码的本地副本添加一些快速调试代码来检查这一点,例如save()
方法中的 print() 语句,并检查它是否 是 是 运行.
Edit/Update 2: 我想我知道为什么你的 slug 字段是空的。我认为问题在于 self.slug
不是 None
,即使该字段不包含字符串或包含空字符串(请参阅 this answer)。所以尝试将 if self.slug is None
更新为:
class Photo(ImageModel):
...
def save(self, *args, **kwargs):
# test if slug is not truthy, including empty string or None-ness
if not self.slug:
self.slug = slugify(self.title)
super(Photo, self).save(*args, **kwargs)
Edit/Update 1: 查看 this answer. It is from Django 1.4 (ancient, I know), but it should solve your problem. If you copy or move the files you are adding to MEDIA_ROOT
before creating the Photo
instances, then you should be good to go. Here is an answer 显示如何在 python 中复制文件。我建议您将自定义命令更改为:
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from photologue.models import Photo, Gallery
# so you can access settings.MEDIA_ROOT
from django.conf import settings
# so you can copy file to MEDIA_ROOT if need be
from shutil import copyfile
import os
from datetime import datetime
import pytz
class Command(BaseCommand):
help = 'Adds a photo to Photologue.'
def add_arguments(self, parser):
parser.add_argument('imagefile', type=str)
# where the imagefile is currently located, assumes MEDIA_ROOT
parser.add_argument('--media_source', type=str)
parser.add_argument('--title', type=str)
parser.add_argument('--date_added', type=str, help="datetime string in 'YYYY-mm-dd HH:MM:SS' format [UTC]")
parser.add_argument('--gallery', type=str)
def handle(self, *args, **options):
# the path of the file relative to media_source
imagefile = options['imagefile']
# if file is not in media root, copy it to there
if options['media_source']:
media_source = os.path.realpath(options['media_source'])
media_target = os.path.realpath(settings.MEDIA_ROOT)
if media_source != media_target:
copyfile(imagefile, os.path.join(media_target, imagefile)
# note: if media_source was not provided, assume file is already in MEDIA_ROOT
if options['title']:
title = options['title']
else:
base = os.path.basename(imagefile)
title = os.path.splitext(base)[0]
if options['date_added']:
date_added = datetime.strptime(options['date_added'],'%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)
else:
date_added = timezone.now()
p = Photo(image=imagefile, title=title, date_added=date_added)
p.save()
现在你的 imagefile
是相对于你的媒体源 (../dataplots
) 的,被复制到 MEDIA_ROOT
,一切都应该按计划进行。这是您的命令的样子
manage.py addphoto 'monitoring/test.png' --media_source='../dataplots'
应该将您的数据图复制到 MEDIA_ROOT
,然后按预期创建 Photo
实体。
原答案:
你能 post 下一行中的 get_storage_path
是什么吗:
class ImageModel(models.Model):
image = models.ImageField(_('image'),
max_length=IMAGE_FIELD_MAX_LENGTH,
upload_to=get_storage_path) # <-- here
在我知道那是什么之前,这个答案会有些不完整,但我想我明白了你的问题。看你的命令是运行:
./manage.py addphoto '../dataplots/monitoring/test.png' --traceback
你的 imagefile
参数是 ../dataplots/monitoring/test.png
。如果 get_storage_path
return 是相同的路径,路径的开头是 ../
,那么您将指定一个不在 MEDIA_ROOT
目录中的上传路径,但在其父目录中。我认为它正在尝试上传到 MEDIA_ROOT/../dataplots/monitoring/test.png
,如回溯中的第一个 SuspiciousFileOperation 所示:
# note: linebreaks and indentation added for readability
django.core.exceptions.SuspiciousFileOperation:
The joined path (/home/user/mysite/dataplots/monitoring/test.png)
is located outside of the base path component (/home/user/mysite/media)
所以它正在尝试上传到 MEDIA_ROOT/imagefile
,但是 imagefile
以 ../
开头,这是不允许的。
如果确实是这个问题(很难说直到你 post 代码 get_storage_path
),那么有很多方法可以解决这个问题。也许最快的解决方法就是将 dataplots
目录移动到与 manage.py
:
mv ../dataplots ./dataplots
这应该可以直接解决您的问题,因为您不再需要 ../
,但您可能不希望项目目录中包含所有这些数据图,因此这是一个快速但效果不佳的解决方案。潜在的问题是源文件的路径和你上传源文件的路径不应该相等。我认为您应该更改命令参数以包含 image_source
和 image_destination
,或者您可以包含 source_media_root
和 imagefile
,其中 ../
是 source_media_root
,并且 imagefile
是相对于 source_media_root
和 MEDIA_ROOT
中所需的目标位置...有很多解决方案,但我不能完全提供正确的代码一个,直到我知道 get_storage_path
是什么(我假设它是一个函数 return 或可以 return imagefile
参数的一种或另一种方式)。
这是我最终创建的自定义 addphoto.py
命令的工作版本。
图像文件需要在MEDIA_ROOT/photologue/photos
以内以便于导入。使用 ./manage.py addphoto 'photologue/photos/test.png'
执行命令。请注意,有一个 --gallery
选项可将图像添加到图库,前提是图库的 slug
.
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from photologue.models import Photo, Gallery
import os
from datetime import datetime
import pytz
class Command(BaseCommand):
help = 'Adds a photo to Photologue.'
def add_arguments(self, parser):
parser.add_argument('imagefile',
type=str)
parser.add_argument('--title',
type=str)
parser.add_argument('--date_added',
type=str,
help="datetime string in 'YYYY-mm-dd HH:MM:SS' format [UTC]")
parser.add_argument('--gallery',
type=str)
def handle(self, *args, **options):
imagefile = options['imagefile']
base = os.path.basename(imagefile)
if options['title']:
title = options['title']
else:
title = os.path.splitext(base)[0]
if options['date_added']:
date_added = datetime.strptime(options['date_added'],'%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.UTC)
else:
date_added = timezone.now()
try:
p = Photo(image=imagefile, title=title, slug=title, date_added=date_added)
except:
raise CommandError('Photo "%s" could not be added' % base)
p.save()
self.stdout.write(self.style.SUCCESS('Successfully added photo "%s"' % p))
if options['gallery']:
try:
g = Gallery.objects.get(slug=options['gallery'])
except:
raise CommandError('Gallery "%s" does not exist' % options['gallery'])
p.galleries.add(g.pk)
p.save()
self.stdout.write(self.style.SUCCESS('Successfully added photo to gallery "%s"' % g))