PyCon Canada 2019
n+1
window
aggregates
annotate
constraints
function
postgres
admin
json
fulltext
queryset
explain
lookups
index
Expressions
Q
F
Arrays
range
explain/analyze
django-debug-tool
managers
search
Product.objects.filter(name__startswith="A")
SQL Representation
Explain
# Get a list of categories Category.objects.all() # Get an iterator of categories Category.objects.iterator() # Get product with categories filled in Product.objects.select_related("category").all() # Get all dates stocks were in ProductItem.objects.dates("stock_in_date", "day") # Find all products that end with "phone" Product.objects.filter(name__endswith="phone") # Find all product items ProductItems.objects.count()
Product.objects\ .select_related("category")\ .values("category__name")\ .annotate(count=Count("pk")
def purchases_by_month(): return ProductItem.objects\ .annotate(month=ExtractMonth("stock_in_date"))\ .values("month")\ .annotate(sales=Avg("product__price")) def total_sale(): return ProductItem.objects\ .select_related("product")\ .aggregate(total=Sum('product__price'))
Category.objects.annotate( total=Subquery( Product.objects.filter( category=OuterRef("pk")).values("category").annotate( total=Count("pk")).values("total")), product_names=Subquery( Product.objects.filter( category=OuterRef("pk")).values("category").annotate( names=StringAgg("name", "||")).values("names")), average_price=Subquery( Product.objects.filter( category=OuterRef("pk")).values("category").annotate( avg_price=Avg("price")).values("avg_price")))
def average_price(): return Product.objects.annotate( average_by_category=Window( expression=Avg('price'), partition_by=[F('category')], order_by=F('category').asc() ) ).values( 'name', 'category__name', 'average_by_category')
class Message(models.Model): when = models.DateTimeField(auto_now=True, db_index=True) sender = models.CharField(max_length=127) recipient = models.CharField(max_length=127) objects = MessageManager() def __str__(self): return f'[{self.when.ctime()}] {self.sender} -> {self.recipient}'
Find last messages between two individuals
class MessageManager(models.Manager): def latest_distinct(self): return super().get_queryset().annotate( user_1=Case( When(sender__gt=F('recipient'), then=F('recipient')), default=F('sender') ), user_2=Case( When(sender__gt=F('recipient'), then=F('sender')), default=F('recipient') ) ).order_by('user_1', 'user_2', '-when').distinct('user_1', 'user_2')
WHY?
from django.db import models from django.contrib.postgres.fields import CITextField, CICharField, JSONField from phonenumber_field.modelfields import PhoneNumberField from common.models import TimeStampedModel class Customer(TimeStampedModel): name = models.CharField(max_length=127) email = models.EmailField(unique=True) phone_number = PhoneNumberField(unique=True) address = CITextField(blank=True) bio = CICharField(blank=True, max_length=31) preferences = JSONField(null=True) def __str__(self): return self.name
Customer.objects.filter(preferences__send_email=True) Customer.objects.filter(preferences__languages__contains="python") Customer.objects.filter(bio__contains="pYThOn")
This one will be fun!!!
class Event(TimeStampedModel): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) time_range = DateTimeRangeField(db_index=True, default=default_time_range) remarks = models.TextField(blank=True) objects = EventManager() def __str__(self): return f'{self.user.username}: ({format_time_range(self.time_range)})' class Meta: ordering = '-time_range', # Uncomment in the event of Python 3.0!!! # constraints = [ # ExclusionConstraint( # name='exclude_overlapping_reservations', # expressions=[ # ('time_range', RangeOperators.OVERLAPS), # ('user', RangeOperators.EQUAL), # ] # ), # ]
def create_event(user, start_time=None, end_time=None) -> Event: return Event.objects.create( user=user, time_range=DateTimeTZRange(lower=start_time or now(), upper=end_time)) def update_event(pk, start_time, end_time) -> Event: return Event.objects.get(id=pk).update( time_range=DateTimeTZRange(lower=start_time, upper=end_time))
class EventManager(models.Manager): def get_queryset(self): return super().get_queryset().annotate( start_time=Lower('time_range'), end_time=Upper('time_range'), is_current=Case( When( end_time__isnull=True, then=True), default=False, output_field=BooleanField()), duration=Case( When(is_current=True, then=ExpressionWrapper(Now() - F('start_time'), output_field=DurationField())), default=ExpressionWrapper(F('end_time') - F('start_time'), output_field=DurationField())) )
def find_in_category_or_product(query): return Product.objects.select_related('category').annotate( category_name=F('category__name'), ).annotate( search=SearchVector('name') + SearchVector('category_name') ).filter(search=query) # FIND Pixel Phones that does not have TAB in it services.find_in_category_or_product( SearchQuery('pixel') & ~SearchQuery('tab')) # FIND ANYTHING WHERE 'phone' IS IN 'name' or 'category name' services.find_in_category_or_product('phone')
def fuzzy_name_finder(term, model=Product): return model.objects.annotate( similarity=TrigramSimilarity('name', term) ).filter(similarity__gt=0.1).order_by('-similarity') # SHOULD GIVE YOU PIXEL services.fuzzy_name_finder("pijsel")