Coverage for jutil/model.py: 86%

# Coverage for jutil/model.py : 86%

## 62 statements

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1 from datetime import timedelta, datetime

2 from time import sleep

3 from typing import Type, List, Tuple, Any, Optional

4 from django.db.models import Model

5 from django.utils.encoding import force_text

6 from django.utils.timezone import now

7 from jutil.dict import choices_label

8

9

10 def get_object_or_none(cls: Any, **kwargs) -> Any:

11 """

12 Returns model instance or None if not found.

13 :param cls: Class or queryset

14 :param kwargs: Filters for get() call

15 :return: Object or None

16 """

17 try:

18 qs = cls._default_manager.all() if hasattr(cls, '_default_manager') else cls # pylint: disable=protected-access

19 return qs.get(**kwargs)

20 except Exception:

21 return None

22

23

24 def wait_object_or_none(cls: Any, timeout: float = 5.0, sleep_interval: float = 1.0, **kwargs) -> Any:

25 """

26 Returns model instance or None if not found after specified timeout.

27 Waits timeout before returning if no object available.

28 Waiting is done by sleeping specified intervals.

29 :param cls: Class or queryset

30 :param timeout: Timeout in seconds

31 :param sleep_interval: Sleep interval in seconds

32 :param kwargs: Filters for get() call

33 :return: Object or None

34 """

35 t0: Optional[datetime] = None

36 t1: Optional[datetime] = None

37 qs0 = cls._default_manager if hasattr(cls, '_default_manager') else cls # pylint: disable=protected-access

38 while t0 is None or t0 < t1: # type: ignore

39 obj = qs0.all().filter(**kwargs).first()

40 if obj is not None:

41 return obj

42 t0 = now()

43 if t1 is None:

44 t1 = t0 + timedelta(seconds=timeout)

45 sleep(sleep_interval)

46 return qs0.all().filter(**kwargs).first()

47

48

49 def get_model_field_label_and_value(instance, field_name: str) -> Tuple[str, str]:

50 """

51 Returns model field label and value (as text).

52 :param instance: Model instance

53 :param field_name: Model attribute name

54 :return: (label, value) tuple

55 """

56 label = field_name

57 value = str(getattr(instance, field_name))

58 for f in instance._meta.fields: 58 ↛ 64line 58 didn't jump to line 64, because the loop on line 58 didn't complete

59 if f.attname == field_name:

60 label = f.verbose_name

61 if hasattr(f, 'choices') and f.choices: 61 ↛ 62line 61 didn't jump to line 62, because the condition on line 61 was never true

62 value = choices_label(f.choices, value)

63 break

64 val = force_text(value)

65 if val is None: 65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true

66 val = ''

67 return label, val

68

69

70 def is_model_field_changed(instance, field_name: str) -> bool:

71 """

72 Compares model instance field value to value stored in DB.

73 If object has not been stored in DB (yet) field is considered unchanged.

74 :param instance: Model instance

75 :param field_name: Model attribute name

76 :return: True if field value has been changed compared to value stored in DB.

77 """

78 assert hasattr(instance, field_name)

79 if not hasattr(instance, 'pk') or instance.pk is None: 79 ↛ 80line 79 didn't jump to line 80, because the condition on line 79 was never true

80 return False

81 qs = instance.__class__.objects.all()

82 params = {'pk': instance.pk, field_name: getattr(instance, field_name)}

83 return qs.filter(**params).first() is None

84

85

86 def get_model_keys(instance, cls: Optional[Type[Model]] = None,

87 exclude_fields: tuple = ('id',), base_class_suffix: str = '_ptr') -> List[str]:

88 if cls is None: 88 ↛ 89line 88 didn't jump to line 89, because the condition on line 88 was never true

89 cls = instance.__class__

90 return [f.name for f in cls._meta.fields if f.name not in exclude_fields and not f.name.endswith(base_class_suffix)]

91

92

93 def clone_model(instance, cls: Optional[Type[Model]] = None, commit: bool = True,

94 exclude_fields: tuple = ('id',), base_class_suffix: str = '_ptr', **kw):

95 """

96 Assigns model fields to new object. Ignores exclude_fields list and

97 attributes ending with pointer suffix (default '_ptr')

98 :param instance: Instance to copy

99 :param cls: Class (opt)

100 :param commit: Save or not

101 :param exclude_fields: List of fields to exclude

102 :param base_class_suffix: End of name for base class pointers, e.g. model Car(Vehicle) has implicit vehicle_ptr

103 :return: New instance

104 """

105 if cls is None: 105 ↛ 107line 105 didn't jump to line 107, because the condition on line 105 was never false

106 cls = instance.__class__

107 keys = get_model_keys(instance, cls=cls, exclude_fields=exclude_fields, base_class_suffix=base_class_suffix)

108 new_instance = cls()

109 for k in keys:

110 setattr(new_instance, k, getattr(instance, k))

111 for k, v in kw.items(): 111 ↛ 112line 111 didn't jump to line 112, because the loop on line 111 never started

112 setattr(new_instance, k, v)

113 if commit: 113 ↛ 115line 113 didn't jump to line 115, because the condition on line 113 was never false

114 new_instance.save()

115 return new_instance