¿Cómo agrego un enlace desde la página de administración de Django de un objeto a la página de administración de un objeto relacionado?

Para lidiar con la falta de inline nesteds en django-admin, he puesto casos especiales en dos de las plantillas para crear enlaces entre las páginas de cambio de administrador y los administradores en línea de dos modelos.

Mi pregunta es: ¿cómo puedo crear un enlace desde la página de cambio de administrador o el administrador en línea de un modelo a la página de cambio de administrador o el administrador en línea de un modelo relacionado de manera limpia, sin trucos en la plantilla?

Me gustaría una solución general que pueda aplicar a la página de cambio de administrador o al administrador en línea de cualquier modelo.


Tengo un modelo, post (no es su nombre real) que está tanto en línea en la página de administración del blog como también tiene su propia página de administración. La razón por la que no puede simplemente estar en línea es que tiene modelos con claves externas que solo tienen sentido cuando se edita con él, y solo tiene sentido cuando se edita con un blog .

Para la página de administración de post , cambié parte de “fieldset.html” de:

 {% if field.is_readonly %} 

{{ field.contents }}

{% else %} {{ field.field }} {% endif %}

a

 {% if field.is_readonly %} 

{{ field.contents }}

{% else %} {% ifequal field.field.name "blog" %}

{{ field.field.form.instance.blog_link|safe }}

{% else %} {{ field.field }} {% endifequal %} {% endif %}

para crear un enlace a la página de administración del blog , donde blog_link es un método en el modelo:

 def blog_link(self): return 'https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s' % (reverse("admin:myblog_blog_change", args=(self.blog.id,)), escape(self.blog)) 

No pude encontrar el id de la instancia del blog ningún lugar fuera de field.field.form.instance .

En la página de administración del blog , donde la post está en línea, modifiqué parte de “stacked.html” de:

 

{{ inline_admin_formset.opts.verbose_name|title }}:  {% if inline_admin_form.original %} {{ inline_admin_form.original }} {% else %}#{{ forloop.counter }}{% endif %}

a

 

{{ inline_admin_formset.opts.verbose_name|title }}:  {% if inline_admin_form.original %} {% ifequal inline_admin_formset.opts.verbose_name "post" %} {{ inline_admin_form.original }} {% else %}{{ inline_admin_form.original }}{% endifequal %} {% else %}#{{ forloop.counter }}{% endif %}

para crear un enlace a la página de administración de post , ya que aquí pude encontrar la id almacenada en el campo de clave externa.


Estoy seguro de que hay una forma mejor y más general de agregar enlaces a los formularios de administración sin repetirme; ¿Qué es?

Nuevo en Django 1.8: show_change_link para administrador en línea .

Establezca show_change_link en Verdadero (Falso por defecto) en su modelo en línea, de modo que los objetos en línea tengan un enlace a su forma de cambio (donde pueden tener sus propias líneas en línea).

 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) 

Utilice readonly_fields :

 class MyInline(admin.TabularInline): model = MyModel readonly_fields = ['link'] def link(self, obj): url = reverse(...) return mark_safe("edit" % url) # the following is necessary if 'link' method is also used in list_display link.allow_tags = True 

Esta es mi solución actual, basada en lo sugerido por Pannu (en su edición) y Mikhail.

Tengo un par de vistas de cambios de administrador de nivel superior. Necesito enlazar con una vista de cambios de administrador de nivel superior de un objeto relacionado, y un par de vistas de cambios de administrador en línea Necesito vincular a la vista de cambios de administrador de nivel superior del mismo objeto Debido a eso, quiero factorizar el método de enlace en lugar de repetir las variaciones de él para cada vista de cambio de administrador.

Utilizo un decorador de clase para crear el link invocable y lo agrego a 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:https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s_https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%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("https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s" % (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 

También puede pasar una llamada personalizable si necesita obtener el texto de su enlace de alguna manera que simplemente llamando a unicode en el objeto al que se está vinculando.

Lo uso así:

 # 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 

Por supuesto, nada de esto sería necesario si pudiera anidar las vistas de cambio de administrador para SubPost y Definition dentro del administrador en línea de Post en la página de cambio de administrador del Blog sin aplicar parches a Django.

Creo que la solución de AGF es bastante impresionante, muchas felicitaciones para él. Pero necesitaba un par de características más:

  • Para poder tener múltiples enlaces para un administrador.
  • Para poder enlazar al modelo en diferentes aplicaciones.

Solución:

 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:https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s_https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%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("https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s" % (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 

Uso:

 # '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') 

Lamento que el ejemplo sea tan ilógico, pero no quería usar mis datos.

Estoy de acuerdo en que es difícil de editar la plantilla, por lo que creo un widget personalizado para mostrar un anchor en la página de vista de cambio de administrador (se puede usar tanto en formularios como en formularios en línea)

Por lo tanto, utilicé el widget de anclaje, junto con el reemplazo de formularios para obtener el enlace en la página.

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"https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s" %(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_https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s" % idx] = forms..CharField(label=Post["name"],required=False,widget=AnchorWidget(attrs={'text':post["desc"],'href':href})) 

ahora en su ModelAdmin anule el atributo de form y debería obtener el resultado deseado. Supuse que tiene una relación OneToOne entre estas tablas. Si tiene una o muchas, entonces el lado de BlogAdmin no funcionará.

actualización: he realizado algunos cambios para agregar enlaces dinámicamente y eso también resuelve el problema de OneToMany con el Blog para publicar. Espero que esto resuelva el problema. 🙂

Después de Pastebin: En tu PostAdmin noté blog_link , eso significa que intentas mostrar el enlace del blog en changelist_view que enumera todas las publicaciones. Si estoy en lo cierto, debes agregar un método para mostrar el enlace en la página.

 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"https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s" %(href,obj.desc)) blog_on_site.allow_tags = True blog_on_site.short_description = 'Blog' 

En cuanto a la post enlaces en BlogAdmin changelist_view , puede hacer lo mismo que arriba. Mi solución anterior le mostrará el enlace de un nivel inferior en la página change_view donde puede editar cada instancia.

Si desea que la página BlogAdmin muestre los enlaces a la post en la página change_view , deberá incluir cada uno de fieldsets dinámicamente al reemplazar el método get_form para la class BlogAdmin y agregar los enlaces dinámicamente, en get_form establecer los self.fieldsets , pero primero no use las tuplas para los conjuntos de fieldsets lugar de usar una lista.

Basándome en las sugerencias de agfs y SummerBreeze, he mejorado el decorador para manejar unicode mejor y poder vincularlo a campos de clave forasteros hacia atrás (ManyRelatedManager con un resultado). También ahora puedes agregar una short_description como un encabezado de lista:

 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 http://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object- to-the-admin-page-o :param target_model: modelname.lower or model :param field: fieldname :param app: appname :param field_name: resulting field name :param link_text: callback to link text function :param short_description: list header :return: """ 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:https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s_https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%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"https://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-to-the-admin-page-o/%s" % (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 

Edición: actualizado debido a que el enlace se ha ido.

Mirar a través de la fuente de las clases de administración es esclarecedor: muestra que hay un objeto en contexto disponible para una vista de administración llamada “original”.

Aquí se presenta una situación similar, en la que necesitaba información agregada a una vista de lista de cambios: Agregar datos a las plantillas de administrador (en mi blog).