Включите дополнительное поле в inlineformset_factory, где имеете дело с ImageField

Используя Django, я создаю эскиз изображения, загруженного с помощью стандартного ImageField, добавляя путь к таблице в отдельном поле "thumbnail" рядом с обычным полем imagefile. Я пытаюсь выяснить, как получить доступ к пути миниатюр, где отображается объект поля изображения в пользовательском шаблоне с использованием набора форм, чтобы я мог его отобразить.

Я предполагаю, что мне нужно добавить 'form=CarImageForm' в inlineformset_factory, а затем изменить свой файл forms.py, но у меня возникают проблемы с разработкой, как это сделать или даже с правильным подходом. Для ясности я не включил свои попытки сделать это в примеры кода ниже.

Моя конечная цель - вернуть уменьшенное изображение, которое ссылается на исходное изображение - уже отображается через поле Imagefile.

Заранее спасибо!

шаблон это:

<form method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset.forms %}
    {% for field in form %}
        {{ field.label }}: {{ field }}<br>
    {% endfor %}
{% endfor %}
<p><input type="submit" value="Enter"/></p>

forms.py:

class CarForm(ModelForm):
    class Meta:
        model = Car
        exclude = ['owner', 'uploaded']

views.py:

# Edit an existing record
@login_required
def edit_existing(request, object_id=False):
    try:
        car = Car.objects.get(pk=object_id)
    except Car.DoesNotExist:    
        raise Http404   
    ImageFormSet = inlineformset_factory(Car, CarImage, extra=1, max_num=1)
    if request.method == 'POST':
        form = forms.CarForm(request.POST, instance=car)
        formset = ImageFormSet(request.POST, request.FILES, instance=car)
        if formset.is_valid() and form.is_valid():
            # Handle form.save() to include user id
            new_car = form.save(commit=False)
            new_car.owner = request.user
            new_car.save()
            # Formset - contains the attached images
            formset.save()  
            return HttpResponseRedirect(new_car.get_absolute_url())
    else:
        form = forms.CarForm(instance=car)
        formset = ImageFormSet(instance=car)
    return render_to_response('edit_existing.html',
        {'form': form, 'formset': formset},
        context_instance=RequestContext(request))

models.py:

class Car(models.Model):
    make = models.CharField(max_length=64)
    model = models.CharField(max_length=64)
    owner = models.ForeignKey(User,editable=False)
    uploaded = models.DateField(default=datetime.date.today,editable=False)

    def get_absolute_url(self):
        return reverse('vehicle_admin.views.car_detail', args=[str(self.id)])

def orig_car_id_folder(instance, filename):
    return 'uploads/images/orig/{0}/{1}'.format(instance.car_id, filename)

def thumb_car_id_folder(instance, filename):
    return 'uploads/images/thumb/{0}/{1}'.format(instance.car_id, filename)

class CarImage(models.Model):
    car = models.ForeignKey(Car)
    imagefile = models.ImageField(upload_to=orig_car_id_folder)
    thumbnail = models.ImageField(upload_to=thumb_car_id_folder, editable=False)

    # PIL tips from 
    # https://snipt.net/danfreak/generate-thumbnails-in-django-with-pil/
    # http://www.mechanicalgirl.com/post/image-resizing-file-uploads-doing-it-easy-way/
    def save(self):
        import os
        from PIL import Image
        from cStringIO import StringIO
        from django.core.files.uploadedfile import SimpleUploadedFile
        THUMBNAIL_SIZE = (75, 75)
        super(CarImage, self).save() # Use the commit=False param here?
        image = Image.open(self.imagefile.path)
        if image.mode not in ('L', 'RGB'):
            image = image.convert('RGB')
        image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
        temp_handle = StringIO()
        image.save(temp_handle, 'png')
        temp_handle.seek(0)
        name_ext = os.path.splitext(os.path.split(self.imagefile.name)[-1])
        suf = SimpleUploadedFile(name_ext[0],
                temp_handle.read(), content_type='image/png')
        self.thumbnail.save(suf.name+'.png', suf, save=False)
        super(CarImage, self).save()

2 ответа

Решение

Я думаю, что это основной упущение с моей стороны.

Моя проблема была установка editable=False в моей модели CarImage для поля 'thumbnail'. Это хорошо для первоначальной отправки данных, но вызывает проблемы, когда редактирование записи, так как это поле автоматически исключается из формы. (См. Мои фрагменты кода выше для получения дополнительной информации.)

Моим следующим шагом будет настройка пользовательской формы для моего представления add_new (я изначально не включал это представление, но именно это я использовал для включения начальной загрузки данных), например так:

class InitialCarImageForm(ModelForm):
    class Meta:
        model = CarImage
        exclude = ['thumbnail']

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

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

Если вы хотите попробовать использовать мою библиотеку django-extra-views, вы можете сделать что-то вроде этого:

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

from extra_views import UpdateWithInlinesView, InlineFormSet


class CarForm(ModelForm):

    def __init__(self, *args, **kwargs):
        self.user = self.kwargs.pop('user')
        super(CarForm, self).__init__(*args, **kwargs)

    def save(self, commit=True)
        instance = super(CarForm, self).save(commit=False)
        instance.user = self.user

        if commit:
            instance.save()
        return instance

    class Meta:
        model = Car
        exclude = ['owner', 'uploaded']


CarImageInline(InlineFormSet):
    model = CarImage
    extra = 1
    max_num = 1


UpdateCarView(UpdateWithInlinesView):
    pk_url_kwarg = 'object_id'
    inlines = [CarImageInline]
    template_name = 'edit_existing.html'
    form_class = CarForm

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(UpdateCarView, self).dispatch(*args, **kwargs)

    def get_form_kwargs(self):
        kwargs = super(UpdateCarView, self).get_form_kwargs()
        kwargs['user'] = self.request.user
        return kwargs

    def get_success_url(self):
        return self.object.get_absolute_url()

Ключевые отличия:

  • Форма становится ответственной за назначение пользователя автомобилю, форма захватывает пользователя, когда он создается (см. Get_form_kwargs). Причина для этого заключается в том, что в представлениях на основе классов переопределение form_valid (или в этом случае forms_valid) немного неуклюже, поэтому я предпочитаю помещать вещи в форму.
  • Вместо получения formset в вашем контексте вы получаете inlines, который является списком форм. Это потому, что технически вы можете иметь столько встроенных наборов форм, сколько захотите, даже если в этом случае у вас есть только один, он фактически такой же, как работает администратор Django.
  • Переопределение dispatch применить login_required Декоратор неуклюжий, к счастью, это что-то, что легко абстрагируется в миксин, см. django-скобки для некоторых готовых.
Другие вопросы по тегам