The approach I took was to use namedtuples. Namedtuples are one of those things I had heard about in passing, but not really paid much attention to. Turns out, they're tuples (immutable sequences) which can be addressed by property names (rather than by index) given to them beforehand. They can't be updated, so they need to be built all at once. That said, they provided the nice access mechanism (just like normal Python objects) that I was looking for.

from collections import namedtuple UserT = namedtuple( "User" , "email last_mailed followed_users followed_repos mail_interval" ) def gen_user_t (email= None , last_mailed= None , followed_users= None , followed_repos= None , mail_interval= None ): return UserT(email=email, last_mailed=last_mailed, followed_repos=followed_repos, followed_users=followed_users, mail_interval=mail_interval) class UserProfile (models.Model): def to_user_t ( self ): return UserT( email= self .user.email, last_mailed= self .last_email_received, followed_users=[], followed_repos=[], mail_interval= self .max_time_interval_between_emails )

From here, I gave my Django models (in this case the one for UserProfile) a method to generate these namedtuples. I then pass these around in my system like normal Python objects. One example of this is the function which determines if we should send an email out to the user.

def should_mail_user (user_t, seed_time= None ): """Returns True if the is within the interval of max_time_interval_between_emails""" if seed_time is None : seed_time = now() if user_t.last_mailed is None : return True td = timedelta(days=interval_to_days(user_t.mail_interval)) return user_t.last_mailed < seed_time - td

If I were writing Django code, I'd likely encode this business logic in an ORM call. Something like the code snippet below. Unfortunately, this encodes business logic in a SQL statement which is very slow to unit test.

min_threshold = now() - timedelta(days=interval_to_days(user_t.mail_interval)) User.objects. filter (Q(last_mailed__isnull= True ) | Q(last_mailed__lt=min_threshold))

My application code now does generic SQL queries (eg Give me all users) in the main method of the app (a management command, in my case), converts these entries into the namedtuples above and my application code deals with those instead, filtering as necessary in plain python code.

As I joked with a few folks I was talking with this about, the path to faster Django tests is to not use any of the things in Django. Push the use of the ORM to the edges of your application code and deal with simpler types to make things easier to test.

The result of this exercise was easily (and quickly) testable code which isolated the ORM from me such that I could add and change fields with little chance of breaking the underlying code. Mission accomplished.