Domanda

Ho una relazione straniera bidirezionale simile alla seguente

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)

Come posso limitare le scelte di Parent.favoritechild solo ai bambini il cui genitore è esso stesso? Ho provato

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

ma ciò impedisce all'interfaccia di amministrazione di non elencare alcun figlio.

È stato utile?

Soluzione

Mi sono appena imbattuto in ForeignKey.limit_choices_to nei documenti Django. Non sono ancora sicuro di come funzioni, ma potrebbe essere la cosa giusta qui.

Aggiornamento: ForeignKey.limit_choices_to consente di specificare una costante, un callable o un oggetto Q per limitare le scelte consentite per la chiave. Una costante ovviamente non serve a niente qui, dal momento che non sa nulla degli oggetti coinvolti.

L'uso di un callable (funzione o metodo di classe o qualsiasi oggetto richiamabile) sembra più promettente. Tuttavia, rimane il problema di come accedere alle informazioni necessarie dall'oggetto HttpRequest. L'uso di archiviazione locale thread può essere una soluzione.

2. Aggiornamento: ecco cosa ha funzionato per me:

Ho creato un middleware come descritto nel link sopra. Estrae uno o più argomenti dalla parte GET della richiesta, come "product = 1", e memorizza queste informazioni nei locali del thread.

Successivamente c'è un metodo di classe nel modello che legge la variabile locale del thread e restituisce un elenco di ID per limitare la scelta di un campo chiave esterna.

@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 restituire una query contenente tutti i possibili ID se nessuno è stato selezionato in modo che le normali pagine di amministrazione funzionino correttamente.

Il campo chiave esterna viene quindi dichiarato come:

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

Il problema è che devi fornire le informazioni per limitare le scelte tramite la richiesta. Non vedo un modo per accedere a " self " qui.

Altri suggerimenti

Il modo "giusto" per farlo è utilizzare un modulo personalizzato. Da lì, puoi accedere a self.instance, che è l'oggetto corrente. Esempio -

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

Il nuovo " right " modo di farlo, almeno dal momento che Django 1.1 è sovrascrivendo AdminModel.formfield_for_foreignkey (self, db_field, request, ** kwargs).

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

Per coloro che non vogliono seguire il link di seguito è presente una funzione di esempio vicina ai modelli di domande precedenti.

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)

Non sono sicuro di come ottenere l'oggetto corrente che viene modificato. Mi aspetto che sia in realtà da qualche parte, ma non sono sicuro.

Non è così che funziona Django. Creeresti la relazione solo in un modo.

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 stessi provando ad accedere ai figli dal genitore, lo faresti parent_object.child_set.all () . Se si imposta un nome_relativo nel campo myparent, questo è ciò a cui si farebbe riferimento. Ad esempio: related_name = 'children' , quindi parent_object.children.all()

Leggi i docs http://docs.djangoproject.com/ it / dev / argomenti / db / models / # relazioni molti-a-uno per di più.

Se hai solo bisogno delle limitazioni nell'interfaccia di amministrazione di Django, questo potrebbe funzionare. L'ho basato su questa risposta da un altro forum - anche se è per le relazioni ManyToMany, dovresti essere in grado di sostituire formfield_for_foreignkey per farlo funzionare. In 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)

Desideri limitare le opzioni disponibili nell'interfaccia di amministrazione durante la creazione / modifica di un'istanza del modello?

Un modo per farlo è la validazione del modello. Ciò consente di generare un errore nell'interfaccia di amministrazione se il campo esterno non è la scelta giusta.

Naturalmente, la risposta di Eric è corretta: qui hai davvero bisogno solo di una chiave esterna, da figlio a genitore.

@Ber: ho aggiunto la convalida al modello simile a questo

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)

che funziona esattamente come voglio, ma sarebbe davvero bello se questa convalida potesse limitare le scelte nel menu a discesa nell'interfaccia di amministrazione anziché convalidare dopo la scelta.

Sto cercando di fare qualcosa di simile. Sembra che tutti quelli che dicono "dovresti avere solo una chiave esterna in un modo" abbiano forse frainteso quello che stai provando a fare.

È un peccato che limit_choices_to = {" myparent " ;: " self "} che volevi fare non funziona ... sarebbe stato semplice e pulito. Sfortunatamente il 'sé' non viene valutato e passa attraverso una semplice stringa.

Ho pensato che forse avrei potuto fare:

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

Ma purtroppo questo dà un errore perché la funzione non viene passata da solo a un argomento :(

Sembra che l'unico modo sia mettere la logica in tutte le forme che usano questo modello (cioè passare un queryset alle scelte per il tuo formform). Il che è facile, ma sarebbe più ASCIUTTO avere questo a livello di modello. La sostituzione del metodo di salvataggio del modello sembra un buon modo per impedire il superamento di scelte non valide.

Aggiorna
Vedi la mia risposta successiva per un altro modo https://stackoverflow.com/a/3753916/202168

Un approccio alternativo sarebbe quello di non avere 'favouritechild' fk come campo nel modello Parent.

Invece potresti avere un campo booleano is_favourite su Child.

Questo può aiutare: https://github.com/anentropic/django-exclusivebooleanfield

In questo modo eviterai l'intero problema di garantire che i bambini possano essere solo i preferiti del genitore a cui appartengono.

Il codice di visualizzazione sarebbe leggermente diverso ma la logica di filtraggio sarebbe semplice.

Nell'amministratore potresti persino avere un inline per i modelli Child che espongono la casella di controllo is_favourite (se hai solo pochi figli per genitore) altrimenti l'amministratore dovrebbe essere fatto dal lato Child.

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
    ]
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top