R P R O J E C T • Real world motivation • Car dealership administration system • New car sales, used car sales, servicing, repairs… • LOTS of deliberate naivety!
O O K U P class RegistrationPlateYearLookup(Lookup): lookup_name = 'year' def as_sql(self, compiler, connection): lhs_sql, lhs_args = self.process_lhs( compiler, connection, ) rhs1 = str(self.rhs)[-2:] rhs2 = str(int(rhs1) + 50)
VA L I D AT I O N >>> UsedVehicle.objects.filter( ... registration_plate__year=27, ... ) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: Year "27" is not valid. Did you mean to query Chariot?
Y R E G I S T R AT I O N Y E A R • 17, 66, 16, 65, 15, 64, 14, …, 52, 02, 51, 01 • Z\d\d\d?\w\w\w, Y\d\d\d?\w\w\w, X\d\d\d?\w\w\w… • \w\w\w\d\d\d?Z, \w\w\w\d\d\d?Y, \w\w\w\d\d\d?X…
C H N O L O G Y >>> UsedVehicle.objects.annotate( ... year=Case( When( registration_plate__regex='…', SubString(…), ), When(…), … ), ... ).order_by('year')
M T H E D ATA B A S E class TransactionTypeField(CharField): … def from_db_value(self, value, expression, connection, context): if value is None: return value return TransactionDescriptor(value)
T O R class TransactionDescriptor(object): def __init__(self, value): self.value = value def __get__(self, instance, cls=None): cls = get_class(self.value) return cls(instance)
Won't need it for the first 1-2 logical chunks • Encapsulates complex, different processes applicable to instances of the same model • Testable • Keeps model class size free from large numbers of methods full of elifs
C A S E • This one is harder! • Vehicle history may come from the time the vehicle was in different tables, and there is no foreign key • We look up by registration plate which is shared between those tables
H E R E L AT E D M A N A G E R >>> used_vehicle.task_set.order_by('-timestamp') [<Task: Clean & Valet>, <Task: Change oil>, …] >>> owned_vehicle.task_set.order_by('-timestamp') [<Task: Clean & Valet>, <Task: Change oil>, …]
T O R class TasksDescriptor( ReverseManyToOneDescriptor): related_manager_cls = RelatedTaskManager def __init__(self): """Don't call super as we don't have a rel object""" pass
E T C H I N G class RelatedTaskManager(Manager): def get_prefetch_queryset( self, instances, queryset): • Instances is a concrete list of Vehicle objects • Queryset is the base queryset of Tasks to start from (e.g. Task.objects.all())
E T C H I N G class RelatedTaskManager(Manager): def get_prefetch_queryset( self, instances, queryset): plates = [ instance.registration_plate for instance in instances ] queryset = queryset.filter( registration_plate__in=plates, )
E T C H I N G return ( queryset, # Queryset of Tasks # Task value matcher lambda result: result.registration_plate, # Vehicle value matcher lambda inst: inst.registration_plate, False, # Many objects not one 'task_set', # cache name )
E T C H O B J E C T def PrefetchVehicleTasks(Prefetch): def __init__(self): qs = Task.objects.select_related( 'employee', ) super().__init__( 'prefetched_tasks', qs, 'task_set', )
P I S A R E P O S S I B L E • Expressions, lookups and transforms are a powerful collection of tools to create domain specific logic • Descriptors are magical • Prefetching can apply to any way you can connect the two objects together