Поле формы Django-nonrel для ListField - PullRequest
9 голосов
/ 10 июня 2011

Я экспериментирую с django-nonrel на appengine и пытаюсь использовать djangotoolbox.fields.ListField для реализации отношения "многие ко многим". Как я читал в документации, ListField - это то, что вы можете использовать, чтобы обойти Джамго-нонрела, не поддерживающего отношения «многие ко многим».

Это выдержка из моей модели:

class MyClass(models.Model):
    field = ListField(models.ForeignKey(AnotherClass))

Так что, если я правильно понимаю, я создаю список внешних ключей для другого класса, чтобы показать связь с несколькими экземплярами другого класса

При таком подходе все работает нормально ... Без исключений. Я могу создавать объекты MyClass в коде и представлениях. Но когда я пытаюсь использовать интерфейс администратора, я получаю следующую ошибку

No form field implemented for <class 'djangotoolbox.fields.ListField'>

Так что я решил попробовать то, чего раньше не делал. Создайте мое собственное поле. Ну на самом деле моя собственная форма для редактирования MyClass экземпляров в интерфейсе администратора. Вот что я сделал:

class MyClassForm(ModelForm):
    field = fields.MultipleChoiceField(choices=AnotherClass.objects.all(), widget=FilteredSelectMultiple("verbose_name", is_stacked=False))
    class Meta:
        model = MyClass

затем я передаю MyClassForm как форму для использования в интерфейсе администратора

class MyClassAdmin(admin.ModelAdmin):
    form = MyClassForm

admin.site.register(MyClass, MyClassAdmin)

Я думал, что это будет работать, но это не так. Когда я иду в интерфейс администратора, я получаю ту же ошибку, что и раньше. Может кто-нибудь сказать, что я делаю не так здесь ... или если у вас есть какие-либо другие предложения или истории успеха использования ListField, SetField и т. Д. Из djangotoolbox.fields в интерфейсе администратора, это было бы очень признательно.

Ответы [ 4 ]

11 голосов
/ 06 июля 2011

ОК, вот что я сделал, чтобы все это заработало ... Я начну с начала

Вот как выглядела моя модель

class MyClass(models.Model):
    field = ListField(models.ForeignKey(AnotherClass))

Я хотелчтобы иметь возможность использовать интерфейс администратора для создания / редактирования экземпляров этой модели с использованием виджета множественного выбора для поля списка.Поэтому я создал несколько пользовательских классов следующим образом:

class ModelListField(ListField):
    def formfield(self, **kwargs):
        return FormListField(**kwargs)

class ListFieldWidget(SelectMultiple):
    pass

class FormListField(MultipleChoiceField):
    """
    This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
    """
    widget = ListFieldWidget

    def clean(self, value):
        #TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
        return value

Эти классы позволяют использовать поле списка в админке.Затем я создал форму для использования на сайте администратора

class MyClassForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(MyClasstForm,self).__init__(*args, **kwargs)
        self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
        if self.instance.pk:
            self.fields['field'].initial = self.instance.field

    class Meta:
        model = MyClass

После этого я создал модель администратора и зарегистрировал ее на сайте администратора

class MyClassAdmin(admin.ModelAdmin):
    form = MyClassForm

    def __init__(self, model, admin_site):
        super(MyClassAdmin,self).__init__(model, admin_site)

admin.site.register(MyClass, MyClassAdmin)

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

3 голосов
/ 11 июня 2011

Насколько я понимаю, вы пытаетесь установить отношения M2M в django-nonrel, что не является готовой функциональностью. Для начала, если вам нужен быстрый взлом, вы можете воспользоваться этим простым классом и использовать CharField для ввода внешних ключей вручную:

class ListFormField(forms.Field):
    """ A form field for being able to display a djangotoolbox.fields.ListField. """

    widget = ListWidget

    def clean(self, value):
        return [v.strip() for v in value.split(',') if len(v.strip()) > 0]

Но если вы хотите иметь множественный выбор из списка моделей, обычно вам нужно использовать ModelMultipleChoiceField, который также не работает в django-nonrel. Вот что я сделал для эмуляции отношения M2M с использованием MultipleSelectField:

Допустим, у вас есть отношения M2M между 2 классами, SomeClass и AnotherClass соответственно. Вы хотите выбрать отношение в форме для SomeClass. Также я предполагаю, что вы хотите хранить ссылки как ListField в SomeClass. (Естественно, вы хотите создать отношения M2M, как они объяснены здесь , для предотвращения взрыва индексов, если вы работаете в App Engine).

Итак, у вас есть такие модели, как:

class SomeClass(models.Model):
    another_class_ids = ListField(models.PositiveIntegerField(), null=True, blank=True)
    #fields go here

class AnotherClass(models.Model):
    #fields go here

И в вашей форме:

class SomeClassForm(forms.ModelForm):

    #Empty field, will be populated after form is initialized
    #Otherwise selection list is not refreshed after new entities are created.
    another_class = forms.MultipleChoiceField(required=False)

def __init__(self, *args, **kwargs):
    super(SomeClassForm,self).__init__(*args, **kwargs)
    self.fields['another_class'].choices = [(item.pk,item) for item in AnotherClass.objects.all()]

    if self.instance.pk: #If class is saved, highlight the instances that are related
        self.fields['another_class'].initial = self.instance.another_class_ids

def save(self, *args, **kwargs):  
    self.instance.another_class_ids = self.cleaned_data['another_class']         
    return super(SomeClassForm, self).save()

class Meta:
    model = SomeClass

Надеюсь, это поможет вам начать, я реализовал эту функциональность для обычных форм, настроить ее для панели администратора не должно быть так сложно.

0 голосов
/ 13 июля 2013

Вы можете избежать использования пользовательского класса формы для такого использования, запросив объект модели

class ModelListField(ListField):
  def __init__(self, embedded_model=None, *args, **kwargs):
  super(ModelListField, self).__init__(*args, **kwargs)
  self._model = embedded_model.embedded_model

  def formfield(self, **kwargs):
    return FormListField(model=self._model, **kwargs)

class ListFieldWidget(SelectMultiple):
  pass

class FormListField(MultipleChoiceField):
  widget = ListFieldWidget

  def __init__(self, model=None, *args, **kwargs):
    self._model = model
    super(FormListField, self).__init__(*args, **kwargs)
    self.widget.choices = [(unicode(i.pk), i) for i in self._model.objects.all()]

  def to_python(self, value):
    return [self._model.objects.get(pk=key) for key in value]

  def clean(self, value):
    return value
0 голосов
/ 10 июня 2011

Это может быть не связано, но для интерфейса администратора убедитесь, что в настройках djangotoolbox указан список после django.contrib.admin. INSTALLED_APPS

...