Skip to content

Python style filter and order_by with field chain access

Compare
Choose a tag to compare
@collerek collerek released this 21 Apr 09:36
· 863 commits to master since this release
0fcdcbd

0.10.4

✨ Features

  • Add Python style to filter and order_by with field access instead of dunder separated strings. #51
    • Accessing a field with attribute access (chain of dot notation) can be used to construct FilterGroups (ormar.and_ and ormar.or_)
    • Field access overloads set of python operators and provide a set of functions to allow same functionality as with dunder separated param names in **kwargs, that means that querying from sample model Track related to model Album now you have more options:
      • exact - exact match to value, sql column = <VALUE>
        • OLD: album__name__exact='Malibu'
        • NEW: can be also written as Track.album.name == 'Malibu
      • iexact - exact match sql column = <VALUE> (case insensitive)
        • OLD: album__name__iexact='malibu'
        • NEW: can be also written as Track.album.name.iexact('malibu')
      • contains - sql column LIKE '%<VALUE>%'
        • OLD: album__name__contains='Mal'
        • NEW: can be also written as Track.album.name % 'Mal')
        • NEW: can be also written as Track.album.name.contains('Mal')
      • icontains - sql column LIKE '%<VALUE>%' (case insensitive)
        • OLD: album__name__icontains='mal'
        • NEW: can be also written as Track.album.name.icontains('mal')
      • in - sql column IN (<VALUE1>, <VALUE2>, ...)
        • OLD: album__name__in=['Malibu', 'Barclay']
        • NEW: can be also written as Track.album.name << ['Malibu', 'Barclay']
        • NEW: can be also written as Track.album.name.in_(['Malibu', 'Barclay'])
      • isnull - sql column IS NULL (and sql column IS NOT NULL)
        • OLD: album__name__isnull=True (isnotnull album__name__isnull=False)
        • NEW: can be also written as Track.album.name >> None
        • NEW: can be also written as Track.album.name.is_null(True)
        • NEW: not null can be also written as Track.album.name.is_null(False)
        • NEW: not null can be also written as ~(Track.album.name >> None)
        • NEW: not null can be also written as ~(Track.album.name.is_null(True))
      • gt - sql column > <VALUE> (greater than)
        • OLD: position__gt=3
        • NEW: can be also written as Track.album.name > 3
      • gte - sql column >= <VALUE> (greater or equal than)
        • OLD: position__gte=3
        • NEW: can be also written as Track.album.name >= 3
      • lt - sql column < <VALUE> (lower than)
        • OLD: position__lt=3
        • NEW: can be also written as Track.album.name < 3
      • lte - sql column <= <VALUE> (lower equal than)
        • OLD: position__lte=3
        • NEW: can be also written as Track.album.name <= 3
      • startswith - sql column LIKE '<VALUE>%' (exact start match)
        • OLD: album__name__startswith='Mal'
        • NEW: can be also written as Track.album.name.startswith('Mal')
      • istartswith - sql column LIKE '<VALUE>%' (case insensitive)
        • OLD: album__name__istartswith='mal'
        • NEW: can be also written as Track.album.name.istartswith('mal')
      • endswith - sql column LIKE '%<VALUE>' (exact end match)
        • OLD: album__name__endswith='ibu'
        • NEW: can be also written as Track.album.name.endswith('ibu')
      • iendswith - sql column LIKE '%<VALUE>' (case insensitive)
        • OLD: album__name__iendswith='IBU'
        • NEW: can be also written as Track.album.name.iendswith('IBU')
  • You can provide FilterGroups not only in filter() and exclude() but also in:
    • get()
    • get_or_none()
    • get_or_create()
    • first()
    • all()
    • delete()
  • With FilterGroups (ormar.and_ and ormar.or_) you can now use:
    • & - as and_ instead of next level of nesting
    • | - as `or_' instead of next level of nesting
    • ~ - as negation of the filter group
  • To combine groups of filters into one set of conditions use & (sql AND) and | (sql OR)
    # Following queries are equivalent:
    # sql: ( product.name = 'Test'  AND  product.rating >= 3.0 ) 
    
    # ormar OPTION 1 - OLD one
    Product.objects.filter(name='Test', rating__gte=3.0).get()
    
    # ormar OPTION 2 - OLD one
    Product.objects.filter(ormar.and_(name='Test', rating__gte=3.0)).get()
    
    # ormar OPTION 3 - NEW one (field access)
    Product.objects.filter((Product.name == 'Test') & (Product.rating >=3.0)).get()
  • Same applies to nested complicated filters
    # Following queries are equivalent:
    # sql: ( product.name = 'Test' AND product.rating >= 3.0 ) 
    #       OR (categories.name IN ('Toys', 'Books'))
    
    # ormar OPTION 1 - OLD one
    Product.objects.filter(ormar.or_(
                              ormar.and_(name='Test', rating__gte=3.0), 
                              categories__name__in=['Toys', 'Books'])
                          ).get()
    
    # ormar OPTION 2 - NEW one (instead of nested or use `|`)
    Product.objects.filter(
                          ormar.and_(name='Test', rating__gte=3.0) | 
                          ormar.and_(categories__name__in=['Toys', 'Books'])
                          ).get()
    
    # ormar OPTION 3 - NEW one (field access)
    Product.objects.filter(
                          ((Product.name='Test') & (Product.rating >= 3.0)) | 
                          (Product.categories.name << ['Toys', 'Books'])
                          ).get()
  • Now you can also use field access to provide OrderActions to order_by()
    • Order ascending:
      • OLD: Product.objects.order_by("name").all()
      • NEW: Product.objects.order_by(Product.name.asc()).all()
    • Order descending:
      • OLD: Product.objects.order_by("-name").all()
      • NEW: Product.objects.order_by(Product.name.desc()).all()
    • You can of course also combine different models and many order_bys:
      Product.objects.order_by([Product.category.name.asc(), Product.name.desc()]).all()

🐛 Fixes

  • Not really a bug but rather inconsistency. Providing a filter with nested model i.e. album__category__name = 'AA'
    is checking if album and category models are included in select_related() and if not it's auto-adding them there.
    The same functionality was not working for FilterGroups (and_ and or_), now it works (also for python style filters which return FilterGroups).