Skip to content

Instantly share code, notes, and snippets.

@paltman
Created May 17, 2014 15:49
Show Gist options
  • Save paltman/90a2d1fd406515e12cfe to your computer and use it in GitHub Desktop.
Save paltman/90a2d1fd406515e12cfe to your computer and use it in GitHub Desktop.
from django.db import models
from django.template.defaultfilters import slugify
from django.utils import timezone
from django.contrib.auth.models import User
from kpitree.measurement.models import Metric, Measurement
from kpitree.organizations.models import Organization
from kpitree.types.period_types import PERIOD_TYPES, period_for_date, period_range, period_display, period_start_end
## These models are about collecting measurements
class CollectionBase(models.Model):
"""
A list of metrics to collect and metadata around how and when to collect
them. A Collection has a single interval and (initially) an initial person
responsible so if two metrics are do be provided by different people or at
different times, they will be in different Collections.
"""
organization = models.ForeignKey(Organization, related_name="collections")
name = models.CharField(max_length=50)
description = models.TextField(blank=True)
slug = models.SlugField()
creator = models.ForeignKey(User)
created = models.DateTimeField(default=timezone.now)
# True means all metrics collected are point-in-time metrics,
# False means they're all interval metrics
point_in_time = models.BooleanField(default=False)
# a period_type exists on a collection even if point in time
period_type = models.CharField(max_length=25, choices=[(x, x) for x in PERIOD_TYPES])
start_period = models.CharField(max_length=12)
# @@@ modeling when collection is due / when email goes out
class Meta:
abstract = True
unique_together = [("organization", "slug")]
def __unicode__(self):
return self.name
def metrics(self):
return Metric.objects.filter(pk__in=self.metric_through_table(collection=self).value_list("pk", flat=True))
def add_metric(self, metric):
if metric.point_in_time != self.point_in_time:
raise ValueError(
"collection can only contain {} metrics".format(
"point-in-time" if self.point_in_time else "interval"))
collection_metric = self.metric_through_table(collection=self, metric=metric)
collection_metric.save()
# signal would go here
return collection_metric
def save(self, *args, **kwargs):
if not self.pk:
created = True
self.slug = slugify(self.name)[:50]
else:
created = False
super(Collection, self).save(*args, **kwargs)
if created:
pass # signal would go here
class AutoCollection(CollectionBase):
PROVIDER_USERVOICE = "uservoice"
PROVIDER_CHOICES = [
(PROVIDER_USERVOICE, "Uservoice")
]
provider = models.CharField(max_length=50, choices=PROVIDER_CHOICES, default=PROVIDER_USERVOICE)
@property
def metric_through_table(self):
return AutoCollectionMetric
class Collection(CollectionBase):
provider = models.ForeignKey(User, related_name="collections")
@property
def metric_through_table(self):
return CollectionMetric
# @@@ validations to add:
# @@@ - metrics must be of same period_type as Collection
# @@@ - provider must be in Organization
def construct_instances(self):
count = 0
for period in period_range(self.start_period, period_for_date(self.period_type)):
collection_instance, created = CollectionInstance.objects.get_or_create(
collection=self,
period=period,
defaults={
"provider": self.provider,
}
)
if created:
count += 1
collection_instance.update_completed()
return count
def construct_all_collection_instances():
count = 0
for collection in Collection.objects.all():
count += collection.construct_instances()
return count
class AutoCollectionMetric(models.Model):
collection = models.ForeignKey(AutoCollection)
metric = models.ForeignKey(Metric)
class CollectionMetric(models.Model):
collection = models.ForeignKey(Collection)
metric = models.ForeignKey(Metric)
class CollectionInstance(models.Model):
"""
An instance of a collection for a particular period
"""
collection = models.ForeignKey(Collection, related_name="instances")
# @@@ do we want point-in-time collections?
period = models.CharField(max_length=12, blank=True)
# this is the actual person who provided the measurements this time
# Collection.provider may change over time but this represents the
# historical record
provider = models.ForeignKey(User, related_name="collection_instances")
# null is incomplete, timestamp of completion when complete
completed = models.DateTimeField(null=True, blank=True)
class Meta:
unique_together = [("collection", "period")]
@property
def period_display(self):
return period_display(self.period)
def metrics(self):
return Metric.objects.filter(collectionmetric__collection=self.collection)
def measurements(self):
qs = Measurement.objects.filter(metric__in=self.metrics().values("id"))
if self.collection.point_in_time:
qs = qs.filter(timestamp__in=period_start_end(self.period))
else:
qs = qs.filter(period=self.period)
qs = qs.select_related("metric", "list_item")
M = {}
for measurement in qs:
if measurement.list_item_id:
key = "{}_{}".format(measurement.metric.slug, measurement.list_item.slug)
else:
key = measurement.metric.slug
M[key] = measurement
return M
def determine_complete(self):
for metric in self.metrics():
if metric.lst:
for list_item in metric.lst.items.all():
measurement = Measurement.objects.filter(metric=metric, list_item=list_item)
if self.collection.point_in_time:
measurement = measurement.filter(timestamp__in=period_start_end(self.period))
else:
measurement = measurement.filter(period=self.period)
if not measurement.exists():
return False
else:
measurement = Measurement.objects.filter(metric=metric, list_item=None)
if self.collection.point_in_time:
measurement = measurement.filter(timestamp__in=period_start_end(self.period))
else:
measurement = measurement.filter(period=self.period)
if not measurement.exists():
return False
return True
def update_completed(self, commit=True):
if self.determine_complete():
measurements = Measurement.objects.filter(metric__in=self.metrics().values("id"))
if self.collection.point_in_time:
measurements = measurements.filter(timestamp__in=period_start_end(self.period))
else:
measurements = measurements.filter(period=self.period)
latest = measurements.order_by("updated")
if latest.exists():
self.completed = latest[0].updated
else:
# It's completed due to the fact it's empty
self.completed = timezone.now()
else:
self.completed = None
if commit:
self.save()
@classmethod
def incomplete_for(cls, user):
return cls.objects.filter(provider=user, completed__isnull=True).order_by("period")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment