Created
May 17, 2014 15:49
-
-
Save paltman/90a2d1fd406515e12cfe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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