Django의 외국 키 선택을 관련 개체로만 제한하는 방법
-
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)
부모님의 선택을 어떻게 부모가있는 어린이에게만 제한합니까? 나는 시도했다
class Parent(models.Model):
name = models.CharField(max_length=255)
favoritechild = models.ForeignKey("Child", blank=True, null=True, limit_choices_to = {"myparent": "self"})
그러나 관리자 인터페이스는 어린이를 나열하지 않습니다.
해결책
나는 방금 만났다 외국인 .limit_choices_to Django 문서에서. 이것이 어떻게 작동하는지 아직 확실하지 않지만 여기서 옳은 일이 될 수 있습니다.
업데이트: 외국 키 .limit_choices_to는 키에 대한 허용 가능한 선택을 제한하기 위해 상수, 호출 가능 또는 Q 객체를 지정할 수 있습니다. 관련된 물체에 대해 아무것도 모르기 때문에 분명히 여기서는 사용되지 않습니다.
호출 가능 (함수 또는 클래스 방법 또는 호출 가능한 객체)을 사용하는 것이 더 유망한 것 같습니다. 그러나 HTTPrequest 객체에서 필요한 정보에 액세스하는 방법에 대한 문제는 여전히 남아 있습니다. 사용 스레드 로컬 스토리지 해결책 일 수 있습니다.
2. 업데이트 : 다음은 저를 위해 일한 것입니다.
위의 링크에 설명 된대로 미들웨어를 만들었습니다. "Product = 1"과 같은 요청의 파트에서 하나 이상의 인수를 추출 하고이 정보를 스레드 로컬에 저장합니다.
다음으로 모델에는 스레드 로컬 변수를 읽고 ID 목록을 반환하여 외래 키 필드의 선택을 제한하는 클래스 메소드가 있습니다.
@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
일반적인 관리 페이지가 정상적으로 작동하도록 선택되지 않은 경우 가능한 모든 ID가 포함 된 쿼리를 반환하는 것이 중요합니다.
그런 다음 외국 키 필드가 다음과 같이 선언됩니다.
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)를 재정의하는 이후 로이 작업을 수행하는 새로운 "올바른"방법.
아래 링크를 따르고 싶지 않은 사람들은 위의 질문 모델에 가까운 예제 기능입니다.
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)
편집중인 현재 객체를 얻는 방법에 대해서는 잘 모르겠습니다. 나는 그것이 실제로 어딘가에있을 것으로 기대하지만 확실하지 않습니다.
이것은 장고가 작동하는 방식이 아닙니다. 당신은 한 방향으로가는 관계 만 만들 것입니다.
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 필드에서 관련 _name을 설정하면 그것이 당신이 그것을 부르는 것입니다. 전: related_name='children'
, 그러면 당신은 할 것입니다 parent_object.children.all()
읽기 문서 http://docs.djangoproject.com/en/dev/topics/db/models/#many-to-one-relationships 이상.
Django Admin 인터페이스에 제한이 필요한 경우 이것이 작동 할 수 있습니다. 나는 그것을 기반으로한다 이 답변 다른 포럼에서 - 많은 사람들의 관계를위한 것이지만 교체 할 수 있어야합니다. 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"} 당신이하고 싶지 않은 것은 부끄러운 일입니다. 깨끗하고 단순했을 것입니다. 불행히도 '자기'는 평가되지 않고 일반 문자열로 겪습니다.
나는 아마도 내가 할 수 있다고 생각했다 :
class MyModel(models.Model):
def _get_self_pk(self):
return self.pk
favourite = models.ForeignKey(limit_choices_to={'myparent__pk':_get_self_pk})
그러나 기능이 자체 arg를 통과하지 않기 때문에 오류가 발생합니다.
유일한 방법은이 모델을 사용하는 모든 형식에 논리를 넣는 것 같습니다 (즉, 쿼리 세트를 Formfield의 선택에 전달합니다). 쉽게 수행되지만 모델 수준에서이를 갖는 것이 더 건조합니다. 모델의 저장 방법을 무시하는 것은 잘못된 선택이 통과되는 것을 방지하는 좋은 방법으로 보입니다.
업데이트
다른 방법으로 나중에 답변을 참조하십시오 https://stackoverflow.com/a/3753916/202168
대안적인 접근법은 부모 모델의 필드로서 'favouritechild'FK를 갖지 않는 것입니다.
대신 아이에게 IS_FAVOURITE 부울 필드를 가질 수 있습니다.
이것은 도움이 될 수 있습니다 :https://github.com/anentropic/django-excluctionbooleanfield
그렇게하면 자녀가 자신이 속한 부모가 가장 좋아하는 것으로 만들 수 있도록하는 모든 문제를 회피 할 수 있습니다.
보기 코드는 약간 다르지만 필터링 로직은 간단합니다.
관리자에서는 IS_FAVOURITE Checkbox (부모당 몇 명의 자녀 만있는 경우)에 노출 된 아동 모델에 대한 인라인을 가질 수도 있습니다. 그렇지 않으면 관리자는 자녀의 측면에서 수행해야합니다.
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
]