6

How to define a Django model field in constant and use everywhere. For example, if I have a model like:-

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=40)
    email = models.EmailField()

And what I want to do is define constant for fields in Author model and provide the constant instead of field name in model like:-

KEY_FIRST_NAME = 'first_name'
KEY_LAST_NAME = 'last_name'
KEY_EMAIL = 'email'

And Author model should use the constant instead of exact key like:-

class Author(models.Model):
        KEY_FIRST_NAME = models.CharField(max_length=30)
        KEY_LAST_NAME = models.CharField(max_length=40)
        KEY_EMAIL = models.EmailField()

How to do something like this, direct assignment to constant won't work here.

I want to store all the field name in constant, and everywhere when it required I want to use the constant instead of string field name.

The purpose of doing this is If there is any change in filed name in future version then I want to only change at one place and it should reflect on all the places.

If it is not possible or it will make code too complex as suggested by one approach by @dirkgroten than what can be the best practice to define the model field as constant and use them in other places (other than inside models like if we are referring those field for admin portal or any other place).

VikasGoyal
  • 3,308
  • 1
  • 22
  • 42
  • It cannot work this way because you are doing something like this: `'first_name' = models.CharField(max_length=30)`. This has no sense. How about inheriting from a model with the default fields you want? – GLR Sep 05 '17 at 11:05
  • django field name it is name of the python class property, did you can declare property by constant? – Brown Bear Sep 05 '17 at 11:05
  • Possible duplicate of this: https://stackoverflow.com/questions/21304025/django-store-common-fields-in-a-parent-model ? – sapit Sep 05 '17 at 11:06
  • @sapit no my question is different as I want to store all the field name in constant and everywhere I want to use the constant instead of string field name. – VikasGoyal Sep 05 '17 at 11:08
  • You'd have to do this in the `__init__` method of your model. You can then `setattr` on `self`: `setattr(self, KEY_FIRST_NAME, models.CharField(args))`. But it makes working with your model very cumbersome because you'll also have to `getattr(object, KEY_FIRST_NAME)` instead of `my_user.first_name` – dirkgroten Sep 05 '17 at 11:08
  • @dirkgroten can you please provide any example it can be really helpful. – VikasGoyal Sep 05 '17 at 11:10
  • I gave you all the information needed. If you don't know how to override `__init__`, read up on Python programming and subclassing. Also: I really think you should not do this, it'll make your code too complex and unreadable. You shouldn't anticipate future name changes like this. There's also no reason why 'email' should change in the future. Also you need to write the migrations for the database yourself if you go down this route. – dirkgroten Sep 05 '17 at 11:14
  • @dirkgroten fields are stored in the data base, are your sure your solution is valid? – Brown Bear Sep 05 '17 at 11:14
  • @BearBrown it's a slippery path. Migrations won't be produced automatically in any case. You might be right that it doesn't work at all. Trying out. – dirkgroten Sep 05 '17 at 11:16
  • 1
    @dirkgroten your solution will NOT work with a Django model - fields have to be class attributes. – bruno desthuilliers Sep 05 '17 at 11:25
  • Ok, tried and doesn't work. You're right. – dirkgroten Sep 05 '17 at 11:26
  • That's over-designing! – Mohammad Jafar Mashhadi Sep 05 '17 at 12:37
  • @Avi I've wondered about all the string constants in the rest of the code (queries, etc.). It would be nice to replace them with constants so that you know there aren't typos. It comes from a C/C++/Java background of avoiding magic numbers/strings in your code. One solution is to have string constants in your model that match the field names, then use the constants throughout the code. Did you find an elegant solution for this? – E Smith Dec 24 '19 at 18:27

2 Answers2

3

Short answer: you can't do this in Python, period (actually I don't think you could do so in any language but someone will certainly prove me wrong xD).

Now if we go back to your real "problem" - not having to change client code if your model's fields names are ever to change - you'd first need to tell whether you mean "the python attribute name" or "the underlying database field name".

For the second case, the database field name does not have to match the Python attribute name, Django models fields take a db_column argument to handle this case.

For the first case, I'd have to say that it's a very generic (and not new by any mean) API-design problem, and the usual answer is "you shouldn't change names that are part of your public API once it's been released". Now sh!t happens and sometimes you have to do it. The best solution here is then to use computed attributes redirecting the old name to the new one for the deprecation period and remove them once all the client code has been ported.

An example with your model, changing 'first_name' to 'firstname':

class Author(models.Model):
    # assuming the database column name didn't change
    # so we can also show how to us `db_column` ;)

    firstname = models.CharField(
        max_length=30, 
        db_column='first_name'
        )

    @property
    def first_name(self): 
        # shoud issue a deprecation warning here
        return self.firstname

    @first_name.setter
    def first_name(self, value):
        # shoud issue a deprecation warning here
        self.firstname = value

If you have a dozen fields to rename you will certainly want to write a custom descriptor (=> computed attribute) instead to keep it dry:

class Renamed(object):
    def __init__(self, new_name):
        self.new_name = new_name

    def __get__(self, instance, cls):
        if instance is None:
            return self

        # should issue a deprecation warning here
        return getattr(instance, self.new_name)

    def __set__(self, instance, value):
        # should issue a deprecation warning here
        setattr(instance, self.new_name, value)


class Author(models.Model):
    firstname = models.CharField(
        max_length=30, 
        db_column='first_name'
        )
    first_name = Renamed("firstname")
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
0

I think the following information could prove beneficial:

To achieve this you first need to think how you can define class parameters from strings. Hence, I came across a way to dynamically create derived classes from base classes: link

Particularly this answer is what I was looking for. You can dynamically create a class with the type() command.

From here on, search how to integrate that with Django. Unsurprisingly someone has tried that already - here.

In one of the answers they mention dynamic Django models. I haven't tried it, but it might be what you are searching for.

sapit
  • 82
  • 7