Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
fixes bug 1165411 - Optimize modelfilter choices
Browse files Browse the repository at this point in the history
  • Loading branch information
peterbe committed May 22, 2015
1 parent c05c013 commit b3ce6a1
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 5 deletions.
23 changes: 21 additions & 2 deletions moztrap/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
All models.
"""
from django.db.models import ProtectedError
from django.db.models import ProtectedError, signals
from django.dispatch import receiver

from registration.models import RegistrationProfile

from .mtmodel import ConcurrencyError
from .core.models import Product, ProductVersion, ApiKey
from .core.models import MTModel, Product, ProductVersion, ApiKey
from .core.auth import User, Role, Permission
from .environments.models import Environment, Profile, Element, Category
from .execution.models import Run, RunSuite, RunCaseVersion, Result, StepResult
Expand All @@ -18,3 +19,21 @@

# version of the REST endpoint APIs for TastyPie
API_VERSION = "v1"


@receiver(signals.post_save, sender=Tag)
@receiver(signals.post_save, sender=User)
@receiver(signals.post_save, sender=Role)
@receiver(signals.post_save, sender=Element)
@receiver(signals.post_save, sender=Suite)
@receiver(signals.post_save, sender=Run)
@receiver(signals.post_save, sender=Product)
@receiver(signals.post_save, sender=ProductVersion)
def invalidate_model_choices(sender, instance, **kwargs):
"""This makes sure that the model choices for forms related to these
are invalidated when changes are made.
The place where the caching is set is in
moztrap.view.lists.filters.ModelFilter
"""
MTModel.delete_modelfilter_choices_cache(sender)
7 changes: 7 additions & 0 deletions moztrap/model/mtmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.db.models.deletion import Collector
from django.db.models.query import QuerySet
from django.db.models.signals import class_prepared
from django.core.cache import cache

from model_utils import Choices

Expand Down Expand Up @@ -178,6 +179,10 @@ class MTModel(models.Model):
# ...but "objects", for use in most code, returns only not-deleted
objects = MTManager(show_deleted=False)

@classmethod
def delete_modelfilter_choices_cache(cls, model):
cache_key = 'modelfilter-choices-%s' % (model._meta,)
cache.delete(cache_key)

def save(self, *args, **kwargs):
"""
Expand All @@ -187,6 +192,8 @@ def save(self, *args, **kwargs):
out-of-date version is being saved.
"""
self.delete_modelfilter_choices_cache(self)

if not kwargs.pop("notrack", False):
user = kwargs.pop("user", None)
now = utcnow()
Expand Down
15 changes: 14 additions & 1 deletion moztrap/view/lists/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from django.core.urlresolvers import reverse, resolve
from django.utils.datastructures import MultiValueDict
from django.db.models import Q
from django.core.cache import cache



Expand Down Expand Up @@ -455,7 +456,19 @@ def options(self, values):
def get_choices(self):
"""Get the options for this filter."""
# always clone to get new data; filter instances are persistent
self._opts = [(obj.pk, self.label_func(obj)) for obj in self.queryset.all()]

# Because these options rarely change we can confidently cache
# them as lists of tuples.
# This cache key gets invalidated on the various signals set
# up in moztrap.model.__init__.
cache_key = 'modelfilter-choices-%s' % (self.queryset.model._meta,)
opts = cache.get(cache_key)
if opts is None:
opts = [
(obj.pk, self.label_func(obj)) for obj in self.queryset.all()
]
cache.set(cache_key, opts, 60 * 60)
self._opts = opts
return self._opts


Expand Down
10 changes: 9 additions & 1 deletion tests/view/lists/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
Tests for queryset-filtering.
"""
from django.http import QueryDict

from mock import Mock

from django.http import QueryDict
from django.core.cache import cache
from django.template.response import TemplateResponse
from django.test import RequestFactory
from django.utils.datastructures import MultiValueDict
Expand Down Expand Up @@ -559,6 +561,11 @@ def test_choices(self):

class ModelFilterTest(FiltersTestCase):
"""Tests for ModelFilter."""

def setUp(self):
super(ModelFilterTest, self)
cache.clear()

@property
def queryset(self):
"""Mock "queryset" of instances with numeric id and unicode repr."""
Expand All @@ -569,6 +576,7 @@ def queryset(self):
o2.pk = 2
o2.__unicode__ = lambda self: "two"
qs = Mock()
qs.model._meta = 'some.Model'
qs.__iter__ = lambda self: iter([o1, o2])
qs.all.return_value = qs
return qs
Expand Down
1 change: 0 additions & 1 deletion tests/view/manage/cases/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class CasesTest(case.view.manage.ListViewTestCase,
form_id = "manage-cases-form"
perm = "manage_cases"


@property
def factory(self):
"""The model factory for this manage list."""
Expand Down

0 comments on commit b3ce6a1

Please sign in to comment.