Django“输入值列表”将ManyToManyField渲染为Textarea时的窗体错误 [英] Django "Enter a list of values" form error when rendering a ManyToManyField as a Textarea

查看:742
本文介绍了Django“输入值列表”将ManyToManyField渲染为Textarea时的窗体错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试学习Django,我遇到了一些混乱的点。我目前无法使用表单创建电影。表单的想法是给用户任何他想填写的字段。用户填写的任何字段将在其相应的sql表中更新(空字段将被忽略)。但是,当我提交表单时,表单会给我错误输入值列表。为了解决这个问题,我以为把数据从表单中填入一个列表,然后返回该列表将解决这个问题。



第一个想法是覆盖 clean()在我的ModelForm中。但是,由于窗体在 is_valid()中检查我的视图, clean_data 变量失败> clean()不包含任何内容。接下来,我试图覆盖 to_python()。但是, to_python()似乎没有被调用。



如果我在相应的模型中放入 __ metaclass__ = models.SubfieldBase ,我收到运行时错误


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


我的方法似乎不起作用。我不知道如何解决输入值列表错误!任何建议?



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

  models.py 

想法:
一部电影由许多设备,演员和照明技术。它还具有特定电影的排名以及标题。

剧院由许多电影组成。

一个国家由许多剧院组成。


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)

instruments = 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


这些Modelforms在models.py

的模型中绑定用户可以写入任何MovieModel中创建影片时的字段。
用户可以将任何字段留空(空字段应忽略,即:不更新数据库)。


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

class MovieModelForm
def __init __(self,* args,** kwargs):
super(MovieModelForm,self).__ init __(* args,** kwargs)
self.fields [ actors]。widget = Textarea()
self.fields [equipment]。widget = Textarea()
self.fields [lights]。widget = Textarea()

def clean_actors(self):
data = self.cleaned_data.get('actors')
打印'清洁演员'
返回[data]


class Meta:
model = MovieModel

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

这将显示用于创建MovieModel的表单$来自django.shortcuts的b
$ b import render_to_response
from django.template import RequestContext
from forms导入MovieModelForm

def add_movie(请求):
如果request.method ==POST:
form = MovieModelForm(request.POST)

如果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))


解决方案

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



请参阅 ModelMultipleChoiceField文档



该字段期待有效的ID列表,但可能会收到一个文本值列表,django无法转换到实际的模型实例。 to_python 将在表单域内失败,而不在表单本身内。因此,这些值从未达到这种形式。



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



在我之前举一个例子,我会如何尝试做你想要的,我必须问;您如何处理已输入的数据库中尚不存在的actor?你可以创建它们,如果它们存在,或者你可以失败。你需要做出这样的决定。

 #只显示演员的例子,你可以使用这样的其他字段

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',无)
如果actors_list不为None:
在actors_list.split(',')中的actor_name:
try:
actor = Actor.objects.get (actor = actor_name)
除了Actor.DoesNotExist:
如果FAIL_ON_NOT_EXIST:#决定是否要此行为或创建它
raise forms.ValidationError('Actor%s不存在'% actor_name)
其他: #创建它,如果它不存在
演员(actor = actor_name).save()
返回actors_list

def save(self,commit = True):
mminstance = super(MovieModelForm,self).save(commit = commit)
actors_list = self.cleaned_data.get('actors_list',None)
如果actors_list不为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,它允许您定义模型中的哪个值用于标准化。

  ##删除的代码,因为它不再相关。参见最后编辑## 

编辑:



哇,我真的应该检查django trac,看看是否已经修复。它是。有关信息,请参阅以下机票。基本上,他们也做了同样的事情。他们使ModelMutipleChoiceField尊重 to_field_name 参数。 这仅适用于django 1.3!



问题是,常规的ModelMultipleChoiceField会看到逗号分隔的字符串,并且因为isn'列表或元组。所以,我们的工作变得有点困难,因为我们必须将字符串更改为列表或元组,然后才能运行常规的清理方法。

 code> class ModelCommaSeparatedChoiceField(ModelMultipleChoiceField):
widget = Textarea
def clean(self,value):
如果值不为None:
value = [item.strip ()for value.split(,)]#remove padding
return super(ModelCommaSeparatedChoiceField,self).clean(value)

所以现在你的表单应该是这样的:

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

class Meta:
model = MovieModel


I'm trying to learn Django and I've ran into some confusing points. I'm currently having trouble creating a movie using a form. The idea of the form is to give the user any field he'd like to fill out. Any field that the user fills out will be updated in its respective sql table (empty fields will be ignored). But, the form keeps giving me the error "Enter a list of values" when I submit the form. To address this, I thought stuffing the data from the form into a list and then returning that list would solve this.

The first idea was to override the clean() in my ModelForm. However, because the form fails the is_valid() check in my views, the cleaned_data variable in clean() doesn't contain anything. Next, I tried to override the to_python(). However, to_python() doesn't seem to be called.

If I put __metaclass__ = models.SubfieldBase in the respective model, I receive the runtime error

"TypeError: Error when calling the metaclass bases metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases"

My approach doesn't seem to work. I'm not sure how to get around the 'Enter a list of values" error! Any advice?

Here is the relevant code (updated):

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))

解决方案

The probable problem is that the list of values provided in the text area can not be normalized into a list of Models.

See the ModelMultipleChoiceField documentation.

The field is expecting a list of valid IDs, but is probably receiving a list of text values, which django has no way of converting to the actual model instances. The to_python will be failing within the form field, not within the form itself. Therefore, the values never even reach the form.

Is there something wrong with using the built in ModelMultipleChoiceField? It will provide the easiest approach, but will require your users to scan a list of available actors (I'm using the actors field as the example here).

Before I show an example of how I'd attempt to do what you want, I must ask; how do you want to handle actors that have been entered that don't yet exist in your database? You can either create them if they exist, or you can fail. You need to make a decision on this.

# 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

The above is all untested code, but something approaching this should work if you really want to use a Textarea for a ModelMultipleChoiceField. If you do go down this route, and you discover errors in my code above, please either edit my answer, or provide a comment so I can. Good luck.

Edit:

The other option is to create a field that understands a comma separated list of values, but behaves in a similar way to ModelMultipleChoiceField. Looking at the source code for ModelMultipleChoiceField, it inhertis from ModelChoiceField, which DOES allow you to define which value on the model is used to normalize.

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

Edit:

Wow, I really should have checked the django trac to see if this was already fixed. It is. See the following ticket for information. Essentially, they've done the same thing I have. They've made ModelMutipleChoiceField respect the to_field_name argument. This is only applicable for django 1.3!

The problem is, the regular ModelMultipleChoiceField will see the comma separated string, and fail because it isn't a List or Tuple. So, our job becomes a little more difficult, because we have to change the string to a list or tuple, before the regular clean method can run.

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)

So, now your form should look like this:

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

这篇关于Django“输入值列表”将ManyToManyField渲染为Textarea时的窗体错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆