Ошибка формы Django «Введите список значений» при рендеринге ManyToManyField как Textarea - PullRequest
9 голосов
/ 10 апреля 2011

Я пытаюсь выучить Джанго, и я столкнулся с некоторыми путаницами.В настоящее время у меня возникают проблемы при создании фильма с использованием формы.Идея формы состоит в том, чтобы предоставить пользователю любое поле, которое он хотел бы заполнить.Любое поле, которое заполняет пользователь, будет обновлено в соответствующей таблице sql (пустые поля будут игнорироваться).Но форма продолжает выдавать ошибку «Введите список значений», когда я отправляю форму.Чтобы решить эту проблему, я решил добавить данные из формы в список и затем вернуть этот список, чтобы решить эту проблему.

Первой идеей было переопределить clean() в моей ModelForm.Однако, поскольку форма не проходит проверку is_valid() в моих представлениях, переменная cleaned_data в clean() ничего не содержит.Далее я попытался переопределить to_python().Тем не менее, to_python(), кажется, не называется.

Если я добавлю __metaclass__ = models.SubfieldBase в соответствующую модель, я получу ошибку времени выполнения

"TypeError: Ошибка при вызове конфликта метакласса баз метаклассов: метакласс производного класса долженбыть (не строгим) подклассом метаклассов всех его баз "

Мой подход, похоже, не работает.Я не уверен, как обойти ошибку «Введите список значений»! Любой совет?

Вот соответствующий код (обновлено):

models.py

""" Idea:
A movie consists of many equipments, actors, and lighting techniques. It also has a rank for the particular movie, as well as a title. 

A Theater consists of many movies.

A nation consists of many theaters. 
"""

from django.db import models
from django.contrib.auth.models import User

class EquipmentModel(models.Model):
        equip = models.CharField(max_length=20)
#       user = models.ForeignKey(User)

class ActorModel(models.Model):
        actor = models.CharField(max_length=20)
#       user = models.ForeignKey(User)

class LightModel(models.Model):
        light = models.CharField(max_length=20)
#       user = models.ForeignKey(User)

class MovieModel(models.Model):
#       __metaclass__ = models.SubfieldBase   
        rank = models.DecimalField(max_digits=5000, decimal_places=3)
        title = models.CharField(max_length=20)

        equipments = models.ManyToManyField(EquipmentModel, blank=True, null=True)
        actors = models.ManyToManyField(ActorModel, blank=True, null=True)
        lights = models.ManyToManyField(LightModel, blank=True, null=True)

class TheaterModel(models.Model):
        movies = models.ForeignKey(MovieModel)

class NationModel(models.Model):
        theaters = models.ForeignKey(TheaterModel)

=====================================
forms.py

""" 
These Modelforms tie in the models from models.py

Users will be able to write to any of the fields in MovieModel when creating a movie.
Users may leave any field blank (empty fields should be ignored, ie: no updates to database).
"""

from django import forms
from models import MovieModel
from django.forms.widgets import Textarea

class MovieModelForm(forms.ModelForm):
      def __init__(self, *args, **kwargs):
             super(MovieModelForm, self).__init__(*args, **kwargs)
             self.fields["actors"].widget = Textarea()
             self.fields["equipments"].widget = Textarea()
             self.fields["lights"].widget = Textarea()

       def clean_actors(self):
             data = self.cleaned_data.get('actors')
             print 'cleaning actors'
             return [data]


      class Meta:
            model = MovieModel

=============================================
views.py

""" This will display the form used to create a MovieModel """

from django.shortcuts import render_to_response
from django.template import RequestContext
from forms import MovieModelForm

def add_movie(request):
       if request.method == "POST":
             form = MovieModelForm(request.POST)

             if form.is_valid():
                    new_moviemodel = form.save()
                    return HttpResponseRedirect('/data/')

       else:
             form = MovieModelForm()

       return render_to_response('add_movie_form.html', {form:form,}, context_instance=RequestContext(request))

Ответы [ 2 ]

11 голосов
/ 10 апреля 2011

Вероятная проблема заключается в том, что список значений, представленных в текстовой области, не может быть нормализован в список моделей.

См. Документацию ModelMultipleChoiceField .

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

Что-то не так с использованием встроенного ModelMultipleChoiceField?Это обеспечит самый простой подход, но потребует от ваших пользователей сканирования списка доступных актеров (я использую поле актеров в качестве примера здесь).

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

# only showing the actor example, you can use something like this for other fields too

class MovieModelForm(forms.ModelForm):
    actors_list = fields.CharField(required=False, widget=forms.Textarea())

    class Meta:
        model = MovieModel
        exclude = ('actors',)

    def clean_actors_list(self):
        data = self.cleaned_data
        actors_list = data.get('actors_list', None)
        if actors_list is not None:
            for actor_name in actors_list.split(','):
                try:
                    actor = Actor.objects.get(actor=actor_name)
                except Actor.DoesNotExist:
                    if FAIL_ON_NOT_EXIST: # decide if you want this behaviour or to create it
                        raise forms.ValidationError('Actor %s does not exist' % actor_name)
                    else: # create it if it doesnt exist
                        Actor(actor=actor_name).save()
        return actors_list

    def save(self, commit=True):
        mminstance = super(MovieModelForm, self).save(commit=commit)
        actors_list = self.cleaned_data.get('actors_list', None)
        if actors_list is not None:
            for actor_name in actors_list.split(","):
                actor = Actor.objects.get(actor=actor_name)
                mminstance.actors.add(actor)

        mminstance.save()
        return mminstance

Выше приведен весь непроверенный код, но что-то подходящее к этому должно работать, если вы действительно хотите использовать Textarea для ModelMultipleChoiceField.Если вы пойдете по этому пути и обнаружите ошибки в моем коде выше, пожалуйста, измените мой ответ или предоставьте комментарий, чтобы я мог.Удачи.

Редактировать:

Другой вариант - создать поле, которое понимает список значений через запятую, но ведет себя аналогично ModelMultipleChoiceField.Глядя на исходный код для ModelMultipleChoiceField, он унаследован от ModelChoiceField, который позволяет определить, какое значение модели используется для нормализации.

## removed code because it's no longer relevant. See Last Edit ##

Редактировать:

Ух, я действительно должен был проверить трек django, чтобы убедиться, что это уже исправлено.Это.См. следующий билет для информации.По сути, они сделали то же самое, что и я.Они заставили ModelMutipleChoiceField соблюдать аргумент to_field_name. Это применимо только для django 1.3!

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

class ModelCommaSeparatedChoiceField(ModelMultipleChoiceField):
    widget = Textarea
    def clean(self, value):
        if value is not None:
            value = [item.strip() for item in value.split(",")] # remove padding
        return super(ModelCommaSeparatedChoiceField, self).clean(value)

Итак, теперь ваша форма должна выглядеть так:

class MovieModelForm(forms.ModelForm):
    actors = ModelCommaSeparatedChoiceField(
               required=False, 
               queryset=Actor.objects.filter(), 
               to_field_name='actor')
    equipments = ModelCommaSeparatedChoiceField(
               required=False,
               queryset=Equipment.objects.filter(),
               to_field_name='equip')
    lights = ModelCommaSeparatedChoiceField(
               required=False, 
               queryset=Light.objects.filter(),
               to_field_name='light')

    class Meta:
        model = MovieModel
0 голосов
/ 10 апреля 2011

to_python AFAIK - это метод для полей, а не для форм.

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

Вы не предоставили примерыдля какого типа данных вводятся, но ответ заключается в очистке поля формы.

http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute

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

update : вы, вероятно, пропускаете ModelForm __init__ - посмотрим, исправит ли это.

class MovieModelForm(forms.ModelForm):
      def __init__(self, *args, **kwargs):
             super(MovieModelForm, self).__init__(*args, **kwargs)
             self.fields["actors"].widget = Textarea()

      def clean_actors(self):
          data = self.cleaned_data.get('actors')
          # validate incoming data. Convert the raw incoming string 
          # to a list of ids this field is expecting.
          # if invalid, raise forms.ValidationError("Error MSG")
          return data.split(',') # just an example if data was '1,3,4'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...