Plain is headed towards 1.0! Subscribe for development updates →

  1import datetime
  2import re
  3from collections import namedtuple
  4
  5from plain.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
  6
  7FieldReference = namedtuple("FieldReference", "to through")
  8
  9COMPILED_REGEX_TYPE = type(re.compile(""))
 10
 11
 12class 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
 23def get_migration_name_timestamp():
 24    return datetime.datetime.now().strftime("%Y%m%d_%H%M")
 25
 26
 27def 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
 53def 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
110def 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
127def 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