Coverage for netbox/models/mixins.py: 64%

48 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 18:35 +0000

1from django.core.exceptions import ValidationError 

2from django.db import models 

3from django.utils.translation import gettext_lazy as _ 

4 

5from netbox.choices import * 

6from utilities.conversion import to_grams, to_meters 

7 

8__all__ = ( 

9 'DistanceMixin', 

10 'OwnerMixin', 

11 'WeightMixin', 

12) 

13 

14 

15class OwnerMixin(models.Model): 

16 """ 

17 Adds a ForeignKey to users.Owner to indicate an object's owner. 

18 """ 

19 owner = models.ForeignKey( 

20 to='users.Owner', 

21 on_delete=models.PROTECT, 

22 related_name='+', 

23 blank=True, 

24 null=True 

25 ) 

26 

27 class Meta: 

28 abstract = True 

29 

30 

31class WeightMixin(models.Model): 

32 weight = models.DecimalField( 

33 verbose_name=_('weight'), 

34 max_digits=8, 

35 decimal_places=2, 

36 blank=True, 

37 null=True 

38 ) 

39 weight_unit = models.CharField( 

40 verbose_name=_('weight unit'), 

41 max_length=50, 

42 choices=WeightUnitChoices, 

43 blank=True, 

44 null=True, 

45 ) 

46 # Stores the normalized weight (in grams) for database ordering 

47 _abs_weight = models.PositiveBigIntegerField( 

48 blank=True, 

49 null=True 

50 ) 

51 

52 class Meta: 

53 abstract = True 

54 

55 @property 

56 def abs_weight(self): 

57 # Public alias for _abs_weight; Django templates cannot access underscore-prefixed attributes. 

58 return self._abs_weight 

59 

60 def save(self, *args, **kwargs): 

61 

62 # Store the given weight (if any) in grams for use in database ordering 

63 if self.weight and self.weight_unit: 63 ↛ 64line 63 didn't jump to line 64 because the condition on line 63 was never true

64 self._abs_weight = to_grams(self.weight, self.weight_unit) 

65 else: 

66 self._abs_weight = None 

67 

68 super().save(*args, **kwargs) 

69 

70 def clean(self): 

71 super().clean() 

72 

73 # Validate weight and weight_unit 

74 if self.weight and not self.weight_unit: 74 ↛ 75line 74 didn't jump to line 75 because the condition on line 74 was never true

75 raise ValidationError(_("Must specify a unit when setting a weight")) 

76 

77 

78class DistanceMixin(models.Model): 

79 distance = models.DecimalField( 

80 verbose_name=_('distance'), 

81 max_digits=8, 

82 decimal_places=2, 

83 blank=True, 

84 null=True, 

85 ) 

86 distance_unit = models.CharField( 

87 verbose_name=_('distance unit'), 

88 max_length=50, 

89 choices=DistanceUnitChoices, 

90 blank=True, 

91 null=True, 

92 ) 

93 # Stores the normalized distance (in meters) for database ordering 

94 _abs_distance = models.DecimalField( 

95 max_digits=13, 

96 decimal_places=4, 

97 blank=True, 

98 null=True 

99 ) 

100 

101 class Meta: 

102 abstract = True 

103 

104 @property 

105 def abs_distance(self): 

106 # Public alias for _abs_distance; Django templates cannot access underscore-prefixed attributes. 

107 return self._abs_distance 

108 

109 def save(self, *args, **kwargs): 

110 # Store the given distance (if any) in meters for use in database ordering 

111 if self.distance is not None and self.distance_unit: 

112 self._abs_distance = to_meters(self.distance, self.distance_unit) 

113 else: 

114 self._abs_distance = None 

115 

116 # Clear distance_unit if no distance is defined 

117 if self.distance is None: 

118 self.distance_unit = None 

119 

120 super().save(*args, **kwargs) 

121 

122 def clean(self): 

123 super().clean() 

124 

125 # Validate distance and distance_unit 

126 if self.distance and not self.distance_unit: 

127 raise ValidationError(_("Must specify a unit when setting a distance"))