Программное сохранение изображения в Django ImageField
Хорошо, я пробовал почти все, и я не могу заставить это работать.
- У меня есть модель Django с ImageField на нем
- У меня есть код, который загружает изображение через HTTP (проверено и работает)
- Изображение сохраняется непосредственно в папке upload_to (то, что загружено в папку ImageField)
- Все, что мне нужно сделать, это связать уже существующий путь к файлу изображения с ImageField
Я написал этот код около 6 разных способов.
Проблема, с которой я сталкиваюсь, состоит в том, что весь код, который я пишу, приводит к следующему поведению: (1) Django создаст второй файл, (2) переименует новый файл, добавив _ в конец файла name, то (3) не передавать какие-либо данные, оставляя их в основном пустым переименованным файлом. В пути "upload_to" осталось 2 файла, один из которых является фактическим изображением, а другой - именем изображения, но пустым, и, конечно, путь ImageField установлен на пустой файл, который пытается создать Django.,
Если это неясно, я попытаюсь проиллюстрировать:
## Image generation code runs....
/Upload
generated_image.jpg 4kb
## Attempt to set the ImageField path...
/Upload
generated_image.jpg 4kb
generated_image_.jpg 0kb
ImageField.Path = /Upload/generated_image_.jpg
Как я могу сделать это, не пытаясь Django повторно сохранить файл? Что мне действительно нравится, так это что-то в этом роде...
model.ImageField.path = generated_image_path
... но, конечно, это не работает.
И да, я рассмотрел другие вопросы, такие как этот, а также документ django в файле
ОБНОВЛЕНИЕ После дальнейшего тестирования это только делает это при работе под Apache на Windows Server. Во время работы под "runserver" на XP он не выполняет это поведение.
Я в тупике.
Вот код, который успешно работает на XP...
f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()
21 ответ
У меня есть некоторый код, который выбирает изображение из Интернета и сохраняет его в модели. Важные биты:
from django.core.files import File # you need this somewhere
import urllib
# The following actually resides in a method of my model
result = urllib.urlretrieve(image_url) # image_url is a URL to an image
# self.photo is the ImageField
self.photo.save(
os.path.basename(self.url),
File(open(result[0], 'rb'))
)
self.save()
Это немного сбивает с толку, потому что это извлечено из моей модели и немного вне контекста, но важными частями являются:
- Изображение, извлеченное из Интернета, не сохраняется в папке upload_to, вместо этого оно сохраняется как временный файл с помощью urllib.urlretrieve(), а затем удаляется.
- Метод ImageField.save() принимает имя файла (бит os.path.basename) и объект django.core.files.File.
Дайте мне знать, если у вас есть вопросы или вам нужны разъяснения.
Изменить: для ясности, вот модель (за вычетом любых требуемых операторов импорта):
class CachedImage(models.Model):
url = models.CharField(max_length=255, unique=True)
photo = models.ImageField(upload_to=photo_path, blank=True)
def cache(self):
"""Store image locally if we have a URL"""
if self.url and not self.photo:
result = urllib.urlretrieve(self.url)
self.photo.save(
os.path.basename(self.url),
File(open(result[0], 'rb'))
)
self.save()
Очень просто, если модель еще не создана:
Сначала скопируйте файл изображения в путь загрузки (предполагается, что в следующем фрагменте = "путь /").
Во-вторых, используйте что-то вроде:
class Layout(models.Model):
image = models.ImageField('img', upload_to='path/')
layout = Layout()
layout.image = "path/image.png"
layout.save()
протестировано и работает в django 1.4, оно может работать и для существующей модели.
Просто небольшое замечание. tvon answer работает, но, если вы работаете с Windows, вы, вероятно, хотите open()
файл с 'rb'
, Как это:
class CachedImage(models.Model):
url = models.CharField(max_length=255, unique=True)
photo = models.ImageField(upload_to=photo_path, blank=True)
def cache(self):
"""Store image locally if we have a URL"""
if self.url and not self.photo:
result = urllib.urlretrieve(self.url)
self.photo.save(
os.path.basename(self.url),
File(open(result[0], 'rb'))
)
self.save()
или вы получите ваш файл урезанным в первый раз 0x1A
байт.
Хорошо, если все, что вам нужно сделать, это связать уже существующий путь к файлу изображения с ImageField, то это решение может быть полезным:
from django.core.files.base import ContentFile
with open('/path/to/already/existing/file') as f:
data = f.read()
# obj.image is the ImageField
obj.image.save('imgfilename.jpg', ContentFile(data))
Что ж, если честно, уже существующий файл изображения не будет связан с ImageField, но копия этого файла будет создана в upload_to dir как "imgfilename.jpg" и будет связана с ImageField.
Вот метод, который хорошо работает и позволяет также преобразовать файл в определенный формат (чтобы избежать ошибки "невозможно записать режим P как JPEG"):
import urllib2
from django.core.files.base import ContentFile
from PIL import Image
from StringIO import StringIO
def download_image(name, image, url):
input_file = StringIO(urllib2.urlopen(url).read())
output_file = StringIO()
img = Image.open(input_file)
if img.mode != "RGB":
img = img.convert("RGB")
img.save(output_file, "JPEG")
image.save(name+".jpg", ContentFile(output_file.getvalue()), save=False)
где image - это django ImageField или your_model_instance.image. Вот пример использования:
p = ProfilePhoto(user=user)
download_image(str(user.id), p.image, image_url)
p.save()
Надеюсь это поможет
Многие из этих ответов были устаревшими, и я провел много часов в разочаровании (я новичок в Django и веб-разработке в целом). Однако я нашел эту отличную суть от @iambibhas: https://gist.github.com/iambibhas/5051911
import requests
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
def save_image_from_url(model, url):
r = requests.get(url)
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(r.content)
img_temp.flush()
model.image.save("image.jpg", File(img_temp), save=True)
Самое простое на мой взгляд решение:
from django.core.files import File
with open('path_to_file', 'r') as f: # use 'rb' mode for python3
data = File(f)
model.image.save('filename', data, True)
Я создал собственное хранилище, которое не будет сохранять файл на диск:
from django.core.files.storage import FileSystemStorage
class CustomStorage(FileSystemStorage):
def _open(self, name, mode='rb'):
return File(open(self.path(name), mode))
def _save(self, name, content):
# here, you should implement how the file is to be saved
# like on other machines or something, and return the name of the file.
# In our case, we just return the name, and disable any kind of save
return name
def get_available_name(self, name):
return name
Затем в моих моделях для моего ImageField я использовал новое пользовательское хранилище:
from custom_storage import CustomStorage
custom_store = CustomStorage()
class Image(models.Model):
thumb = models.ImageField(storage=custom_store, upload_to='/some/path')
Если вы хотите просто "установить" фактическое имя файла, не неся при этом затрат на загрузку и повторное сохранение файла (!!) или прибегая к использованию поля char (!!!), вы можете попробовать что-то вроде этого - -
model_instance.myfile = model_instance.myfile.field.attr_class(model_instance, model_instance.myfile.field, 'my-filename.jpg')
Это осветит ваш model_instance.myfile.url и все остальные, как если бы вы действительно загрузили файл.
Как говорит @t-stone, мы действительно хотим установить instance.myfile.path = 'my-filename.jpg', но Django в настоящее время не поддерживает это.
Это может быть не тот ответ, который вы ищете. но вы можете использовать charfield для хранения пути к файлу вместо ImageFile. Таким образом, вы можете программно связать загруженное изображение с полем без воссоздания файла.
В Django 3 с такой моделью, как эта:
class Item(models.Model):
name = models.CharField(max_length=255, unique=True)
photo= models.ImageField(upload_to='image_folder/', blank=True)
если изображение уже загружено, мы можем сделать следующее:
Item.objects.filter(...).update(photo='image_folder/sample_photo.png')
или
my_item = Item.objects.get(id=5)
my_item.photo='image_folder/sample_photo.png'
my_item.save()
class Pin(models.Model):
"""Pin Class"""
image_link = models.CharField(max_length=255, null=True, blank=True)
image = models.ImageField(upload_to='images/', blank=True)
title = models.CharField(max_length=255, null=True, blank=True)
source_name = models.CharField(max_length=255, null=True, blank=True)
source_link = models.CharField(max_length=255, null=True, blank=True)
description = models.TextField(null=True, blank=True)
tags = models.ForeignKey(Tag, blank=True, null=True)
def __unicode__(self):
"""Unicode class."""
return unicode(self.image_link)
def save(self, *args, **kwargs):
"""Store image locally if we have a URL"""
if self.image_link and not self.image:
result = urllib.urlretrieve(self.image_link)
self.image.save(os.path.basename(self.image_link), File(open(result[0], 'r')))
self.save()
super(Pin, self).save()
Ты можешь попробовать:
model.ImageField.path = os.path.join('/Upload', generated_image_path)
За работой! Вы можете сохранить изображение с помощью FileSystemStorage. проверьте пример ниже
def upload_pic(request):
if request.method == 'POST' and request.FILES['photo']:
photo = request.FILES['photo']
name = request.FILES['photo'].name
fs = FileSystemStorage()
##### you can update file saving location too by adding line below #####
fs.base_location = fs.base_location+'/company_coverphotos'
##################
filename = fs.save(name, photo)
uploaded_file_url = fs.url(filename)+'/company_coverphotos'
Profile.objects.filter(user=request.user).update(photo=photo)
class tweet_photos(models.Model):
upload_path='absolute path'
image=models.ImageField(upload_to=upload_path)
image_url = models.URLField(null=True, blank=True)
def save(self, *args, **kwargs):
if self.image_url:
import urllib, os
from urlparse import urlparse
file_save_dir = self.upload_path
filename = urlparse(self.image_url).path.split('/')[-1]
urllib.urlretrieve(self.image_url, os.path.join(file_save_dir, filename))
self.image = os.path.join(file_save_dir, filename)
self.image_url = ''
super(tweet_photos, self).save()
Только для тех, кто уже некоторое время боролся с этой проблемой. Я потратил несколько часов, чтобы понять, что нужно для того, чтобы из кода сохранить изображение в ImageField в Django. Вам необходимо передать объект File в ImageField, как описано в примере ниже, файл должен быть открыт, и вам необходимо сохранить модель, содержащую ваш ImageField, прежде чем закрывать файл. Этот образец работает нормально:
`from pathlib import Путь из файла импорта django.core.files
path = Path(image_file_str_path)
with path.open(mode="rb") as f:
yourmodel.img_field = File(f, name=path.name)
yourmodel.save()
`
Я сохраняю изображение с помощью uuid в django 2 python 3, потому что именно так это делает django:
import uuid
from django.core.files import File
import urllib
httpUrl = "https://miimgeurl/image.jpg"
result = urllib.request.urlretrieve(httpUrl)
mymodel.imagefield.save(os.path.basename(uuid.uuid4()),File(open(result[0], 'rb')))
mymodel.save()
class DemoImage(models.Model):
title = models.TextField(max_length=255, blank=False)
image = models.ImageField(blank=False, upload_to="images/DemoImages/")
import requests
import urllib.request
from django.core.files import File
url = "https://path/to/logo.jpg"
# Below 3 lines is to fake as browser agent
# as many sites block urllib class suspecting to be bots
opener = urllib.request.build_opener()
opener.addheaders = [("User-agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# Issue command to actually download and create temp img file in memory
result = urllib.request.urlretrieve(url)
# DemoImage.objects.create(title="title", image=File(open(result[0], "rb")))
# ^^ This erroneously results in creating the file like
# images/DemoImages/path/to/temp/dir/logo_image_file
# as opposed to
# images/DemoImages/logo_image_file
# Solution to get the file in images/DemoImages/
reopen = open(result[0], "rb") # Returns a BufferedReader object of the temp image
django_file = File(reopen) # Create the file from the BufferedReader object
demoimg = DemoImage()
demoimg.title = "title"
demoimg.image.save("logo.png", django_file, save=True)
Этот подход также запускает загрузку файла в облачный /S3, если он настроен.
Итак, если у вас есть модель с полем изображения с установленным атрибутом upload_to, например:
class Avatar(models.Model):
image_file = models.ImageField(upload_to=user_directory_path_avatar)
тогда довольно легко изменить изображение, по крайней мере, в django 3.15.
В представлении, когда вы обрабатываете изображение, вы можете получить изображение из:
self.request.FILES['avatar']
который является экземпляром типа InMemoryUploadedFile, если ваша html-форма имеет набор enctype и поле для аватара ...
<form method="post" class="avatarform" id="avatarform" action="{% url avatar_update_view' %}" enctype="multipart/form-data">
{% csrf_token %}
<input id="avatarUpload" class="d-none" type="file" name="avatar">
</form>
Затем установить новое изображение в представлении так же просто, как и следующее (где профиль - это модель профиля для self.request.user)
profile.avatar.image_file.save(self.request.FILES['avatar'].name, self.request.FILES['avatar'])
Нет необходимости сохранять profile.avatar, поле image_field уже сохранено, и в правильном месте из-за функции обратного вызова upload_to.
если вы используете admin.py, вы можете решить проблему переопределения (doc on django):
def save_model(self, request, obj, form, change):
obj.image_data = bytes(obj.image_name.read())
super().save_model(request, obj, form, change)
с моделями.py:
image_name = models.ImageField()
image_data = models.BinaryField()
Вы можете использовать Django REST Framework и библиотеку Python Requests для программного сохранения изображения в Django ImageField.
Вот пример:
import requests
def upload_image():
# PATH TO DJANGO REST API
url = "http://127.0.0.1:8080/api/gallery/"
# MODEL FIELDS DATA
data = {'first_name': "Rajiv", 'last_name': "Sharma"}
# UPLOAD FILES THROUGH REST API
photo = open('/path/to/photo'), 'rb')
resume = open('/path/to/resume'), 'rb')
files = {'photo': photo, 'resume': resume}
request = requests.post(url, data=data, files=files)
print(request.status_code, request.reason)