web-dev-qa-db-fra.com

Comment puis-je afficher un modèle en ligne d'administration Django dans un ensemble de champs?

Considérez le ModelAdmin suivant. Dans ce cas, j'aimerais que l'interface utilisateur "Livre" en ligne s'affiche entre le jeu de champs "Aucun" et le jeu de champs Notes. Est-ce possible?

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline]

    fieldsets = (
            (None, {
                'fields': ('author_name', 'date_of_birth')
            }),
            ('Notes', {
                'fields': (['notes'])
            }),
    )
23
Huuuze

Malheureusement, cela n’est pas possible avec (le modèle standard de) Django. Si vous examinez le modèle de change_form, vous remarquerez que les inlines sont toujours restitués séparément après l'ensemble de champs: https://github.com/Django/django/blob/master/Django/contrib/ admin/templates/admin/change_form.html

La seule solution que je vois est d’écrire un modèle personnalisé en fonction de l’ordre que vous souhaitez.

6
zvyn

Bertrand Bortage a publié une autre solution ici: https://groups.google.com/forum/#!topic/Django-users/yUq2Nvx_4eM

Une réponse tardive indiquant que je venais de proposer une solution relativement propre à ce problème Dans l'un de mes projets: https://github.com/dezede/dezede/commit/ed13ccaf34494e71fd913fd785cd2285c229052f6acdc8 .

L’idée est de définir fieldsens_and_inlines_order dans votre ModelAdmin (s), une valeur itérable des caractères 'f' et 'i' (pour "fieldset" Et "inline") qui spécifie l’ordre entre les premiers champs et inlines. Si len (fieldsets_and_inlines_order) <len (fieldsets) + Len (inlines), le reste suit le comportement d'origine (fieldsets En premier, puis tous les éléments en ligne).

Exemple: vous avez 5 jeux de champs et 3 lignes, définissant Fieldsets_and_inlines_order = ('f', 'f', 'i', 'f', 'i') vous donneront: Fieldset fieldset champs en ligne champs en ligne champs champs en ligne Hope ça aide, Bertrand

J'ai eu une autre idée qui mérite d'être considérée. Créez un champ d'espace réservé en lecture seule dans vos champs de champs pour chaque ligne, puis utilisez jQuery pour déplacer les lignes en place pour chaque espace réservé. Quelque chose comme ceci (jQuery omis car je ne l’ai pas encore écrit):

fieldsets = (
        (None, {
            'fields': (
                ('inline_images',)
                ('thumbnail_image',),
                ('inline_authors',)
                ('title', 'is_active', 'order',)
            ),
        }),
    )

readonly_fields = ('inline_images', 'inline_authors')

inline_images = '<span class="replaceme inline_images"></span>'
inline_images.allow_tags = True
inline_authors = '<span class="replaceme inline_authors"></span>'
inline_authors.allow_tags = True

Une dernière chose - il y a un numéro en suspens dans Django qui demande ce positionnement d'inlines: https://code.djangoproject.com/ticket/4848

13
Andy Baker

J'ai construit une autre solution assez générique ...

Dans votre admin.py, ajoutez un nouveau champ à votre Inline:

class YourModelInline(admin.TabularInline):
    model = YourModel
    after_field = "fieldname_of_field_before_inline"

Personnalisez ensuite render_change_form de AdminClass du modèle qui contient Inline:

class EditModelAdmin(model.ModelAdmin):
    inlines = [YourModelInline,]

    def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
        sorted_inline_formsets = {}
        inline_admin_formsets = context['inline_admin_formsets']
        formsets_to_remove = []

        for inline_formset in inline_admin_formsets:
            if hasattr(inline_formset.opts, 'after_field'):
                fieldname = inline_formset.opts.after_field
                if fieldname in sorted_inline_formsets:
                    sorted_inline_formsets[fieldname].append(inline_formset)
                else:
                    sorted_inline_formsets.update({
                        fieldname: [inline_formset,]
                    })
                formsets_to_remove.append(inline_formset)
        for inline_formset in formsets_to_remove:
            inline_admin_formsets.remove(inline_formset)

        context.update({
            'sorted_inline_formsets': sorted_inline_formsets,
            'inline_admin_formsets': inline_admin_formsets
        })
        return super(EditModelAdmin, self).render_change_form(request, context, add=add,
                                                       change=change, obj=obj, form_url=form_url)

Nous déplaçons toutes les Inlines avec champ supplémentaire dans leur propre dictionnaire avec nom de champ comme clé ... Pour qu'il soit restitué correctement, créez le fichier /templates/admin/includes/fieldset.html qui remplace le champ standard Django fieldset.html avec: contenu:

{% load custom_filter %}
<fieldset class="module aligned {{ fieldset.classes }}">
        {% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
        {% if fieldset.description %}
            <div class="description">{{ fieldset.description|safe }}</div>
        {% endif %}
        {% for line in fieldset %}
            <div class="form-row{% if line.fields|length_is:'1' and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
                {% if line.fields|length_is:'1' %}{{ line.errors }}{% endif %}
                {% for field in line %}
                    <div{% if not line.fields|length_is:'1' %} class="field-box{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}"{% Elif field.is_checkbox %} class="checkbox-row"{% endif %}>
                        {% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
                        {% if field.is_checkbox %}
                            {{ field.field }}{{ field.label_tag }}
                        {% else %}
                            {{ field.label_tag }}
                            {% if field.is_readonly %}
                                <div class="readonly">{{ field.contents }}</div>
                            {% else %}
                                {{ field.field }}
                            {% endif %}
                        {% endif %}
                        {% if field.field.help_text %}
                            <div class="help">{{ field.field.help_text|safe }}</div>
                        {% endif %}
                    </div>
                    {% if field.field.name %}
                        {% with field.field.name as fieldname %}
                            {% if sorted_inline_formsets|get_dict_value:fieldname != False %}
                                {% for inline_admin_formset in sorted_inline_formsets|get_dict_value:fieldname %}
                                    {% include inline_admin_formset.opts.template %}
                                {% endfor %}
                            {% endif %}
                        {% endwith %}
                    {% endif %}
                {% endfor %}
            </div>
        {% endfor %}
    </fieldset>

Ceci ajoutera des lignes triées après le champ correspondant ... Maintenant, vous n'avez plus besoin que du custom_filter pour travailler avec le dictionnaire dans le modèle Django, créez des templatetags/custom_filter.py et ajoutez:

@register.filter
def get_dict_value(dict, key):
    if key in dict:
        return dict[key]
    else:
        return False

Et voila: vous pouvez entrer n'importe quel nom de champ dans n'importe quel Inline pour l'ajouter après ce champ ... C'est un peu fastidieux à configurer, mais si vous avez plusieurs inlines à trier, cela pourrait être plus simple ...

1
chakmear