Secara terprogram menyimpan gambar ke Django ImageField

203

Oke, saya sudah mencoba hampir semuanya dan saya tidak bisa menyelesaikannya.

  • Saya memiliki model Django dengan ImageField di atasnya
  • Saya memiliki kode yang mengunduh gambar melalui HTTP (teruji dan berfungsi)
  • Gambar disimpan langsung ke folder 'upload_to' (upload_to menjadi yang diatur di ImageField)
  • Yang perlu saya lakukan adalah menghubungkan jalur file gambar yang sudah ada dengan ImageField

Saya telah menulis kode ini tentang 6 cara berbeda.

Masalah yang saya hadapi adalah semua kode yang saya tulis menghasilkan perilaku berikut: (1) Django akan membuat file ke-2, (2) mengganti nama file baru, menambahkan _ ke akhir file nama, maka (3) tidak mentransfer salah satu data dari meninggalkannya pada dasarnya file kosong yang diberi nama baru. Apa yang tersisa di jalur 'upload_to' adalah 2 file, satu yang merupakan gambar aktual, dan satu yang adalah nama gambar, tetapi kosong, dan tentu saja jalur ImageField diatur ke file kosong yang coba dibuat oleh Django untuk dibuat .

Jika tidak jelas, saya akan mencoba mengilustrasikan:

## 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

Bagaimana saya bisa melakukan ini tanpa Django mencoba menyimpan kembali file tersebut? Yang saya benar-benar suka adalah sesuatu untuk efek ini ...

model.ImageField.path = generated_image_path

... tapi tentu saja itu tidak berhasil.

Dan ya saya telah melalui pertanyaan-pertanyaan lain di sini seperti ini serta dokumen Django pada File

PEMBARUAN Setelah pengujian lebih lanjut, ini hanya melakukan perilaku ini ketika berjalan di bawah Apache pada Windows Server. Saat berjalan di bawah 'runserver' di XP itu tidak menjalankan perilaku ini.

Saya bingung.

Berikut adalah kode yang berjalan dengan sukses di XP ...

f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()
T. Stone
sumber
Pertanyaan Django yang luar biasa. Saya telah melakukan beberapa upaya untuk menyelesaikan masalah ini tanpa hasil. File yang dibuat dalam direktori unggahan rusak dan hanya sebagian kecil dibandingkan dengan aslinya (disimpan di tempat lain).
westmark
UPDATE Anda tidak berfungsi
AmiNadimi

Jawaban:

166

Saya memiliki beberapa kode yang mengambil gambar dari web dan menyimpannya dalam model. Bit penting adalah:

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

Itu agak membingungkan karena ditarik keluar dari model saya dan sedikit di luar konteks, tetapi bagian-bagian penting adalah:

  • Gambar yang ditarik dari web tidak disimpan di folder upload_to, melainkan disimpan sebagai tempfile oleh urllib.urlretrieve () dan kemudian dibuang.
  • Metode ImageField.save () mengambil nama file (bit os.path.basename) dan objek django.core.files.File.

Beri tahu saya jika Anda memiliki pertanyaan atau perlu klarifikasi.

Sunting: demi kejelasan, inilah modelnya (dikurangi pernyataan impor yang diperlukan):

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()
tvon
sumber
2
tvon - Saya telah mencoba sesuatu untuk efek ini, tetapi mungkin saya akan mencoba lagi, pada kenyataannya, saya memiliki kode yang terlihat sangat mirip dengan ini. (Bahkan jika di luar konteks saya dapat melihat cara kerjanya).
T. Stone
2
Saya sarankan menggunakan url parse juga untuk menghindari url paramatar gunk terpasang pada gambar. import urlparse. os.path.basename(urlparse.urlparse(self.url).path). Terima kasih atas posnya, sangat membantu.
dennmat
1
Saya mendapatkan django.core.exceptions.SuspiciousOperation: Akses percobaan ke '/images/10.jpg' ditolak.
DataGreed
2
@ DataGreed Anda harus menghapus garis miring '/' dari definisi unggah ke model. Ini ditujukan di sini .
tsikov
Saya mendapatkan pesan kesalahan seperti ini:prohibited to prevent data loss due to unsaved related object 'stream'.
Dipak
95

Sangat mudah jika model belum dibuat:

Pertama , salin file gambar Anda ke jalur unggah (diasumsikan = 'jalur /' dalam cuplikan berikut).

Kedua , gunakan sesuatu seperti:

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

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

diuji dan bekerja di Django 1.4, mungkin berhasil juga untuk model yang ada.

Rabih Kodeih
sumber
10
Ini jawaban yang tepat, butuh lebih banyak suara !!! Menemukan solusi ini di sini juga.
Andrew Swihart
Hai. Saya punya pertanyaan. Saya menggunakan penyimpanan Django dengan backend Amazon S3. Apakah ini akan memicu unggahan baru?
Salvatore Iovene
OP bertanya "tanpa Django mencoba menyimpan kembali file", dan ini adalah jawabannya!
frnhr
2
Django memiliki beberapa logika yang ada untuk menjelaskan duplikat nama file pada disk. Metode ini mengganggu logika itu karena pengguna dibiarkan memeriksa duplikasi nama file.
Chris Conlan
1
@Conlan: tambahkan panduan ke nama file.
Rabih Kodeih
41

Hanya sedikit komentar. tvon answer berfungsi tetapi, jika Anda bekerja di windows, Anda mungkin ingin open()file tersebut 'rb'. Seperti ini:

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

atau file Anda akan terpotong pada 0x1Abyte pertama .

Tiago Almeida
sumber
1
Terima kasih, saya cenderung lupa detail jendela tingkat rendah seperti yang kita hadapi.
mike_k
fml ... apa yang terjadi ketika parameter tersebut dilewatkan pada mesin linux?
DMac the Destroyer
1
jawab pertanyaanku sendiri ... maaf untuk spam. menemukan beberapa dokumentasi untuk ini di sini . "Di Unix, tidak ada salahnya menambahkan 'b' ke mode, sehingga Anda dapat menggunakannya platform-independen untuk semua file biner."
DMac the Destroyer
16

Berikut adalah metode yang berfungsi dengan baik dan memungkinkan Anda untuk mengonversi file ke format tertentu juga (untuk menghindari kesalahan "tidak dapat menulis mode P sebagai 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)

di mana gambar adalah ImageField django atau your_model_instance.image di sini adalah contoh penggunaan:

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

Semoga ini membantu

michalk
sumber
12

Oke, jika yang perlu Anda lakukan adalah mengaitkan jalur file gambar yang sudah ada dengan ImageField, maka solusi ini mungkin membantu:

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

Nah, jika harus sungguh-sungguh, file gambar yang sudah ada tidak akan dikaitkan dengan ImageField, tetapi salinan file ini akan dibuat di upload_to dir sebagai 'imgfilename.jpg' dan akan dikaitkan dengan ImageField.

rmnff
sumber
2
Tidakkah Anda membukanya sebagai file biner?
Mariusz Jamro
Persis seperti yang dikatakan @MariuszJamro, seharusnya seperti ini:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns
juga jangan lupa untuk menyimpan objek:obj.save()
rahmatns
11

Apa yang saya lakukan adalah membuat penyimpanan sendiri yang tidak akan menyimpan file ke disk:

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

Kemudian, dalam model saya, untuk ImageField saya, saya telah menggunakan penyimpanan khusus baru:

from custom_storage import CustomStorage

custom_store = CustomStorage()

class Image(models.Model):
    thumb = models.ImageField(storage=custom_store, upload_to='/some/path')
Nicu Surdu
sumber
7

Jika Anda hanya ingin "mengatur" nama file yang sebenarnya, tanpa mengeluarkan biaya pemuatan dan menyimpan kembali file (!!), atau menggunakan charfield (!!!), Anda mungkin ingin mencoba sesuatu seperti ini - -

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

Ini akan menerangi model_instance.myfile.url Anda dan yang lainnya seolah-olah Anda benar-benar mengunggah file tersebut.

Seperti kata @ t-stone, apa yang benar-benar kita inginkan, adalah untuk dapat mengatur instance.myfile.path = 'my-filename.jpg', tetapi Django saat ini tidak mendukung itu.

s29
sumber
Jika model_instance adalah instance dari model yang berisi file .. apa artinya "instance" lainnya ??
h3.
7

Solusi paling sederhana menurut saya:

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)
Ivan Semochkin
sumber
3

Banyak jawaban ini sudah ketinggalan zaman, dan saya menghabiskan banyak waktu dalam frustrasi (saya cukup baru untuk Django & web dev pada umumnya). Namun, saya menemukan inti yang sangat bagus ini oleh @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)
Zaya
sumber
2

Ini mungkin bukan jawaban yang Anda cari. tetapi Anda bisa menggunakan charfield untuk menyimpan path file alih-alih ImageFile. Dengan cara itu Anda dapat secara terprogram mengaitkan gambar yang diunggah ke bidang tanpa membuat ulang file.

Mohamed
sumber
Ya, saya tergoda untuk menyerah pada ini, dan baik menulis ke MySQL secara langsung, atau hanya menggunakan CharField ().
T. Stone
1

Anda dapat mencoba:

model.ImageField.path = os.path.join('/Upload', generated_image_path)
panda
sumber
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
sumber
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
sumber
1

Kerja! Anda dapat menyimpan gambar dengan menggunakan FileSystemStorage. periksa contoh di bawah ini

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)
Nids Barthwal
sumber
Terima kasih Nids, benar-benar mengerjakan solusi ini! Anda menghemat banyak waktu saya :)
Mehmet Burak Ibis
0

Anda dapat menggunakan kerangka Django REST dan Permintaan python pustaka untuk secara terprogram menyimpan gambar ke Django ImageField

Berikut ini sebuah Contoh:

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) 
Rajiv Sharma
sumber
0

Dengan Django 3, dengan model seperti ini:

class Item(models.Model):
   name = models.CharField(max_length=255, unique=True)
   photo= models.ImageField(upload_to='image_folder/', blank=True)

jika gambar sudah diunggah, kita bisa langsung melakukan:

Item.objects.filter(...).update(photo='image_folder/sample_photo.png')

atau

my_item = Item.objects.get(id=5)
my_item.photo='image_folder/sample_photo.png'
my_item.save()
Skratt
sumber