zaadeh
zaadeh

Reputation: 1871

How to define a django model field with the same name as a Python keyword

I need to define a Django model field with the name in, which is a Python language keyword. This is a syntax error:

class MyModel(models.Model):
    in = jsonfield.JSONField()

How can I make this work?

The reason I need this name is when I use django-rest-framework's ModelSerializer class, field name is used as the key for serialization output, and I thought it might be easier to manipulate django's Model class instead of ModelSerializer class to get the output I want.

Upvotes: 5

Views: 3874

Answers (4)

Martijn Pieters
Martijn Pieters

Reputation: 1124978

Generally speaking, you don't. Avoid the use of keywords in your identifiers. The general Python convention is to add an underscore to such names; here that'd be in_:

class MyModel(models.Model):
    in_ = jsonfield.JSONField()

However, Django prohibits names ending in an underscore because the underscore clashes with their filter naming conventions, so you have to come up with a different name still; pick one that still describes your case; I picked contained in rather than in, as a guess to what you want to do here:

class MyModel(models.Model):
    contained_in = jsonfield.JSONField()

If you are trying to match an existing database schema, use the db_column attribute:

class MyModel(models.Model):
    contained_in = jsonfield.JSONField(db_column='in')

If you want to be stubborn, in normal classes you could use setattr() after creating the class to use a string instead of an identifier:

class Foo:
    pass

setattr(Foo, 'in', 'some value')

but you'll have to use setattr(), getattr(), delattr() and/or vars() everywhere in your code to be able to access this.

In Django you'll have the added complication that a models.Model subclass uses a metaclass to parse out your class members into others structures, and adding an extra field with setattr() doesn't work without (a lot of) extra work to re-do what the metaclass does. You could instead use the field.contribute_to() method, calling it after the class has been prepared by Django (technique taken from this blog post):

from django.db.models.signals import class_prepared

def add_field(sender, **kwargs):
    if sender.__name__ == "MyModel":
        field = jsonfield.JSONField('in')
        field.contribute_to_class(sender, 'in')

class_prepared.connect(add_field)

but you have to make sure this hook is registered before you create your model class.

Upvotes: 10

munsu
munsu

Reputation: 2062

You mentioned the use of django rest framework. Here's how to make it work on the serializer layer. The keyword used is from. to is just an example of a non-keyword if you want it mapped to a different name.

from django.db import models
from rest_framework import serializers


SP_FIELD_MAP = {
    'from': 'sender'
}


# would be in models.py
class Transaction(models.Model):
    recipient = models.CharField(max_length=16)
    sender = models.CharField(max_length=64)


# would be in serializers.py
class TransactionSerializer(serializers.ModelSerializer):
    to = serializers.CharField(source='recipient')

    class Meta:
        model = Transaction
        fields = ('id', 'to', 'from')
        # `from` is a python keyword hence this
        extra_kwargs = {'from': {'source': 'sender'}}

    def build_field(self, field_name, info, model_class, nested_depth):
        # Catches python keywords like `from` and maps to its proper field
        field_name = SP_FIELD_MAP.get(field_name, field_name)
        return super(TransactionSerializer, self).build_field(
            field_name, info, model_class, nested_depth)

Tested on CharField using POST and GET methods only but I don't see how it won't work on other methods. You might need special stuff for other field types though. I suggest going into the source. There's tons of fun stuff going on in DRF's source.

Upvotes: 2

Sayse
Sayse

Reputation: 43330

You should be giving all your variables descriptive names that clearly state what they are to be used for, and where possible it should be easy to assertain what type of variable it is.

in, to me, would appear at first glance to be a boolean so in order to use this variable in my own extension to the code I'd need to find other usages of it before I knew how I could use it.

Therefore, simply don't try to hack something together just so you can get this terrible variable name into your model, it offers no value to you to do so, its not really any quicker to type since intellisense is available in most places. Figure out what "in" relates to and then formulate a proper name that is descriptive.

Upvotes: 1

alexanderlukanin13
alexanderlukanin13

Reputation: 4725

There is no way to make it work, and it's a bad idea anyway. Choose a different name.

If, for some reason, you want to have column name that matches some reserved keyword, use db_column argument for that field.

in_something = models.CharField(db_column='in', max_length=100)

Upvotes: 2

Related Questions