Django代表User.groups ManyToManyField作为表单中的选择

时间:2013-02-20 00:03:14

标签: python django

对于我的应用程序,我使用用户组来表示一种用户。在我的特定情况下,用户只能在一个组中。在实施中,我有两个选择:

  1. 将ManyToMany覆盖为ForeignKey
  2. 代表ManyToMany为 在我的表单上的MultipleChoiceField,只接受1提交然后 从那里开始。
  3. 我使用了选项2,因为有时候,让用户成为2组的一部分对于测试很有用(只是方便)。我不认为两者在实施方面存在差异(但建议表示赞赏)。

    在我看来,然后我编写代码来关联这两个代码(这是User的UserProfile扩展类中的ManyToMany) - 我不确定这是否正常。

    我遇到的主要错误是表单不允许验证,并且说ManyToMany需要“值列表”才能继续。

    我有以下一组代码:

    forms.py

    from django.forms import ModelForm, Textarea
    from django.contrib.auth.models import User, Group
    from registration.models import UserProfile
    from django import forms
    from django.db import models
    
    class RegistrationForm(ModelForm):
        class Meta:
            model = User
            fields = ('username', 'password', 'email', 'first_name', 'last_name', 'groups')
            widgets = {
                'groups': forms.Select,
                'password': forms.PasswordInput,
            #    'text': Textarea(attrs = {'rows': 3, 'class': 'span10', 'placeholder': 'Post Content'}),
            }
    
        def __init__(self, *args, **kwargs):
            super(RegistrationForm, self).__init__(*args, **kwargs)
            self.fields['groups'].label = 'Which category do you fall under?'
    

    views.py

    def get_registration(request):
        if request.method == 'POST':
            register_form = RegistrationForm(request.POST)
            company_form = CompanyRegistrationForm(request.POST, request.FILES)
    
            if register_form.is_valid() and company_form.is_valid(): # check CSRF
                if (request.POST['terms'] == True):
                    new_user = register_form.save()
                    new_company = company_form.save()
    
                    new_profile = UserProfile(user = user, agreed_terms = True)
                    new_profile.companies_assoc.add(new_company)
                    new_profile.save()
    
                    return HttpResponseRedirect(reverse('companyengine.views.get_company'))
            return render(request, 'registration/register.html', { 'register_form': register_form, 'company_form': company_form } )
    
        else:
            first_form = RegistrationForm
            second_form = CompanyRegistrationForm
            return render(request, 'registration/register.html', { 'register_form': register_form, 'company_form': company_form } )
    

    templates.html

    <h2>Sign Up</h2>
    <form action="/register" method="POST" enctype="multipart/form-data">{% csrf_token %}
        <p>{{ register_form.non_field_error }}</p>
        {% for field in register_form %}
        <div class="control-group">
            {{ field.errors }}
            <label class="control-label">{{ field.label }}</label>
            <div class="controls">
                {{ field }}
            </div>
        </div>
        {% endfor %}
    
        <div id="company_fields">
            <p>{{ register_form.non_field_error }}</p>
            {% for field in company_form %}
            <div class="control-group">
                {{ field.errors }}
                <label class="control-label">{{ field.label }}</label>
                <div class="controls">
                    {{ field }}
                </div>
            </div>
            {% endfor %}
        </div>
    
        <label><input type="checkbox" name="terms" id="terms"> I agree with the <a href="#">Terms and Conditions</a>.</label>
        <input type="submit" value="Sign up" class="btn btn-primary center">
        <div class="clearfix"></div>
    </form>
    

    一切似乎都非常好。但是表单不会超过is_valid(),因为“组”字段需要“值列表”。我见过其他人问过如何从TextField / TextArea解析信息,但我不明白为什么我需要拆分我的信息,因为它只有1。

    非常感谢您的建议。

1 个答案:

答案 0 :(得分:2)

首选解决方案

首先,我认为您应该重新考虑使用M:M关系来表示1:M的关系。很可能在某些情况下,用户会获得多个组,并且可能会在稍后阶段导致代码中的错误,这些问题很难追踪。

由于您已经在使用UserProfile类,我会在用户配置文件模型上放置一个外键,这将提供应该存在的数据结构的准确表示(即使它意味着在测试时登录和注销)

更新

您可以通过更改模型来执行此操作:

class UserProfile(models.Model):
    # existing fields here
    single_group = models.ForeignKey(Group)

如果您有许多使用现有用户组关系的现有代码,这是一个不太实际的解决方案。但是,如果你真的需要强制这个限制(每个用户/ userprofile一个组),那么这样做。

解决您的具体问题

如果出于某种原因,您认为我的上述评论不合适(我不知道您的代码存在的具体情况)......

我认为您遇到的问题是由于select小部件将单个项目返回到表单,而SelectMultiple将返回值列表。由于表单需要一个列表,这就是您的问题所在。

我建议继承SelectMultiple小部件,以便它实际上在表单上呈现为选择单,但仍使用现有逻辑返回列表。

这是SelectMultiple Widget中的当前渲染功能:

class SelectMultiple(Select):
    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = []
        final_attrs = self.build_attrs(attrs, name=name)
        output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
        options = self.render_options(choices, value)
        if options:
            output.append(options)
        output.append('</select>')
        return mark_safe(u'\n'.join(output))

如果你按照以下方式对render方法进行了子类化和覆盖:

class CustomSelectSingleAsList(SelectMultiple):
    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = []
        final_attrs = self.build_attrs(attrs, name=name)
        output = [u'<select %s>' % flatatt(final_attrs)] # NOTE removed the multiple attribute
        options = self.render_options(choices, value)
        if options:
            output.append(options)
        output.append('</select>')
        return mark_safe(u'\n'.join(output))

这将呈现一个选择单一,但检索项目列表。

然后在表单元中,只需使用新的自定义类:

小部件= {             'groups':myforms.CustomSelectSingleAsList,             'password':forms.PasswordInput,         #'text':Textarea(attrs = {'rows':3,'class':'span10','placeholder':'Post Content'}),         }

替代

或者,您可以覆盖“选择”小部件以返回列表:

class SelectSingleAsList(Select):
    def value_from_datadict(self, data, files, name):
        if isinstance(data, (MultiValueDict, MergeDict)):
            return data.getlist(name)  # NOTE this returns a list rather than a single value.
        return data.get(name, None)

如果其中任何一个解决了您的问题,请告诉我。