Skip to main content

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.

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 CachedModelChoiceField.

The solution

Form with CachedModelChoiceField:

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 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.

Modelform

But what about modelforms? They still use original ModelChoiceField for ForeignKey fields. This app has its own ModelForm that uses CachedModelChoiceField and 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
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

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

Compatibility

For sure is works fine with Django 1.2-1.4. Altering ModelForm has required some copy-pasting from Django source code. It couldn’t be done with inheritance. I don’t think there will be problems with futher versions of Django, but don’t forget to run the tests if something seems wrong.

Metadata

Release files for django-cached-modelforms 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-cached-modelforms 0.2.3
File Size Uploaded Provenance Actions
django-cached-modelforms-0.2.3.tar.gz 8.0 kB Without provenance Details for django-cached-modelforms-0.2.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-cached-modelforms 0.2.3
File Interpreter ABI Platform Actions
django_cached_modelforms-0.2.3-py3-none-any.whl Python 3 none any Details for django_cached_modelforms-0.2.3-py3-none-any.whl

Total release size: 16.8 kB

Release files / django-cached-modelforms-0.2.3.tar.gz

Download URL django-cached-modelforms-0.2.3.tar.gz
Size 8.0 kB
Tags Source
SHA-256 checksum
How to use checksums
c742227dc4fafd9fd2a75a946dfdfe38a3348504f62791fe96db52bb24e271d3
BLAKE2b-256 checksum
How to use checksums
88b7f434cff4b7439316f1ec7ddb64d1c44da1a6e7db5e84033faede5c4aafcc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via Python-urllib/3.7

Release files / django_cached_modelforms-0.2.3-py3-none-any.whl

Download URL django_cached_modelforms-0.2.3-py3-none-any.whl
Size 8.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b46d15e91b592c0fd29638f5420a39242acaffb964d7ed4d6a6d0debaf4fb695
BLAKE2b-256 checksum
How to use checksums
f7c7a99093dfa8e3d87ae829014059c97554b80461cbcde0b7d10a953601f0af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via Python-urllib/3.7

Security for django-cached-modelforms 0.2.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page