'form.is_valid()' 当我想保存带有基于两个模型的选择的表单时失败
'form.is_valid()' fails when I want to save form with choices based on two models
我想让用户选择他们的国家。我有 2 个模型 = Countries
和一些数字和 CountriesTranslations
。我正在尝试使用 country
(因为用户对此模型有 FK)及其 translation
制作元组。在前端,我看到了国家/地区的下拉列表,但是当我尝试保存表单时,我看到了
错误:Exception Value: Cannot assign "'AF'": "UserProfile.country" must be a "Countries" instance.
错误发生在 if user_profile_form.is_valid():
行
# admindivisions.models
class Countries(models.Model):
osm_id = models.IntegerField(db_index=True, null=True)
status = models.IntegerField()
population = models.IntegerField(null=True)
iso3166_1 = models.CharField(max_length=2, blank=True)
iso3166_1_a2 = models.CharField(max_length=2, blank=True)
iso3166_1_a3 = models.CharField(max_length=3, blank=True)
class Meta:
db_table = 'admindivisions_countries'
verbose_name = 'Country'
verbose_name_plural = 'Countries'
class CountriesTranslations(models.Model):
common_name = models.CharField(max_length=81, blank=True, db_index=True)
formal_name = models.CharField(max_length=100, blank=True)
country = models.ForeignKey(Countries, on_delete=models.CASCADE, verbose_name='Details of Country')
lang_group = models.ForeignKey(LanguagesGroups, on_delete=models.CASCADE, verbose_name='Language of Country',
null=True)
class Meta:
db_table = 'admindivisions_countries_translations'
verbose_name = 'Country Translation'
verbose_name_plural = 'Countries Translations'
# profiles.forms
class UserProfileForm(forms.ModelForm):
# PREPARE CHOICES
country_choices = ()
lang_group = Languages.objects.get(iso_code='en').group
for country in Countries.objects.filter(status=1):
eng_name = country.countriestranslations_set.filter(lang_group=lang_group).first()
if eng_name:
country_choices += ((country, eng_name.common_name),)
country_choices = sorted(country_choices, key=lambda tup: tup[1])
country = forms.ChoiceField(choices=country_choices, required=False)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
# profiles.views
def profile_settings(request):
if request.method == 'POST':
user_profile_form = UserProfileForm(request.POST, instance=request.user)
if user_profile_form.is_valid():
user_profile_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_profile_form = UserProfileForm(instance=request.user)
return render(request, 'profiles/profiles_settings.html', {
'user_profile_form': user_profile_form,
})
据我了解,((country, eng_name.common_name),)
中的 country
已转换为 str
。在表格中保留 country instance
的正确方法是什么?或者如果我以错误的方式进行操作,哪种方式是正确的?
已编辑:
一种可能的解决方案是使用 ModelChoiceField
并覆盖 label_from_instance
,如下所示:
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang='en', *args, **kwargs):
super(CountriesChoiceField, self).__init__(*args, **kwargs)
self.user_lang = user_lang
def label_from_instance(self, obj):
return obj.countriestranslations_set.get(lang_group=self.user_lang)
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(
queryset=Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name'),
widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
但是由于 label_from_instance
中的查询和页面加载速度太慢,此解决方案会产生太多查询。非常感谢任何建议。
您可能希望使用 forms.ModelChoiceField 而不是下拉列表的 forms.ChoiceField。
ModelChoiceField 基于 QuerySet 构建并保留模型实例。
好像解决了
下面的版本在上面的 EDITED 中产生 7 queries in 29.45ms
与 73 queries in 92.52ms
。我认为如果为某些字段设置 unique_together
可以使其更快。
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang, *args, **kwargs):
queryset = Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name')
super(CountriesChoiceField, self).__init__(queryset, *args, **kwargs)
self.translations = OrderedDict()
for country in queryset:
name = country.countriestranslations_set.get(lang_group=user_lang).common_name
self.translations[country] = name
def label_from_instance(self, obj):
return self.translations[obj]
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
因此,现在可以基于两 (2) 个模型进行选择,而且速度很快。还应用了 DRY 原则,因此如果需要以不同的形式多次使用选择 - 没问题。
我想让用户选择他们的国家。我有 2 个模型 = Countries
和一些数字和 CountriesTranslations
。我正在尝试使用 country
(因为用户对此模型有 FK)及其 translation
制作元组。在前端,我看到了国家/地区的下拉列表,但是当我尝试保存表单时,我看到了
错误:Exception Value: Cannot assign "'AF'": "UserProfile.country" must be a "Countries" instance.
错误发生在 if user_profile_form.is_valid():
# admindivisions.models
class Countries(models.Model):
osm_id = models.IntegerField(db_index=True, null=True)
status = models.IntegerField()
population = models.IntegerField(null=True)
iso3166_1 = models.CharField(max_length=2, blank=True)
iso3166_1_a2 = models.CharField(max_length=2, blank=True)
iso3166_1_a3 = models.CharField(max_length=3, blank=True)
class Meta:
db_table = 'admindivisions_countries'
verbose_name = 'Country'
verbose_name_plural = 'Countries'
class CountriesTranslations(models.Model):
common_name = models.CharField(max_length=81, blank=True, db_index=True)
formal_name = models.CharField(max_length=100, blank=True)
country = models.ForeignKey(Countries, on_delete=models.CASCADE, verbose_name='Details of Country')
lang_group = models.ForeignKey(LanguagesGroups, on_delete=models.CASCADE, verbose_name='Language of Country',
null=True)
class Meta:
db_table = 'admindivisions_countries_translations'
verbose_name = 'Country Translation'
verbose_name_plural = 'Countries Translations'
# profiles.forms
class UserProfileForm(forms.ModelForm):
# PREPARE CHOICES
country_choices = ()
lang_group = Languages.objects.get(iso_code='en').group
for country in Countries.objects.filter(status=1):
eng_name = country.countriestranslations_set.filter(lang_group=lang_group).first()
if eng_name:
country_choices += ((country, eng_name.common_name),)
country_choices = sorted(country_choices, key=lambda tup: tup[1])
country = forms.ChoiceField(choices=country_choices, required=False)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
# profiles.views
def profile_settings(request):
if request.method == 'POST':
user_profile_form = UserProfileForm(request.POST, instance=request.user)
if user_profile_form.is_valid():
user_profile_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_profile_form = UserProfileForm(instance=request.user)
return render(request, 'profiles/profiles_settings.html', {
'user_profile_form': user_profile_form,
})
据我了解,((country, eng_name.common_name),)
中的 country
已转换为 str
。在表格中保留 country instance
的正确方法是什么?或者如果我以错误的方式进行操作,哪种方式是正确的?
已编辑:
一种可能的解决方案是使用 ModelChoiceField
并覆盖 label_from_instance
,如下所示:
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang='en', *args, **kwargs):
super(CountriesChoiceField, self).__init__(*args, **kwargs)
self.user_lang = user_lang
def label_from_instance(self, obj):
return obj.countriestranslations_set.get(lang_group=self.user_lang)
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(
queryset=Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name'),
widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
但是由于 label_from_instance
中的查询和页面加载速度太慢,此解决方案会产生太多查询。非常感谢任何建议。
您可能希望使用 forms.ModelChoiceField 而不是下拉列表的 forms.ChoiceField。
ModelChoiceField 基于 QuerySet 构建并保留模型实例。
好像解决了
下面的版本在上面的 EDITED 中产生 7 queries in 29.45ms
与 73 queries in 92.52ms
。我认为如果为某些字段设置 unique_together
可以使其更快。
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang, *args, **kwargs):
queryset = Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name')
super(CountriesChoiceField, self).__init__(queryset, *args, **kwargs)
self.translations = OrderedDict()
for country in queryset:
name = country.countriestranslations_set.get(lang_group=user_lang).common_name
self.translations[country] = name
def label_from_instance(self, obj):
return self.translations[obj]
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
因此,现在可以基于两 (2) 个模型进行选择,而且速度很快。还应用了 DRY 原则,因此如果需要以不同的形式多次使用选择 - 没问题。