*_set Django模型的属性

Ven*_*tta 18 django django-models

我有一个非常基本的问题django.db.models.

在这个官方的django教程中,如果你搜索单词" choice_set",你会发现变量" choice_set"没有在任何地方声明,但是神奇地说,我们可以在代码中开始使用它.

我想知道,这会django.db.models.Model奇迹般地创建*_set变量,它创建了哪些其他变量?

Dav*_*son 29

您可以使用函数获取类的属性的完整列表,包括您定义的类和为其定义的属性dir,只需执行此操作

 dir(Poll)
Run Code Online (Sandbox Code Playgroud)

你会得到一些看起来有点像的东西(尽管不完全是 - 我是以迂回的方式构建它):

['DoesNotExist', 'MultipleObjectsReturned', '__class__', '__delattr__',
'__dict__', '__doc__', '__eq__', '__format__', '__getattribute__', '__hash__',
'__init__', '__metaclass__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', 
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__',
 '__weakref__', '_base_manager', '_default_manager', '_deferred', '_get_FIELD_display', 
'_get_next_or_previous_by_FIELD', '_get_next_or_previous_in_order', '_get_pk_val', 
'_get_unique_checks', '_meta', '_perform_date_checks', '_perform_unique_checks', '_set_pk_val', 
'clean', 'clean_fields', 'curve_set', 'date_error_message', 'delete', 'full_clean', 'objects', 
'pk', 'prepare_database_save', 'save', 'save_base', 'choice_set',
'serializable_value', 'unique_error_message', 'validate_unique']
Run Code Online (Sandbox Code Playgroud)

这是很多价值观!我们可以看到类似的异常DoesNotExistMultipleObjectsReturned,最重要的一个一起objects.但Django没有添加其中一些属性.如果你这样做,dir(object())你会在所有对象中找到属性列表:

['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Run Code Online (Sandbox Code Playgroud)

大多数情况下,你可以忽略以两个开头和结尾的那些__.大多数其他人都是由Django添加的.


至于它实际设置的方式和位置:Django使用models.Model元类动态设置每个新模型的大部分属性.首先要知道的是,您可以使用以下setattr函数动态地向类添加成员或方法:

class X:
    pass
setattr(X, "q", 12)
print X.q  # prints 12
Run Code Online (Sandbox Code Playgroud)

这就是它如何根据您的属性名称创建新属性.

在本教程中,允许它开始定义这些额外属性的重要一行是:

class Poll(models.Model):
Run Code Online (Sandbox Code Playgroud)

这意味着Poll该类继承了models.Model该类(属于Django).继承有许多有用的属性 - 基本上,Poll类继承了models.Model类已设置的一些行为- 但它定义大多数这些新属性的位置在Model 元类中.元类是一个棘手的概念,但基本上它们可以作为创建新类的一个方法,并且通过定义一个,Django可以在models.py定义元类时直接进入,并定义任何新类.

模型元类的代码可以在这里找到(从第55行开始) - 它是一组代码,实际上是从头开始创建一个类.尽管看起来很复杂,但只要查看变量名称,就可以从中获得很多.例如,看看有前途的add_to_class方法:

def add_to_class(cls, name, value):
    if hasattr(value, 'contribute_to_class'):
        value.contribute_to_class(cls, name)
    else:
        setattr(cls, name, value)
Run Code Online (Sandbox Code Playgroud)

在一个特殊情况之外'contribute_to_class(对您的兴趣不重要),这是一种向类添加新属性(例如方法或成员)的方法.它被调用的地方给我们提示它添加的内容:

 class.add_to_class('DoesNotExist', subclass_exception(str('DoesNotExist') ...<truncated>...
Run Code Online (Sandbox Code Playgroud)

这里是添加DoesNotExist异常,如果你要求Poll不存在则返回异常.(通过跑步Poll.objects.get(pk=1337)或直接输入来查看Poll.DoesNotExist).

但实际上Django比这更复杂.您要询问的特定 _set属性不是为每个模型构建的 - 当一个字段与另一个字段相关时ForeignKey(就像您Poll和您的那样Choice),它就会被创建.它被分配的各个地方非常复杂,但它基本上都返回到related.py中的这个get_accessor_name函数

def get_accessor_name(self):
    # This method encapsulates the logic that decides what name to give an
    # accessor descriptor that retrieves related many-to-one or
    # many-to-many objects. It uses the lower-cased object_name + "_set",
    # but this can be overridden with the "related_name" option.
    if self.field.rel.multiple:
        # If this is a symmetrical m2m relation on self, there is no reverse accessor.
        if getattr(self.field.rel, 'symmetrical', False) and self.model == self.parent_model:
            return None
        return self.field.rel.related_name or (self.opts.object_name.lower() + '_set')
    else:
        return self.field.rel.related_name or (self.opts.object_name.lower())
Run Code Online (Sandbox Code Playgroud)

这就是提出这个名字 - 追溯它以确定如何将它添加到课堂中并不是一件容易的事.但我希望你从中看到Django有很多机会添加这样的属性.


Dan*_*elB 11

这是一些ForeignKey魔术:)

Choice模型具有属性poll,这是一个ForeignKey到一个Poll对象.Django choice_setPoll对象添加了方便方法,它将QuerySet包含Choice引用该PollObject的所有对象.

所以,给定这个(伪)代码

myPoll = Poll(question='Is Django awesome?')
option_yes = Choice(poll=myPoll, choice='Yes')
option_no = Choice(poll=myPoll, choice='No')
Run Code Online (Sandbox Code Playgroud)

你以后可以跑

poll = Poll.objects.get(question='Is Django awesome?')
options = poll.choice_set.all()
Run Code Online (Sandbox Code Playgroud)

和选项将包括相关的Choice对象.

您可以related_name在定义时使用该选项更改此属性的名称ForeignKey.