Coverage for jutil/model.py : 86%

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
8from jutil.format import choices_label
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
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()
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))
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 val = force_text(value)
66 if val is None: 66 ↛ 67line 66 didn't jump to line 67, because the condition on line 66 was never true
67 val = ''
68 return label, val
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
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)]
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