Comment puis-je ajouter un lien depuis la page admin de Django d'un objet à la page d'administration d'un objet?

pour faire face au manque de lignes imbriquées dans django-admin, j'ai mis des cas spéciaux dans deux des modèles pour créer des liens entre les pages de changement d'admin et les admins inline de deux modèles.

ma question Est la suivante: Comment puis-je créer un lien à partir de la page de changement d'administrateur ou de l'administrateur en ligne d'un modèle vers la page de changement d'administrateur ou l'administrateur en ligne d'un modèle apparenté de manière claire, sans que le modèle soit piraté?

je voudrais une solution générale que je puisse appliquer à la page de changement d'administrateur ou à l'administrateur en ligne de n'importe quel modèle.


j'ai un modèle, post (pas son vrai nom) qui est à la fois un inline sur la page d'admin blog , et a aussi sa propre page d'admin. La raison pour laquelle il ne peut pas seulement être en ligne est qu'il a des modèles avec des clés étrangères qui n'ont de sens que lorsqu'il est édité avec lui, et il n'a de sens que lorsqu'il est édité avec blog .

pour la page d'administration post , j'ai changé une partie de " fieldset.html":

{% if field.is_readonly %}
    <p>{{ field.contents }}</p>
{% else %}
    {{ field.field }}
{% endif %}

à

{% if field.is_readonly %}
    <p>{{ field.contents }}</p>
{% else %}
    {% ifequal field.field.name "blog" %}
        <p>{{ field.field.form.instance.blog_link|safe }}</p>
    {% else %}
        {{ field.field }}
    {% endifequal %}
{% endif %}

pour créer un lien vers la page d'administration blog , où blog_link est une méthode sur le modèle:

def blog_link(self):
      return '<a href="%s">%s</a>' % (reverse("admin:myblog_blog_change",  
                                        args=(self.blog.id,)), escape(self.blog))

Je n'ai pas trouvé le id de l'instance blog nulle part en dehors de field.field.form.instance .

sur le blog admin de la page, où post est en ligne, j'ai modifié une partie de "empilés.html":

<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;
<span class="inline_label">{% if inline_admin_form.original %}
    {{ inline_admin_form.original }}
{% else %}#{{ forloop.counter }}{% endif %}</span>

à

<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>&nbsp;
<span class="inline_label">{% if inline_admin_form.original %}
    {% ifequal inline_admin_formset.opts.verbose_name "post" %}
    <a href="/admin/myblog/post/{{ inline_admin_form.pk_field.field.value }}/">
            {{ inline_admin_form.original }}</a>
{% else %}{{ inline_admin_form.original }}{% endifequal %}
{% else %}#{{ forloop.counter }}{% endif %}</span>

pour créer un lien vers la page d'administration post car ici j'ai pu trouver le id stocké dans le champ de la clé étrangère.


je suis sûr qu'il y a une meilleure façon, plus générale d'ajouter des liens aux formulaires administratifs sans me répéter; qu'est-ce que c'est?

26
demandé sur Community 2012-03-29 09:53:00

7 réponses

Nouveau dans Django 1.8 : show_change_link pour les admin .

Set show_change_link à Vrai (False par défaut) dans votre inline modèle, de sorte que les objets ont un lien avec leur changement de forme (où ils peuvent avoir leur propre inlines).

from django.contrib import admin

class PostInline(admin.StackedInline):
    model = Post
    show_change_link = True
    ...

class BlogAdmin(admin.ModelAdmin):
    inlines = [PostInline]
    ...

class ImageInline(admin.StackedInline):
    # Assume Image model has foreign key to Post
    model = Image
    show_change_link = True
    ...

class PostAdmin(admin.ModelAdmin):
    inlines = [ImageInline]
    ...

admin.site.register(Blog, BlogAdmin)
admin.site.register(Post, PostAdmin)
12
répondu bitnik 2015-07-29 08:09:58

Use readonly_fields :

class MyInline(admin.TabularInline):
    model = MyModel
    readonly_fields = ['link']

    def link(self, obj):
        url = reverse(...)
        return mark_safe("<a href='%s'>edit</a>" % url)

    # the following is necessary if 'link' method is also used in list_display
    link.allow_tags = True
14
répondu Mikhail Korobov 2012-04-05 12:58:10

C'est ma solution actuelle, basée sur ce qui a été suggéré par Pannu (dans son édition) et Mikhail.

j'ai quelques vues de changement d'administrateur de haut niveau j'ai besoin de créer un lien vers une vue de changement d'administrateur de haut niveau d'un objet lié, et quelques vues de changement d'administrateur en ligne j'ai besoin de créer un lien vers la vue de changement d'administrateur de haut niveau du même objet. Pour cette raison, je veux exclure la méthode de lien plutôt que d'en répéter les variations pour chaque vue de changement d'administrateur.

j'utilise un décorateur de classe pour créer le link appelable, et l'ajouter à readonly_fields .

def add_link_field(target_model = None, field = '', link_text = unicode):
    def add_link(cls):
        reverse_name = target_model or cls.model.__name__.lower()
        def link(self, instance):
            app_name = instance._meta.app_label
            reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
            link_obj = getattr(instance, field, None) or instance
            url = reverse(reverse_path, args = (link_obj.id,))
            return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
        link.allow_tags = True
        link.short_description = reverse_name + ' link'
        cls.link = link
        cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + ['link']
        return cls
    return add_link

vous pouvez également passer un callable personnalisé si vous avez besoin d'obtenir votre texte de lien d'une certaine manière que simplement appeler unicode sur l'objet que vous liez.

je l'utilise comme ceci:

# the first 'blog' is the name of the model who's change page you want to link to
# the second is the name of the field on the model you're linking from
# so here, Post.blog is a foreign key to a Blog object. 
@add_link_field('blog', 'blog')
class PostAdmin(admin.ModelAdmin):
    inlines = [SubPostInline, DefinitionInline]
    fieldsets = ((None, {'fields': (('link', 'enabled'),)}),)
    list_display = ('__unicode__', 'enabled', 'link')

# can call without arguments when you want to link to the model change page
# for the model of an inline model admin.
@add_link_field()
class PostInline(admin.StackedInline):
    model = Post
    fieldsets = ((None, {'fields': (('link', 'enabled'),)}),)
    extra = 0

bien sûr, rien de tout cela ne serait nécessaire si je pouvais emboîter le pas à l'administrateur pour SubPost et Definition à l'intérieur de la ligne admin de Post sur le Blog admin changer de page sans avoir à Django.

10
répondu agf 2012-06-06 00:42:58

je pense que la solution d'agf est assez impressionnante -- beaucoup de félicitations pour lui. Mais j'avais besoin de quelques fonctionnalités supplémentaires:

  • pour pouvoir avoir plusieurs liens pour un seul administrateur
  • pour pouvoir se relier au modèle dans différentes app

Solution:

def add_link_field(target_model = None, field = '', app='', field_name='link',
                   link_text=unicode):
    def add_link(cls):
        reverse_name = target_model or cls.model.__name__.lower()
        def link(self, instance):
            app_name = app or instance._meta.app_label
            reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
            link_obj = getattr(instance, field, None) or instance
            url = reverse(reverse_path, args = (link_obj.id,))
            return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
        link.allow_tags = True
        link.short_description = reverse_name + ' link'
        setattr(cls, field_name, link)
        cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + \
            [field_name]
        return cls
    return add_link

Utilisation:

# 'apple' is name of model to link to
# 'fruit_food' is field name in `instance`, so instance.fruit_food = Apple()
# 'link2' will be name of this field
@add_link_field('apple','fruit_food',field_name='link2')
# 'cheese' is name of model to link to
# 'milk_food' is field name in `instance`, so instance.milk_food = Cheese()
# 'milk' is the name of the app where Cheese lives
@add_link_field('cheese','milk_food', 'milk')
class FoodAdmin(admin.ModelAdmin):
    list_display = ("id", "...", 'link', 'link2')

je suis désolé que l'exemple est tellement illogique, mais je ne voulais pas utiliser mon données.

10
répondu SummerBreeze 2012-11-08 10:49:03

je suis d'accord que son édition de modèle difficile à faire, je crée un widget personnalisé pour afficher un anchor sur la page de vue de changement d'administrateur(peut être utilisé à la fois sur les formulaires et les formulaires en ligne).

donc, j'ai utilisé le widget anchor, avec form overriding pour obtenir le lien sur la page.

forms.py:

class AnchorWidget(forms.Widget):

    def _format_value(self,value):
        if self.is_localized:
            return formats.localize_input(value)
        return value

    def render(self, name, value, attrs=None):
        if not value:
            value = u''

        text = unicode("")
        if self.attrs.has_key('text'):
            text = self.attrs.pop('text')

        final_attrs = self.build_attrs(attrs,name=name)

        return mark_safe(u"<a %s>%s</a>" %(flatatt(final_attrs),unicode(text)))

class PostAdminForm(forms.ModelForm):
    .......

    def __init__(self,*args,**kwargs):
        super(PostAdminForm, self).__init__(*args, **kwargs)
        instance = kwargs.get('instance',None)
        if instance.blog:
            href = reverse("admin:appname_Blog_change",args=(instance.blog))  
            self.fields["link"] = forms.CharField(label="View Blog",required=False,widget=AnchorWidget(attrs={'text':'go to blog','href':href}))


 class BlogAdminForm(forms.ModelForm):
    .......
    link = forms..CharField(label="View Post",required=False,widget=AnchorWidget(attrs={'text':'go to post'}))

    def __init__(self,*args,**kwargs):
        super(BlogAdminForm, self).__init__(*args, **kwargs)
        instance = kwargs.get('instance',None)
        href = ""
        if instance:
            posts = Post.objects.filter(blog=instance.pk)
            for idx,post in enumerate(posts):
                href = reverse("admin:appname_Post_change",args=(post["id"]))  
                self.fields["link_%s" % idx] = forms..CharField(label=Post["name"],required=False,widget=AnchorWidget(attrs={'text':post["desc"],'href':href}))

maintenant dans votre ModelAdmin outrepassez l'attribut form et vous devriez obtenir le résultat souhaité. J'ai supposé que vous avez une relation OneToOne entre ces tables, Si vous avez un à plusieurs alors le côté BlogAdmin ne fonctionnera pas.

mise à jour: J'ai fait quelques changements pour ajouter dynamiquement des liens et cela résout également le problème OneToMany avec le Blog à Post espérons que cela résout le problème. :)

D'Après Pastebin: Dans Votre PostAdmin I remarqué blog_link , cela signifie que vous essayez de montrer le lien blog sur changelist_view qui liste tous les messages. Si j'ai raison, alors vous devriez ajouter une méthode pour afficher le lien sur la page.

class PostAdmin(admin.ModelAdmin):
    model = Post
    inlines = [SubPostInline, DefinitionInline]
    list_display = ('__unicode__', 'enabled', 'blog_on_site')

    def blog_on_site(self, obj):
        href = reverse("admin:appname_Blog_change",args=(obj.blog))
        return mark_safe(u"<a href='%s'>%s</a>" %(href,obj.desc))
    blog_on_site.allow_tags = True
    blog_on_site.short_description = 'Blog'

en ce qui concerne les liens post sur BlogAdmin changelist_view vous pouvez faire la même chose que ci-dessus. Ma solution précédente vous montrera le lien un niveau plus bas à la page change_view où vous pouvez éditer chaque instance.

si vous voulez que la page BlogAdmin montre les liens vers le post dans la page change_view alors vous devrez inclure chacun dans le fieldsets dynamiquement en remplaçant le get_form méthode pour class BlogAdmin et en ajoutant le lien de manière dynamique, dans get_form définir le self.fieldsets , mais d'abord n'utilisez pas tuples pour fieldsets à la place utilisez une liste.

2
répondu Pannu 2012-04-02 15:12:13

basé sur agfs et les suggestions de SummerBreeze, j'ai amélioré le décorateur pour mieux gérer unicode et pour être en mesure de faire le lien avec backward-foreignkey fields (ManyRelatedManager avec un résultat). Vous pouvez aussi ajouter une description courte comme en-tête de liste:

from django.core.urlresolvers import reverse
from django.core.exceptions import MultipleObjectsReturned
from django.utils.safestring import mark_safe

def add_link_field(target_model=None, field='', app='', field_name='link',
                   link_text=unicode, short_description=None):
    """
    decorator that automatically links to a model instance in the admin;
    inspired by /q/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-of-a-related-object-45207/"""
    def add_link(cls):
        reverse_name = target_model or cls.model.__name__.lower()

        def link(self, instance):
            app_name = app or instance._meta.app_label
            reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
            link_obj = getattr(instance, field, None) or instance

            # manyrelatedmanager with one result?
            if link_obj.__class__.__name__ == "RelatedManager":
                try:
                    link_obj = link_obj.get()
                except MultipleObjectsReturned:
                    return u"multiple, can't link"
                except link_obj.model.DoesNotExist:
                    return u""

            url = reverse(reverse_path, args = (link_obj.id,))
            return mark_safe(u"<a href='%s'>%s</a>" % (url, link_text(link_obj)))
        link.allow_tags = True
        link.short_description = short_description or (reverse_name + ' link')
        setattr(cls, field_name, link)
        cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + \
            [field_name]
        return cls
    return add_link

Edit: mis à jour en raison de lier être allé.

2
répondu panni 2015-04-18 21:37:06

regarder à travers la source des classes admin est instructif: cela montre qu'il y a un objet en contexte disponible pour une vue admin appelée "original".

Voici une situation similaire, où j'avais besoin de quelques informations ajoutées à une vue de liste de changement: ajouter des données aux gabarits d'administrateur (sur mon blog).

0
répondu Matthew Schinckel 2012-04-05 12:57:28