tutorial theme template personalizar createsuperuser bootstrap python django django-models django-admin django-templates

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>&nbsp; <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>&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>

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