Подтвердить что ты не робот

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

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

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"})

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

4b9b3361

Ответ 1

Я просто наткнулся на ForeignKey.limit_choices_to в документах Django. Пока еще не уверен, как это работает, но это может быть просто правильно.

Обновление: 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,
    },
)

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

Ответ 2

"Правильный" способ сделать это - использовать пользовательскую форму. Оттуда вы можете получить доступ к 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

Ответ 3

Новый "правильный" способ сделать это, по крайней мере с тех пор, как 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)

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

Ответ 4

Это не то, как работает 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(). Если вы установите родственное имя в поле myparent, то это то, на что вы ссылаетесь. Пример: related_name='children', тогда вы сделали бы parent_object.children.all()

Прочитайте docs http://docs.djangoproject.com/en/dev/topics/db/models/#many-to-one-relationships для больше.

Ответ 5

Если вам нужны только ограничения в интерфейсе администратора 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)

Ответ 6

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

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

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

Ответ 7

@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)

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

Ответ 8

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

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

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

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

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

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

Обновление
См. Мой более поздний ответ для другого способа fooobar.com/questions/137544/...

Ответ 9

Альтернативным подходом было бы не иметь favouritechild fk как поле на родительской модели.

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

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

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

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

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

Ответ 10

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
    ]