Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

*_set attributes on Django Models

I have a very basic question about django.db.models.

In this official django tutorial, if you search for word "choice_set", you will see that variable "choice_set" is not declared anywhere, though magically, we can start using it in the code.

I wonder, what does django.db.models.Model do that magically created the *_set variable, and what other variables does it create?

like image 878
CuriousMind Avatar asked Jan 09 '13 04:01

CuriousMind


People also ask

What is set () in Django?

The _set is a reverse lookup class variable django puts in for you. The reason the reverse is a queryset is, ForeignKey is 1-to-many relationship. Hence, the reverse is a queryset. The _set object is made available when related_name is not specified.

What is attribute in model Django?

In Django , a model field pertains to a column in the database. On the other hand, a model attribute pertains to a method or property that is added to a model .

What is __ Str__ in Django model?

The __str__ method just tells Django what to print when it needs to print out an instance of the any model. It is also what lets your admin panel, go from this. Note: how objects are just plainly numbered. to this.


2 Answers

You can get a full list of the attributes of a class, both those you defined and the ones that are defined for it, using the dir function, just do

 dir(Poll)

You'll end up with something that looks a little like (though not exactly- I'm constructing it in a roundabout way):

['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']

That's a lot of values! We can see exceptions like DoesNotExist and MultipleObjectsReturned, along with the most important one, objects. But some of these attributes weren't added by Django. If you do dir(object()) you'll find a list of the attributes in all objects:

['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

Mostly you can ignore the ones that start and end with two __. Most of the others were added by Django.


As for how and where it actually sets these: Django sets most of each new model's attributes dynamically using the models.Model metaclass. The first thing to know is that you can add a member or method to a class dynamically, using the setattr function:

class X:
    pass
setattr(X, "q", 12)
print X.q  # prints 12

That's how it can create new attributes just based on the name of your attribute.

In the tutorial, the important line that allows it to start defining these extra attributes is:

class Poll(models.Model):

This means that the Poll class inherits the models.Model class (which belongs to Django). Inheritance has many useful properties- basically, the Poll class inherits some of the behavior that the models.Model class has set up- but the place it defines most of these new attributes is in the Model metaclass. Metaclasses are a tricky concept, but basically they serve as a recipe for creating new classes, and by defining one, Django gets to step in right when the models.py metaclass is being defined, and define any new .

The code for the Model metaclass can be found here (starting at line 55)- it's a set of code that is actually step-by-step creating a class from the ground up. As complicated as it looks, you can get a lot out of it just by looking at the variable names. For instance, look at the promisingly named add_to_class method:

def add_to_class(cls, name, value):
    if hasattr(value, 'contribute_to_class'):
        value.contribute_to_class(cls, name)
    else:
        setattr(cls, name, value)

Outside of the one special case of 'contribute_to_class (not important for your interest), this is a method for adding a new attribute (such as a method or a member) to a class. The places where it is called give us hints of what it is adding:

 class.add_to_class('DoesNotExist', subclass_exception(str('DoesNotExist') ...<truncated>...

Here it is adding the DoesNotExist exception, which is what is returned if you ask for a Poll that doesn't exist. (See for yourself by running Poll.objects.get(pk=1337), or directly by typing in Poll.DoesNotExist).

But Django is actually even more complicated than that. The specific _set attribute you're asking about isn't constructed for every model- it's created when a field is related to another by a ForeignKey (as are your Poll and Choice). The various places where it gets assigned are very complicated, but it basically all comes back to this get_accessor_name function in related.py

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())

That's just coming up with the name- tracing it back to figure out how it gets added to the class is no small feat. But I hope you see from this that Django has many chances to add attributes like this.

like image 172
David Robinson Avatar answered Sep 19 '22 18:09

David Robinson


This is some ForeignKey magic :)

The Choice model has the attribute poll, which is a ForeignKey to a Poll object. Django adds the convenience method choice_set to Poll objects, which will give a QuerySet containing all Choice objects that reference that Poll Object.

So, given this (pseudo) code

myPoll = Poll(question='Is Django awesome?')
option_yes = Choice(poll=myPoll, choice='Yes')
option_no = Choice(poll=myPoll, choice='No')

You could later run

poll = Poll.objects.get(question='Is Django awesome?')
options = poll.choice_set.all()

and options would include the relevant Choice objects.

You can change the name of this attribute using the related_name option when defining the ForeignKey.

like image 23
DanielB Avatar answered Sep 16 '22 18:09

DanielB