django admin - ajoutez des champs de formulaire personnalisés qui ne font pas partie du modèle

106

J'ai un modèle enregistré sur le site d'administration. L'un de ses champs est une expression de chaîne longue. Je voudrais ajouter des champs de formulaire personnalisés à la page d'ajout / de mise à jour de ce modèle dans l'administrateur qui, sur la base de ces valeurs de champs, je créerai la longue expression de chaîne et l'enregistrerai dans le champ de modèle approprié.

Comment puis je faire ça?

MISE À JOUR: Fondamentalement, ce que je fais est de créer une expression mathématique ou de chaîne à partir de symboles, l'utilisateur choisit des symboles (ce sont les champs personnalisés qui ne font pas partie du modèle) et quand il clique sur Enregistrer, je crée une représentation d'expression de chaîne à partir du liste des symboles et enregistrez-la dans la base de données. Je ne veux pas que les symboles fassent partie du modèle et DB, seulement l'expression finale.

michalv82
la source

Réponses:

156

Soit dans votre admin.py, soit dans un formulaire forms.py séparé, vous pouvez ajouter une classe ModelForm, puis déclarer vos champs supplémentaires à l'intérieur comme vous le feriez normalement. J'ai également donné un exemple de la façon dont vous pourriez utiliser ces valeurs dans form.save ():

from django import forms
from yourapp.models import YourModel


class YourModelForm(forms.ModelForm):

    extra_field = forms.CharField()

    def save(self, commit=True):
        extra_field = self.cleaned_data.get('extra_field', None)
        # ...do something with extra_field here...
        return super(YourModelForm, self).save(commit=commit)

    class Meta:
        model = YourModel

Pour que les champs supplémentaires apparaissent dans l'administrateur simplement:

  1. modifiez votre admin.py et définissez la propriété du formulaire pour qu'elle fasse référence au formulaire que vous avez créé ci-dessus
  2. inclure vos nouveaux champs dans votre déclaration de champs ou jeux de champs

Comme ça:

class YourModelAdmin(admin.ModelAdmin):

    form = YourModelForm

    fieldsets = (
        (None, {
            'fields': ('name', 'description', 'extra_field',),
        }),
    )

MISE À JOUR: Dans django 1.8, vous devez ajouter fields = '__all__'à la métaclasse de YourModelForm.

Vishnu
la source
2
Oui, je veux vraiment connaître la raison
Vishnu
13
Vous devriez ajouter fields = '__all__'dans votre Metaclasse, sinon django se plaint:Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is deprecated
IsaacKleiner
1
@sthzg, parce que ce n'est pas correct. Ça me donne l'erreur:YourModelAdmin.list_display[0], 'extra_field' is not a callable or an attribute of 'YourModelAdmin' or found in the model 'YourModel'.
Cerin
7
Si pour une raison quelconque, vous obtenez un AttributeError: Unable to lookup "extra_field"..., essayez d'ajouter un labelà la extra_fielddéfinition. Il semble que django essaie de "deviner" son étiquette, en regardant le Modelet le ModelAdminpour une telle définition d'attribut.
alxs
1
Cela fonctionnait à merveille si extra_field est un CharField (). S'il s'agit d'un hiddenField [Dans Django 1.11], une erreur est générée Unknown field(s) (extra_field) specified for YourModel. Check fields/fieldsets/exclude attributes of class YourModelAdmin.. La solution de contournement pour cela estextra_field = forms.CharField(widget=forms.HiddenInput())
kakoma
35

Il est possible de le faire dans l'administrateur, mais il n'y a pas de moyen très simple de le faire. De plus, je voudrais vous conseiller de conserver la plupart de la logique métier dans vos modèles, afin que vous ne soyez pas dépendant de l'administrateur Django.

Peut-être que ce serait plus facile (et peut-être même mieux) si vous avez les deux champs séparés sur votre modèle. Ajoutez ensuite une méthode sur votre modèle qui les combine.

Par exemple:

class MyModel(models.model):

    field1 = models.CharField(max_length=10)
    field2 = models.CharField(max_length=10)

    def combined_fields(self):
        return '{} {}'.format(self.field1, self.field2)

Ensuite, dans l'administrateur, vous pouvez ajouter le combined_fields()champ en lecture seule:

class MyModelAdmin(models.ModelAdmin):

    list_display = ('field1', 'field2', 'combined_fields')
    readonly_fields = ('combined_fields',)

    def combined_fields(self, obj):
        return obj.combined_fields()

Si vous souhaitez stocker le combined_fieldsdans la base de données, vous pouvez également l'enregistrer lorsque vous enregistrez le modèle:

def save(self, *args, **kwargs):
    self.field3 = self.combined_fields()
    super(MyModel, self).save(*args, **kwargs)
gitaarik
la source
4
Merci pour la réponse, mais ce n'est pas ce que je recherche. Je ne veux pas que les champs personnalisés soient enregistrés dans la base de données, uniquement la chaîne calculée. Fondamentalement, ce que je fais est de créer une expression mathématique ou de chaîne à partir de symboles, l'utilisateur choisit des symboles (ce sont les champs personnalisés qui ne font pas partie du modèle) et quand il clique sur Enregistrer, je crée une représentation d'expression de chaîne à partir de la liste de symboles et enregistrez-le dans la base de données.
michalv82
@ michalv82 Vous pouvez également l'enregistrer dans la base de données dans la save()méthode du modèle , vérifier les mises à jour de ma réponse.
gitaarik
1
merci encore, mais le problème est que je ne veux pas stocker les champs qui combinent le champ final (c'est-à-dire les symboles), je veux juste que la chaîne finale soit enregistrée
michalv82
La sauvegarde des 2 champs pose-t-elle un problème? Peut-être que cela peut être utile, si vous voulez savoir comment le champ combiné a été généré.
gitaarik
6
merci encore mais ce n'est pas 2 champs, ce sera probablement plus. Encore une fois, je ne veux PAS les stocker dans DB, donc cette solution ne peut pas fonctionner pour moi.
michalv82
5

Django 2.1.1 La réponse principale m'a amené à mi-chemin à répondre à ma question. Cela ne m'a pas aidé à enregistrer le résultat dans un champ de mon modèle actuel. Dans mon cas, je voulais un champ de texte dans lequel un utilisateur pourrait entrer des données, puis lorsqu'une sauvegarde se produisait, les données seraient traitées et le résultat placé dans un champ du modèle et sauvegardé. Alors que la réponse originale montrait comment obtenir la valeur du champ supplémentaire, elle ne montrait pas comment la sauvegarder dans le modèle au moins dans Django 2.1.1

Cela prend la valeur d'un champ personnalisé non lié, traite et l'enregistre dans mon champ de description réel:

class WidgetForm(forms.ModelForm):
    extra_field = forms.CharField(required=False)

    def processData(self, input):
        # example of error handling
        if False:
            raise forms.ValidationError('Processing failed!')

        return input + " has been processed"

    def save(self, commit=True):
        extra_field = self.cleaned_data.get('extra_field', None)

        # self.description = "my result" note that this does not work

        # Get the form instance so I can write to its fields
        instance = super(WidgetForm, self).save(commit=commit)

        # this writes the processed data to the description field
        instance.description = self.processData(extra_field)

        if commit:
            instance.save()

        return instance

    class Meta:
        model = Widget
        fields = "__all__"
MangoLassi
la source
4

vous pouvez toujours créer un nouveau modèle d'administrateur et faire ce dont vous avez besoin dans votre admin_view (remplacer l'URL d'ajout d'admin à votre admin_view):

 url(r'^admin/mymodel/mymodel/add/$' , 'admin_views.add_my_special_model')
Eyal Ch
la source
3

Si vous ne voulez absolument stocker que le champ combiné sur le modèle et non les deux champs séparés, vous pouvez faire quelque chose comme ceci:

Je n'ai jamais fait quelque chose comme ça, donc je ne suis pas tout à fait sûr de savoir comment cela fonctionnera.

gitaarik
la source