L'administrateur de django rend un champ en lecture seule lors de la modification d'obj mais requis lors de l'ajout d'un nouvel obj

91

En administrateur, je voudrais désactiver un champ lors de la modification d'un objet, mais le rendre obligatoire lors de l'ajout d'un nouvel objet.

Quelle est la manière django de traiter celui-ci?

frnhr
la source

Réponses:

178

Vous pouvez remplacer la get_readonly_fieldsméthode de l'administrateur :

class MyModelAdmin(admin.ModelAdmin):

    def get_readonly_fields(self, request, obj=None):
        if obj: # editing an existing object
            return self.readonly_fields + ('field1', 'field2')
        return self.readonly_fields
Bernhard Vallant
la source
21
Mise en garde mineure / majeure: cela ne fonctionne pas pour les inlines. Le bouton dynamique «ajouter un autre X» affiche le champ en lecture seule comme «(Aucun)», et non un champ de formulaire comme vous vous y attendez.
Cerin
17

Si vous souhaitez définir tous les champs en lecture seule uniquement dans la vue des modifications, remplacez les champs get_readonly_fields de l'administrateur:

def get_readonly_fields(self, request, obj=None):
    if obj: # editing an existing object
        # All model fields as read_only
        return self.readonly_fields + tuple([item.name for item in obj._meta.fields])
    return self.readonly_fields

Et si vous souhaitez masquer les boutons d'enregistrement lors de la modification de la vue :

  1. Changer la vue

    def change_view(self, request, object_id, form_url='', extra_context=None):
        ''' customize edit form '''
        extra_context = extra_context or {}
        extra_context['show_save_and_continue'] = False
        extra_context['show_save'] = False
        extra_context['show_save_and_add_another'] = False # this not works if has_add_permision is True
        return super(TransferAdmin, self).change_view(request, object_id, extra_context=extra_context)
    
  2. Modifiez les autorisations si l'utilisateur tente de modifier:

    def has_add_permission(self, request, obj=None):
       # Not too much elegant but works to hide show_save_and_add_another button
        if '/change/' in str(request):
            return False
        return True
    

    Cette solution a été testée sur Django 1.11

DVicenteR
la source
Parfait. C'est exactement ce dont j'avais besoin!
wogsland
3

FYI: au cas où quelqu'un d'autre rencontrerait les deux mêmes problèmes que j'ai rencontrés:

  1. Vous devez toujours déclarer tous les readonly_fields en permanence dans le corps de la classe, car l'attribut de classe readonly_fields sera accessible à partir de la validation (voir django.contrib.admin.validation: validate_base (), line.213 appx)

  2. Cela ne fonctionnera pas avec Inlines car l'obj passé à get_readonly_fields () est l'obj parent (j'ai deux solutions plutôt hacky et peu sécurisées utilisant css ou js)

Tim Diggins
la source
2
2. point - cela est dû à un bug dans l'admin: # 15602 Il semble que ce ne sera pas corrigé si tôt (dernière activité il y a 2 ans), donc il semble que nous soyons laissés aux solutions CSS / JS.
Frnhr
2

Une variation basée sur l'excellente suggestion précédente de Bernhard Vallant, qui préserve également toute personnalisation possible fournie par la classe de base (le cas échéant):

class MyModelAdmin(BaseModelAdmin):

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = super(MyModelAdmin, self).get_readonly_fields(request, obj)
        if obj: # editing an existing object
            return readonly_fields + ['field1', ..]
        return readonly_fields
Mario Orlandi
la source
2

La situation avec les formulaires en ligne n'est toujours pas corrigée pour Django 2.2.x mais la solution de John est en fait assez intelligente.

Code légèrement adapté à ma situation:

class NoteListInline(admin.TabularInline):
""" Notes list, readonly """
    model = Note
    verbose_name = _('Note')
    verbose_name_plural = _('Notes')
    extra = 0
    fields = ('note', 'created_at')
    readonly_fields = ('note', 'created_at')

    def has_add_permission(self, request, obj=None):
    """ Only add notes through AddInline """
    return False

class NoteAddInline(admin.StackedInline):
    """ Notes edit field """
    model = Note
    verbose_name = _('Note')
    verbose_name_plural = _('Notes')
    extra = 1
    fields = ('note',)
    can_delete = False

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        return queryset.none()  # no existing records will appear

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    # ...
    inlines = (NoteListInline, NoteAddInline)
    # ...
jlapoutre
la source
0

Vous pouvez le faire en remplaçant la méthode formfield_for_foreignkey de ModelAdmin:

from django import forms
from django.contrib import admin

from yourproject.yourapp.models import YourModel

class YourModelAdmin(admin.ModelAdmin):

    class Meta:
        model = YourModel

    def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
        # Name of your field here
        if db_field.name == 'add_only':
            if request:
                add_opts = (self._meta.app_label, self._meta.module_name)
                add = u'/admin/%s/%s/add/' % add_opts
                if request.META['PATH_INFO'] == add:
                    field = db_field.formfield(**kwargs)
                else:
                    kwargs['widget'] = forms.HiddenInput()
                    field = db_field.formfield(**kwargs)
            return field
        return admin.ModelAdmin(self, db_field, request, **kwargs)
David Miller
la source
0

Vous avez un problème similaire. Je l'ai résolu avec "add_fieldsets" et "restricted_fieldsets" dans ModelAdmin.

from django.contrib import admin  
class MyAdmin(admin.ModelAdmin):
 declared_fieldsets = None
 restricted_fieldsets = (
    (None, {'fields': ('mod_obj1', 'mod_obj2')}),
    ( 'Text', {'fields': ('mod_obj3', 'mod_obj4',)}),
 )

 add_fieldsets = (
            (None, {
             'classes': ('wide',),
             'fields': ('add_obj1', 'add_obj2', )}),
             )

Veuillez consulter par exemple: http://code.djangoproject.com/svn/django/trunk/django/contrib/auth/admin.py

Mais cela ne protège pas votre modèle des modifications ultérieures de "add_objX". Si vous voulez cela aussi, je pense que vous devez passer par la fonction "save" de la classe Model et y vérifier les changements.

Voir: www.djangoproject.com/documentation/models/save_delete_hooks/

Greez, Nick

Nick Ma.
la source