146

I have a model registered on the admin site. One of its fields is a long string expression. I'd like to add custom form fields to the add/update pages of this model in the admin. Based on the values of these fields I will build the long string expression and save it in the relevant model field.

How can I do this?

I'm building a mathematical or string expression from symbols. The user chooses symbols (these are the custom fields that are not part of the model) and when they click save then I create a string expression representation from the list of symbols and store it in the DB. I don't want the symbols to be part of the model and DB, only the final expression.

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
michalv82
  • 1,949
  • 4
  • 16
  • 17

8 Answers8

191

Either in your admin.py or in a separate forms.py you can add a ModelForm class and then declare your extra fields inside that as you normally would. I've also given an example of how you might use these values in 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

To have the extra fields appearing in the admin just:

  1. Edit your admin.py and set the form property to refer to the form you created above.
  2. Include your new fields in your fields or fieldsets declaration.

Like this:

class YourModelAdmin(admin.ModelAdmin):

    form = YourModelForm

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

UPDATE:

In Django 1.8 you need to add fields = '__all__' to the metaclass of YourModelForm.

Vishnu
  • 3,899
  • 2
  • 18
  • 19
  • How would I access the Meta class's model property from within the save method? – SAS Apr 10 '15 at 19:30
  • Please help! This is great and works almost perfectly for my needs but for some reason when i hit save in the admin, it actually 'saves' 3 times. So on the line:~ # ...do something with extra_field here... if i put a print here it prints 3 times.. (in my case it sends 3 emails!) – joeskru Nov 19 '15 at 11:40
  • 2
    @sthzg, Because it's not correct. It gives me the error: `YourModelAdmin.list_display[0], 'extra_field' is not a callable or an attribute of 'YourModelAdmin' or found in the model 'YourModel'.` – Cerin Jun 13 '16 at 15:39
  • This answer isn't working for me in django 1.8, there are no errors but the extra field simply does not show up in the add/change form. – bparker Mar 13 '17 at 14:38
  • 7
    If for some reason, you get an `AttributeError: Unable to lookup "extra_field"...`, try adding a `label` to the `extra_field` definition. It seems that django tries to "guess" the label for it, by looking at the `Model` and the `ModelAdmin` for such an attribute definition. – alekosot Apr 12 '17 at 16:00
  • Adding imports will help copy/paste of example: from django import forms from yourmodule.models import YourModel – Manel Clos Jun 19 '17 at 06:46
  • Nice! Is there a way to make this dynamic? I'd like to have an extra field only on initial creation, but not on following edits. – monkut Jul 11 '18 at 16:22
  • 1
    This worked beautifully if extra_field is a CharField(). If it is a hiddenField though[In Django 1.11], an error's generated `Unknown field(s) (extra_field) specified for YourModel. Check fields/fieldsets/exclude attributes of class YourModelAdmin.`. The workaround for that is `extra_field = forms.CharField(widget=forms.HiddenInput())` – kakoma Oct 04 '18 at 04:09
  • @Vishnu sorry for asking a question here, but my question is related this this answer so please take a look here https://stackoverflow.com/questions/61910594/adding-extended-profile-model-into-custom-user-models-admin – Rahul Verma May 20 '20 at 22:39
  • 1
    To answer @Cerin 's comment, in order to prevent that error I've created a method in the admin: `def extra_field(self, obj): pass`, just so the admin doesn't complain and then the form is able to actually render the `extra_field` by itself. – Luca Bezerra Aug 11 '20 at 18:56
  • Marked this one is the most useful among all the answers but however, after Django 1.8+, you do not need to add the fieldsets tuple to the YourModelAdmin when you register your form to the admin panel. Unless, you are looking to totally customize the fields, and not adding an extra field to the existing as described. I proposed an edit for this – JimShapedCoding Jul 24 '22 at 09:07
51

It it possible to do in the admin, but there is not a very straightforward way to it. Also, I would like to advice to keep most business logic in your models, so you won't be dependent on the Django Admin.

Maybe it would be easier (and maybe even better) if you have the two seperate fields on your model. Then add a method on your model that combines them.

For example:

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)

Then in the admin you can add the combined_fields() as a readonly field:

class MyModelAdmin(models.ModelAdmin):

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

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

If you want to store the combined_fields in the database you could also save it when you save the model:

def save(self, *args, **kwargs):
    self.field3 = self.combined_fields()
    super(MyModel, self).save(*args, **kwargs)
gitaarik
  • 42,736
  • 12
  • 98
  • 105
  • 6
    Thanks for the answer, but this is not what I'm looking for. I do not want the custom fields to be saved in the DB, only the calculated string. Basically what I'm doing is building a mathematical or string expression from symbols, the user chooses symbols (these are the custom fields that are not part of the model) and when he clicks save then I create a string expression representation from the list of symbols and store it in the DB. – michalv82 Jul 30 '13 at 13:42
  • @michalv82 You can also save it to the database in the model's `save()` method, check the updates of my answer. – gitaarik Jul 30 '13 at 13:53
  • 1
    thanks again, but the issue is that I don't want to store the fields that combine the final field (i.e. the symbols), I just want the final string to be saved – michalv82 Jul 30 '13 at 14:04
  • Is it a problem to save the 2 fields? Maybe it can come in handy, if you want to know how the combined field got generated. – gitaarik Jul 30 '13 at 14:17
  • 7
    thanks again but it's not 2 fields, it will probably be more. Again, I DO NOT want to store them in DB, so this solution cannot work for me. – michalv82 Jul 30 '13 at 14:25
10

Django 2.1.1 The primary answer got me halfway to answering my question. It did not help me save the result to a field in my actual model. In my case I wanted a textfield that a user could enter data into, then when a save occurred the data would be processed and the result put into a field in the model and saved. While the original answer showed how to get the value from the extra field, it did not show how to save it back to the model at least in Django 2.1.1

This takes the value from an unbound custom field, processes, and saves it into my real description field:

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
  • 577
  • 6
  • 7
4

You can always create new admin template, and do what you need in your admin_view (override the admin add URL to your admin_view):

url(r'^admin/mymodel/mymodel/add/$','admin_views.add_my_special_model')
Eyal Ch
  • 9,552
  • 5
  • 44
  • 54
3

If you absolutely only want to store the combined field on the model and not the two seperate fields, you could do something like this:

I never done something like this so I'm not completely sure how it will work out.

gitaarik
  • 42,736
  • 12
  • 98
  • 105
3

The first (highest score) solution (https://stackoverflow.com/a/23337009/10843740) was accurate, but I have more.

If you declare fields by code, that solution works perfectly, but what if you want to build those dynamically?

In this case, creating fields in the __init__ function for the ModelForm won't work. You will need to pass a custom metaclass and override the declared_fields in the __new__ function!

Here is a sample:

class YourCustomMetaClass(forms.models.ModelFormMetaclass):
    """
    For dynamically creating fields in ModelForm to be shown on the admin panel,
        you must override the `declared_fields` property of the metaclass.
    """
    def __new__(mcs, name, bases, attrs):
        new_class = super(NamedTimingMetaClass, mcs).__new__(
            mcs, name, bases, attrs)

        # Adding fields dynamically.
        new_class.declared_fields.update(...)
        return new_class

# don't forget to pass the metaclass
class YourModelForm(forms.ModelForm, metaclass=YourCustomMetaClass):
    """
    `metaclass=YourCustomMetaClass` is where the magic happens!
    """
    # delcare static fields here

    class Meta:
        model = YourModel
        fields = '__all__'
Ali
  • 31
  • 4
1

This is what I did to add the custom form field "extra_field" which is not the part of the model "MyModel" as shown below:

# "admin.py"

from django.contrib import admin
from django import forms
from .models import MyModel

class MyModelForm(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().save(commit=commit)

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):

    form = MyModelForm
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
0

You might get help from my answer at : my response previous on multicheckchoice custom field

You can also extend multiple forms having different custom fields and then assigning them to your inlines class like stackedinline or tabularinline:

form =

This way you can avoid formset complication where you need to add multiple custom fields from multiple models.

so your modeladmin looks like:

inlines = [form1inline, form2inline,...]

In my previous response to the link here, you will find init and save methods.

init will load when you view the page and save will send it to database.

in these two methods you can do your logic to add strings and then save thereafter view it back in Django admin change_form or change_list depending where you want. list_display will show your fields on change_list. Let me know if it helps ... ....

class CohortDetailInline3(admin.StackedInline):
    model = CohortDetails
    form =  DisabilityTypesForm
...

class CohortDetailInline2(admin.StackedInline):
    model = CohortDetails
    form =  StudentRPLForm

... ...

@admin.register(Cohort)
class CohortAdmin(admin.ModelAdmin):         
        form = CityInlineForm
        inlines = [uploadInline,   cohortDetailInline1,
        CohortDetailInline2, CohortDetailInline3]
    
        list_select_related = True
    
        list_display = ['rto_student_code', 'first_name', 'family_name',]

...

G-2
  • 11
  • 1
  • 5
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/30207575) – Khaleel Oct 29 '21 at 10:48