Как ограничить выбор внешних ключей связанными объектами только в Django

StackOverflow https://stackoverflow.com/questions/232435

  •  04-07-2019
  •  | 
  •  

Вопрос

У меня есть двусторонние международные отношения, подобные следующим

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True)

class Child(models.Model):
  name = models.CharField(max_length=255)
  myparent = models.ForeignKey(Parent)

Как ограничить выбор для Parent.favoritechild только детьми, родителем которых является он сам?Я пытался

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True, limit_choices_to = {"myparent": "self"})

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

Это было полезно?

Решение

я только что наткнулся ForeignKey.limit_choices_to в документации Джанго.Пока не знаю, как это работает, но, возможно, здесь это правильно.

Обновлять: ForeignKey.limit_choices_to позволяет указать константу, вызываемый объект или объект Q, чтобы ограничить допустимый выбор ключа.Константа, очевидно, здесь бесполезна, поскольку она ничего не знает о задействованных объектах.

Использование вызываемого объекта (функции, метода класса или любого вызываемого объекта) кажется более перспективным.Однако остается проблема, как получить доступ к необходимой информации из объекта HttpRequest.С использованием локальное хранилище потоков может быть решением.

2.Обновлять: Вот что сработало для меня:

Я создал промежуточное программное обеспечение, как описано в ссылке выше.Он извлекает один или несколько аргументов из части GET запроса, например «product=1», и сохраняет эту информацию в локальных файлах потока.

Далее в модели есть метод класса, который считывает локальную переменную потока и возвращает список идентификаторов, чтобы ограничить выбор поля внешнего ключа.

@classmethod
def _product_list(cls):
    """
    return a list containing the one product_id contained in the request URL,
    or a query containing all valid product_ids if not id present in URL

    used to limit the choice of foreign key object to those related to the current product
    """
    id = threadlocals.get_current_product()
    if id is not None:
        return [id]
    else:
        return Product.objects.all().values('pk').query

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

Поле внешнего ключа затем объявляется как:

product = models.ForeignKey(
    Product,
    limit_choices_to={
        id__in=BaseModel._product_list,
    },
)

Загвоздка в том, что вы должны предоставить информацию, чтобы ограничить выбор через запрос.Я не вижу здесь способа получить доступ к «я».

Другие советы

«Правильный» способ сделать это - использовать пользовательскую форму. Оттуда вы можете получить доступ к self.instance, который является текущим объектом. Пример -

from django import forms
from django.contrib import admin 
from models import *

class SupplierAdminForm(forms.ModelForm):
    class Meta:
        model = Supplier
        fields = "__all__" # for Django 1.8+


    def __init__(self, *args, **kwargs):
        super(SupplierAdminForm, self).__init__(*args, **kwargs)
        if self.instance:
            self.fields['cat'].queryset = Cat.objects.filter(supplier=self.instance)

class SupplierAdmin(admin.ModelAdmin):
    form = SupplierAdminForm

Новое " право " способ сделать это, по крайней мере, так как Django 1.1, переопределив AdminModel.formfield_for_foreignkey (self, db_field, request, ** kwargs).

См. http: // docs.djangoproject.com/en/1.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey

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

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "favoritechild":
            kwargs["queryset"] = Child.objects.filter(myparent=request.object_id)
        return super(MyModelAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)

Я не уверен, как получить текущий редактируемый объект. Я ожидаю, что это где-то на самом деле, но я не уверен.

Это не так, как работает Django. Вы бы только создали отношение, идущее в одну сторону.

class Parent(models.Model):
  name = models.CharField(max_length=255)

class Child(models.Model):
  name = models.CharField(max_length=255)
  myparent = models.ForeignKey(Parent)

И если бы вы пытались получить доступ к детям от родителей, вы бы сделали <Код> parent_object.child_set.all () . Если вы установили related_name в поле myparent, то это то, на что вы бы ссылались. Например: related_name = 'children' , тогда вы должны выполнить parent_object.children.all ()

Прочитайте документы . http://docs.djangoproject.com/ ru / dev / themes / db / models / # отношения многие-к-одному для получения дополнительной информации.

Если вам нужны только ограничения в интерфейсе администратора Django, это может сработать. Я основал его на этом ответе с другого форума - хотя это для отношений ManyToMany, вы должны иметь возможность заменить formfield_for_foreignkey для его работы. В admin.py :

class ParentAdmin(admin.ModelAdmin):
    def get_form(self, request, obj=None, **kwargs):
        self.instance = obj
        return super(ParentAdmin, self).get_form(request, obj=obj, **kwargs)

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        if db_field.name == 'favoritechild' and self.instance:       
            kwargs['queryset'] = Child.objects.filter(myparent=self.instance.pk)
        return super(ChildAdmin, self).formfield_for_foreignkey(db_field, request=request, **kwargs)

Вы хотите ограничить варианты, доступные в интерфейсе администратора, при создании / редактировании экземпляра модели?

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

Конечно, ответ Эрика верен: вам действительно нужен только один внешний ключ, от ребенка к родителю.

@Ber: я добавил проверку в модель, подобную этой

class Parent(models.Model):
  name = models.CharField(max_length=255)
  favoritechild = models.ForeignKey("Child", blank=True, null=True)
  def save(self, force_insert=False, force_update=False):
    if self.favoritechild is not None and self.favoritechild.myparent.id != self.id:
      raise Exception("You must select one of your own children as your favorite")
    super(Parent, self).save(force_insert, force_update)

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

Я пытаюсь сделать что-то подобное.Кажется, что все, кто говорит: «У вас должен быть внешний ключ только в одну сторону», возможно, неправильно поняли, что вы пытаетесь сделать.

Жаль, что limit_choices_to={"myparent":"self"}, что вы хотели сделать, не работает...это было бы чисто и просто.К сожалению, «self» не оценивается и передается как простая строка.

Я подумал, может быть, я мог бы сделать:

class MyModel(models.Model):
    def _get_self_pk(self):
        return self.pk
    favourite = models.ForeignKey(limit_choices_to={'myparent__pk':_get_self_pk})

Но, увы, это выдает ошибку, потому что функции не передается собственный аргумент :(

Кажется, что единственный способ - поместить логику во все формы, использующие эту модель (т.е. передать набор запросов в варианты выбора для вашего поля формы).Это легко сделать, но было бы СУХО, если бы это было на уровне модели.Переопределение метода сохранения модели кажется хорошим способом предотвратить принятие неверных решений.

Обновлять
Смотрите мой более поздний ответ, чтобы узнать другой способ. https://stackoverflow.com/a/3753916/202168

Альтернативный подход состоит в том, чтобы не использовать «favouritechild» fk в качестве поля в родительской модели.

Вместо этого у вас может быть логическое поле is_favourite для Child.

Это может помочь: https://github.com/anentropic/django-exclusivebooleanfield

Таким образом, вы можете обойти всю проблему, заключающуюся в том, что детей можно сделать только фаворитом того родителя, которому они принадлежат.

Код представления будет немного другим, но логика фильтрации будет простой.

В админке у вас даже может быть встроенная модель для детей, которая выставляет флажок is_favourite (если у вас только несколько детей на одного родителя), в противном случае администратор должен будет работать со стороны ребенка.

from django.contrib import admin
from sopin.menus.models import Restaurant, DishType

class ObjInline(admin.TabularInline):
    def __init__(self, parent_model, admin_site, obj=None):
        self.obj = obj
        super(ObjInline, self).__init__(parent_model, admin_site)

class ObjAdmin(admin.ModelAdmin):

    def get_inline_instances(self, request, obj=None):
        inline_instances = []
        for inline_class in self.inlines:
            inline = inline_class(self.model, self.admin_site, obj)
            if request:
                if not (inline.has_add_permission(request) or
                        inline.has_change_permission(request, obj) or
                        inline.has_delete_permission(request, obj)):
                    continue
                if not inline.has_add_permission(request):
                    inline.max_num = 0
            inline_instances.append(inline)

        return inline_instances



class DishTypeInline(ObjInline):
    model = DishType

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        field = super(DishTypeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
        if db_field.name == 'dishtype':
            if self.obj is not None:
                field.queryset = field.queryset.filter(restaurant__exact = self.obj)  
            else:
                field.queryset = field.queryset.none()

        return field

class RestaurantAdmin(ObjAdmin):
    inlines = [
        DishTypeInline
    ]
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top