Добавление общего поля изображения в ModelForm в django

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

Надеялся, что кто-то, кто немного более знаком с формами django, сможет указать, где я ошибся.

Обновлять:

Я попытался пояснить, почему я выбрал этот дизайн, в комментариях к текущим ответам. Обобщить:

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

Поскольку ни один из ответов до сих пор не касался того, как сделать это немного более СУХИМ, я придумал свое собственное решение, которое заключалось в том, чтобы добавить путь загрузки в качестве атрибута класса в модель изображения и ссылаться на него каждый раз, когда это необходимо.

# Models
class Image(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')
    image = models.ImageField(_('Image'),
                                height_field='',
                                width_field='',
                                upload_to='uploads/images',
                                max_length=200)
class Room(models.Model):
    name = models.CharField(max_length=50)
    image_set = generic.GenericRelation('Image') 

# The form
class AddRoomForm(forms.ModelForm):
    image_1 = forms.ImageField()

    class Meta:
        model = Room

# The view
def handle_uploaded_file(f):

    # DRY violation, I've already specified the upload path in the image model
    upload_suffix = join('uploads/images', f.name)
    upload_path = join(settings.MEDIA_ROOT, upload_suffix)
    destination = open(upload_path, 'wb+')
    for chunk in f.chunks():
        destination.write(chunk)
    destination.close()
    return upload_suffix

def add_room(request, apartment_id, form_class=AddRoomForm, template='apartments/add_room.html'):
    apartment  = Apartment.objects.get(id=apartment_id)

    if request.method == 'POST':
        form = form_class(request.POST, request.FILES)
        if form.is_valid():
            room = form.save()
            image_1 = form.cleaned_data['image_1']

            # Instead of writing a special function to handle the image, 
            # shouldn't I just be able to pass it straight into Image.objects.create
            # ...but it doesn't seem to work for some reason, wrong syntax perhaps?

            upload_path = handle_uploaded_file(image_1)
            image = Image.objects.create(content_object=room, image=upload_path)
            return HttpResponseRedirect(room.get_absolute_url())
    else:
        form = form_class()
    context = {'form': form, }
    return direct_to_template(request, template, extra_context=context)

Где код вашей модели?

muhuk 22.01.2009 10:47
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
1
7 817
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Почему бы вам просто не использовать ImageField? Не вижу необходимости в классе Image.

# model
class Room(models.Model):
    name = models.CharField(max_length=50)
    image = models.ImageField(upload_to = "uploads/images/")

# form
from django import forms

class UploadFileForm(forms.Form):
    name = forms.CharField(max_length=50)
    image  = forms.FileField()

Взгляните на Основные загрузки файлов и Как использовать поля изображения и файла?

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

prairiedogg 23.01.2009 13:35
Ответ принят как подходящий

Вам не обязательно использовать класс Image. Как предлагается ДЗПМ, преобразовать поле изображения в ImageField. Вам также необходимо внести некоторые изменения в представление.

Вместо использования обработчика загрузки вы можете создать объект Image с загруженными данными и присоединить объект Image к объекту Room.

Чтобы сохранить объект Image, вам нужно сделать что-то вроде этого в представлении:

from django.core.files.base import ContentFile

if request.FILES.has_key('image_1'):
    image_obj = Image()
    image_obj.file.save(request.FILES['image_1'].name,\
                        ContentFile(request.FILES['image_1'].read()))
    image_obj.save()
    room_obj.image_set.create(image_obj)
    room_obj.save()

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

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

prairiedogg 23.01.2009 13:27

Да, тогда используйте ForeignKey. Обновите модели в вопросе, и я расскажу, как написать представление.

Baishampayan Ghose 23.01.2009 14:16

Я ценю это, но привязка класса Image к одной модели - это не то, что я хочу здесь. Вопрос был в том, как сделать это СУХИМ или использовать соглашение, чтобы сделать его чище. Я решил проблему DRY (см. Обновление), а также переместил логику создания изображения из представления в метод form.save (). Работает для меня.

prairiedogg 23.01.2009 18:28

Как насчет использования двух форм на странице: одной для комнаты и одной для изображения?

Вам просто нужно сделать общие поля внешнего ключа формы изображения необязательными и заполнить их значения в представлении после сохранения комнаты.

Django поддерживает ваш вариант использования, по крайней мере, до определенного момента:

  • наборы форм отображают повторяющиеся формы
  • наборы форм для моделей обрабатывают повторяющиеся формы моделей
  • встроенные наборы форм привязывают наборы форм модели к связанным объектам экземпляра
  • общие встроенные наборы форм делает то же самое для общих отношений

Общие встроенные наборы форм были введены в набор изменений [8279]. См. изменения в модульных тестах, чтобы увидеть, как они используются.

С общими встроенными наборами форм вы также сможете отображать несколько уже сохраненных изображений для существующих комнат в вашей форме.

Кажется, что встроенные наборы форм ожидают существующего родительского экземпляра в аргументе instance=. Интерфейс администратора позволяет вам заполнять строки перед сохранением родительского экземпляра, поэтому должен быть способ добиться этого. Просто я сам никогда не пробовал.

В определенный момент, когда возникла проблема, я смотрел на общие встроенные наборы форм. Они действительно кажутся мне именно тем, что я хочу использовать, но в то время я просто не мог заставить их работать. Спасибо за чаевые.

prairiedogg 15.03.2009 07:30

Я обнаружил, что эта страница ищет решение той же проблемы.

Вот моя информация - надеюсь, поможет.

МОДЕЛИ: Изображение, Обзор, Производитель, Профиль

Я хочу, чтобы «Обзор», «Производитель», «Профиль» были связаны с моделью «Изображение». Но у вас должна быть возможность иметь несколько изображений для каждого объекта. (То есть в одном обзоре может быть 5 изображений, в другом - 3 и т. д.)

Первоначально я сделал

images = ManyToManyField(Image)

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

Еще одна вещь, над которой я сейчас работаю, - это наличие нескольких внешних ключей.

class Image(models.Model):
    description = models.TextField(blank=True)
    image = models.ImageField(upload_to = "media/")
    user_profile = models.ForeignKey(UserProfile)
    mfgr = models.ForeignKey(Manufacturer)
    review = models.ForeignKey(Review)

но как ты сказал. Это выглядит довольно небрежно, и мне это просто не нравится.

Еще одна вещь, которую я только что обнаружил, но не полностью обернул (и не уверен, насколько она прозрачна после реализации), - это общие отношения (или общие внешние ключи), которые могут быть решением. Хорошо, когда я все это пойму. Потребность больше кофеина.

http://www.djangoproject.com/documentation/models/generic_relations/

Дайте мне знать, если вы разберетесь с этим или что-то из этого поможет. Спасибо!

Дайте мне знать, поможет ли это или у вас есть другие решения.

Хорошо, я понял это, прочитав еще немного ... Я чувствую, что вы хотите сделать именно то, что сделал я, так что вот оно.

Для этого я буду использовать GenericForeignKeys.

Сначала импорт для models.py

from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

Теперь добавьте следующее в свою модель изображения

class Image(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey()

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

images = generic.GenericRelation(Image)

Теперь в admin.py нужно добавить следующие вещи.

from django.contrib.contenttypes.generic import GenericTabularInline

class ImageInline(GenericTabularInline):
    model = Image
    extra = 3
    ct_field_name = 'content_type'
    id_field_name = 'object_id'

А затем включите его в объявление администратора

class ReviewAdmin(admin.ModelAdmin):
    inlines = [ImageInline]

Вот и все. Здесь отлично работает. Надеюсь, это поможет человеку! .Адам.

Если вы читаете мой исходный вопрос, это именно то, что я сделал с точки зрения схемы (вернитесь и посмотрите на мою модель изображения). Мой вариант использования был для чего-то за пределами администратора, но если бы это было в админке, да, это более или менее то, что я бы сделал.

prairiedogg 15.03.2009 07:27

Используйте две формы, одну для комнаты и одну для изображения:

класс Image (models.Model)

content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
image = models.ImageField(upload_to='')

класс UploadImage (forms.ModelForm):

class Meta:
    model = Image
    fields = ('image')

класс Room (models.Model):

name = models.CharField(max_length=50)
images = models.ManyToManyField(Image) 

класс RoomForm (forms.ModelForm):

class Meta:
    model = Room

в представлениях

если request.method == "POST":

    ##2 form, una per l'annuncio ed una per la fotografia
    form = RoomForm(request.POST)
    image_form = UploadImage(request.POST, request.FILES)
    #my_logger.debug('form.is_valid() : ' + str(form.is_valid()))
    if form.is_valid() and image_form.is_valid():
        ##save room
        room = room.save()

        ##save image
        image = image_form.save()

        ##ManyToMany
        room.images = [image]
        room.save()

Другие вопросы по теме