python - theme - django-admin command
¿Cómo agrego un enlace de la página de administración de Django de un objeto a la página de administración de un objeto relacionado? (7)
Creo que la solución de agf es bastante increíble, muchas felicitaciones para él. Pero necesitaba un par de características más:
- para poder tener múltiples enlaces para un administrador
- para poder vincular al modelo en una aplicación diferente
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:%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
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.
Para lidiar con la falta de líneas anidadas en django-admin, he incluido 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 administración de cambios o administrador en línea de un modelo a la página de administración de cambios o administrador en línea de un modelo relacionado limpiamente, sin hacks desagradables en la plantilla?
Me gustaría una solución general que pueda aplicar a la página de cambio de administrador o administrador en línea de cualquier modelo.
Tengo un modelo, post
(no es su nombre real) que es tanto una línea en la página de administración del blog
, y 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 editan con él, y solo tiene sentido cuando se edita con blog
.
Para la página de administración de post
, cambié parte de "fieldset.html" de:
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
a
{% 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 %}
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 ''<a href="%s">%s</a>'' % (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:
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>
<span class="inline_label">{% if inline_admin_form.original %}
{{ inline_admin_form.original }}
{% else %}#{{ forloop.counter }}{% endif %}</span>
a
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>
<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>
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 la clave externa.
Estoy seguro de que hay una forma mejor y más general de agregar enlaces a formularios de administración sin repetirme; ¿Qué es?
En base a las sugerencias de agfs y SummerBreeze, he mejorado el decorador para manejar unicode mejor y para poder vincular los campos de clave externa hacia atrás (ManyRelatedManager con un resultado). También ahora puede agregar short_description como 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://.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:%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
Editar: actualizado debido a que el enlace se ha ido.
Esta es mi solución actual, basada en lo sugerido por Pannu (en su edición) y Mikhail.
Tengo un par de vistas administrativas de alto nivel que necesito vincular a una vista de administrador de nivel superior de un objeto relacionado, y un par de vistas de cambio de administrador en línea que necesito vincular a la vista de cambio de administrador de nivel superior del mismo objeto. Por eso, quiero factorizar el método de enlace en lugar de repetir las variaciones de cada vista de cambio de administrador.
Utilizo un decorador de clases 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:%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
También puede pasar una llamada personalizable si necesita obtener el texto del enlace de alguna manera que simplemente llamar a unicode
en el objeto al que 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 parchar a Django.
Estoy de acuerdo en que es difícil de hacer la edición de plantilla, así 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).
Entonces, utilicé el widget delimitador, junto con la anulación de formulario 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"<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}))
ahora en su ModelAdmin
anule el atributo de form
y debe obtener el resultado deseado. Supuse que tienes una relación de OneToOne
entre estas tablas. Si tienes una para muchas, el lado de BlogAdmin
no funcionará.
actualización: hice 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 su PostAdmin
noté blog_link
, eso significa que trata de mostrar el enlace del blog
en changelist_view
que enumera todas las publicaciones. Si estoy en lo correcto, entonces 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"<a href=''%s''>%s</a>" %(href,obj.desc))
blog_on_site.allow_tags = True
blog_on_site.short_description = ''Blog''
En cuanto a la post
enlaces de BlogAdmin
en BlogAdmin
changelist_view
, puede hacer lo mismo que arriba. Mi solución anterior le mostrará el enlace un nivel más bajo 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 una de ellas en los conjuntos de fieldsets
dinámicamente anulando el método get_form
para la class BlogAdmin
y agregando el enlace dinámicamente, en get_form
estableciendo self.fieldsets
, pero primero no use tuplas para conjuntos de fieldsets
en su lugar use una lista.
Mirar a través de la fuente de las clases de administrador es esclarecedor: muestra que hay un objeto en contexto disponible para una vista de administrador llamada "original".
Aquí hay una situación similar, donde necesitaba información agregada a una vista de lista de cambios: Agregar datos a plantillas de administrador (en mi blog).
Nuevo en Django 1.8: show_change_link para administrador en línea .
Establezca show_change_link en True (False de forma predeterminada) en su modelo en línea, de modo que los objetos en línea tengan un enlace a su formulario de modificación (donde pueden tener sus propias líneas).
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)
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