What's new? | Help | Directory | Sign in
Google
  
  
  
  
    
Code License: New BSD License
Labels: django, Python, ORM
Links:
Join project
Project owners:
  cracka80

QManager is an abstracted Django manager which filters via custom queries.

The QManager class is a subclass of django.db.models.Manager which can remove some of the boilerplate in manager definitions. Managers may be used to define 'table-level' operations on your models, and one of their most common uses is to provide a pre-defined QuerySet for model data. The typical programming idiom goes something like this:

  1. Define your custom manager as a subclass of django.db.models.Manager, overriding the get_query_set method to return a filtered query set. For example:
  2.             class MyManager(models.Manager):
                    def get_query_set(self):
                        return super(MyManager, self).get_query_set().filter(
                            field1='x', field2='y')
  3. On your model, instantiate the manager. Following from the previous example:
  4.             class MyModel(models.Model):
                    field1 = models.CharField(...)
                    field2 = models.CharField(...)
                    ...
                    objects = models.Manager() # Default manager instance.
                    my_mgr = MyManager() # Custom manager instance.

The problem with this pattern is that it ends up being repeated several times for each model as you try to define several useful custom managers for each type of data. The QManager class abstracts this idiom by providing a simple way of defining managers to return query sets. Now, what originally involved two parts is now down to one:

    class MyModel(models.Model):
        field1 = models.CharField(...)
        field2 = models.CharField(...)
        ...
        objects = models.Manager()
        my_mgr = QManager(Q(field1='x', field2='y'))

As you can see, a custom manager is defined by passing QManager's __init__ method a Q object. Q objects are used in Django to specify queries of arbitrary complexity, and by using QManager you don't need to write all the boilerplate of the first example: all it takes is a simple Q object.

Another feature of Q instances is that they can be combined using the & (and) and | (or) logical operators. These must not be the Python literal and and or operators, as these behave differently to the symbols. Let's say, for example, that you have two query sets defined by Q instances whose intersection (logical and) you want to find. Each of these query sets would be defined by an instance of Q, like so:

    query1 = Q(public=True)
    query2 = Q(confirmed=True)

Each of these return query sets which contain those records which are public and confirmed, respectively. Such query sets would be obtained by calling the filter method on one of the model's managers (i.e. objects). Suppose you now want to find all the records which are both public and confirmed, in one query set. Typically, you would define a Q instance such as Q(public=True, confirmed=True), but this violates the DRY principle, as the same information is repeated. Instead, one may simply do this:

    query3 = query1 & query2

This is a shortcut which allows you to cut down the amount of extra code you write, and maintain DRY in your code. The QManager class provides a wrapper which lets you do the same thing in your manager definitions. An example might be:

    class MyModel(models.Model):
        public = models.BooleanField()
        confirmed = models.BooleanField()
        ...
        public_objects = QManager(Q(public=True))
        confirmed_objects = QManager(Q(confirmed=True))
        public_confirmed = public_objects & confirmed_objects

This use of & and | is not restricted to just QManager instances; Q objects, QuerySet instances and regular managers may all be combined with QManager instances via the logical operators. A caveat, however: in these statements, the QManager instance must come before anything else. It is valid, for example, to do this:

    public_confirmed = public_objects & Q(confirmed=True)

It is an error, however, to do the following:

    public_confirmed = Q(confirmed=True) & public_objects

It is advisable to ensure that errors like these do not happen, as they will be difficult to debug if they do: an error will most likely be raised somewhere in Django's codebase, rather than for QManager.

Another feature of query set manipulation is exclusion. QuerySet and Manager instances have exclude methods which, when called with a Q object, will return the set of all data not matching the conditions given in the query. This is called finding the complement of a set of data, and QManager instances support this through the negation operator (-). For example:

    class MyModel(models.Model):
        ...
        public_objects = QManager(Q(public=True))
        non_public_objects = -public_objects

If a reference to a QManager instance, whether through their definition (i.e. -QManager(...)) or a variable (i.e. -public_objects), is prepended with a - symbol, then it's __neg__ magic method will be called. In the case of QManager instances, this will return a new instance whose query is defined as the complement of the old query (Note on implementation: this is done via django.db.models.query.QNot). This may be used in conjunction with the other operators, for example:

    class MyModel(models.Model):
        ...
        public_objects = QManager(Q(public=True))
        non_public_confirmed = confirmed_objects & -public_objects
        non_confirmed_public = -confirmed_objects & public_objects

A common design pattern which emerges when using QManager is the definition of queries which apply to several similar models. For example, if several of your models have public fields which are boolean flags telling whether or not a record is public, then you may want a manager to get only public records. Because, however, you are defining managers across several models, the query Q(public=True) will be repeated several times. If, one day, you decide to change the field to being called is_public, then each query will have to be edited individually. A typical set-up might look something like this:

    class A(models.Model):
        public = models.BooleanField()
        ...
        public_objects = QManager(Q(public=True))
    
    class B(models.Model):
        public = models.BooleanField()
        ...
        public_objects = QManager(Q(public=True))

As you can see, because the managers are defined on different models they may not be reused. A simple way around this is to use the QManagerFactory function in this module. At the top of your models file (or a separate file for managers, if you wish), place the following line:

    PublicManager = QManagerFactory(Q(public=True), name='PublicManager')

PublicManager will now be a class, with the name 'PublicManager' (due to the name keyword), which is a subclass of QManager, and has the query attribute pre-defined as Q(public=True). On each model, you now only need to place the following:

    class A(models.Model):
        public = models.BooleanField()
        ...
        public_objects = PublicManager()

And likewise for B. For more information on the QManagerFactory function, you are advised to consult its docstring and source code.

For more information, esp. implementation details, consult the source code. It may also be useful to look at the official Django documentation for the Q class and database API.