Giter VIP home page Giter VIP logo

djorm-ext-filtered-contenttypes's Introduction

djorm-ext-filtered-contenttypes

Build Status Coverage Status

A GenericForeignKey, that can be filtered & indexed server-side using subqueries.

Supports Python 2.7 and 3.3. Requires Django 1.7.

Created for and tested with PosgreSQL - feel free to submit patches for other databases.

Introduction

Django supports a mechanism for storing a ForeignKey-like reference to any object, using the django.contrib.contenttypes app. The key, called GenericForeignKey is internally stored as 2 id fields, content_type_id and object_id.

Current Django documentation says, that it is impossible to filter using GenericForeignKey field. In some use cases this may be a serious limitation of otherwise working ORM. This package fixes that.

So, when your model looks like this:

    from django.db import models
    from django.contrib.contenttypes.models import ContentType
    from django.contrib.contenttypes.fields import GenericForeignKey
    
    class Foo(models.Model):
        content_type = models.ForeignKey(ContentType)
        object_id = models.PositiveIntegerField()
        item = GenericForeignKey('content_type', 'object_id')

All you need to use this package is to replace GenericForeignKey with FilteredGenericForeignKey like this:

    from django.db import models
    from django.contrib.contenttypes.models import ContentType
    from django.contrib.contenttypes.fields import GenericForeignKey
    from filtered_contenttypes.fields import FilteredGenericForeignKey
    
    class Foo(models.Model):
        content_type = models.ForeignKey(ContentType)
        object_id = models.PositiveIntegerField()
        item = FilteredGenericForeignKey('content_type', 'object_id')

and then, you can use it in your application:

    >>> Foo.objects.filter(item__in=SometItem.objects.filter(...))
    [<Foo>, <Foo>, <Foo>]
    >>> Foo.objects.filter(item=OtherItem.objects.get(pk=5))
    [<Foo>]

Database benefits

As the author of this package (ab)uses PostgreSQL on a daily basis, this package does no different. First, it is imporant, that you create a proper index, using two fields:

CREATE UNIQUE INDEX foo_item_idx ON foo(content_type_id, object_id)

From the database point of view, the generated query looks like this:

    SELECT ... FROM ... WHERE (table.content_type_id, table.object_id) IN (...)

Yes - we are querying 2 fields at once. And this, in turn, uses that unique index created just a while ago (you created it, didn't you?).

Perhaps the best thing about this package in terms of scalability is, that when you pass a QuerySet to filtering function or a Q object, the query will be executed server-side. Using it like this:

    Foo.objects.filter(item__in=SomeOther.objects.filter(...))

will generate a single query.

Classes

filtered_contenttypes.fields.FilteredGenericForeignKeyField - a subclass of GenericForeignKey, that supports filtering.

How to use it

Just use FilteredGenericForeignKey instead of GenericForeignKey field. There should be no side-effects, as the only new functionality is the filter lookups.

.. code-block:: python

from filtered_contenttypes.fields import FilteredGenericForeignKey
from django.db import models

class Bread(models.Model):
    weight = models.IntegerField(...)

class Butter(models.Model):
    how_much_fat = models.DecimalField(...)

class Milk(models.Model):
    bottle_type = models.TextField(...)

class ShoppingCartEntry(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()

    item = FilteredGenericForeignKey('content_type', 'object_id')
    quantity = models.PositiveIntegerField()

Now, somewhere, preferably in your migrations, create a compound index for the GenericForeignKey:

Now, let's play:

.. code-block:: python

# After having some items in the cart:

# return all entries with glass milk bottles
ShoppingCart.objects.filter(
    item__in=Milk.objects.filter(bottle_type='glass'))

# return all entries with bread ~0.5kg or milk in glass bottle
ShoppingCart.objects.filter(
    item=[Bread.objects.get(weight=500),
          Milk.objects.get(bottle_type='glass')])

# in some cases, it may be useful to query directly for a list of
# (content_type_id, object_id) entries.
ShoppingCart.objects.filter(item__in=[(3,2), (3,3), (3,4)])

Changelog

0.1

  • Initial release

djorm-ext-filtered-contenttypes's People

Contributors

mpasternak avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.