Programowo zapisywanie obrazu do Django ImageField

203

Ok, próbowałem już prawie wszystkiego i nie mogę tego uruchomić.

  • Mam model Django z ImageField
  • Mam kod, który pobiera obraz przez HTTP (przetestowany i działa)
  • Obraz jest zapisywany bezpośrednio w folderze „upload_to” (upload_to to ten ustawiony w ImageField)
  • Wszystko, co muszę zrobić, to powiązać już istniejącą ścieżkę pliku obrazu z ImageField

Napisałem ten kod na około 6 różnych sposobów.

Problem, na który napotykam, polega na tym, że cały kod, który piszę, powoduje następujące zachowanie: (1) Django utworzy drugi plik, (2) zmieni nazwę nowego pliku, dodając _ na końcu pliku name, a następnie (3) nie przesyłaj żadnych danych, pozostawiając w zasadzie pusty plik o zmienionej nazwie. W ścieżce „upload_to” pozostały 2 pliki, jeden rzeczywisty obraz i jeden, który jest nazwą obrazu, ale jest pusty, i oczywiście ścieżka ImageField jest ustawiona na pusty plik, który Django próbuje utworzyć .

W przypadku niejasności postaram się zilustrować:

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

Jak mogę to zrobić bez konieczności ponownego zapisywania pliku przez Django? To, co naprawdę chciałbym, to coś w tym celu ...

model.ImageField.path = generated_image_path

... ale to oczywiście nie działa.

I tak, przeszedłem przez inne pytania tutaj, takie jak to, a także dokument django na temat pliku

AKTUALIZACJA Po dalszych testach zachowuje się tak tylko, gdy działa pod Apache na Windows Server. Podczas działania pod „runserver” na XP nie wykonuje tego zachowania.

Jestem zakłopotany.

Oto kod, który działa poprawnie na XP ...

f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()
T. Stone
źródło
Kolejne świetne pytanie Django. Podjąłem kilka prób rozwiązania tego problemu bez powodzenia. Pliki utworzone w katalogu przesyłania są uszkodzone i mają tylko ułamek rozmiaru w porównaniu do oryginałów (przechowywanych gdzie indziej).
westmark
twoja AKTUALIZACJA nie działa
AmiNadimi,

Odpowiedzi:

166

Mam kod, który pobiera obraz z Internetu i przechowuje go w modelu. Ważne bity to:

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

Jest to trochę mylące, ponieważ zostało wyciągnięte z mojego modelu i trochę z kontekstu, ale najważniejsze części to:

  • Obraz pobrany z sieci nie jest przechowywany w folderze upload_to, zamiast tego jest zapisywany jako plik tymczasowy przez urllib.urlretrieve (), a następnie odrzucany.
  • Metoda ImageField.save () pobiera nazwę pliku (bit os.path.basename) i obiekt django.core.files.File.

Daj mi znać, jeśli masz pytania lub potrzebujesz wyjaśnień.

Edycja: dla jasności, oto model (bez wszelkich wymaganych instrukcji importu):

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
źródło
2
tvon - Próbowałem czegoś w tym celu, ale może spróbuję jeszcze raz, w rzeczywistości miałem kod, który wyglądał bardzo podobnie do tego. (Nawet jeśli nie jest to kontekst, widzę, jak to działa).
T. Stone
2
Sugeruję również użycie parsowania adresu URL, aby uniknąć dołączenia do obrazu gunk paramatar adresu URL. import urlparse. os.path.basename(urlparse.urlparse(self.url).path). Dzięki za post, był pomocny.
dennmat
1
Otrzymuję django.core.exceptions.SuspiciousOperation: Odmowa dostępu do pliku „/images/10.jpg”.
DataGreed,
2
@DataGreed powinieneś usunąć ukośnik „/” z definicji upload_to w modelu. Zostało to rozwiązane tutaj .
tsikov
Dostaję taki błąd:prohibited to prevent data loss due to unsaved related object 'stream'.
Dipak,
95

Super łatwe, jeśli model nie został jeszcze utworzony:

Najpierw skopiuj plik obrazu do ścieżki przesyłania (zakładane = „ścieżka /” w poniższym fragmencie).

Po drugie , użyj czegoś takiego:

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

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

przetestowany i działający w django 1.4, może działać również dla istniejącego modelu.

Rabih Kodeih
źródło
10
To właściwa odpowiedź, potrzebuje więcej głosów !!! Tutaj także znalazłem to rozwiązanie .
Andrew Swihart
Cześć. Mam pytanie. Używam django-storages z backendem Amazon S3. Czy spowoduje to ponowne przesłanie?
Salvatore Iovene,
OP prosi „bez konieczności ponownego zapisywania pliku przez Django”, i to jest odpowiedź na to!
frnhr
2
Django ma pewną istniejącą logikę do uwzględnienia zduplikowanych nazw plików na dysku. Ta metoda blokuje tę logikę, ponieważ użytkownik musi sprawdzić duplikację nazw plików.
Chris Conlan
1
@ Conlan: dołącz identyfikator do nazwy pliku.
Rabih Kodeih
41

Tylko mała uwaga. Odpowiedź tvon działa, ale jeśli pracujesz w open()systemie Windows, prawdopodobnie chcesz użyć pliku 'rb'. Lubię to:

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

lub plik zostanie obcięty przy pierwszym 0x1Abajcie.

Tiago Almeida
źródło
1
Dzięki, często zapominam o takich detalach, z którymi mają do czynienia okna.
mike_k
fml ... co się stanie, gdy ten parametr zostanie przekazany na maszynie z systemem Linux?
DMac Niszczyciel
1
odpowiedziałem na własne pytanie ... przepraszam za spam. znalazłem trochę dokumentacji na ten temat tutaj . „W systemie Unix dodanie„ b ”do trybu nie boli, więc możesz używać go niezależnie od platformy dla wszystkich plików binarnych.”
DMac Niszczyciel
16

Oto metoda, która działa dobrze i pozwala również przekonwertować plik na określony format (aby uniknąć błędu „nie można zapisać trybu P jako 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)

gdzie obraz to django ImageField lub twoja_modelka_instance.image tutaj jest przykładem użycia:

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

Mam nadzieję że to pomoże

Michalk
źródło
12

Ok, jeśli wszystko, co musisz zrobić, to powiązać już istniejącą ścieżkę pliku obrazu z ImageField, to rozwiązanie może być pomocne:

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

Cóż, jeśli tak powiem, istniejący już plik obrazu nie zostanie powiązany z ImageField, ale kopia tego pliku zostanie utworzona w katalogu upload_to jako 'imgfilename.jpg' i zostanie powiązana z ImageField.

rmnff
źródło
2
Nie powinieneś otworzyć go jako plik binarny?
Mariusz Jamro
Tak jak powiedział @MariuszJamro, powinno być tak:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns
również nie zapomnij zapisać obiektu:obj.save()
rahmatns
11

Zrobiłem to, aby stworzyć własną pamięć, która po prostu nie zapisze pliku na dysku:

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

Następnie w moich modelach do mojego ImageField użyłem nowej niestandardowej pamięci:

from custom_storage import CustomStorage

custom_store = CustomStorage()

class Image(models.Model):
    thumb = models.ImageField(storage=custom_store, upload_to='/some/path')
Nicu Surdu
źródło
7

Jeśli chcesz po prostu „ustawić” rzeczywistą nazwę pliku, bez ponoszenia nakładów związanych z ładowaniem i ponownym zapisywaniem pliku (!!) lub uciekaniem się do korzystania z charfield (!!!), możesz spróbować czegoś takiego - -

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

Spowoduje to podświetlenie twojego modelu_instance.myfile.url i wszystkich pozostałych, tak jakbyś faktycznie przesłał plik.

Jak mówi @ t-stone, tak naprawdę chcemy, aby móc ustawić instance.myfile.path = 'moja-nazwa_pliku.jpg', ale Django obecnie tego nie obsługuje.

s29
źródło
Jeśli instancja_modelu jest instancją modelu zawierającego plik .. co oznacza druga „instancja”?
h3.
7

Najprostsze rozwiązanie moim zdaniem:

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
źródło
3

Wiele z tych odpowiedzi było nieaktualnych i spędziłem wiele godzin z frustracją (jestem całkiem nowy w Django i ogólnie web dev). Znalazłem jednak tę doskonałą treść autorstwa @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
źródło
2

To może nie być odpowiedź, której szukasz. ale możesz użyć charfield do przechowywania ścieżki do pliku zamiast ImageFile. W ten sposób możesz programowo powiązać przesłany obraz z polem bez odtwarzania pliku.

Mohamed
źródło
Tak, miałem ochotę zrezygnować z tego i albo napisać bezpośrednio do MySQL, albo po prostu użyć CharField ().
T. Stone,
1

Możesz spróbować:

model.ImageField.path = os.path.join('/Upload', generated_image_path)
panda
źródło
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
źródło
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
źródło
1

Pracujący! Możesz zapisać obraz za pomocą FileSystemStorage. sprawdź poniższy przykład

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
źródło
Dziękuję Nids, absolutnie działające to rozwiązanie! Zaoszczędziłeś mi dużo czasu :)
Mehmet Burak Ibis
0

Możesz użyć frameworka REST Django i biblioteki żądań Pythona, aby programowo zapisać obraz w Django ImageField

Oto przykład:

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
źródło
0

Z Django 3 z modelem takim jak ten:

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

jeśli obraz został już przesłany, możemy bezpośrednio:

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

lub

my_item = Item.objects.get(id=5)
my_item.photo='image_folder/sample_photo.png'
my_item.save()
Skratt
źródło