บันทึกภาพโดยโปรแกรมไปยัง Django ImageField


203

ตกลงฉันได้ลองทุกอย่างแล้วและไม่สามารถใช้งานได้

  • ฉันมีโมเดล Django ที่มี ImageField อยู่
  • ฉันมีรหัสที่ดาวน์โหลดรูปภาพผ่านทาง HTTP (ผ่านการทดสอบและใช้งานได้)
  • ภาพจะถูกบันทึกลงในโฟลเดอร์ 'upload_to' โดยตรง (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 doc ในไฟล์

UPDATE หลังจากการทดสอบเพิ่มเติมจะทำเช่นนี้เมื่อทำงานภายใต้ Apache บน Windows Server เท่านั้น ในขณะที่ทำงานภายใต้ 'runserver' บน XP มันไม่ได้ทำการกระทำนี้

ฉันนิ่งงัน

นี่คือรหัสที่ทำงานได้สำเร็จบน XP ...

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

อีกคำถามที่ดี Django ฉันพยายามหลายครั้งเพื่อแก้ปัญหานี้โดยไม่มีโชค ไฟล์ที่สร้างในไดเรกทอรีอัปโหลดจะใช้งานไม่ได้และมีขนาดเพียงเล็กน้อยเมื่อเทียบกับต้นฉบับ (เก็บไว้ที่อื่น)
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 แต่จะถูกเก็บไว้เป็น tempfile แทนโดย 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 - ฉันลองอะไรบางอย่างเพื่อเอฟเฟกต์นี้ แต่บางทีฉันอาจจะให้มันไปอีกอันที่จริงแล้วฉันมีโค้ดที่ดูคล้ายกันมาก (แม้ว่ามันจะอยู่นอกบริบทฉันสามารถดูว่ามันทำงานอย่างไร)
T. Stone

2
ฉันขอแนะนำให้ใช้ url parse เช่นกันเพื่อหลีกเลี่ยงการได้รับ gunk 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 ในแบบจำลอง นี้ได้รับการแก้ไขที่นี่
tsikov

ฉันได้รับข้อผิดพลาดเช่นนี้:prohibited to prevent data loss due to unsaved related object 'stream'.
Dipak

95

ง่ายสุด ๆ ถ้ายังไม่ได้สร้างโมเดล:

ก่อนอื่นให้คัดลอกไฟล์รูปภาพของคุณไปยังเส้นทางการอัปโหลด (สันนิษฐานว่า = 'path /'ในตัวอย่างต่อไปนี้)

ประการที่สองใช้สิ่งที่ชอบ:

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

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

ผ่านการทดสอบและทำงานใน django 1.4 มันอาจใช้ได้กับรุ่นที่มีอยู่


10
นี่คือคำตอบที่ถูกต้องต้องการคะแนนมากกว่า !!! พบโซลูชันนี้ที่นี่เช่นกัน
Andrew Swihart

สวัสดี ฉันมีคำถาม. ฉันใช้ django-storages กับ Amazon S3 backend สิ่งนี้จะทริกเกอร์การอัปโหลดใหม่หรือไม่
Salvatore Iovene

OP ถามว่า "ไม่ต้อง Django ลองเก็บไฟล์ใหม่" และนี่คือคำตอบสำหรับสิ่งนั้น!
frnhr

2
Django มีตรรกะบางอย่างที่มีอยู่ในการบัญชีชื่อไฟล์ที่ซ้ำกันบนดิสก์ วิธีนี้จะปิดบังตรรกะนั้นเพราะผู้ใช้เหลือที่จะตรวจสอบการทำซ้ำชื่อไฟล์
Chris Conlan

1
@Conlan: ผนวก guid ให้กับชื่อไฟล์
Rabih Kodeih

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)

โดยที่ image เป็น 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
คุณไม่เปิดมันเป็นไฟล์ไบนารีใช่ไหม
Mariusz Jamro

เช่นเดียวกับ @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_instance.myfile.url ของคุณสว่างขึ้นและส่วนที่เหลือทั้งหมดจะเหมือนกับว่าคุณได้อัปโหลดไฟล์จริง

Like @ t-stone กล่าวว่าสิ่งที่เราต้องการจริงๆคือสามารถตั้งค่า instance.myfile.path = 'my-filename.jpg' แต่ Django ไม่สนับสนุนสิ่งนี้


ถ้า 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 และผู้พัฒนาเว็บทั่วไป) อย่างไรก็ตามฉันพบส่วนสำคัญนี้โดย @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 ()
T. Stone


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)

ขอบคุณ Nids ใช้งานโซลูชันนี้ได้อย่างสมบูรณ์! คุณประหยัดเวลาได้มาก :)
เมห์เม็ตบูราคไอบิส

0

คุณสามารถใช้Django REST frameworkและ python Requests library เพื่อบันทึกรูปภาพโดยโปรแกรมไปยัง 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()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.