2016-09-23 72 views
0

我想讓用戶選擇他們的國家。我有2個模型= Countries與一些數字和CountriesTranslations。我正嘗試使用country(因爲用戶對此型號有FK)及其translation作出元組。在前端我看到國家的下拉列表中,但是當我嘗試保存的形式,我看到 錯誤:Exception Value: Cannot assign "'AF'": "UserProfile.country" must be a "Countries" instance. 錯誤發生在該行if user_profile_form.is_valid():'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轉化爲strcountry instance的形式是什麼正確的方法?或者如果我以錯誤的方式進行,那麼正確的方法是什麼?

EDITED: 作爲一種可能的解決方案是使用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太多的查詢,因爲查詢和頁面加載速度太慢。希望得到任何建議。

回答

0

似乎有待解決。

以下版本產生7 queries in 29.45ms vs 73 queries in 92.52msEDITED上面。我認爲如果爲某些字段設置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原則也適用,所以如果需要以不同形式多次使用選擇 - 沒有問題。

0

您可能想使用forms.ModelChoiceField而不是forms.ChoiceField作爲下拉列表。

ModelChoiceField基於QuerySet構建並保留模型實例。

+0

問題是,我需要結合來自2個模型的數據 - 我需要從'Countries'模型​​獲取'country'實例,並從'CountriesTranslations'模型(向用戶顯示此模型)獲取其'translations'。如果我理解正確,'ModelChoiceField'不能在我的情況下提供國家的翻譯名稱,只是'country'實例本身。 – TitanFighter

+0

可選參數'to_field_name'似乎允許您自定義顯示名稱。我不確定該功能的靈活性如何,但它認爲可能值得嘗試設置,我認爲您可以在其中動態計算某些內容,只要它具有HTML模板語言的語法 –

+0

剛剛嘗試過。不要炒鍋。 'to_field_name',正如我所能理解的,只是改變html'option value' - https://docs.djangoproject.com/en/1.10/ref/forms/fields/#django.forms.ModelChoiceField.to_field_name – TitanFighter

相關問題