Programmgesteuertes Speichern von Bildern in Django ImageField

203

Ok, ich habe fast alles ausprobiert und kann das nicht zum Laufen bringen.

  • Ich habe ein Django-Modell mit einem ImageField
  • Ich habe Code, der ein Bild über HTTP herunterlädt (getestet und funktioniert)
  • Das Bild wird direkt im Ordner 'upload_to' gespeichert (der upload_to ist derjenige, der auf dem ImageField festgelegt ist).
  • Ich muss lediglich den bereits vorhandenen Bilddateipfad mit ImageField verknüpfen

Ich habe diesen Code auf 6 verschiedene Arten geschrieben.

Das Problem, auf das ich stoße, ist, dass der gesamte Code, den ich schreibe, zu folgendem Verhalten führt: (1) Django erstellt eine zweite Datei, (2) benennt die neue Datei um und fügt am Ende der Datei ein _ hinzu Name, dann (3) keine der Daten übertragen, so dass es im Grunde eine leere umbenannte Datei bleibt. Was im Pfad 'upload_to' übrig bleibt, sind 2 Dateien, eine, die das eigentliche Bild ist, und eine, die den Namen des Bildes enthält, aber leer ist, und natürlich wird der ImageField-Pfad auf die leere Datei gesetzt, die Django zu erstellen versucht .

Falls das unklar war, werde ich versuchen zu veranschaulichen:

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

Wie kann ich dies tun, ohne dass Django versucht, die Datei erneut zu speichern? Was ich wirklich gerne hätte, ist etwas in diesem Sinne ...

model.ImageField.path = generated_image_path

... aber das funktioniert natürlich nicht.

Und ja, ich habe die anderen Fragen hier wie diese sowie das Django-Dokument in der Datei durchgesehen

UPDATE Nach weiteren Tests wird dieses Verhalten nur ausgeführt, wenn es unter Windows Server unter Apache ausgeführt wird. Während der Ausführung unter 'runserver' unter XP wird dieses Verhalten nicht ausgeführt.

Ich bin ratlos.

Hier ist der Code, der unter XP erfolgreich ausgeführt wird ...

f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()
T. Stone
quelle
Eine weitere großartige Django-Frage. Ich habe mehrere Versuche unternommen, dieses Problem ohne Glück zu lösen. Die im Upload-Verzeichnis erstellten Dateien sind fehlerhaft und nur einen Bruchteil der Größe im Vergleich zu den Originalen (an anderer Stelle gespeichert).
Westmark
Ihr UPDATE funktioniert nicht
AmiNadimi

Antworten:

166

Ich habe einen Code, der ein Bild aus dem Web abruft und in einem Modell speichert. Die wichtigen Bits sind:

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

Das ist ein bisschen verwirrend, weil es aus meinem Modell herausgezogen und ein bisschen aus dem Kontext gerissen wurde, aber die wichtigen Teile sind:

  • Das aus dem Web gezogene Bild wird nicht im Ordner upload_to gespeichert, sondern von urllib.urlretrieve () als Tempfile gespeichert und später verworfen.
  • Die ImageField.save () -Methode verwendet einen Dateinamen (das Bit os.path.basename) und ein Objekt django.core.files.File.

Lassen Sie mich wissen, wenn Sie Fragen haben oder eine Klärung benötigen.

Bearbeiten: Aus Gründen der Übersichtlichkeit ist hier das Modell (abzüglich aller erforderlichen Importanweisungen):

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
quelle
2
tvon - Ich hatte etwas in diesem Sinne versucht, aber vielleicht werde ich es noch einmal versuchen, tatsächlich hatte ich Code, der diesem sehr ähnlich sah. (Auch wenn es außerhalb des Kontexts liegt, kann ich sehen, wie es funktioniert).
T. Stone
2
Ich würde vorschlagen, auch URL-Analyse zu verwenden, um zu vermeiden, dass URL-Paramatar-Gunk an das Bild angehängt wird. import urlparse. os.path.basename(urlparse.urlparse(self.url).path). Danke für den Beitrag, war hilfreich.
Dennmat
1
Ich erhalte django.core.exceptions.SuspiciousOperation: Versuchter Zugriff auf '/images/10.jpg' verweigert.
DataGreed
2
@DataGreed Sie sollten den Schrägstrich '/' aus der upload_to-Definition im Modell entfernen. Dies wurde hier angesprochen .
Tsikov
Ich erhalte folgende Fehlermeldung:prohibited to prevent data loss due to unsaved related object 'stream'.
Dipak
95

Super einfach, wenn das Modell noch nicht erstellt wurde:

Erstens , kopieren Sie Ihre Image - Datei auf den Upload - Pfad (angenommen = ‚Pfad /‘ in folgenden Ausschnitt).

Zweitens verwenden Sie etwas wie:

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

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

getestet und funktioniert in Django 1.4, es könnte auch für ein vorhandenes Modell funktionieren.

Rabih Kodeih
quelle
10
Dies ist die richtige Antwort, braucht mehr Stimmen !!! Habe diese Lösung auch hier gefunden .
Andrew Swihart
Hallo. Ich habe eine Frage. Ich verwende Django-Speicher mit dem Amazon S3-Backend. Wird dies einen neuen Upload auslösen?
Salvatore Iovene
OP fragt "ohne dass Django versucht, die Datei erneut zu speichern", und dies ist die Antwort darauf!
Freitag,
2
Django verfügt über eine vorhandene Logik, um doppelte Dateinamen auf der Festplatte zu berücksichtigen. Diese Methode blockiert diese Logik, da der Benutzer die Duplizierung von Dateinamen überprüfen muss.
Chris Conlan
1
@Conlan: Fügen Sie dem Dateinamen eine Anleitung hinzu.
Rabih Kodeih
41

Nur eine kleine Bemerkung. Die Antwort funktioniert, aber wenn Sie an Windows arbeiten, möchten Sie wahrscheinlich open()die Datei mit 'rb'. So was:

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

oder Sie werden Ihre Datei beim ersten 0x1AByte abgeschnitten .

Tiago Almeida
quelle
1
Vielen Dank, ich neige dazu, solche Details zu vergessen, mit denen uns Fenster konfrontiert sind.
Mike_k
fml ... was passiert, wenn dieser Parameter auf einem Linux-Computer übergeben wird?
DMac der Zerstörer
1
beantwortete meine eigene Frage ... Entschuldigung für den Spam. eine Dokumentation zu diesem hier . "Unter Unix tut es nicht weh, ein 'b' an den Modus anzuhängen, sodass Sie es plattformunabhängig für alle Binärdateien verwenden können."
DMac der Zerstörer
16

Hier ist eine Methode, die gut funktioniert und es Ihnen ermöglicht, die Datei auch in ein bestimmtes Format zu konvertieren (um den Fehler "Modus P kann nicht als JPEG geschrieben werden" zu vermeiden):

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)

Dabei ist das Bild das Django ImageField oder your_model_instance.image. Hier ist ein Verwendungsbeispiel:

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

Hoffe das hilft

michalk
quelle
12

Ok, wenn Sie lediglich den bereits vorhandenen Bilddateipfad mit ImageField verknüpfen müssen, ist diese Lösung möglicherweise hilfreich:

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

Wenn Sie es ernst meinen, wird die bereits vorhandene Bilddatei nicht mit ImageField verknüpft, aber die Kopie dieser Datei wird in upload_to dir als 'imgfilename.jpg' erstellt und mit ImageField verknüpft.

rmnff
quelle
2
Solltest du es nicht als Binärdatei öffnen?
Mariusz Jamro
Genau wie @MariuszJamro sagte, sollte es so sein:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns
Vergessen Sie auch nicht, das Objekt zu speichern:obj.save()
rahmatns
11

Was ich getan habe, war, meinen eigenen Speicher zu erstellen, der die Datei einfach nicht auf der Festplatte speichert:

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

Dann habe ich in meinen Modellen für mein ImageField den neuen benutzerdefinierten Speicher verwendet:

from custom_storage import CustomStorage

custom_store = CustomStorage()

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

Wenn Sie nur den tatsächlichen Dateinamen "festlegen" möchten, ohne den Aufwand für das Laden und erneute Speichern der Datei (!!) oder die Verwendung eines Zeichenfelds (!!!) zu verursachen, möchten Sie möglicherweise Folgendes ausprobieren: - -

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

Dadurch werden Ihre model_instance.myfile.url und alle anderen so beleuchtet, als hätten Sie die Datei tatsächlich hochgeladen.

Wie @ t-stone sagt, wollen wir wirklich instance.myfile.path = 'my-filename.jpg' setzen können, aber Django unterstützt dies derzeit nicht.

s29
quelle
Wenn model_instance die Instanz des Modells ist, das die Datei enthält. Wofür steht die andere "Instanz"?
h3.
7

Die meiner Meinung nach einfachste Lösung:

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
quelle
3

Viele dieser Antworten waren veraltet und ich verbrachte viele Stunden frustriert (ich bin ziemlich neu bei Django & Web Dev im Allgemeinen). Ich fand diesen hervorragenden Inhalt jedoch von @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
quelle
2

Dies ist möglicherweise nicht die Antwort, nach der Sie suchen. Sie können jedoch charfield verwenden, um den Pfad der Datei anstelle von ImageFile zu speichern. Auf diese Weise können Sie hochgeladene Bilder programmgesteuert dem Feld zuordnen, ohne die Datei neu zu erstellen.

Mohamed
quelle
Ja, ich war versucht, dies aufzugeben und entweder direkt in MySQL zu schreiben oder einfach ein CharField () zu verwenden.
T. Stone
1

Du kannst es versuchen:

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

Arbeiten! Sie können Bilder mithilfe von FileSystemStorage speichern. Überprüfen Sie das folgende Beispiel

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
quelle
Vielen Dank, Nids, ich arbeite absolut an dieser Lösung! Du hast mir viel Zeit gespart :)
Mehmet Burak Ibis
0

Sie können das Django REST-Framework und die Python Requests- Bibliothek verwenden, um Bilder programmgesteuert in Django ImageField zu speichern

Hier ist ein Beispiel:

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
quelle
0

Mit Django 3, mit einem Modell wie diesem:

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

Wenn das Bild bereits hochgeladen wurde, können wir direkt Folgendes tun:

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

oder

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