Last active
February 1, 2024 08:28
-
-
Save code-yeongyu/375f95abb4eb0c3e3232a668611e092d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# License: MIT | |
from collections.abc import Callable | |
from functools import wraps | |
from typing import Any, TypeVar | |
from django.db import models | |
from django.db.models.fields import reverse_related | |
T = TypeVar("T", bound=models.Model) | |
def add_safe_accessors(cls: type[T]) -> type[T]: | |
""" | |
This class decorator enriches models with OneToOneField relationships by adding a 'safe_<fieldname>' attribute. | |
Normally, accessing a OneToOneField incurs a database query, | |
potentially raising a DoesNotExist exception if no related object is found. | |
The 'safe_<fieldname>' attribute mitigates this by safely accessing the field, | |
returning None instead of throwing an exception when the related object is absent. | |
Recommend to define fields like this: | |
``` | |
class User(models.Model): | |
... | |
safe_profile_image: models.OneToOneField[ProfileImage, Any] | None | |
``` | |
""" | |
original_init = cls.__init__ | |
@wraps(original_init) | |
def __init__(self, *args, **kwargs): | |
original_init(self, *args, **kwargs) | |
for field in self._meta.get_fields(): | |
if isinstance(field, reverse_related.OneToOneRel): | |
safe_field_name = f"safe_{field.name}" | |
def make_safe_accessor(field_name: str) -> Callable[[T], Any]: | |
def safe_accessor(self: T) -> Any: | |
return getattr(self, field_name, None) | |
return safe_accessor | |
setattr(cls, safe_field_name, property(make_safe_accessor(field.name))) | |
setattr(cls, "__init__", __init__) | |
return cls |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment