在将ManyToManyField呈现为Textarea时,Django“输入值列表”形式错误

时间:2011-04-09 23:14:46

标签: django

我正在尝试学习Django,但我遇到了一些令人困惑的问题。我目前在使用表单创建电影时遇到问题。表单的想法是为用户提供他想要填写的任何字段。用户填写的任何字段都将在其各自的sql表中更新(将忽略空字段)。但是,当我提交表单时,表单不断向我提供错误“输入值列表”。为了解决这个问题,我认为将表单中的数据填充到列表中,然后返回该列表就可以解决这个问题。

第一个想法是覆盖我的ModelForm中的clean()。但是,由于表单未能通过is_valid()检查我的观看次数,cleaned_data中的clean()变量不包含任何内容。接下来,我尝试覆盖to_python()。但是,to_python()似乎没有被调用。

如果我将__metaclass__ = models.SubfieldBase放在相应的模型中,我会收到运行时错误

  

“TypeError:调用时出错   元类基础       元类冲突:派生类的元类必须是a   (非严格的)子类   所有基础的元类“

我的方法似乎不起作用。我不确定如何绕过'输入值列表'错误!有什么建议吗?

以下是相关代码(已更新):

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 个答案:

答案 0 :(得分:11)

可能的问题是文本区域中提供的值列表无法标准化为模型列表。

请参阅ModelMultipleChoiceField documentation

该字段期望有效ID列表,但可能正在接收文本值列表,django无法转换为实际模型实例。 to_python将在表单字段中失败,而不是在表单本身内失败。因此,这些价值甚至都达不到形式。

使用内置的ModelMultipleChoiceField有什么问题吗?它将提供最简单的方法,但需要您的用户扫描可用的actor列表(我在这里使用actors字段作为示例)。

在我举例说明我如何尝试做你想做的事之前,我必须要问;你想如何处理数据库中尚不存在的已输入的演员?您可以创建它们(如果存在),也可以失败。你需要对此做出决定。

# 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 trac,看看这是否已经修好了。它是。有关信息,请参阅following ticket。基本上,他们做了同样的事情。他们使ModelMutipleChoiceField尊重to_field_name参数。 这仅适用于django 1.3!

问题是,常规ModelMultipleChoiceField将看到逗号分隔的字符串,并且因为它不是List或Tuple而失败。因此,我们的工作变得有点困难,因为在常规清理方法可以运行之前,我们必须将字符串更改为列表或元组。

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

答案 1 :(得分:0)

to_python AFAIK是字段的方法,而不是表单。

单个字段清理后发生

clean(),因此ModelMultipleChoiceFields clean()方法会引发验证错误,因此cleaned_data不包含任何内容。

您没有提供输入什么类型数据的示例,但答案在于表单字段清理。

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

您需要编写特定于该字段的验证,以便以您的字段所期望的格式返回正确的数据,或者引发ValidationError,以便您的视图可以使用错误消息重新呈现表单。

更新:您可能错过了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'