0

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

  1. 将 ManyToMany 覆盖为 ForeignKey
  2. 在我的表单上将 ManyToMany 表示为 MultipleChoiceField,只接受 1 个提交,然后从那里开始。

我选择了选项 2,因为有时让一个用户成为 2 个组的一部分对测试很有用(只是方便)。我认为两者在实施方面没有区别(但您的建议值得赞赏)。

在我看来,然后我编写代码将两者关联起来(这是 UserProfile 扩展类中的 ManyToMany) - 我不确定这是否有效。

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

我有以下一组代码:

表格.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?'

视图.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() 因为 Groups 字段需要“值列表”。我见过其他人问如何解析来自 TextField/TextArea 的信息,但我不明白为什么我需要拆分我的信息,因为它只有 1。

非常感谢您的建议。

4

1 回答 1

2

首选解决方案

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

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

更新

你可以通过改变模型来做到这一点:

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

如果您有大量使用现有用户组关系的现有代码,那么这是一个不太实用的解决方案。但是,如果您确实需要强制执行此限制(每个用户/用户配置文件一组),那么可以这样做。

解决您的具体问题

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

我认为您遇到的问题是由于选择小部件将单个项目返回到表单,而 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))

如果您按如下方式对渲染方法进行子类化并覆盖:

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': '发布内容'}), }

选择

或者,您可以覆盖 Select 小部件以返回列表:

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)

让我知道这些是否能解决您的问题。

于 2013-02-20T02:07:00.243 回答