Установите значение по умолчанию ImageField для случайного изображения из списка изображений в Django.

У меня есть несколько изображений внутри статического каталога, и я хочу создать модель с ImageField. Я хочу установить поле по умолчанию для любого из этих изображений. Я пытался использовать это -

def randomImage():
    return ImageFile('media/blog/image/' + str(random.randrange(1, 15, 1)) + '.jpg')

# ----------------------- Model for each post in the blog-------------------
class Post(models.Model):
    heading = models.CharField(max_length=150)
    author = models.ForeignKey(User)
    postBody = models.TextField()
    postDate = models.DateTimeField('posting date')
    postImage = models.ImageField(upload_to='media/blog/image/'+str(int(time.time())), default=randomImage)

person user3324847    schedule 09.04.2015    source источник
comment
Это связано: stackoverflow.com /вопросы/1276887/   -  person abidibo    schedule 09.04.2015
comment
@abidibo Нет, приведенная выше ссылка ничего не говорит об использовании функции генерации случайного пути к изображению. Я хочу использовать функцию, которая возвращает случайный путь к изображению, а затем установить его в качестве изображения для этой записи.   -  person user3324847    schedule 13.04.2015
comment
Я назначаю награду за это, поскольку на этот вопрос нет ответа, а связанный ответ совершенно не связан.   -  person Routhinator    schedule 27.09.2018
comment
@Routhinator Я добавил один ответ, попробуйте :)   -  person JPG    schedule 27.09.2018
comment
@JPG Посмотрев, я присужу награду, как только я подтвержу, что это работает хорошо. :)   -  person Routhinator    schedule 27.09.2018
comment
Я мог бы прикрепить минимальную ссылку на проект, если это необходимо :)   -  person JPG    schedule 27.09.2018
comment
У меня есть ряд проблем с этим решением, которые я пытаюсь решить, прежде чем предлагать задачи. Во-первых, django не ищет статические файлы, а просматривает медиафайлы, что не соответствует значениям по умолчанию. Другое дело, что статические файлы не существуют при вызове django.setup(), но статические не могут быть собраны до тех пор, пока не будет вызван django.setup. Другое дело, что функция нуждается в settings.BASE_DIR для проверки вашего файла, но должна возвращать относительный путь без BASE_DIR по умолчанию.   -  person Routhinator    schedule 27.09.2018
comment
Работая над проблемами, мне пришлось добавить обработку исключений в функцию, чтобы разрешить запуск django, однако логика всегда не может найти существующие изображения .... все еще пытаюсь понять, почему.   -  person Routhinator    schedule 27.09.2018
comment
Мне также пришлось сделать STATIC_ROOT подкаталогом MEDIA_ROOT.   -  person Routhinator    schedule 27.09.2018


Ответы (2)


Здесь я делаю некоторые предположения,
1. Ваши изображения по умолчанию находятся в каталоге static
2. Внутри < strong>static, все файлы являются изображениями

В чем заключается основной трюк?

Django нужен действительный путь к файлу только для создания или обновления записи файла (изображение также является файлом). Итак, что мы здесь делаем, так это перечисляем все файлы (при условии, что это только изображения) и выбираем одну запись случайным образом, используя random.choice() и повторно настроить абсолютный путь (что-то вроде static/my_img.jpg) к аргументу default

import time
from random import choice
from os.path import join as path_join
from os import listdir
from os.path import isfile


def random_img():
    dir_path = 'static'
    files = [content for content in listdir(dir_path) if isfile(path_join(dir_path, content))]
    return path_join(dir_path, choice(files))


class Post(models.Model):
    # other fields
    postImage = models.ImageField(
        upload_to='media/blog/image/' + str(int(time.time())),
        default=random_img)



ОБНОВЛЕНИЕ-1

Я создал минимальный пример в Django 2.1.1, который можно найти в следующем репозитории git
Ссылка на репозиторий -> django2X
1. клонируйте репозиторий, создайте виртуальную среду и установите зависимости (при наличии файла requirements.txt)
2. создайте нового суперпользователя или используйте мое (имя пользователя -> admin, пароль-> jerin123@)
3. Запустите сервер и войдите в систему администратора django: (скриншот-1)
4. Создайте новый экземпляр Post (снова и снова)

Готово

ОБНОВЛЕНИЕ-2

Я внес несколько изменений в мой минимальный пример, а именно
1. Создал простое создание объекта (здесь объект модели Post) при каждом запуске django (checkout sample/app.py)
2. Добавлены MEDIA_ROOT и MEDIA_URL в файл settings.py

Теперь запустите свой проект (он будет создавать 3 объекта при каждом запуске) и перейдите по адресу http://127.0.0.1:8000/admin/sample/post/.
Затем откройте любой экземпляр и щелкните ссылку на изображение (скриншот-2), и он откроет изображение (скриншот-3)

person JPG    schedule 27.09.2018
comment
Похоже, это не сработает. Django выполняет случайную функцию при вызове django.start(), то есть до сбора статики. Он получает ответ по умолчанию, который я добавил, если файлы не существуют, а затем он использует этот ответ для настройки по умолчанию статически. Он не вызывает функцию для model.save(), что делает функцию бесполезной. - person Routhinator; 27.09.2018
comment
Может быть, я говорил слишком рано. Это работает для новых пользователей, зарегистрированных после запуска приложения, но не для пользователей, сохраняемых от первоначального импорта при запуске. Попытка выяснить, почему. - person Routhinator; 27.09.2018
comment
Что вы подразумеваете под начальным импортом. я не понимаю - person JPG; 27.09.2018
comment
Я переношу существующий сайт в Django. После запуска сбора статических файлов и миграции в моем сценарии startup.py я запускаю пользовательскую команду управления под названием installlegacy, которая проверяет, были ли импортированы пользователи и данные. Это использует устаревшее соединение с БД для захвата пользовательских объектов из старой БД, а затем сопоставляет их свойства с новой пользовательской моделью, а затем вызывает user.save(). По какой-то причине во время этого процесса функция не может найти какие-либо изображения. , хотя статические файлы уже собраны. - person Routhinator; 27.09.2018
comment
чтобы дать вам полную картину, я создал фрагменты сценария запуска, процесса импорта, модифицированной версии вашего примера и models.py для участников. Функция random_img() установлена ​​по умолчанию для фонового изображения: gitlab.routh.io/users /Рутинатор/фрагменты - person Routhinator; 28.09.2018
comment
Я думаю, что на самом деле здесь происходит сценарий запуска — это однократное выполнение кода django, поэтому результат random_image вызывается только один раз, а затем используется при каждом сохранении в этом выполнении, тогда как сценарий регистрации, в котором это работает, — это выполнение для каждого запроса. , значит работает. - person Routhinator; 28.09.2018
comment
@Routhinator TLDR, когда я просматриваю эту строку, я обнаружил, что вам предоставляется < b>значение вместо вызываемой функции - person JPG; 28.09.2018
comment
чтобы получить динамический результат random_img функции, вы должны предоставить функцию как вызываемую (без круглые скобки ) - person JPG; 28.09.2018
comment
AFAIK, поэтому вы не можете передавать какие-либо аргументы функции из аргумента модели по умолчанию - person JPG; 28.09.2018
comment
На самом деле он работает для запросов после загрузки приложения, с вызываемым и всем остальным, и если я собираю статические данные до вызова сценария запуска, он загружает один фон, но затем использует его для ВСЕХ пользователей при импорте. Таким образом, я думаю, что вызываемый метод работает большую часть времени, однако он не будет работать при сохранении нескольких моделей в одном запросе/выполнении, поскольку модель загружается только один раз, а значение по умолчанию устанавливается только при загрузке модели. Так что это должно быть перенесено в функцию сохранения, чтобы работать в 100% ситуаций. - person Routhinator; 28.09.2018
comment
однако это не будет работать при сохранении нескольких моделей в одном запросе/выполнении. Что вы имеете в виду? и значение по умолчанию устанавливается только при загрузке модели, это неверно. Если вы установите вызываемую функцию, Django будет вызывать эту функцию при каждом создании объекта. - person JPG; 28.09.2018
comment
Можете ли вы создать минимальный проект django, отражающий вашу текущую ситуацию? - person JPG; 28.09.2018
comment
Давайте продолжим обсуждение в чате. - person Routhinator; 28.09.2018

Мое решение состоит в том, чтобы переопределить метод сохранения модели и проверить, не создается ли модель впервые, а также проверить, не пусто ли поле изображения postImage. Если это так, заполните поле postImage содержимым изображения Radom. Django справится с остальным

Если мы используем путь к изображению Radom непосредственно в нашей модели, мы в конечном итоге будем обслуживать некоторые файлы пост-модели из папки media, а некоторые — из статического каталога, что не рекомендуется. Вместо этого мы передаем содержимое файла изображения в поле postImage, и Django сохранит изображение в папку мультимедиа, и, таким образом, мы можем обслуживать все изображения нашей модели из самой папки мультимедиа. Воля

Код

Следующий код протестирован в Python 3.6. Добавьте код в свой models.py.

from pathlib import Path
from random import randint
import time
from django.core.files import File
from django.db import models

allowed_image_extensions = ['.jpg', '.png', '.jpeg']


def get_file_extension(file_path):
    return Path(file_path).suffix


def get_files_in_directory(directory, absolute_path=False):
    from os import listdir
    from os.path import isfile
    only_files = [f for f in listdir(directory) if isfile("{}/{}".format(directory, f))]

    if not absolute_path:
        return only_files

    else:
        return ["{}/{}".format(directory, file_) for file_ in only_files]


def get_random_image_from_directory(directory_path, image_extension=None):
    files_in_directory_path = get_files_in_directory(directory_path, absolute_path=True)

    if image_extension:
        allowed_file_types = [image_extension]
    else:
        allowed_file_types = allowed_image_extensions

    # filter out files of type other than required file types
    filtered_files_list = [_file for _file in files_in_directory_path if
                       get_file_extension(_file).lower() in allowed_file_types]

   random_index = randint(0, len(filtered_files_list) - 1)
   random_file_path = filtered_files_list[random_index]
   random_file_content = File(open(random_file_path, 'rb'))

   return random_file_content


def get_post_image_path(instance, filename):
    path_first_component = 'posts'
    ext = get_file_extension(filename)
    current_time_stamp = int(time.time())
    file_name = '{}/posts_{}_{}{}'.format(path_first_component, instance.id, current_time_stamp, ext)
    full_path = path_first_component + file_name
    return full_path

class Post(models.Model):
    heading = models.CharField(max_length=150)
    author = models.ForeignKey(User)
    postBody = models.TextField()
    postDate = models.DateTimeField('posting date')
    postImage = models.ImageField(blank=True, null=True, upload_to=get_post_image_path)

    # override model save method
    def save(self, *args, **kwargs):

        # check model is new and postImage is empty
        if self.pk is None and not self.postImage:
            random_image = get_random_image_from_directory(settings.STATIC_ROOT)
            self.postImage = random_image
            random_image.close()


    super(Post, self).save(*args, **kwargs)

Также нет необходимости устанавливать ‘/media’ в пути upload_to. Django будет читать медиа-путь из переменной настроек

Лучше всего переместить этот набор изображений по умолчанию из статического каталога в другую папку, возможно, в другую папку с ресурсами имен или любым другим значимым именем, поскольку содержимое статического каталога будет часто меняться по мере роста проекта.

person Iyvin Jose    schedule 28.09.2018
comment
Проверим это, я думаю, что это обещает, что это заставит случайное изображение действительно вызываться при сохранении. Я думаю, что с методом @JPG происходит то, что он вызывается один раз за выполнение, когда модель загружается django, поэтому мой импорт выполняет его только один раз, прежде чем будут собраны статические файлы, а затем использует результат сбоя для каждого пользователя. Принимая во внимание, что это будет принудительно вызываться при каждом вызове сохранения и будет работать все время. - person Routhinator; 28.09.2018
comment
Ах, этот код не застрахован от django.core.exceptions.SuspiciousFileOperation: The joined path (/usr/src/app/static/images/avatars/wolf_avatar1.jpg) is located outside of the base path component (/usr/src/app/media) - person Routhinator; 28.09.2018
comment
Удалось заставить его работать с небольшим изменением: .self.postImage.save( str(self.postDate) + get_file_extension(random_image.name), random_image, save=True) - person Routhinator; 28.09.2018
comment
Присуждена награда. Кажется, это самое надежное решение, которое работает при сохранении любой модели. Пожалуйста, обновите свой ответ с небольшой необходимой модификацией. - person Routhinator; 28.09.2018
comment
Еще две небольшие модификации. В этой строке вы создаете список списков, что заставляет фильтр расширения отфильтровывать все, даже если оно действительно. Он должен быть allowed_file_types = allowed_image_extensions (без [] вокруг var), и необходим абсолютный путь, поэтому при сохранении модели мы должны вызвать его с настройками django. STATIC_ROOT var: get_random_image_from_directory(settings.STATIC_ROOT) - person Routhinator; 28.09.2018
comment
Одна последняя модификация random_image.close() должна быть вызвана после save(), иначе производительность упадет. - person Routhinator; 28.09.2018
comment
@Routhinator Спасибо, что указали на ошибки. я обновил код - person Iyvin Jose; 01.10.2018