@@ -32,6 +32,21 @@ class Product(ormar.Model):
3232 category = ormar .ForeignKey (Category )
3333
3434
35+ class Supplier (ormar .Model ):
36+ ormar_config = base_ormar_config .copy (tablename = "suppliers" )
37+
38+ id : int = ormar .Integer (primary_key = True )
39+ name : str = ormar .String (max_length = 100 )
40+
41+
42+ class Item (ormar .Model ):
43+ ormar_config = base_ormar_config .copy (tablename = "items" )
44+
45+ id : int = ormar .Integer (primary_key = True )
46+ name : str = ormar .String (max_length = 100 )
47+ supplier = ormar .ForeignKey (Supplier , name = "supplier_id" )
48+
49+
3550create_test_database = init_tests (base_ormar_config )
3651
3752
@@ -62,8 +77,40 @@ def test_fields_access():
6277 assert curr_field ._access_chain == "categories__products__rating"
6378 assert curr_field ._source_model == PriceList
6479
80+ # FK accessor accepts the same operators as a regular field
81+ sample_category = Category (id = 7 , name = "x" )
82+ assert (Product .category == 3 )._kwargs_dict == {"category__exact" : 3 }
83+ assert (Product .category == sample_category )._kwargs_dict == {
84+ "category__exact" : sample_category
85+ }
86+ assert (Product .category >= 3 )._kwargs_dict == {"category__gte" : 3 }
87+ assert (Product .category <= 3 )._kwargs_dict == {"category__lte" : 3 }
88+ assert (Product .category > 3 )._kwargs_dict == {"category__gt" : 3 }
89+ assert (Product .category < 3 )._kwargs_dict == {"category__lt" : 3 }
90+ assert (Product .category << [1 , 2 ])._kwargs_dict == {"category__in" : [1 , 2 ]}
91+ assert Product .category .in_ ([1 , 2 ])._kwargs_dict == {"category__in" : [1 , 2 ]}
92+ assert (Product .category >> None )._kwargs_dict == {"category__isnull" : True }
93+ assert Product .category .isnull (False )._kwargs_dict == {"category__isnull" : False }
94+
95+ # FK accessor with an explicit db alias (name="supplier_id") still works
96+ # because the check keys on the ormar field registry, not on table.columns
97+ sample_supplier = Supplier (id = 9 , name = "acme" )
98+ assert (Item .supplier == 2 )._kwargs_dict == {"supplier__exact" : 2 }
99+ assert (Item .supplier == sample_supplier )._kwargs_dict == {
100+ "supplier__exact" : sample_supplier
101+ }
102+ assert (Item .supplier << [sample_supplier , 5 ])._kwargs_dict == {
103+ "supplier__in" : [sample_supplier , 5 ]
104+ }
105+ assert (Item .supplier >= 2 )._kwargs_dict == {"supplier__gte" : 2 }
106+
107+ # m2m accessor has no own column - comparison still raises
65108 with pytest .raises (AttributeError ):
66- assert Product .category >= 3
109+ assert Category .price_lists >= 3
110+
111+ # reverse FK accessor (virtual relation) - comparison still raises
112+ with pytest .raises (AttributeError ):
113+ assert Category .products >= 3
67114
68115
69116@pytest .mark .parametrize (
@@ -204,3 +251,48 @@ async def test_filtering_by_field_access():
204251
205252 check = await Product .objects .get (Product .name == "My Little Pony" )
206253 assert check == product2
254+
255+
256+ @pytest .mark .asyncio
257+ async def test_filtering_fk_by_field_access ():
258+ async with base_ormar_config .database :
259+ async with base_ormar_config .database .transaction (force_rollback = True ):
260+ toys = await Category (name = "Toys" ).save ()
261+ books = await Category (name = "Books" ).save ()
262+ pony = await Product (
263+ name = "My Little Pony" , rating = 3.8 , category = toys
264+ ).save ()
265+ await Product (name = "Novel" , rating = 4.2 , category = books ).save ()
266+
267+ # by scalar PK - should match kwargs form exactly
268+ via_accessor = await Product .objects .filter (
269+ Product .category == toys .pk
270+ ).all ()
271+ via_kwargs = await Product .objects .filter (category = toys .pk ).all ()
272+ assert {p .pk for p in via_accessor } == {pony .pk }
273+ assert {p .pk for p in via_accessor } == {p .pk for p in via_kwargs }
274+
275+ # by model instance
276+ via_instance = await Product .objects .filter (Product .category == toys ).all ()
277+ assert {p .pk for p in via_instance } == {pony .pk }
278+
279+ # `in_` / `<<` returns matches for several PKs
280+ all_products = await Product .objects .all ()
281+ via_in = await Product .objects .filter (
282+ Product .category << [toys .pk , books .pk ]
283+ ).all ()
284+ assert {p .pk for p in via_in } == {p .pk for p in all_products }
285+
286+ # aliased FK field (name="supplier_id")
287+ sup = await Supplier (name = "Acme" ).save ()
288+ other_sup = await Supplier (name = "Globex" ).save ()
289+ gadget = await Item (name = "gadget" , supplier = sup ).save ()
290+ await Item (name = "widget" , supplier = other_sup ).save ()
291+ via_aliased_pk = await Item .objects .filter (Item .supplier == sup .pk ).all ()
292+ via_aliased_instance = await Item .objects .filter (Item .supplier == sup ).all ()
293+ via_aliased_in = await Item .objects .filter (
294+ Item .supplier << [sup .pk , other_sup .pk ]
295+ ).all ()
296+ assert {i .pk for i in via_aliased_pk } == {gadget .pk }
297+ assert {i .pk for i in via_aliased_instance } == {gadget .pk }
298+ assert len (via_aliased_in ) == 2
0 commit comments