vishes_shell
vishes_shell

Reputation: 23514

Purpose of django.db.models.fields.Field.name argument

Recently i discovered that there is not-documented django.db.models.fields.Field.name option:

@total_ordering
class Field(RegisterLookupMixin):  #   here we have it
    ...                                   ↓↓↓↓↓↓↓↓↓
    def __init__(self, verbose_name=None, name=None, primary_key=False,
                 max_length=None, unique=False, blank=False, null=False,
                 db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
                 serialize=True, unique_for_date=None, unique_for_month=None,
                 unique_for_year=None, choices=None, help_text='', db_column=None,
                 db_tablespace=None, auto_created=False, validators=(),
                 error_messages=None):
        ...

There is mention of it in doc-way:

# A guide to Field parameters:
#
#   * name:      The name of the field specified in the model.
#   * attname:   The attribute to use on the model object. This is the same as
#                "name", except in the case of ForeignKeys, where "_id" is
#                appended.
#   * db_column: The db_column specified in the model (or None).
#   * column:    The database column for this field. This is the same as
#                "attname", except if db_column is specified.
#
# Code that introspects values, or does other dynamic things, should use
# attname. For example, this gets the primary key value of object "obj":
#
#     getattr(obj, opts.pk.attname)

Description above is related with #683 ([patch] Saving with custom db_column fails) ticket.

So if we look through whole django.db.models.fields.Field class, this seems as name option is setting attribute name, which make real name of variable invalid:

Suppose we have our model:

# models.py
from django.db import models


class SomeModel(models.Model):
    first = models.CharField(max_length=50, verbose_name='first', name='second')
    third = models.CharField(max_length=50, verbose_name='third')

What django-admin shell tells us:

In[2]: from app.models import SomeModel
In[3]: SomeModel.objects.create(first='first', third='third')
Traceback (most recent call last):
  File "/Users/ailove/Home/personal/untitled/venv/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-3-08e446dfd6e3>", line 1, in <module>
    SomeModel.objects.create(first='first', third='third')
  File "/Users/ailove/Home/personal/untitled/venv/lib/python3.6/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/ailove/Home/personal/untitled/venv/lib/python3.6/site-packages/django/db/models/query.py", line 415, in create
    obj = self.model(**kwargs)
  File "/Users/ailove/Home/personal/untitled/venv/lib/python3.6/site-packages/django/db/models/base.py", line 495, in __init__
    raise TypeError("'%s' is an invalid keyword argument for this function" % kwarg)
TypeError: 'first' is an invalid keyword argument for this function
In[4]: obj = SomeModel.objects.create(second='second', third='third')
In[5] obj.third
Out[5]: 'third'
In[6]: obj.second
Out[6]: 'second'
In[7]: obj.first
Traceback (most recent call last):
  File "/Users/ailove/Home/personal/untitled/venv/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-7-f0deaec10795>", line 1, in <module>
    obj.first
AttributeError: 'SomeModel' object has no attribute 'first'

Question is kinda broad, but i am also curious.

Is this name option is a thing that only helped to develop django, or ordinary developers can also make use of it? And if we can, what for?

Upvotes: 11

Views: 2278

Answers (2)

vishes_shell
vishes_shell

Reputation: 23514

Another example is useful when you want to have one of name from keyword.kwlist, e.g.

class Emails(models.Model):
    from_ = models.CharField(name='from', ...)  # workaround to have `.from` field
    to = models.CharField(...)
    content = models.TextField(...)

since initially it will forbid to set field with name to from with exception: SyntaxError: invalid syntax

Upvotes: 0

Louis
Louis

Reputation: 151441

I've found name useful if I want a model's field to have a getter and setter and hide the naming convention introduced by the getter/setter from the Django ORM and the database.

A fairly common pattern in Python is to have the getter and setter be named after the public name of the field, and have the field that holds the value of the field start with an underscore which by convention indicates that it is private. So for instance you'd have a setter and getter named foo and the "private" field for it named _foo:

class Something(object):
    _foo = "some default value"

    @property
    def foo(self):
        return self._foo

    @foo.setter
    def foo(self, val):
        self._foo = val

The code above is barebones. Presumably, in a real-world scenario you'd have additional code in your getter or setter to do some additional work. (Otherwise, there's no reason for the getter and setter.) Assuming an instance of the class above named instance, you access instance.foo and you do not touch instance._foo because the _foo field is not part of the public API.

If you want to take the pattern above and implement it on a Django model you could just do this:

class MyModel(models.Model):
    _foo = models.TextField()

    @property
    def foo(self):
        return self._foo

    @foo.setter
    def foo(self, val):
        self._foo = val

However, the net result is that your field is known to the Django ORM as _foo and it is stored in a column named _foo in the database. Some people will be okay with this, but in my projects I prefer that the existence of the getter/setter in Python not affect the name of the field elsewhere. In order to have the same name in the Django ORM and for the column name, you can do:

_foo = models.TextField(name="foo")

Doing this will set the name of the field as seen in the Django ORM, so this works:

MyModels.objects.get(foo=...)

Otherwise, you'd have to use the underscore and do MyModels.objects.get(_foo=...). And it also sets the name of the database column so in raw SQL you'd access the column as foo. If you happen to want a different column name, you have to use the db_column argument to set the name: models.TextField(name="foo", db_column="something_else")

Upvotes: 9

Related Questions