Програмно збереження зображення на Django ImageField


203

Гаразд, я пробував майже все, і я не можу змусити це працювати.

  • У мене є модель Django з ImageField на ній
  • У мене є код, який завантажує зображення через HTTP (перевірено і працює)
  • Зображення зберігається безпосередньо у папці "upload_to" (upload_to є тим, що встановлено на ImageField)
  • Все, що мені потрібно зробити, - це пов’язати вже існуючий шлях до файлу зображень із полем ImageField

Я написав цей код про 6 різних способів.

Проблема, з якою я стикаюся, полягає у всьому коді, який я записую, результатами такої поведінки: (1) Django створить другий файл, (2) перейменує новий файл, додавши _ в кінці файлу name, тоді (3) не передавати жодну з даних, залишаючи в основному порожній перейменований файл. Що залишилось у шляху "upload_to" - це два файли, один, який є власне зображенням, і той, який є ім'ям зображення, але порожнім, і звичайно шлях 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

... але, звичайно, це не працює.

І так, я переглянув інші запитання тут, як це , а також документ про джанго у файлі

ОНОВЛЕННЯ Після подальшого тестування воно виконує лише таку поведінку під час роботи під Apache на Windows Server. Під час роботи під 'runserver' на XP він не виконує цю поведінку.

Я спотикався.

Ось код, який успішно працює на XP ...

f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()

Ще одне чудове питання про Джанго. Я зробив кілька спроб вирішити цю проблему без удачі. Файли, створені в каталозі завантаження, розбиваються і мають лише частку за розміром порівняно з оригіналами (зберігаються в іншому місці).
westmark

ваша ОНОВЛЕННЯ не працює
AmiNadimi

Відповіді:


166

У мене є код, який знімає зображення з Інтернету і зберігає його в моделі. Важливими бітами є:

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-зображення до параматарської гармати. 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'.
Dipak

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
Це правильна відповідь, потрібно більше голосів !!! Знайшли це рішення і тут .
Ендрю Свіхарт

Привіт. У мене є питання. Я використовую сховища джанго із архівом Amazon S3. Чи спровокує це нове завантаження?
Сальваторе Іовене

ОП просить "не намагаючись Джанго повторно зберігати файл", і це відповідь на це!
frnhr

2
Django має певну логіку для обліку дублікатів назви файлів на диску. Цей метод клобує цю логіку, оскільки користувач залишається перевіряти дублювання імен файлів.
Кріс Конлан

1
@Conlan: додайте інструкцію до імені файлу.
Рабіх Кодейх

41

Просто невелике зауваження. відповідь tvon працює, але якщо ви працюєте над 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)

де зображення - зображення django ImageField або your_model_instance.image, ось приклад використання:

p = ProfilePhoto(user=user)
download_image(str(user.id), p.image, image_url)
p.save()

Сподіваюсь, це допомагає


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.


2
Чи не відкриєте ви це як двійковий файл?
Маріуш Джамро

Так, як сказав @MariuszJamro, воно повинно бути таким:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns

також не забудьте зберегти об’єкт:obj.save()
rahmatns

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

Якщо ви хочете просто "встановити" власне ім'я файлу, не створюючи накладних витрат на завантаження та повторне збереження файлу (!!) або не вдаючись до використання charfield (!!!), ви можете спробувати щось подібне - -

model_instance.myfile = model_instance.myfile.field.attr_class(model_instance, model_instance.myfile.field, 'my-filename.jpg')

Це засвітить ваш model_instan.myfile.url та всі інші так само, як ніби ви фактично завантажили файл.

Як каже @ t-stone, ми хочемо встановити instance.myfile.path = 'my-filename.jpg', але Django цього не підтримує.


Якщо model_instan є екземпляром моделі, яка містить файл .., що означає інший "екземпляр" ??
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. Таким чином ви можете програмно пов’язати завантажене зображення з полем, не відтворюючи файл.


Так, я спокусився відмовитись від цього і або написати на 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()

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()

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)

Дякую, Нідс, абсолютно працюю над цим рішенням! Ви врятували мені багато часу :)
Мехмет Бурак Ібіс

0

Ви можете використовувати рамку Django REST та бібліотеку запитів пітона для програмного збереження зображення на 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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.