Skip to content
🌟 ModelChoiceField implementation that can accept lists of objects, not just querysets
Branch: develop
Clone or download
Latest commit 40b77d4 Mar 30, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
cached_modelforms improvend test suite Mar 30, 2019
.gitignore updated .gitignore Mar 30, 2019
.travis.yml support for django 2.x Mar 29, 2019
LICENSE Update AUTHORS Mar 30, 2019
MANIFEST.in Initial commit Oct 25, 2011
README.rst Update README.rst Mar 30, 2019
setup.py improvend test suite Mar 30, 2019
test_settings.py improvend test suite Mar 30, 2019

README.rst

django-cached-modelforms

The application provides ModelForm, ModelChoiceField, ModelMultipleChoiceField implementations that can accept lists of objects, not just querysets. This can prevent these fields from hitting DB every time they are created.

Authored by Vlad Starostin, and some great contributors.

The problem

Imagine the following form:

class MyForm(forms.Form):
    obj = ModelChoiceField(queryset=MyModel.objects.all())

Every time you render the form ModelChoiceField field will hit DB. What if you don't want it? Can't you just pass the list of objects (from cache) to the field? You can't. What to do? Use cached_modelforms.CachedModelChoiceField.

The solution

Form with cached_modelforms.CachedModelChoiceField:

# forms.py

from cached_modelforms import CachedModelChoiceField

class MyForm(forms.Form):
   obj = CachedModelChoiceField(objects=lambda:[obj1, obj2, obj3])

This field will act like regular ModelChoiceField, but you pass a callable that returns the list of objects, not queryset, to it. Calable is needed because we don't want to evaluate the list out only once.

A callable can return:

  • a list of objects: [obj1, obj2, obj3, ...]. obj should have pk property and be coercible to unicode.
  • a list of tuples: [(pk1, obj1), (pk2, obj2), (pk3, obj3), ...].
  • a dict: {pk1: obj1, pk2: obj2, pk3: obj3, ...}. Note that dict is unsorted so the items will be ordered by pk lexicographically.

Same is for cached_modelforms.CachedModelMultipleChoiceField.

Warnings

There is no special validation here. The field won't check that the object is an instance of a particular model, it won't even check that object is a model instance. And it's up to you to keep cache relevant. Usually it's not a problem.

Model Form

But what about model forms? They still use original ModelChoiceField for ForeignKey fields. This app has its own ModelForm class that uses cached_modelforms.CachedModelChoiceField and cached_modelforms.CachedModelMultipleChoiceField. The usage is following:

# models.py

class Category(models.Model):
    title = CharField(max_length=64)

class Tag(models.Model):
    title = CharField(max_length=64)

class Product(models.Model):
    title = CharField(max_length=64)
    category = models.ForeignKey(Category)
    tags = models.ManyToManyField(Tag)

# forms.py

import cached_modelforms

class ProductForm(cached_modelforms.ModelForm):
    class Meta:
        model = Product
        objects = {
            'category': lambda:[...], # your callable here
            'tags': lambda:[...], # and here
        }

That's all. If you don't specify objects for some field, regular Model[Multiple]ChoiceField will be used.

m2m_initials

If you use ManyToManyField in ModelForm and load an instance to it, it will make one extra DB request (JOINed!) – to get initials for this field. Can we cache it too? Yes. You need a function that accepts model instance and returns a list of pk's – initials for the field. Here's a modification of previous example:

# models.py

class Product(models.Model):
    title = CharField(max_length=64)
    category = models.ForeignKey(Category)
    tags = models.ManyToManyField(Tag)

    def tags_cached(self):
        cache_key = 'tags_for_%(product_pk)d' % {'product_pk': self.pk}
        cached = cache.get(cache_key)
        if cached is not None:
            return cached
        result = list(self.tags.all())
        cache.set(cache_key, result)
        return result

# forms.py

import cached_modelforms

class ProductForm(cached_modelforms.ModelForm):
    class Meta:
        model = Product
        objects = {
            'category': lambda:[...], # your callable here
            'tags': lambda:[...], # and here
        }
        m2m_initials = {'tags': lambda instance: [x.pk for x in instance.tags_cached()]}

Contributing

If you've found a bug, implemented a feature or customized the template and think it is useful then please consider contributing. Patches, pull requests or just suggestions are welcome!

License

django-cached-modelforms is released under the BSD license.

You can’t perform that action at this time.