Como faço para restringir chaves estrangeiras opções para objetos relacionados apenas na django

votos
42

Eu tenho uma relação externa dois sentidos semelhante à seguinte

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)

Como faço para restringir as escolhas para Parent.favoritechild apenas para as crianças cujos pais é em si? eu tentei

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

mas isso faz com que a interface de administração para não listar todas as crianças.

Publicado 24/10/2008 em 04:52
fonte usuário
Em outras línguas...                            


10 respostas

votos
29

Deparei-me com ForeignKey.limit_choices_to na documentação do Django. Ainda não tenho certeza como isso funciona, mas que poderia ser apenas o direito acho que aqui.

Update: ForeignKey.limit_choices_to permite especificar uma constante, um pode ser chamado ou um objeto Q para restringir as escolhas permitidos para a chave. Uma constante, obviamente, não adianta aqui, uma vez que não sabe nada sobre os objetos envolvidos.

Usando um (função ou classe método ou qualquer objeto que pode ser chamado) exigível parece mais promissor. O problema permanece como acessar as informações necessárias formar o objeto HttpRequest. Usando fio de armazenamento local pode ser uma solução.

2. Update: Aqui está o que tens trabalhado para mim:

Eu criei um middleware, conforme descrito no link acima. Ele extrai um ou mais argumentos de parte GET do pedido, como "product = 1" e armazena essas informações nos locals de rosca.

A seguir é um método de classe no modelo que lê a variável local de segmento e retorna uma lista de ids para limitar a escolha de um campo de chave estrangeira.

@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

É importante para retornar uma consulta contendo todos os ids possíveis se nenhum foi selecionado de modo que as páginas de administração normais trabalhar ok.

O campo de chave externo é então declarado como:

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

O problema é que você tem que fornecer as informações para restringir as escolhas através da solicitação. Eu não vejo uma maneira de acessar o "eu" aqui.

Respondeu 31/10/2008 em 00:07
fonte usuário

votos
22

A maneira 'certa' de fazer isso é usar um formulário personalizado. A partir daí, você pode acessar self.instance, que é o objeto atual. exemplo -

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

class SupplierAdminForm(forms.ModelForm):
    class Meta:
        model = Supplier

    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
Respondeu 24/10/2013 em 04:25
fonte usuário

votos
13

A nova maneira "certa" de fazer isso, pelo menos desde Django 1.1 é, substituindo o AdminModel.formfield_for_foreignkey (self, db_field, pedido, ** kwargs).

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

Para aqueles que não querem seguir o link abaixo é uma função de exemplo que está perto para os modelos de perguntas acima.

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)

Eu só não tenho certeza sobre como obter o objeto atual que está sendo editado. Espero que na verdade é sobre a auto algum lugar, mas eu não tenho certeza.

Respondeu 11/01/2011 em 02:44
fonte usuário

votos
12

Isto não é como funciona o Django. Você só iria criar a relação indo em uma direção.

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

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

E se você estava tentando acessar os filhos do pai faria parent_object.child_set.all(). Se você definir um related_name no campo myParent, então é isso que você se referem a ele como. Ex: related_name='children', então você fariaparent_object.children.all()

Leia as docs http://docs.djangoproject.com/en/dev/topics/db/models/#many-to-one-relationships para mais.

Respondeu 24/10/2008 em 07:28
fonte usuário

votos
3

Se você só precisa das limitações na interface de administração do Django, isso pode funcionar. Baseei-lo em esta resposta de outro fórum - embora seja para relacionamentos ManyToMany, você deve ser capaz de substituir formfield_for_foreignkeypara que ele funcione. em 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)
Respondeu 05/04/2015 em 08:24
fonte usuário

votos
3

@Ber: Eu adicionei validação para o modelo semelhante a este

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)

que funciona exatamente como eu quero, mas seria muito bom se essa validação pode restringir opções no menu suspenso na interface de administração, em vez de validar após a escolha.

Respondeu 24/10/2008 em 17:44
fonte usuário

votos
3

Você quer restringir as escolhas disponíveis no interface de administração ao criar / editar uma instância de modelo?

Uma maneira de fazer isso é a validação do modelo. Isso permite que você gerar um erro na interface de administração se o campo estrangeiro não é a escolha certa.

Naturalmente, a resposta de Eric está correto: Você só precisa realmente uma chave estrangeira, do filho para o pai aqui.

Respondeu 24/10/2008 em 11:01
fonte usuário

votos
2

Eu estou tentando fazer algo semelhante. Parece que todo mundo dizendo 'você deve ter apenas uma maneira de chave estrangeira' talvez tenha entendido mal o que você está tentando fazer.

É uma vergonha o limit_choices_to = { "myParent": "auto"} que você queria fazer não funciona ... que teria sido limpo e simples. Infelizmente, o 'self' não se avaliados e atravessa como uma cadeia simples.

Eu pensei que talvez eu poderia fazer:

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

Mas, infelizmente, que dá um erro porque a função não conseguir passar uma auto arg :(

Parece que a única maneira é colocar a lógica em todas as formas que usam este modelo (ou seja, passar um queryset para as escolhas para a sua formfield). Que é facilmente feito, mas seria mais seco para ter este no nível do modelo. Seu substituindo o método save do modelo parece uma boa maneira de evitar escolhas inválidos passar.

Atualização
Veja a minha resposta depois de outra maneira https://stackoverflow.com/a/3753916/202168

Respondeu 17/11/2009 em 15:40
fonte usuário

votos
1

Uma abordagem alternativa seria não ter fk 'favouritechild' como um campo no modelo de pai.

Em vez disso você pode ter um campo booleano is_favourite sobre a criança.

Isso pode ajudar: https://github.com/anentropic/django-exclusivebooleanfield

Dessa forma, você iria contornar o problema todo de assegurar que as crianças só poderia ser feita o favorito do pai a que pertencem.

O código de vista seria um pouco diferente, mas a lógica de filtragem seria simples.

No Admin Você poderia até mesmo ter uma linha de modelos para crianças que expuseram a caixa is_favourite (se você só tem alguns filhos por mãe) caso contrário, o administrador teria que ser feito a partir do lado da criança.

Respondeu 20/09/2010 em 18:36
fonte usuário

votos
-1
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
    ]
Respondeu 14/02/2016 em 18:04
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more