| Total Complexity | 112 |
| Total Lines | 501 |
| Duplicated Lines | 0 % |
Complex classes like TranslatableModelMixin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | """ |
||
| 218 | class TranslatableModelMixin(object): |
||
| 219 | """ |
||
| 220 | Base model mixin class to handle translations. |
||
| 221 | |||
| 222 | All translatable fields will appear on this model, proxying the calls to the :class:`TranslatedFieldsModel`. |
||
| 223 | """ |
||
| 224 | #: Access to the metadata of the translatable model |
||
| 225 | _parler_meta = None |
||
| 226 | |||
| 227 | #: Access to the language code |
||
| 228 | language_code = LanguageCodeDescriptor() |
||
| 229 | |||
| 230 | def __init__(self, *args, **kwargs): |
||
| 231 | # Still allow to pass the translated fields (e.g. title=...) to this function. |
||
| 232 | translated_kwargs = {} |
||
| 233 | current_language = None |
||
| 234 | if kwargs: |
||
| 235 | current_language = kwargs.pop('_current_language', None) |
||
| 236 | for field in self._parler_meta.get_all_fields(): |
||
| 237 | try: |
||
| 238 | translated_kwargs[field] = kwargs.pop(field) |
||
| 239 | except KeyError: |
||
| 240 | pass |
||
| 241 | |||
| 242 | # Have the attributes available, but they can't be ready yet; |
||
| 243 | # self._state.adding is always True at this point, |
||
| 244 | # the QuerySet.iterator() code changes it after construction. |
||
| 245 | self._translations_cache = None |
||
| 246 | self._current_language = None |
||
| 247 | |||
| 248 | # Run original Django model __init__ |
||
| 249 | super(TranslatableModelMixin, self).__init__(*args, **kwargs) |
||
| 250 | |||
| 251 | # Assign translated args manually. |
||
| 252 | self._translations_cache = defaultdict(dict) |
||
| 253 | self._current_language = normalize_language_code(current_language or get_language()) # What you used to fetch the object is what you get. |
||
| 254 | |||
| 255 | if translated_kwargs: |
||
| 256 | self._set_translated_fields(self._current_language, **translated_kwargs) |
||
| 257 | |||
| 258 | def _set_translated_fields(self, language_code=None, **fields): |
||
| 259 | """ |
||
| 260 | Assign fields to the translated models. |
||
| 261 | """ |
||
| 262 | objects = [] # no generator, make sure objects are all filled first |
||
| 263 | for parler_meta, model_fields in self._parler_meta._split_fields(**fields): |
||
| 264 | translation = self._get_translated_model(language_code=language_code, auto_create=True, meta=parler_meta) |
||
| 265 | for field, value in six.iteritems(model_fields): |
||
| 266 | setattr(translation, field, value) |
||
| 267 | |||
| 268 | objects.append(translation) |
||
| 269 | return objects |
||
| 270 | |||
| 271 | def create_translation(self, language_code, **fields): |
||
| 272 | """ |
||
| 273 | Add a translation to the model. |
||
| 274 | |||
| 275 | The :func:`save_translations` function is called afterwards. |
||
| 276 | |||
| 277 | The object will be saved immediately, similar to |
||
| 278 | calling :func:`~django.db.models.manager.Manager.create` |
||
| 279 | or :func:`~django.db.models.fields.related.RelatedManager.create` on related fields. |
||
| 280 | """ |
||
| 281 | meta = self._parler_meta |
||
| 282 | if self._translations_cache[meta.root_model].get(language_code, None): # MISSING evaluates to False too |
||
| 283 | raise ValueError("Translation already exists: {0}".format(language_code)) |
||
| 284 | |||
| 285 | # Save all fields in the proper translated model. |
||
| 286 | for translation in self._set_translated_fields(language_code, **fields): |
||
| 287 | self.save_translation(translation) |
||
| 288 | |||
| 289 | def get_current_language(self): |
||
| 290 | """ |
||
| 291 | Get the current language. |
||
| 292 | """ |
||
| 293 | # not a property, so won't conflict with model fields. |
||
| 294 | return self._current_language |
||
| 295 | |||
| 296 | def set_current_language(self, language_code, initialize=False): |
||
| 297 | """ |
||
| 298 | Switch the currently activate language of the object. |
||
| 299 | """ |
||
| 300 | self._current_language = normalize_language_code(language_code or get_language()) |
||
| 301 | |||
| 302 | # Ensure the translation is present for __get__ queries. |
||
| 303 | if initialize: |
||
| 304 | self._get_translated_model(use_fallback=False, auto_create=True) |
||
| 305 | |||
| 306 | def get_fallback_language(self): |
||
| 307 | """ |
||
| 308 | .. deprecated:: 1.5 |
||
| 309 | Use :func:`get_fallback_languages` instead. |
||
| 310 | """ |
||
| 311 | fallbacks = self.get_fallback_languages() |
||
| 312 | return fallbacks[0] if fallbacks else None |
||
| 313 | |||
| 314 | def get_fallback_languages(self): |
||
| 315 | """ |
||
| 316 | Return the fallback language codes, |
||
| 317 | which are used in case there is no translation for the currently active language. |
||
| 318 | """ |
||
| 319 | lang_dict = get_language_settings(self._current_language) |
||
| 320 | fallbacks = [lang for lang in lang_dict['fallbacks'] if lang != self._current_language] |
||
| 321 | return fallbacks or [] |
||
| 322 | |||
| 323 | def has_translation(self, language_code=None, related_name=None): |
||
| 324 | """ |
||
| 325 | Return whether a translation for the given language exists. |
||
| 326 | Defaults to the current language code. |
||
| 327 | |||
| 328 | .. versionadded 1.2 Added the ``related_name`` parameter. |
||
| 329 | """ |
||
| 330 | if language_code is None: |
||
| 331 | language_code = self._current_language |
||
| 332 | |||
| 333 | meta = self._parler_meta._get_extension_by_related_name(related_name) |
||
| 334 | |||
| 335 | try: |
||
| 336 | # Check the local cache directly, and the answer is known. |
||
| 337 | # NOTE this may also return newly auto created translations which are not saved yet. |
||
| 338 | return self._translations_cache[meta.model][language_code] is not MISSING |
||
| 339 | except KeyError: |
||
| 340 | # If there is a prefetch, will be using that. |
||
| 341 | # However, don't assume the prefetch contains all possible languages. |
||
| 342 | # With Django 1.8, there are custom Prefetch objects. |
||
| 343 | # TODO: improve this, detect whether this is the case. |
||
| 344 | if language_code in self._read_prefetched_translations(meta=meta): |
||
| 345 | return True |
||
| 346 | |||
| 347 | # Try to fetch from the cache first. |
||
| 348 | # If the cache returns the fallback, it means the original does not exist. |
||
| 349 | object = get_cached_translation(self, language_code, related_name=related_name, use_fallback=True) |
||
| 350 | if object is not None: |
||
| 351 | return object.language_code == language_code |
||
| 352 | |||
| 353 | try: |
||
| 354 | # Fetch from DB, fill the cache. |
||
| 355 | self._get_translated_model(language_code, use_fallback=False, auto_create=False, meta=meta) |
||
| 356 | except meta.model.DoesNotExist: |
||
| 357 | return False |
||
| 358 | else: |
||
| 359 | return True |
||
| 360 | |||
| 361 | def get_available_languages(self, related_name=None, include_unsaved=False): |
||
| 362 | """ |
||
| 363 | Return the language codes of all translated variations. |
||
| 364 | |||
| 365 | .. versionadded 1.2 Added the ``include_unsaved`` and ``related_name`` parameters. |
||
| 366 | """ |
||
| 367 | meta = self._parler_meta._get_extension_by_related_name(related_name) |
||
| 368 | |||
| 369 | prefetch = self._get_prefetched_translations(meta=meta) |
||
| 370 | if prefetch is not None: |
||
| 371 | # TODO: this will break when using custom Django 1.8 Prefetch objects? |
||
| 372 | db_languages = sorted(obj.language_code for obj in prefetch) |
||
| 373 | else: |
||
| 374 | qs = self._get_translated_queryset(meta=meta) |
||
| 375 | db_languages = qs.values_list('language_code', flat=True).order_by('language_code') |
||
| 376 | |||
| 377 | if include_unsaved: |
||
| 378 | local_languages = (k for k, v in six.iteritems(self._translations_cache[meta.model]) if v is not MISSING) |
||
| 379 | return list(set(db_languages) | set(local_languages)) |
||
| 380 | else: |
||
| 381 | return db_languages |
||
| 382 | |||
| 383 | def get_translation(self, language_code, related_name=None): |
||
| 384 | """ |
||
| 385 | Fetch the translated model |
||
| 386 | """ |
||
| 387 | meta = self._parler_meta._get_extension_by_related_name(related_name) |
||
| 388 | return self._get_translated_model(language_code, meta=meta) |
||
| 389 | |||
| 390 | def _get_translated_model(self, language_code=None, use_fallback=False, auto_create=False, meta=None): |
||
| 391 | """ |
||
| 392 | Fetch the translated fields model. |
||
| 393 | """ |
||
| 394 | if self._parler_meta is None: |
||
| 395 | raise ImproperlyConfigured("No translation is assigned to the current model!") |
||
| 396 | if self._translations_cache is None: |
||
| 397 | raise RuntimeError("Accessing translated fields before super.__init__() is not possible.") |
||
| 398 | |||
| 399 | if not language_code: |
||
| 400 | language_code = self._current_language |
||
| 401 | if meta is None: |
||
| 402 | meta = self._parler_meta.root # work on base model by default |
||
| 403 | |||
| 404 | local_cache = self._translations_cache[meta.model] |
||
| 405 | |||
| 406 | # 1. fetch the object from the local cache |
||
| 407 | try: |
||
| 408 | object = local_cache[language_code] |
||
| 409 | |||
| 410 | # If cached object indicates the language doesn't exist, need to query the fallback. |
||
| 411 | if object is not MISSING: |
||
| 412 | return object |
||
| 413 | except KeyError: |
||
| 414 | # 2. No cache, need to query |
||
| 415 | # Check that this object already exists, would be pointless otherwise to check for a translation. |
||
| 416 | if not self._state.adding and self.pk is not None: |
||
| 417 | prefetch = self._get_prefetched_translations(meta=meta) |
||
| 418 | if prefetch is not None: |
||
| 419 | # 2.1, use prefetched data |
||
| 420 | # If the object is not found in the prefetched data (which contains all translations), |
||
| 421 | # it's pointless to check for memcached (2.2) or perform a single query (2.3) |
||
| 422 | for object in prefetch: |
||
| 423 | if object.language_code == language_code: |
||
| 424 | local_cache[language_code] = object |
||
| 425 | _cache_translation(object) # Store in memcached |
||
| 426 | return object |
||
| 427 | else: |
||
| 428 | # 2.2, fetch from memcached |
||
| 429 | object = get_cached_translation(self, language_code, related_name=meta.rel_name, use_fallback=use_fallback) |
||
| 430 | if object is not None: |
||
| 431 | # Track in local cache |
||
| 432 | if object.language_code != language_code: |
||
| 433 | local_cache[language_code] = MISSING # Set fallback marker |
||
| 434 | local_cache[object.language_code] = object |
||
| 435 | return object |
||
| 436 | elif local_cache.get(language_code, None) is MISSING: |
||
| 437 | # If get_cached_translation() explicitly set the "does not exist" marker, |
||
| 438 | # there is no need to try a database query. |
||
| 439 | pass |
||
| 440 | else: |
||
| 441 | # 2.3, fetch from database |
||
| 442 | try: |
||
| 443 | object = self._get_translated_queryset(meta).get(language_code=language_code) |
||
| 444 | except meta.model.DoesNotExist: |
||
| 445 | pass |
||
| 446 | else: |
||
| 447 | local_cache[language_code] = object |
||
| 448 | _cache_translation(object) # Store in memcached |
||
| 449 | return object |
||
| 450 | |||
| 451 | # Not in cache, or default. |
||
| 452 | # Not fetched from DB |
||
| 453 | |||
| 454 | # 3. Auto create? |
||
| 455 | if auto_create: |
||
| 456 | # Auto create policy first (e.g. a __set__ call) |
||
| 457 | kwargs = { |
||
| 458 | 'language_code': language_code, |
||
| 459 | } |
||
| 460 | if self.pk: |
||
| 461 | # ID might be None at this point, and Django 1.8 does not allow that. |
||
| 462 | kwargs['master'] = self |
||
| 463 | |||
| 464 | object = meta.model(**kwargs) |
||
| 465 | local_cache[language_code] = object |
||
| 466 | # Not stored in memcached here yet, first fill + save it. |
||
| 467 | return object |
||
| 468 | |||
| 469 | # 4. Fallback? |
||
| 470 | fallback_msg = None |
||
| 471 | lang_dict = get_language_settings(language_code) |
||
| 472 | |||
| 473 | if language_code not in local_cache: |
||
| 474 | # Explicitly set a marker for the fact that this translation uses the fallback instead. |
||
| 475 | # Avoid making that query again. |
||
| 476 | local_cache[language_code] = MISSING # None value is the marker. |
||
| 477 | if not self._state.adding or self.pk is not None: |
||
| 478 | _cache_translation_needs_fallback(self, language_code, related_name=meta.rel_name) |
||
| 479 | |||
| 480 | fallback_choices = [lang_dict['code']] + list(lang_dict['fallbacks']) |
||
| 481 | if use_fallback and fallback_choices: |
||
| 482 | # Jump to fallback language, return directly. |
||
| 483 | # Don't cache under this language_code |
||
| 484 | for fallback_lang in fallback_choices: |
||
| 485 | if fallback_lang == language_code: # Skip the current language, could also be fallback 1 of 2 choices |
||
| 486 | continue |
||
| 487 | |||
| 488 | try: |
||
| 489 | return self._get_translated_model(fallback_lang, use_fallback=False, auto_create=auto_create, meta=meta) |
||
| 490 | except meta.model.DoesNotExist: |
||
| 491 | pass |
||
| 492 | |||
| 493 | fallback_msg = " (tried fallbacks {0})".format(', '.join(lang_dict['fallbacks'])) |
||
| 494 | |||
| 495 | # None of the above, bail out! |
||
| 496 | raise meta.model.DoesNotExist( |
||
| 497 | "{0} does not have a translation for the current language!\n" |
||
| 498 | "{0} ID #{1}, language={2}{3}".format(self._meta.verbose_name, self.pk, language_code, fallback_msg or '' |
||
| 499 | )) |
||
| 500 | |||
| 501 | def _get_any_translated_model(self, meta=None): |
||
| 502 | """ |
||
| 503 | Return any available translation. |
||
| 504 | Returns None if there are no translations at all. |
||
| 505 | """ |
||
| 506 | if meta is None: |
||
| 507 | meta = self._parler_meta.root |
||
| 508 | |||
| 509 | tr_model = meta.model |
||
| 510 | local_cache = self._translations_cache[tr_model] |
||
| 511 | if local_cache: |
||
| 512 | # There is already a language available in the case. No need for queries. |
||
| 513 | # Give consistent answers if they exist. |
||
| 514 | check_languages = [self._current_language] + self.get_fallback_languages() |
||
| 515 | try: |
||
| 516 | for fallback_lang in check_languages: |
||
| 517 | trans = local_cache.get(fallback_lang, None) |
||
| 518 | if trans: |
||
| 519 | return trans |
||
| 520 | return next(t for t in six.itervalues(local_cache) if t is not MISSING) |
||
| 521 | except StopIteration: |
||
| 522 | pass |
||
| 523 | |||
| 524 | try: |
||
| 525 | # Use prefetch if available, otherwise perform separate query. |
||
| 526 | prefetch = self._get_prefetched_translations(meta=meta) |
||
| 527 | if prefetch is not None: |
||
| 528 | translation = prefetch[0] # Already a list |
||
| 529 | else: |
||
| 530 | translation = self._get_translated_queryset(meta=meta)[0] |
||
| 531 | except IndexError: |
||
| 532 | return None |
||
| 533 | else: |
||
| 534 | local_cache[translation.language_code] = translation |
||
| 535 | _cache_translation(translation) |
||
| 536 | return translation |
||
| 537 | |||
| 538 | def _get_translated_queryset(self, meta=None): |
||
| 539 | """ |
||
| 540 | Return the queryset that points to the translated model. |
||
| 541 | If there is a prefetch, it can be read from this queryset. |
||
| 542 | """ |
||
| 543 | # Get via self.TRANSLATIONS_FIELD.get(..) so it also uses the prefetch/select_related cache. |
||
| 544 | if meta is None: |
||
| 545 | meta = self._parler_meta.root |
||
| 546 | |||
| 547 | accessor = getattr(self, meta.rel_name) |
||
| 548 | if django.VERSION >= (1, 6): |
||
| 549 | # Call latest version |
||
| 550 | return accessor.get_queryset() |
||
| 551 | else: |
||
| 552 | # Must call RelatedManager.get_query_set() and avoid calling a custom get_queryset() |
||
| 553 | # method for packages with Django 1.6/1.7 compatibility. |
||
| 554 | return accessor.get_query_set() |
||
| 555 | |||
| 556 | def _get_prefetched_translations(self, meta=None): |
||
| 557 | """ |
||
| 558 | Return the queryset with prefetch results. |
||
| 559 | """ |
||
| 560 | if meta is None: |
||
| 561 | meta = self._parler_meta.root |
||
| 562 | |||
| 563 | related_name = meta.rel_name |
||
| 564 | try: |
||
| 565 | # Read the list directly, avoid QuerySet construction. |
||
| 566 | # Accessing self._get_translated_queryset(parler_meta)._prefetch_done is more expensive. |
||
| 567 | return self._prefetched_objects_cache[related_name] |
||
| 568 | except (AttributeError, KeyError): |
||
| 569 | return None |
||
| 570 | |||
| 571 | def _read_prefetched_translations(self, meta=None): |
||
| 572 | # Load the prefetched translations into the local cache. |
||
| 573 | if meta is None: |
||
| 574 | meta = self._parler_meta.root |
||
| 575 | |||
| 576 | local_cache = self._translations_cache[meta.model] |
||
| 577 | prefetch = self._get_prefetched_translations(meta=meta) |
||
| 578 | |||
| 579 | languages_seen = [] |
||
| 580 | if prefetch is not None: |
||
| 581 | for translation in prefetch: |
||
| 582 | lang = translation.language_code |
||
| 583 | languages_seen.append(lang) |
||
| 584 | if lang not in local_cache or local_cache[lang] is MISSING: |
||
| 585 | local_cache[lang] = translation |
||
| 586 | |||
| 587 | return languages_seen |
||
| 588 | |||
| 589 | def save(self, *args, **kwargs): |
||
| 590 | super(TranslatableModelMixin, self).save(*args, **kwargs) |
||
| 591 | |||
| 592 | # Makes no sense to add these for translated model |
||
| 593 | # Even worse: mptt 0.7 injects this parameter when it avoids updating the lft/rgt fields, |
||
| 594 | # but that misses all the translated fields. |
||
| 595 | kwargs.pop('update_fields', None) |
||
| 596 | self.save_translations(*args, **kwargs) |
||
| 597 | |||
| 598 | def delete(self, using=None): |
||
| 599 | _delete_cached_translations(self) |
||
| 600 | super(TranslatableModelMixin, self).delete(using) |
||
| 601 | |||
| 602 | def validate_unique(self, exclude=None): |
||
| 603 | """ |
||
| 604 | Also validate the unique_together of the translated model. |
||
| 605 | """ |
||
| 606 | # This is called from ModelForm._post_clean() or Model.full_clean() |
||
| 607 | errors = {} |
||
| 608 | try: |
||
| 609 | super(TranslatableModelMixin, self).validate_unique(exclude=exclude) |
||
| 610 | except ValidationError as e: |
||
| 611 | errors = e.message_dict # Django 1.5 + 1.6 compatible |
||
| 612 | |||
| 613 | for local_cache in six.itervalues(self._translations_cache): |
||
| 614 | for translation in six.itervalues(local_cache): |
||
| 615 | if translation is MISSING: # Skip fallback markers |
||
| 616 | continue |
||
| 617 | |||
| 618 | try: |
||
| 619 | translation.validate_unique(exclude=exclude) |
||
| 620 | except ValidationError as e: |
||
| 621 | errors.update(e.message_dict) |
||
| 622 | |||
| 623 | if errors: |
||
| 624 | raise ValidationError(errors) |
||
| 625 | |||
| 626 | def save_translations(self, *args, **kwargs): |
||
| 627 | """ |
||
| 628 | The method to save all translations. |
||
| 629 | This can be overwritten to implement any custom additions. |
||
| 630 | This method calls :func:`save_translation` for every fetched language. |
||
| 631 | |||
| 632 | :param args: Any custom arguments to pass to :func:`save`. |
||
| 633 | :param kwargs: Any custom arguments to pass to :func:`save`. |
||
| 634 | """ |
||
| 635 | # Copy cache, new objects (e.g. fallbacks) might be fetched if users override save_translation() |
||
| 636 | # Not looping over the cache, but using _parler_meta so the translations are processed in the order of inheritance. |
||
| 637 | local_caches = self._translations_cache.copy() |
||
| 638 | for meta in self._parler_meta: |
||
| 639 | local_cache = local_caches[meta.model] |
||
| 640 | translations = list(local_cache.values()) |
||
| 641 | |||
| 642 | # Save all translated objects which were fetched. |
||
| 643 | # This also supports switching languages several times, and save everything in the end. |
||
| 644 | for translation in translations: |
||
| 645 | if translation is MISSING: # Skip fallback markers |
||
| 646 | continue |
||
| 647 | |||
| 648 | self.save_translation(translation, *args, **kwargs) |
||
| 649 | |||
| 650 | def save_translation(self, translation, *args, **kwargs): |
||
| 651 | """ |
||
| 652 | Save the translation when it's modified, or unsaved. |
||
| 653 | |||
| 654 | .. note:: |
||
| 655 | |||
| 656 | When a derived model provides additional translated fields, |
||
| 657 | this method receives both the original and extended translation. |
||
| 658 | To distinguish between both objects, check for ``translation.related_name``. |
||
| 659 | |||
| 660 | :param translation: The translation |
||
| 661 | :type translation: TranslatedFieldsModel |
||
| 662 | :param args: Any custom arguments to pass to :func:`save`. |
||
| 663 | :param kwargs: Any custom arguments to pass to :func:`save`. |
||
| 664 | """ |
||
| 665 | if self.pk is None or self._state.adding: |
||
| 666 | raise RuntimeError("Can't save translations when the master object is not yet saved.") |
||
| 667 | |||
| 668 | # Translation models without any fields are also supported. |
||
| 669 | # This is useful for parent objects that have inlines; |
||
| 670 | # the parent object defines how many translations there are. |
||
| 671 | if translation.is_modified or (translation.is_empty and not translation.pk): |
||
| 672 | if not translation.master_id: # Might not exist during first construction |
||
| 673 | translation._state.db = self._state.db |
||
| 674 | translation.master = self |
||
| 675 | translation.save(*args, **kwargs) |
||
| 676 | |||
| 677 | def safe_translation_getter(self, field, default=None, language_code=None, any_language=False): |
||
| 678 | """ |
||
| 679 | Fetch a translated property, and return a default value |
||
| 680 | when both the translation and fallback language are missing. |
||
| 681 | |||
| 682 | When ``any_language=True`` is used, the function also looks |
||
| 683 | into other languages to find a suitable value. This feature can be useful |
||
| 684 | for "title" attributes for example, to make sure there is at least something being displayed. |
||
| 685 | Also consider using ``field = TranslatedField(any_language=True)`` in the model itself, |
||
| 686 | to make this behavior the default for the given field. |
||
| 687 | |||
| 688 | .. versionchanged 1.5:: The *default* parameter may also be a callable. |
||
| 689 | """ |
||
| 690 | meta = self._parler_meta._get_extension_by_field(field) |
||
| 691 | |||
| 692 | # Extra feature: query a single field from a other translation. |
||
| 693 | if language_code and language_code != self._current_language: |
||
| 694 | try: |
||
| 695 | tr_model = self._get_translated_model(language_code, meta=meta, use_fallback=True) |
||
| 696 | return getattr(tr_model, field) |
||
| 697 | except TranslationDoesNotExist: |
||
| 698 | pass |
||
| 699 | else: |
||
| 700 | # By default, query via descriptor (TranslatedFieldDescriptor) |
||
| 701 | # which also attempts the fallback language if configured to do so. |
||
| 702 | try: |
||
| 703 | return getattr(self, field) |
||
| 704 | except TranslationDoesNotExist: |
||
| 705 | pass |
||
| 706 | |||
| 707 | if any_language: |
||
| 708 | translation = self._get_any_translated_model(meta=meta) |
||
| 709 | if translation is not None: |
||
| 710 | try: |
||
| 711 | return getattr(translation, field) |
||
| 712 | except KeyError: |
||
| 713 | pass |
||
| 714 | |||
| 715 | if callable(default): |
||
| 716 | return default() |
||
| 717 | else: |
||
| 718 | return default |
||
| 719 | |||
| 1172 |