Hide keyboard shortcuts

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

1from datetime import timedelta, datetime 

2from time import sleep 

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

4from django.db.models import Model 

5from django.utils.encoding import force_text 

6from django.utils.timezone import now 

7 

8from jutil.format import choices_label 

9 

10 

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

12 """ 

13 Returns model instance or None if not found. 

14 :param cls: Class or queryset 

15 :param kwargs: Filters for get() call 

16 :return: Object or None 

17 """ 

18 try: 

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

20 return qs.get(**kwargs) 

21 except Exception: 

22 return None 

23 

24 

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

26 """ 

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

28 Waits timeout before returning if no object available. 

29 Waiting is done by sleeping specified intervals. 

30 :param cls: Class or queryset 

31 :param timeout: Timeout in seconds 

32 :param sleep_interval: Sleep interval in seconds 

33 :param kwargs: Filters for get() call 

34 :return: Object or None 

35 """ 

36 t0: Optional[datetime] = None 

37 t1: Optional[datetime] = None 

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

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

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

41 if obj is not None: 

42 return obj 

43 t0 = now() 

44 if t1 is None: 

45 t1 = t0 + timedelta(seconds=timeout) 

46 sleep(sleep_interval) 

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

48 

49 

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

51 """ 

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

53 :param instance: Model instance 

54 :param field_name: Model attribute name 

55 :return: (label, value) tuple 

56 """ 

57 label = field_name 

58 value = str(getattr(instance, field_name)) if hasattr(instance, field_name) else None 

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

60 if f.attname == field_name: 

61 label = f.verbose_name 

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

63 value = choices_label(f.choices, value) 

64 break 

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

66 value = '' 

67 val = force_text(value) 

68 return label, val 

69 

70 

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

72 """ 

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

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

75 :param instance: Model instance 

76 :param field_name: Model attribute name 

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

78 """ 

79 assert hasattr(instance, field_name) 

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

81 return False 

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

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

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

85 

86 

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

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

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

90 cls = instance.__class__ 

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

92 

93 

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

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

96 """ 

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

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

99 :param instance: Instance to copy 

100 :param cls: Class (opt) 

101 :param commit: Save or not 

102 :param exclude_fields: List of fields to exclude 

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

104 :return: New instance 

105 """ 

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

107 cls = instance.__class__ 

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

109 new_instance = cls() 

110 for k in keys: 

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

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

113 setattr(new_instance, k, v) 

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

115 new_instance.save() 

116 return new_instance