Программное сохранение изображения в Django ImageField

203

Хорошо, я пробовал почти все, и я не могу заставить это работать.

  • У меня есть модель Django с ImageField на нем
  • У меня есть код, который загружает изображение через HTTP (проверено и работает)
  • Изображение сохраняется непосредственно в папку upload_to (то, что загружено в папку ImageField)
  • Все, что мне нужно сделать, это связать уже существующий путь к файлу изображения с ImageField

Я написал этот код около 6 разных способов.

Проблема, с которой я сталкиваюсь, заключается в том, что весь код, который я пишу, приводит к следующему поведению: (1) Django создаст второй файл, (2) переименует новый файл, добавив _ в конец файла имя, то (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()
Т. Стоун
источник
Еще один замечательный вопрос Джанго. Я сделал несколько попыток решить эту проблему без удачи. Файлы, созданные в каталоге загрузки, повреждены и имеют лишь небольшую долю по сравнению с оригиналами (хранятся в другом месте).
Westmark
ваше ОБНОВЛЕНИЕ не работает
AmiNadimi

Ответы:

167

У меня есть код, который выбирает изображение из Интернета и сохраняет его в модели. Важные биты:

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()
твон
источник
2
tvon - я пытался что-то с этой целью, но, возможно, я сделаю еще один шаг, на самом деле, у меня был код, который выглядел очень похоже на это (Даже если это вне контекста, я вижу, как это работает).
Т. Стоун
2
Я бы посоветовал также использовать url-анализ, чтобы избежать привязки url paramatar к изображению. import urlparse, os.path.basename(urlparse.urlparse(self.url).path), Спасибо за пост, было полезно.
dennmat
1
Я получаю django.core.exceptions.SuspiciousOperation: Попытка доступа к '/images/10.jpg' запрещена.
DataGreed
2
@DataGreed, вы должны удалить косую черту '/' из определения upload_to в модели. Это было решено здесь .
Циков
Я получаю ошибку, как это:prohibited to prevent data loss due to unsaved related object 'stream'.
Дипак
95

Очень просто, если модель еще не создана:

Сначала скопируйте файл изображения в путь загрузки (предполагается, что в следующем фрагменте = «путь /» ).

Во-вторых , используйте что-то вроде:

class Layout(models.Model):
    image = models.ImageField('img', upload_to='path/')

layout = Layout()
layout.image = "path/image.png"
layout.save()

протестировано и работает в django 1.4, оно может работать и для существующей модели.

Рабих Кодейх
источник
10
Это правильный ответ, нужно больше голосов !!! Нашел это решение и здесь .
Эндрю Свихарт
Здравствуй. У меня есть вопрос. Я использую django-хранилища с бэкэндом Amazon S3. Будет ли это вызвать новую загрузку?
Сальваторе Йовене
ОП спрашивает «без попытки Django восстановить файл», и это ответ на этот вопрос!
фрнр
2
Django имеет некоторую существующую логику для учета повторяющихся имен файлов на диске. Этот метод забивает эту логику, потому что пользователю остается проверять дублирование имени файла.
Крис Конлан
1
@Conlan: добавить направляющую к имени файла.
Рабих Кодейх
41

Просто небольшое замечание. 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байте.

Тиаго Алмейда
источник
1
Спасибо, я склонен забывать о таких низкоуровневых деталях, с которыми сталкиваются окна.
mike_k
FML ... что происходит, когда этот параметр передается на машине Linux?
DMac the Destroyer
1
ответил на мой собственный вопрос ... извините за спам. нашел некоторую документацию для этого здесь . «В Unix это не помешает добавить в режим« b », так что вы можете использовать его независимо от платформы для всех двоичных файлов».
DMac the Destroyer
16

Вот метод, который хорошо работает и позволяет также преобразовать файл в определенный формат (чтобы избежать ошибки «невозможно записать режим 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()

Надеюсь это поможет

michalk
источник
12

Хорошо, если все, что вам нужно сделать, это связать уже существующий путь к файлу изображения с 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.

rmnff
источник
2
Разве вы не открываете его как бинарный файл?
Мариуш Джамро
Как сказал @MariuszJamro, так и должно быть:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns
Также не забудьте сохранить объект:obj.save()
Рахматнс
11

Я создал собственное хранилище, которое не будет сохранять файл на диск:

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')
Нику Сурду
источник
7

Если вы хотите просто «установить» фактическое имя файла, не неся при этом затрат на загрузку и повторное сохранение файла (!!) или прибегая к использованию поля 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 этого не поддерживает.

s29
источник
Если model_instance является экземпляром модели, которая содержит файл ... что означает другой "экземпляр" ??
h3.
7

Самое простое на мой взгляд решение:

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)
Иван Семочкин
источник
3

Многие из этих ответов устарели, и я провел много часов в отчаянии (я довольно новичок в Django & web dev в целом). Тем не менее, я нашел эту превосходную идею @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)
Зая
источник
2

Возможно, это не тот ответ, который вы ищете. но вы можете использовать charfield для хранения пути к файлу вместо ImageFile. Таким образом, вы можете программно связать загруженное изображение с полем без воссоздания файла.

Mohamed
источник
Да, у меня возникло желание отказаться от этого и либо написать в MySQL напрямую, либо просто использовать CharField ().
Т. Стоун
1

Можешь попробовать:

model.ImageField.path = os.path.join('/Upload', generated_image_path)
панда
источник
1
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()
Sawan Gupta
источник
1
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()
Nishant
источник
1

Работает! Вы можете сохранить изображение с помощью 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)
Нидс Бартвал
источник
Спасибо Нидс, абсолютно работает это решение! Вы сэкономили мне много времени :)
Мехмет Бурак Ibis
0

Вы можете использовать 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) 
Раджив Шарма
источник
0

С 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()
Skratt
источник