Salvataggio programmatico dell'immagine su Django ImageField


203

Ok, ho provato quasi tutto e non riesco a farlo funzionare.

  • Ho un modello Django con un ImageField su di esso
  • Ho un codice che scarica un'immagine via HTTP (testato e funziona)
  • L'immagine viene salvata direttamente nella cartella 'upload_to' (l'upload_per essere quello impostato su ImageField)
  • Tutto quello che devo fare è associare il percorso del file di immagine già esistente a ImageField

Ho scritto questo codice in 6 modi diversi.

Il problema in cui mi imbatto è tutto il codice che sto scrivendo si traduce nel seguente comportamento: (1) Django creerà un secondo file, (2) rinominerà il nuovo file, aggiungendo un _ alla fine del file name, quindi (3) non trasferire alcun dato oltre a lasciarlo sostanzialmente un file rinominato vuoto. Ciò che rimane nel percorso 'upload_to' sono 2 file, uno che è l'immagine reale e uno che è il nome dell'immagine, ma è vuoto, e ovviamente il percorso ImageField è impostato sul file vuoto che Django tenta di creare .

Nel caso non fosse chiaro, proverò a illustrare:

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

Come posso farlo senza che Django tenti di ri-archiviare il file? Quello che mi piacerebbe davvero è qualcosa in tal senso ...

model.ImageField.path = generated_image_path

... ma ovviamente non funziona.

E sì, ho esaminato le altre domande qui come questa e il documento django su File

AGGIORNARE Dopo ulteriori test, esegue questo comportamento solo quando viene eseguito in Apache su Windows Server. Durante l'esecuzione sotto il "runtime server" su XP non esegue questo comportamento.

Sono perplesso.

Ecco il codice che funziona correttamente su XP ...

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

Un'altra grande domanda di Django. Ho fatto diversi tentativi per risolvere questo problema senza fortuna. I file creati nella directory di caricamento sono interrotti e solo una frazione in termini di dimensioni rispetto agli originali (memorizzati altrove).
Westmark,

il tuo AGGIORNAMENTO non funziona
AmiNadimi,

Risposte:


167

Ho del codice che recupera un'immagine dal Web e la memorizza in un modello. I bit importanti sono:

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

È un po 'confuso perché è stato estratto dal mio modello e un po' fuori contesto, ma le parti importanti sono:

  • L'immagine estratta dal web non lo è archiviata nella cartella upload_to, ma viene invece archiviata come file temporaneo da urllib.urlretrieve () e successivamente eliminata.
  • Il metodo ImageField.save () accetta un nome file (il bit os.path.basename) e un oggetto django.core.files.File.

Fammi sapere se hai domande o hai bisogno di chiarimenti.

Modifica: per motivi di chiarezza, ecco il modello (meno eventuali dichiarazioni di importazione richieste):

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 - Avevo provato qualcosa in tal senso, ma forse ci proverò di nuovo, infatti, avevo un codice molto simile a questo. (Anche se è fuori contesto, posso vedere come funziona).
T. Stone,

2
Suggerirei di usare anche url parse per evitare di aggiungere url paramatar gunk all'immagine. import urlparse. os.path.basename(urlparse.urlparse(self.url).path). Grazie per il post, è stato utile.
Dennmat,

1
Ottengo django.core.exceptions.SuspiciousOperation: tentativo di accesso a "/images/10.jpg" negato.
Data accordo

2
@DataGreed è necessario rimuovere la barra "/" dalla definizione upload_to nel modello. Questo è stato affrontato qui .
Tsikov,

Ricevo un errore del genere:prohibited to prevent data loss due to unsaved related object 'stream'.
Dipak,

95

Super facile se il modello non è stato ancora creato:

Innanzitutto , copia il tuo file di immagine nel percorso di caricamento (assunto = 'percorso /' nel seguente frammento).

Secondo , usa qualcosa come:

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

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

testato e funzionante in django 1.4, potrebbe funzionare anche con un modello esistente.


10
Questa è la risposta giusta, ha bisogno di più voti !!! Ho trovato questa soluzione anche qui .
Andrew Swihart,

Ciao. Ho una domanda. Sto usando archivi django con il backend Amazon S3. Questo attiverà un nuovo caricamento?
Salvatore Iovene,

OP chiede "senza che Django tenti di ri-archiviare il file", e questa è la risposta!
dal

2
Django ha alcune logiche esistenti per tenere conto dei nomi di file duplicati sul disco. Questo metodo limita questa logica perché l'utente è lasciato a controllare la duplicazione del nome file.
Chris Conlan,

1
@Conlan: aggiungi una guida al nome del file.
Rabih Kodeih,

41

Solo una piccola osservazione. tvon answer funziona ma, se stai lavorando su Windows, probabilmente vorrai open()il file con 'rb'. Come questo:

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

o otterrai il tuo file troncato al primo 0x1Abyte.


1
Grazie, tendo a dimenticare i dettagli di così basso livello che le finestre ci affrontano.
mike_k,

fml ... cosa succede quando quel parametro viene passato su una macchina linux?
DMac the Destroyer,

1
ha risposto alla mia domanda ... scusate lo spam. ho trovato della documentazione per questo qui . "Su Unix, non fa male aggiungere una" b "alla modalità, quindi puoi usarla in modo indipendente dalla piattaforma per tutti i file binari."
DMac il distruttore il

16

Ecco un metodo che funziona bene e ti permette di convertire il file anche in un determinato formato (per evitare l'errore "impossibile scrivere la modalità P come 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)

dove image è django ImageField o your_model_instance.image ecco un esempio di utilizzo:

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

Spero che questo ti aiuti


12

Ok, se tutto ciò che devi fare è associare il percorso del file di immagine già esistente a ImageField, questa soluzione può essere utile:

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

Bene, se è sincero, il file di immagine già esistente non sarà associato a ImageField, ma la copia di questo file verrà creata in upload_to dir come 'imgfilename.jpg' e sarà associata a ImageField.


2
Non lo apri come file binario?
Mariusz Jamro,

Proprio come ha detto @MariuszJamro, dovrebbe essere così:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns,

inoltre, non dimenticare di salvare l'oggetto:obj.save()
rahmatns,

11

Quello che ho fatto è stato creare il mio spazio di archiviazione che non salverà il file sul disco:

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

Quindi, nei miei modelli, per il mio ImageField, ho usato il nuovo archivio personalizzato:

from custom_storage import CustomStorage

custom_store = CustomStorage()

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

7

Se vuoi semplicemente "impostare" il nome del file attuale, senza incorrere nel sovraccarico di caricamento e ri-salvataggio del file (!!), o ricorrendo all'utilizzo di un charfield (!!!), potresti provare qualcosa del genere - -

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

Questo illuminerà il tuo model_instance.myfile.url e tutti gli altri come se avessi effettivamente caricato il file.

Come dice @ t-stone, quello che vogliamo davvero è poter impostare instance.myfile.path = 'my-nomefile.jpg', ma Django attualmente non lo supporta.


Se model_instance è l'istanza del modello che contiene il file .. cosa significa l'altra "istanza"?
h3.

7

La soluzione più semplice secondo me:

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

Molte di queste risposte erano obsolete e ho trascorso molte ore in frustrazione (sono abbastanza nuovo per Django e gli sviluppatori web in generale). Tuttavia, ho trovato questa eccellente sintesi di @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

Questa potrebbe non essere la risposta che stai cercando. ma puoi utilizzare charfield per memorizzare il percorso del file anziché ImageFile. In questo modo è possibile associare a livello di codice l'immagine caricata al campo senza ricreare il file.


Sì, sono stato tentato di rinunciare a questo e di scrivere direttamente su MySQL o semplicemente di usare un CharField ().
T. Stone,

1

Puoi provare:

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

Lavorando! È possibile salvare l'immagine utilizzando FileSystemStorage. controlla l'esempio sotto

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)

Grazie Nids, assolutamente funzionante questa soluzione! Mi hai risparmiato un sacco di tempo :)
Mehmet Burak Ibis

0

Puoi usare il framework Django REST e la libreria Richieste python per salvare a livello di codice l'immagine su Django ImageField

Ecco un esempio:

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

Con Django 3, con un modello come questo:

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

se l'immagine è già stata caricata, possiamo fare direttamente:

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

o

my_item = Item.objects.get(id=5)
my_item.photo='image_folder/sample_photo.png'
my_item.save()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.