Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/migrations/utils.py: 62%

1 import datetime

2 import re

3 from collections import namedtuple

4

5 from plain.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT

6

7 FieldReference = namedtuple("FieldReference", "to through")

8

9 COMPILED_REGEX_TYPE = type(re.compile(""))

10

11

12 class RegexObject:

13 def __init__(self, obj):

14 self.pattern = obj.pattern

15 self.flags = obj.flags

16

17 def __eq__(self, other):

18 if not isinstance(other, RegexObject):

19 return NotImplemented

20 return self.pattern == other.pattern and self.flags == other.flags

21

22

23 def get_migration_name_timestamp():

24 return datetime.datetime.now().strftime("%Y%m%d_%H%M")

25

26

27 def resolve_relation(model, package_label=None, model_name=None):

28 """

29 Turn a model class or model reference string and return a model tuple.

30

31 package_label and model_name are used to resolve the scope of recursive and

32 unscoped model relationship.

33 """

34 if isinstance(model, str):

35 if model == RECURSIVE_RELATIONSHIP_CONSTANT:

36 if package_label is None or model_name is None:

37 raise TypeError(

38 "package_label and model_name must be provided to resolve "

39 "recursive relationships."

40 )

41 return package_label, model_name

42 if "." in model:

43 package_label, model_name = model.split(".", 1)

44 return package_label, model_name.lower()

45 if package_label is None:

46 raise TypeError(

47 "package_label must be provided to resolve unscoped model relationships."

48 )

49 return package_label, model.lower()

50 return model._meta.package_label, model._meta.model_name

51

52

53 def field_references(

54 model_tuple,

55 field,

56 reference_model_tuple,

57 reference_field_name=None,

58 reference_field=None,

59):

60 """

61 Return either False or a FieldReference if `field` references provided

62 context.

63

64 False positives can be returned if `reference_field_name` is provided

65 without `reference_field` because of the introspection limitation it

66 incurs. This should not be an issue when this function is used to determine

67 whether or not an optimization can take place.

68 """

69 remote_field = field.remote_field

70 if not remote_field:

71 return False

72 references_to = None

73 references_through = None

74 if resolve_relation(remote_field.model, *model_tuple) == reference_model_tuple:

75 to_fields = getattr(field, "to_fields", None)

76 if (

77 reference_field_name is None

78 or

79 # Unspecified to_field(s).

80 to_fields is None

81 or

82 # Reference to primary key.

83 (

84 None in to_fields

85 and (reference_field is None or reference_field.primary_key)

86 )

87 or

88 # Reference to field.

89 reference_field_name in to_fields

90 ):

91 references_to = (remote_field, to_fields)

92 through = getattr(remote_field, "through", None)

93 if through and resolve_relation(through, *model_tuple) == reference_model_tuple:

94 through_fields = remote_field.through_fields

95 if (

96 reference_field_name is None

97 or

98 # Unspecified through_fields.

99 through_fields is None

100 or

101 # Reference to field.

102 reference_field_name in through_fields

103 ):

104 references_through = (remote_field, through_fields)

105 if not (references_to or references_through):

106 return False

107 return FieldReference(references_to, references_through)

108

109

110 def get_references(state, model_tuple, field_tuple=()):

111 """

112 Generator of (model_state, name, field, reference) referencing

113 provided context.

114

115 If field_tuple is provided only references to this particular field of

116 model_tuple will be generated.

117 """

118 for state_model_tuple, model_state in state.models.items():

119 for name, field in model_state.fields.items():

120 reference = field_references(

121 state_model_tuple, field, model_tuple, *field_tuple

122 )

123 if reference:

124 yield model_state, name, field, reference

125

126

127 def field_is_referenced(state, model_tuple, field_tuple):

128 """Return whether `field_tuple` is referenced by any state models."""

129 return next(get_references(state, model_tuple, field_tuple), None) is not None