2009-06-04 6 views
37

J'ai un modèle pour les commandes dans une application webshop, avec une clé primaire auto-incrémentée et une clé étrangère à elle-même, puisque les commandes peuvent être divisées en plusieurs commandes, mais la relation à l'ordre original doit être maintenue.Django: accéder à l'instance du modèle depuis ModelAdmin?

class Order(models.Model): 
    ordernumber = models.AutoField(primary_key=True) 
    parent_order = models.ForeignKey('self', null=True, blank=True, related_name='child_orders') 
    # .. other fields not relevant here 

J'ai enregistré une classe OrderAdmin pour le site d'administration. Pour l'affichage détaillé, j'ai inclus parent_order dans l'attribut fieldsets. Bien sûr, par défaut, cela liste toutes les commandes dans une boîte de sélection, mais ce n'est pas le comportement souhaité. Au lieu de cela, pour les ordres qui n'ont pas d'ordre parent (c'est-à-dire qui n'ont pas été séparés d'un autre ordre, parent_order est NULL/Aucun), aucune commande ne doit être affichée. Pour les commandes qui ont été fractionnées, cela ne devrait afficher que l'ordre du parent unique.

Il existe une méthode ModelAdmin plutôt nouvelle, formfield_for_foreignkey, qui semble parfaite pour cela, puisque le jeu de requête peut être filtré à l'intérieur. Imaginons que nous regardions la vue détaillée de la commande n ° 11234, qui a été séparée de la commande n ° 11208. Le code est ci-dessous

def formfield_for_foreignkey(self, db_field, request, **kwargs): 
    if db_field.name == 'parent_order': 
     # kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=11234) 
     return db_field.formfield(**kwargs) 
    return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) 

La ligne commentée fonctionne lorsqu'il est exécuté dans un shell Python, un retour queryset item unique contenant l'ordre # 11208 pour # 11234 et toutes les autres commandes qui peuvent avoir été séparées de celle-ci.

Bien sûr, nous ne pouvons pas coder en dur le numéro de commande ici. Nous avons besoin d'une référence au champ ordernumber de l'instance de commande dont nous examinons la page détaillée. Comme ceci:

kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=?????) 

Je n'ai trouvé aucun moyen de remplacer ????? avec une référence à l'instance de commande "actuelle", et j'ai creusé assez profondément. self à l'intérieur formfield_for_foreignkey fait référence à l'instance ModelAdmin, et bien que cela ait un attribut model, ce n'est pas l'instance du modèle de commande (c'est une référence ModelBase, self.model() renvoie une instance, mais son numéro de commande est None).

Une solution pourrait être de tirer le numéro de commande de request.path (/ admin/orders/order/11234 /), mais c'est vraiment moche. J'aimerais vraiment qu'il y ait un meilleur moyen.

Répondre

54

Je pense que vous pourriez avoir besoin d'aborder cela d'une manière légèrement différente - en modifiant le ModelForm, plutôt que la classe admin. Quelque chose comme ceci:

class OrderForm(forms.ModelForm): 

    def __init__(self, *args, **kwargs): 
     super(OrderForm, self).__init__(*args, **kwargs) 
     self.fields['parent_order'].queryset = Order.objects.filter(
      child_orders__ordernumber__exact=self.instance.pk) 

class OrderAdmin(admin.ModelAdmin): 
    form = OrderForm 
+1

Cela fonctionne! Merci beaucoup!Je suis complètement nouveau à toutes ces affaires ModelForm/ModelAdmin, et regardais au mauvais endroit. –

+0

Je me rends compte que ce post est ancien, mais cela a également fonctionné comme une solution de contournement décent pour moi pour get_readonly_fields sur un InlineModelAdmin depuis le paramètre obj passé à lui est actuellement l'objet parent, pas l'objet de l'instance inline. À toutes fins utiles, cela a rendu mon objet en lecture seule en me permettant de ne retourner qu'un seul objet dans ma clé étrangère. – dgraves

+0

Assurez-vous d'avoir appelé super .__ init__ en premier. Cela définit self.instance. – yellottyellott

5

J'ai modélisé ma classe en ligne de cette façon. Il est un peu moche sur la façon dont il obtient l'ID du formulaire parent pour filtrer les données en ligne, mais cela fonctionne. Il filtre les unités par entreprise à partir du formulaire parent.

Le concept original est expliqué ici http://www.stereoplex.com/blog/filtering-dropdown-lists-in-the-django-admin

class CompanyOccupationInline(admin.TabularInline): 

    model = Occupation 
    # max_num = 1 
    extra = 0 
    can_delete = False 
    formset = RequiredInlineFormSet 

    def formfield_for_dbfield(self, field, **kwargs): 

     if field.name == 'unit': 
      parent_company = self.get_object(kwargs['request'], Company) 
      units = Unit.objects.filter(company=parent_company) 
      return forms.ModelChoiceField(queryset=units) 
     return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs) 

    def get_object(self, request, model): 
     object_id = request.META['PATH_INFO'].strip('/').split('/')[-1] 
     try: 
      object_id = int(object_id) 
     except ValueError: 
      return None 
     return model.objects.get(pk=object_id) 
+5

Une façon un peu plus propre d'aborder ce serait de tirer parti du résolveur d'URL: 'object_id = resolve (request.path) .args [0]' – philipk

3

La réponse ci-dessus de Erwin Julius a travaillé pour moi, sauf que je trouve que le nom de conflits « de get_object » avec une fonction Django nom si la fonction « my_get_object ».

class CompanyOccupationInline(admin.TabularInline): 

    model = Occupation 
    # max_num = 1 
    extra = 0 
    can_delete = False 
    formset = RequiredInlineFormSet 

    def formfield_for_dbfield(self, field, **kwargs): 

     if field.name == 'unit': 
      parent_company = self.my_get_object(kwargs['request'], Company) 
      units = Unit.objects.filter(company=parent_company) 
      return forms.ModelChoiceField(queryset=units) 
     return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs) 

    def my_get_object(self, request, model): 
     object_id = request.META['PATH_INFO'].strip('/').split('/')[-1] 
     try: 
      object_id = int(object_id) 
     except ValueError: 
      return None 
     return model.objects.get(pk=object_id) 

Il m'a dit de ne pas « répondre » aux réponses des autres, mais je ne suis pas autorisé à « répondre » encore, et je l'ai cherché pendant un certain temps si l'espoir que ce sera utile pour les autres . Je ne suis pas encore autorisé à upvote encore ou je le ferais totalement!

+0

Alors qu'il serait préférable de laisser cette information comme un commentaire, je Comprenez que vous n'avez pas assez de réputation pour cela - savoir 'get_object' cause qu'une collision est précieuse. – BlackVegetable

Questions connexes