@Oyaseo
beginner programmist

Как заставить django скачать и загрузить картинку?

Доброго времени суток, необходимо сохранить картинку по ссылке в поле ImageField. Никак не могу придумать способ это сделать. Подскажите пожалуйста!
Вот функция, которую я накидал(делал по примеру):
def baza(request):
	baza = urllib.request.urlopen("BAZA_URL").read().decode('utf-8-sig').replace(u'\xa0', ' ').replace(u'\r', ' ').replace(u'\n', ' ')
	baza = baza.split('<br>')
	for line in baza:
		line = line.split('--')
		try:
			categories = Categories.objects.get(name=line[0])
			img_url = line[1]
			content = urllib.request.urlopen(img_url).read()
			Item(categories=categories, img=content, name=line[2], text=line[3], options=line[4]).save()
		except:
			categories = Categories(name=line[0])
			img_url = line[1]
			content = urllib.request.urlopen(img_url).read()
			Item(categories=categories, img=content, name=line[2], text=line[3], options=line[4]).save()
	return HttpResponse(render_to_string('baza.html', locals()))
  • Вопрос задан
  • 1359 просмотров
Решения вопроса 1
@marazmiki
Укротитель питонов
Даже если закрыть глаза на оформление кода, постоянное дублирование, то это всё равно очень странный код с неопределённым поведением. И вот почему:
try:
    categories = Categories.objects.get(name=line[0])    # Метка 1
      img_url = line[1]   # Метка 2
      content = urllib.request.urlopen(img_url).read()        # Метка 3
      Item(categories=categories, img=content, name=line[2], text=line[3], options=line[4]).save()
    except:
      categories = Categories(name=line[0])    # Метка 4
      img_url = line[1]
      content = urllib.request.urlopen(img_url).read()
      Item(categories=categories, img=content, name=line[2], text=line[3], options=line[4]).save()    # Метка 5
Первым делом хочу обратить внимание на строку с комментарием "Метка 4". Вы, насколько я понял, хотите создать модель Categories, если её не нашлось в строке "Метка 1". Создать-то Вы её создали, но не сохранили! В базе такого объекта нет. Поэтому при вызове сохранения Item (см. "Метка 5") Вы можете схватить ошибку.

А можете не схватить, потому что возможна другая ситуация, когда категория (см. "Метка 1") нашлась, но картинка для скачивания определилась неправильно, и скачивание сломалось. Или просто сеть недоступна. В этом случае вывалится urllib, а категория будет определена правильно.

Далее: очень непонятный момент про img=content. Насколько я понял, Вы прямо берёте саму картинку (набор байт) и пишете его в БД? Если да, то это в корне неверно, ImageField работает по-другому. Во-первых, в базе данных это обычный varying charfield. Во-вторых, он хранит не сам контент картинки, а имя файла, под которым файл был сохранён при аплоаде. Сам файл хранится в другом месте (по умолчанию — в файловой системе рядышком в директории, указанной в настройке MEDIA_ROOT) Например, если в таком поле записано значение 'path/to/my/photo.jpg', то это значит, что при обращении model_name.img.url (обратите внимание, что мы обращаемся к атрибуту url поля img модели model_name) будет возвращено что-то типа /media/path/to/my/photo.jpg, где /media/ — корень хранилища загружаемых файлов (по умолчанию задаётся в настройке MEDIA_URL и имеет значение "/media/").

Советую переписать код.
  • Во-первых, при оформлении настоятельно советую поддерживаться PEP8.
  • Во-вторых, как уже рекомендовали выше, использовать requests.
  • В-третьих, использовать шорткаты, которые предоставляются Джангой. В данном случае я говорю про функцию render вместо пары HttpResponse(render_to_string) и метод get_or_create() у менеджера модели.
  • Избавиться от дублирования кода
  • Не использовать жадные except'ы. Ловить следует только те исключения, которые могут возникнуть штатно в процессе работы.


С учётом вышеизложенного, Ваш код может выглядеть как-то так:
from django.shortcuts import render
import requests
import requests.exceptions as rex

def baza(request):
    # В этом месте мы скачали страничку по адресу BAZA_URL/
    try:
        page = requests.get("BAZA_URL")
        page.raise_for_status()

    # Попутно обработали ситуацию, если страница по каким-то причинам
    # недоступна (например, её переместили, сервер ушёл в даун или просто
    # сеть недоступна). Все остальные возможные ошибки мы в этом месте
    # не обрабатываем.

    except (rex.ConnectionError, rex.HTTPError) as ex:
        return HttpResponse("Unable to open BAZA_URL")

    baza = page.content.split('<br>')

    for line in page:
        # Здесь мы просто ввели локальные переменные для упрощения
        # кода. Согласитесь, когда в поле модели name нужно подставить
        # имя, то проще написать переменную name, чем высчитывать, какой
        # же это по счёту элемент массива :)
        #
        # К тому же, если вдруг формат данных изменится, код придётся
        # править только в одном месте. так что мы заодно избавились от
        # дублирования.
        cat_name, img_url, name, text, options = line.split('--')[:4]

        # Воспользовались шорткатом от Джанги. Модель будет взята из базы,
        # если на существует с таким именем. Если нет, то будет создана.
        # Тоже избавление от дублирования
        categories, created = Categories.objects.get_or_create(name=cat_name)

        # Создание модели Item с помощью метода create у менеджера. Сразу
        # же мы решаем здесь возможную проблему с сетью. Если какой-то файл
        # окажется недоступным, мы его не будем сохранять, а перейдём к
        # следующему

        try:
            pic = requests.get(img_url)
            pic.raise_for_status()

            Item.objects.create(categories=categories,
                                img=ContentFile(pic.content),
                                name=name,
                                text=text,
                                options=options)
        except (rex.ConnectionError, rex.HTTPError) as ex:
            # Пришлось немного продублировать код, потому что здесь
            # возможны те же ошибки, что и в самом начале вьюхи.
            continue
Некоторые вещи я намеренно опустим (например то, что для всех HTTP-запросов с помощью requests можно использовать одно соединение, не создавая новое для каждого запроса)

И ещё один совет: операции, которые выполняются длительное время и которые могут свалиться по независящим от Вас причинам, лучше не делать в цикле запроса-ответа, а вынести в фоновую обработку. Например, воспользоваться celery
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@deliro
Советую пользоваться обёрткой над urllib - requests.
# Вместо такой конструкции
Item(categories=categories, img=content, name=line[2], text=line[3], options=line[4]).save()
# Лучше использовать
Item.objects.create(categories=categories, img=content, name=line[2], text=line[3], options=line[4])

except без эксепшена никогда не используй. Там тебе нужно писать except Categories.DoesNotExist:
Общую часть не дублируй. Вынеси в finally.

А в чём ошибка-то?
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы