12

I want to add few fields to every model in my django application. This time it's created_at, updated_at and notes. Duplicating code for every of 20+ models seems dumb. So, I decided to use abstract base class which would add these fields. The problem is that fields inherited from abstract base class come first in the field list in admin. Declaring field order for every ModelAdmin class is not an option, it's even more duplicate code than with manual field declaration.

In my final solution, I modified model constructor to reorder fields in _meta before creating new instance:

class MyModel(models.Model):
    # Service fields
    notes = my_fields.NotesField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

    last_fields = ("notes", "created_at", "updated_at")
    def __init__(self, *args, **kwargs):
        new_order = [f.name for f in self._meta.fields]
        for field in self.last_fields:
            new_order.remove(field)
            new_order.append(field)
        self._meta._field_name_cache.sort(key=lambda x: new_order.index(x.name))
        super(MyModel, self).__init__(*args, **kwargs)

class ModelA(MyModel):
    field1 = models.CharField()
    field2 = models.CharField()
    #etc ... 

It works as intended, but I'm wondering, is there a better way to acheive my goal?

Alexander Lebedev
  • 5,968
  • 1
  • 20
  • 30
  • 2
    12 years n and I have exactly the same need, and am impressed with your solution yet have the same question (if there is not a more canonical method of doing just this - adding fields at the end of field order with a mixin). Alas in Django 4 there is `_field_name_cache` attribute on `self._meta` so the hunt continues for a way to do it in Django 4 ... P.S. I find answers here focussed on Admin views pointless, it is the standard form order I'm interested in as respected by `as_table()` and their ilk and I have no interest in the Admin site. – Bernd Wechner Feb 26 '23 at 10:28
  • A cursory play with Django now suggests `self._meta.concrete_fields` is the thing to use, alas it's immutable assigning to it causes trouble. Still delving. – Bernd Wechner Feb 26 '23 at 11:49
  • `_field_name_cache` does not seem to exist anymore and `local_fields` being the only mutable similar item does not seem to cause the same effects. Also note that this "solution" here is for an instance of the model not for the model creation itself. – Akaisteph7 Aug 10 '23 at 17:43
  • To think that 13 years later there would still not be an actual way to achieve this. Most of the "solutions" are only targeting Django admin. What is wanted is something that will persist in all situations. – Akaisteph7 Aug 10 '23 at 17:52

3 Answers3

7

I was having the very same problem, but I found these solutions to be problematic, so here's what I did:

class BaseAdmin(admin.ModelAdmin):
    def get_fieldsets(self, request, obj = None):
        res = super(BaseAdmin, self).get_fieldsets(request, obj)
        # I only need to move one field; change the following
        # line to account for more.
        res[0][1]['fields'].append(res[0][1]['fields'].pop(0))
        return res

Changing the fieldset in the admin makes more sense to me, than changing the fields in the model.

pgcd
  • 538
  • 6
  • 13
5

If you mainly need the ordering for Django's admin you could also create your "generic"-admin class via sub-classing Django's admin class. See http://docs.djangoproject.com/en/dev/intro/tutorial02/#customize-the-admin-form for customizing the display of fields in the admin. You could overwrite the admin's __init__ to setup fields/fieldsets on creation of the admin instance as you wish. E.g. you could do something like:

class MyAdmin(admin.ModelAdmin):
    def __init__(self, model, admin_site):
        general_fields = ['notes', 'created_at', 'updated_at']
        fields = [f.name for f in self.model._meta.fields if f.name not in general_fields]
        self.fields = fields + general_fields
        super(admin.ModelAdmin, self).__init__(model, admin_site)

Besides that i think it's not a good practice to modify the (private) _field_name_cache!

Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148
2

I ALSO didn't like the other solutions, so I instead just modified the migrations files directly.

Whenever you create a new table in models.py, you will have to run "python manage.py makemigrations" (I believe this in Django >= v1.7.5). Once you do this, open up the newly created migrations file in your_app_path/migrations/ directory and simply move the rows to the order you want them to be in. Then run "python manage.py migrate". Voila! By going into "python manage.py dbshell" you can see that the order of the columns is exactly how you wanted them!

Downside to this method: You have to do this manually for each table you create, but fortunately the overhead is minimal. And this can only be done when you're creating a new table, not to modify an existing one.

jball037
  • 1,780
  • 1
  • 14
  • 18
  • This is very smart but that caveat of having to do it for every new table as well later is a killer. However, this is the closest to an actual solution for this problem. – Akaisteph7 Aug 10 '23 at 17:52