@@ -442,56 +442,6 @@ internally. Only the following combinations are allowed:
442442 - Any value not contained in `truthy` / `falsy` will raise a `ValidationError`
443443 when deserializing from FileMaker.
444444
445- # ## Converting a portal row to a layout model (`as_layout_model`)
446-
447- Sometimes you have a portal record and you want to work with it as if it were
448- coming from a dedicated layout (for example to reuse an existing model class ,
449- map fields with different Python names, or upload a container field that
450- belongs to that layout). `portal_record.as_layout_model(model_class)` lets you do exactly
451- this.
452-
453- Consider this portal model and a corresponding layout model:
454-
455- ```python
456- class AddressPortal(PortalModel):
457- class Meta:
458- table_occurrence = ADDRESS_PORTAL_TABLE_OCCURRENCE
459-
460- city = fmdata.String(field_name = f " { ADDRESS_PORTAL_TABLE_OCCURRENCE } ::City " , field_type = FMFieldType.Text, )
461- picture = fmdata.Container(field_name = f " { ADDRESS_PORTAL_TABLE_OCCURRENCE } ::Picture " , )
462-
463-
464- class AddressLayoutModel(Model):
465- class Meta:
466- layout = " address_layout" # a layout based on the same table
467-
468- # Note how the Python name can differ from the portal field name
469- the_city = fmdata.String(field_name = " City" , field_type = FMFieldType.Text)
470- picture = fmdata.Container(field_name = " Picture" , field_type = FMFieldType.Container)
471-
472-
473- # Given a portal record
474- address_portal_record = person.addresses.all()[0 ]
475- # Convert the portal row to the layout model
476- address_as_layout_record = address_portal_record.as_layout_model(model_class = AddressLayoutModel)
477- ```
478-
479- # ### Updating a portal container field
480-
481- The Data API does not support updating a portal container field directly. So we use `as_layout_model` to convert the
482- given portal record to a layout model record and then use `update_container()` on that:
483-
484- ```python
485- # Given a portal record
486- address_portal_record = person.addresses.all()[0 ]
487- # Convert the portal row to the layout model
488- address_as_layout_record = address_portal_record.as_layout_model(model_class = AddressLayoutModel)
489-
490- # Upload a file to the portal container field via the layout model
491- with open (" /path/to/file.pdf" , " rb" ) as file :
492- address_as_layout_record.update_container(" picture" , file )
493- ```
494-
495445# # Working with records
496446
497447# ## Model records
@@ -631,7 +581,7 @@ address.delete()
631581
632582# ### Create a portal record (without saving it to the database)
633583
634- Avoid it in FMS17 ! The `.save()` will not update the record_id of the record, so the next `.save() will create another
584+ Avoid it in FMS17 ! The `.save()` will not update the record_id of the record, so the next `.save()` will create another
635585record!
636586
637587```python
@@ -717,9 +667,8 @@ arguments to control **what** is written and **how** the Data API call behaves:
717667>
718668> - `update_fields` is a list of field names that restricts ** which fields are allowed to be written** .
719669> - It works ** together** with `only_updated_fields` :
720- >
721- - First, we compute the set of fields that would normally be updated (either only the updated ones, or all of them
722- > if `only_updated_fields=False ` ).
670+ > - First, we compute the set of fields that would normally be updated (either only the updated ones, or all of them
671+ > if `only_updated_fields=False ` ).
723672> - Then, we intersect that set with `update_fields` .
724673> - The default is `None ` , which means “no extra restriction” (all candidate fields are written).
725674
@@ -890,17 +839,26 @@ chunked iteration.
890839You can filter records using simple keyword arguments, field lookups and also raw criteria:
891840
892841```python
893- # Basic equality
842+ # Basic equality (exact match)
894843people = Person.objects.find(name = " Alice" )
895844
845+ # String operators (__startswith, __endswith, __contains)
846+ people = Person.objects.find(name__contains = " Alice" )
847+ people = Person.objects.find(name__startswith = " Alice" )
848+ people = Person.objects.find(name__endswith = " Smith" )
849+
896850# Comparison operators (__gt, __gte, __lt, __lte)
897851people = Person.objects.find(birth_date__gt = date(1990 , 1 , 1 ))
898852
853+ # Range operator (__range)
854+ people = Person.objects.find(age__range = (18 , 30 ))
855+ people = Person.objects.find(birth_date__range = (date(1990 , 1 , 1 ), date(2000 , 12 , 31 )))
856+
899857# Multiple criteria are AND‑ed by default
900858people = Person.objects.find(name = " Alice" , is_active = True )
901859
902860# Raw criteria (using FileMaker's own query syntax)
903- people = Person.objects.find(name__raw = " Alice*" , last_name__raw = " Sm*" )
861+ people = Person.objects.find(name__raw = " Alice*" , last_name__raw = " * Sm*" )
904862```
905863
906864Behind the scenes each call to `.find()` generates one or more ** Find
@@ -1066,6 +1024,58 @@ for addresses in person.addresses.all().chunked(1000):
10661024> For fully consistent snapshots, avoid concurrent modifications while iterating, or load all data at once if the
10671025> dataset is small enough.
10681026
1027+ # # Model utilities
1028+ # ## Converting a portal row to a layout model (`as_layout_model`)
1029+
1030+ Sometimes you have a portal record and you want to work with it as if it were
1031+ coming from a dedicated layout (for example to reuse an existing model class ,
1032+ map fields with different Python names, or upload a container field that
1033+ belongs to that layout). `portal_record.as_layout_model(model_class)` lets you do exactly
1034+ this.
1035+
1036+ Consider this portal model and a corresponding layout model:
1037+
1038+ ```python
1039+ class AddressPortal(PortalModel):
1040+ class Meta:
1041+ table_occurrence = ADDRESS_PORTAL_TABLE_OCCURRENCE
1042+
1043+ city = fmdata.String(field_name = f " { ADDRESS_PORTAL_TABLE_OCCURRENCE } ::City " , field_type = FMFieldType.Text, )
1044+ picture = fmdata.Container(field_name = f " { ADDRESS_PORTAL_TABLE_OCCURRENCE } ::Picture " , )
1045+
1046+
1047+ class AddressLayoutModel(Model):
1048+ class Meta:
1049+ layout = " address_layout" # a layout based on the same table
1050+
1051+ # Note how the Python name can differ from the portal field name
1052+ the_city = fmdata.String(field_name = " City" , field_type = FMFieldType.Text)
1053+ picture = fmdata.Container(field_name = " Picture" , field_type = FMFieldType.Container)
1054+
1055+
1056+ # Given a portal record
1057+ address_portal_record = person.addresses.all()[0 ]
1058+ # Convert the portal row to the layout model
1059+ address_as_layout_record = address_portal_record.as_layout_model(model_class = AddressLayoutModel)
1060+ ```
1061+
1062+ # ### Updating a portal container field
1063+
1064+ The Data API does not support updating a portal container field directly. So we use `as_layout_model` to convert the
1065+ given portal record to a layout model record and then use `update_container()` on that:
1066+
1067+ ```python
1068+ # Given a portal record
1069+ address_portal_record = person.addresses.all()[0 ]
1070+ # Convert the portal row to the layout model
1071+ address_as_layout_record = address_portal_record.as_layout_model(model_class = AddressLayoutModel)
1072+
1073+ # Upload a file to the portal container field via the layout model
1074+ with open (" /path/to/file.pdf" , " rb" ) as file :
1075+ address_as_layout_record.update_container(" picture" , file )
1076+ ```
1077+
1078+
10691079# # Low-Level API Access
10701080
10711081For direct FileMaker Data API access:
0 commit comments