import inspect from orchestra.apps.accounts.models import Account def get_related_objects(origin, max_depth=2): """ Introspects origin object and return the first related service object WARNING this is NOT an exhaustive search but a compromise between cost and flexibility. A more comprehensive approach may be considered if a use-case calls for it. """ def related_iterator(node): for field in node._meta.virtual_fields: if hasattr(field, 'ct_field'): yield getattr(node, field.name) for field in node._meta.fields: if field.rel: yield getattr(node, field.name) # BFS model relation transversal queue = [[origin]] while queue: models = queue.pop(0) if len(models) > max_depth: return None node = models[-1] if len(models) > 1: if hasattr(node, 'account') or isinstance(node, Account): return node for related in related_iterator(node): if related and related not in models: new_models = list(models) new_models.append(related) queue.append(new_models) def get_register_or_cancel_events(porders, order, ini, end): assert ini <= end, "ini > end" CANCEL = 'cancel' REGISTER = 'register' changes = {} counter = 0 for num, porder in enumerate(porders.order_by('registered_on'), start=1): if porder == order: position = num if porder.cancelled_on: cancel = porder.cancelled_on if porder.billed_until and porder.cancelled_on < porder.billed_until: cancel = porder.billed_until if cancel > ini and cancel < end: changes.setdefault(cancel, []) changes[cancel].append((CANCEL, num)) if porder.registered_on <= ini: counter += 1 elif porder.registered_on < end: changes.setdefault(porder.registered_on, []) changes[porder.registered_on].append((REGISTER, num)) pointer = ini total = float((end-ini).days) for date in sorted(changes.keys()): yield counter, position, (date-pointer).days/total for change, num in changes[date]: if change is CANCEL: counter -= 1 if num < position: position -= 1 else: counter += 1 pointer = date yield counter, position, (end-pointer).days/total def get_register_or_renew_events(handler, porders, order, ini, end): total = float((end-ini).days) for sini, send in handler.get_pricing_slots(ini, end): counter = 0 position = -1 for porder in porders.order_by('registered_on'): if porder == order: position = abs(position) elif position < 0: position -= 1 if porder.registered_on >= sini and porder.registered_on < send: counter += 1 elif porder.billed_until > send or porder.cancelled_on > send: counter += 1 yield counter, position, (send-sini)/total def cmp_billed_until_or_registered_on(a, b): """ 1) billed_until greater first 2) registered_on smaller first """ if a.billed_until == b.billed_until: return (a.registered_on-b.registered_on).days elif a.billed_until and b.billed_until: return (b.billed_until-a.billed_until).days elif a.billed_until: return (b.registered_on-a.billed_until).days return (b.billed_until-a.registered_on).days class Interval(object): def __init__(self, ini, end, order=None): self.ini = ini self.end = end self.order = order def __len__(self): return max((self.end-self.ini).days, 0) def __sub__(self, other): remaining = [] if self.ini < other.ini: remaining.append(Interval(self.ini, min(self.end, other.ini))) if self.end > other.end: remaining.append(Interval(max(self.ini,other.end), self.end)) return remaining def __repr__(self): return "Start: %s End: %s" % (self.ini, self.end) def intersect(self, other, remaining_self=None, remaining_other=None): if remaining_self is not None: remaining_self += (self - other) if remaining_other is not None: remaining_other += (other - self) result = Interval(max(self.ini, other.ini), min(self.end, other.end)) if len(result)>0: return result else: return None def get_intersections(order, compensations): intersections = [] for compensation in compensations: intersection = compensation.intersect(order) if intersection: intersections.append((len(intersection), intersection)) return intersections # Intervals should not overlap def intersect(compensation, order_intervals): compensated = [] not_compensated = [] unused_compensation = [] for interval in order_intervals: compensated.append(compensation.intersect(interval, unused_compensation, not_compensated)) return (compensated, not_compensated, unused_compensation) def update_intersections(not_compensated, compensations): intersections = [] for (_,compensation) in compensations: intersections += get_intersections(compensation, not_compensated) return intersections def compensate(order, compensations): intersections = get_intersections(order, compensations) not_compensated = [order] result = [] while intersections: # Apply the biggest intersection intersections.sort(reverse=True) (_,intersection) = intersections.pop() (compensated, not_compensated, unused_compensation) = intersect(intersection, not_compensated) # Reorder de intersections: intersections = update_intersections(not_compensated, intersections) result += compensated return result