F() can be used to reference a model field, field of a related model, annotation or an attribute of a JSON field. It can be used in the following ways: - as a field reference, e.g. F("id") - as a related field reference, e.g. F("related_field__field") will return the value of the f
| 111 | |
| 112 | |
| 113 | class F(Expression): |
| 114 | """ |
| 115 | F() can be used to reference a model field, field of a related model, annotation or |
| 116 | an attribute of a JSON field. It can be used in the following ways: |
| 117 | |
| 118 | - as a field reference, e.g. F("id") |
| 119 | - as a related field reference, e.g. F("related_field__field") will return the value of the field |
| 120 | of the related model. |
| 121 | - as a JSON field reference, e.g. F("json_field__attribute") will return the value of the "attribute" |
| 122 | property of the JSON field value. The reference can be nested, e.g. F("json_field__attribute__subattribute") |
| 123 | - as a JSON field array element reference, e.g. F("json_field__0") will return the first element of the array. |
| 124 | |
| 125 | :param name: The name of the field to reference. |
| 126 | """ |
| 127 | |
| 128 | def __init__(self, name: str) -> None: |
| 129 | self.name = name |
| 130 | |
| 131 | def resolve(self, resolve_context: ResolveContext) -> ResolveResult: |
| 132 | term: Term |
| 133 | joins: list[TableCriterionTuple] = [] |
| 134 | output_field = None |
| 135 | |
| 136 | main_name_part, __, rest_name_parts = self.name.partition("__") |
| 137 | if main_name_part in resolve_context.model._meta.fetch_fields: |
| 138 | # field in the format of "related_field__field" or "related_field__another_rel_field__field" |
| 139 | term, joins, output_field = resolve_nested_field( |
| 140 | resolve_context.model, resolve_context.table, self.name |
| 141 | ) |
| 142 | elif ( |
| 143 | rest_name_parts |
| 144 | and main_name_part in resolve_context.model._meta.fields_map |
| 145 | and isinstance(resolve_context.model._meta.fields_map[main_name_part], JSONField) |
| 146 | ): |
| 147 | # Accessing a JSON field, e.g. F("json_field__a__b") |
| 148 | key_parts = [ |
| 149 | int(item) if item.isdigit() else str(item) for item in rest_name_parts.split("__") |
| 150 | ] |
| 151 | term = resolve_field_json_path( |
| 152 | PypikaField(resolve_context.model._meta.fields_db_projection[main_name_part]), |
| 153 | key_parts, |
| 154 | ) |
| 155 | elif self.name in resolve_context.annotations: |
| 156 | # reference to another annotation, e.g. M.annotate(f1=...).annotate(f2=F("f1")).values('field') |
| 157 | annotation = resolve_context.annotations[self.name] |
| 158 | if isinstance(annotation, Term): |
| 159 | term = annotation |
| 160 | else: |
| 161 | term = annotation.resolve(resolve_context).term |
| 162 | else: |
| 163 | # a regular model field, e.g. F("id") |
| 164 | try: |
| 165 | meta = resolve_context.model._meta |
| 166 | term = PypikaField(meta.fields_db_projection[self.name]) |
| 167 | |
| 168 | if (output_field := meta.fields_map.get(self.name, None)) and ( |
| 169 | func := output_field.get_for_dialect( |
| 170 | meta.db.capabilities.dialect, "function_cast" |
no outgoing calls