Программное сохранение изображения в 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) 
Другие вопросы по тегам