Вход в свободную форму для поля ForeignKey в Django ModelForm

У меня есть две модели, связанные с внешним ключом:

# models.py    
class TestSource(models.Model):
  name        = models.CharField(max_length=100)

class TestModel(models.Model):
  name        = models.CharField(max_length=100)
  attribution = models.ForeignKey(TestSource, null=True)

По умолчанию django ModelForm представляет это как <select> с <option> s; однако я бы предпочел, чтобы эта функция как вход свободной формы, <input type="text"/> и за кулисами, создала или создала необходимый объект TestSource, а затем связала его с объектом TestModel.

Я попытался определить пользовательский ModelForm и Field, чтобы выполнить это:

# forms.py
class TestField(forms.TextInput):
  def to_python(self, value):
    return TestSource.objects.get_or_create(name=value)

class TestForm(ModelForm):
  class Meta:
    model=TestModel
    widgets = {
      'attribution' : TestField(attrs={'maxlength':'100'}),
    }

К сожалению, я получаю: invalid literal for int() with base 10: 'test3' при попытке проверить is_valid на представленной форме. Где я иду не так? Их и более простой способ сделать это?

Ответ 1

Что-то вроде этого должно работать:

class TestForm(ModelForm):
  attribution = forms.CharField(max_length=100)

  def save(self, commit=True):
      attribution_name = self.cleaned_data['attribution']
      attribution = TestSource.objects.get_or_create(name=attribution_name)[0]  # returns (instance, <created?-boolean>)
      self.instance.attribution = attribution

      return super(TestForm, self).save(commit)

  class Meta:
    model=TestModel
    exclude = ('attribution')

Ответ 2

Здесь есть несколько проблем.

Во-первых, вы определили поле, а не виджет, поэтому вы не можете использовать его в словаре widgets. Вам необходимо переопределить объявление поля на верхнем уровне формы.

Во-вторых, get_or_create возвращает два значения: извлеченный или созданный объект и логическое значение, чтобы показать, было ли оно создано. Вы действительно хотите вернуть первое из этих значений из вашего метода to_python.

Я не уверен, что любой из них вызвал вашу фактическую ошибку. Вы должны опубликовать фактическую трассировку для нас, чтобы быть уверенным.

Ответ 3

TestForm.attribution ожидает ключ int value для модели TestSource.

Возможно, эта версия модели будет более удобной для вас:

class TestSource(models.Model):
    name = models.CharField(max_length=100, primary_key=True)

Ответ 4

Взято из:

Как сделать редактируемое поле внешнего ключа модели формы в шаблоне Django?

class CompanyForm(forms.ModelForm):
s_address = forms.CharField(label='Address', max_length=500, required=False)

def __init__(self, *args, **kwargs):
    super(CompanyForm, self).__init__(*args, **kwargs)
    try:
        self.fields['s_address'].initial = self.instance.address.address1
    except ObjectDoesNotExist:
        self.fields['s_address'].initial = 'looks like no instance was passed in'

def save(self, commit=True):
    model = super(CompanyForm, self).save(commit=False)
    saddr = self.cleaned_data['s_address']
    if saddr:
        if model.address:
            model.address.address1 = saddr
            model.address.save()
        else:
            model.address = Address.objects.create(address1=saddr)
            # or you can try to look for appropriate address in Address table first
            # try:
            #     model.address = Address.objects.get(address1=saddr)
            # except Address.DoesNotExist:
            #     model.address = Address.objects.create(address1=saddr)

    if commit:
        model.save()

    return model

class Meta:
    exclude = ('address',) # exclude form own address field

Эта версия устанавливает начальные данные поля s_address как FK от self, во время init - я добавил попытку, за исключением исключения ошибки ObjectDoesNotExist, чтобы она работала с данными или без передачи данных в форму.

Хотя мне бы хотелось узнать, есть ли в Django более простой встроенный метод переопределения.