Check accessor and reverse query name clashes.
(self)
| 235 | return [] |
| 236 | |
| 237 | def _check_clashes(self): |
| 238 | """Check accessor and reverse query name clashes.""" |
| 239 | from django.db.models.base import ModelBase |
| 240 | |
| 241 | errors = [] |
| 242 | opts = self.model._meta |
| 243 | |
| 244 | # f.remote_field.model may be a string instead of a model. Skip if |
| 245 | # model name is not resolved. |
| 246 | if not isinstance(self.remote_field.model, ModelBase): |
| 247 | return [] |
| 248 | |
| 249 | # Consider that we are checking field `Model.foreign` and the models |
| 250 | # are: |
| 251 | # |
| 252 | # class Target(models.Model): |
| 253 | # model = models.IntegerField() |
| 254 | # model_set = models.IntegerField() |
| 255 | # |
| 256 | # class Model(models.Model): |
| 257 | # foreign = models.ForeignKey(Target) |
| 258 | # m2m = models.ManyToManyField(Target) |
| 259 | |
| 260 | # rel_opts.object_name == "Target" |
| 261 | rel_opts = self.remote_field.model._meta |
| 262 | # If the field doesn't install a backward relation on the target model |
| 263 | # (so `is_hidden` returns True), then there are no clashes to check |
| 264 | # and we can skip these fields. |
| 265 | rel_is_hidden = self.remote_field.hidden |
| 266 | rel_name = self.remote_field.accessor_name # i. e. "model_set" |
| 267 | rel_query_name = self.related_query_name() # i. e. "model" |
| 268 | # i.e. "app_label.Model.field". |
| 269 | field_name = "%s.%s" % (opts.label, self.name) |
| 270 | |
| 271 | # Check clashes between accessor or reverse query name of `field` |
| 272 | # and any other field name -- i.e. accessor for Model.foreign is |
| 273 | # model_set and it clashes with Target.model_set. |
| 274 | potential_clashes = rel_opts.fields + rel_opts.many_to_many |
| 275 | for clash_field in potential_clashes: |
| 276 | if not rel_is_hidden and clash_field.name == rel_name: |
| 277 | clash_name = f"{rel_opts.label}.{clash_field.name}" |
| 278 | errors.append( |
| 279 | checks.Error( |
| 280 | f"Reverse accessor '{rel_opts.object_name}.{rel_name}' " |
| 281 | f"for '{field_name}' clashes with field name " |
| 282 | f"'{clash_name}'.", |
| 283 | hint=( |
| 284 | "Rename field '%s', or add/change a related_name " |
| 285 | "argument to the definition for field '%s'." |
| 286 | ) |
| 287 | % (clash_name, field_name), |
| 288 | obj=self, |
| 289 | id="fields.E302", |
| 290 | ) |
| 291 | ) |
| 292 | |
| 293 | if clash_field.name == rel_query_name: |
| 294 | clash_name = f"{rel_opts.label}.{clash_field.name}" |
no test coverage detected