django admin: agregue campos de formulario personalizados que no son parte del modelo

106

Tengo un modelo registrado en el sitio de administración. Uno de sus campos es una expresión de cadena larga. Me gustaría agregar campos de formulario personalizados a la página de agregar / actualizar de este modelo en el administrador que, según los valores de estos campos, construiré la expresión de cadena larga y la guardaré en el campo del modelo relevante.

¿Cómo puedo hacer eso?

ACTUALIZACIÓN: Básicamente, lo que estoy haciendo es construir una expresión matemática o de cadena a partir de símbolos, el usuario elige símbolos (estos son los campos personalizados que no forman parte del modelo) y cuando hace clic en guardar, creo una representación de expresión de cadena desde el lista de símbolos y almacenarla en la base de datos. No quiero que los símbolos sean parte del modelo y DB, solo la expresión final.

michalv82
fuente

Respuestas:

156

Ya sea en su admin.py o en un forms.py separado, puede agregar una clase ModelForm y luego declarar sus campos adicionales dentro de eso como lo haría normalmente. También he dado un ejemplo de cómo podría usar estos valores en 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

Para que los campos adicionales aparezcan en el administrador simplemente:

  1. edite su admin.py y configure la propiedad del formulario para hacer referencia al formulario que creó anteriormente
  2. incluir sus nuevos campos en sus campos o declaración de conjuntos de campos

Me gusta esto:

class YourModelAdmin(admin.ModelAdmin):

    form = YourModelForm

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

ACTUALIZACIÓN: En django 1.8 necesita agregar fields = '__all__'a la metaclase de YourModelForm.

Vishnu
fuente
2
Sí, realmente quiero saber la razón
Vishnu
13
Debería agregar fields = '__all__'en su Metaclase, de lo contrario django se queja:Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is deprecated
IsaacKleiner
1
@sthzg, porque no es correcto. Me da el error:YourModelAdmin.list_display[0], 'extra_field' is not a callable or an attribute of 'YourModelAdmin' or found in the model 'YourModel'.
Cerin
7
Si por alguna razón obtiene un AttributeError: Unable to lookup "extra_field"..., intente agregar un labela la extra_fielddefinición. Parece que django intenta "adivinar" la etiqueta, mirando en Modely en la ModelAdmindefinición de ese atributo.
alxs
1
Esto funcionó a la perfección si extra_field es un CharField (). Sin embargo, si es un hiddenField [en Django 1.11], se genera un error Unknown field(s) (extra_field) specified for YourModel. Check fields/fieldsets/exclude attributes of class YourModelAdmin.. La solución para eso esextra_field = forms.CharField(widget=forms.HiddenInput())
kakoma
35

Es posible hacerlo en el administrador, pero no hay una forma muy sencilla de hacerlo. Además, me gustaría recomendarle mantener la mayor parte de la lógica empresarial en sus modelos, para que no dependa del administrador de Django.

Quizás sería más fácil (y quizás incluso mejor) si tiene los dos campos separados en su modelo. Luego agregue un método en su modelo que los combine.

Por ejemplo:

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)

Luego, en el administrador puede agregar combined_fields()como un campo de solo lectura:

class MyModelAdmin(models.ModelAdmin):

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

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

Si desea almacenar el combined_fieldsen la base de datos, también puede guardarlo cuando guarde el modelo:

def save(self, *args, **kwargs):
    self.field3 = self.combined_fields()
    super(MyModel, self).save(*args, **kwargs)
gitaarik
fuente
4
Gracias por la respuesta, pero esto no es lo que estoy buscando. No quiero que los campos personalizados se guarden en la base de datos, solo la cadena calculada. Básicamente, lo que estoy haciendo es construir una expresión matemática o de cadena a partir de símbolos, el usuario elige símbolos (estos son los campos personalizados que no son parte del modelo) y cuando hace clic en guardar, creo una representación de expresión de cadena de la lista de símbolos y almacenarlos en la base de datos.
michalv82
@ michalv82 También puede guardarlo en la base de datos en el save()método del modelo , verifique las actualizaciones de mi respuesta.
gitaarik
1
gracias de nuevo, pero el problema es que no quiero almacenar los campos que combinan el campo final (es decir, los símbolos), solo quiero que se guarde la cadena final
michalv82
¿Es un problema guardar los 2 campos? Tal vez pueda ser útil, si desea saber cómo se generó el campo combinado.
gitaarik
6
gracias de nuevo pero no son 2 campos, probablemente serán más. Una vez más, NO quiero almacenarlos en DB, por lo que esta solución no puede funcionar para mí.
michalv82
5

Django 2.1.1 La respuesta principal me llevó a la mitad de la respuesta a mi pregunta. No me ayudó a guardar el resultado en un campo en mi modelo real. En mi caso, quería un campo de texto en el que un usuario pudiera ingresar datos, luego, cuando ocurriera un guardado, los datos se procesarían y el resultado se colocaría en un campo en el modelo y se guardaría. Si bien la respuesta original mostró cómo obtener el valor del campo adicional, no mostró cómo guardarlo de nuevo en el modelo al menos en Django 2.1.1

Esto toma el valor de un campo personalizado independiente, lo procesa y lo guarda en mi campo de descripción real:

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__"
Mango Lassi
fuente
4

siempre puede crear una nueva plantilla de administrador y hacer lo que necesite en su vista de administración (anule la URL de adición de administrador a su vista de administración):

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

Si absolutamente solo desea almacenar el campo combinado en el modelo y no los dos campos separados, puede hacer algo como esto:

Nunca hice algo como esto, así que no estoy completamente seguro de cómo funcionará.

gitaarik
fuente