| 1 | import hashlib, datetime |
|---|
| 2 | |
|---|
| 3 | from django.db import models |
|---|
| 4 | from django.core.serializers import serialize, deserialize |
|---|
| 5 | from django.contrib.contenttypes.models import ContentType |
|---|
| 6 | from django.contrib.contenttypes.generic import GenericForeignKey |
|---|
| 7 | |
|---|
| 8 | class FossilManager(models.Manager): |
|---|
| 9 | def create_for_object(self, obj): |
|---|
| 10 | if hasattr(obj, 'serialize_for_fossil'): |
|---|
| 11 | serialized = obj.serialize_for_fossil() |
|---|
| 12 | else: |
|---|
| 13 | serialized = serialize('json', [obj]) |
|---|
| 14 | |
|---|
| 15 | hash_key = hashlib.sha256(serialized).hexdigest() |
|---|
| 16 | |
|---|
| 17 | return Fossil.objects.get_or_create( |
|---|
| 18 | pk=hash_key, |
|---|
| 19 | defaults={ |
|---|
| 20 | 'serialized': serialized, |
|---|
| 21 | 'display_text': unicode(obj), |
|---|
| 22 | 'content_type': ContentType.objects.get_for_model(obj), |
|---|
| 23 | 'object_id': unicode(obj.pk), |
|---|
| 24 | }, |
|---|
| 25 | )[0] |
|---|
| 26 | |
|---|
| 27 | def fossils_of_object(self, obj): |
|---|
| 28 | """ |
|---|
| 29 | Returns a list of fossils of a given object, ordered by date |
|---|
| 30 | """ |
|---|
| 31 | qs = self.get_query_set() |
|---|
| 32 | c_type = ContentType.objects.get_for_model(obj) |
|---|
| 33 | |
|---|
| 34 | return qs.filter(content_type=c_type, object_id=obj.pk).order_by('creation') |
|---|
| 35 | |
|---|
| 36 | def indexed(self, **kwargs): |
|---|
| 37 | """ |
|---|
| 38 | Find fossils by fossil indexes |
|---|
| 39 | """ |
|---|
| 40 | qs = self.get_query_set() |
|---|
| 41 | |
|---|
| 42 | # Find all indexes by given key and value |
|---|
| 43 | indexeds = FossilIndexer.objects.all() |
|---|
| 44 | for k,v in kwargs.items(): |
|---|
| 45 | indexeds = indexeds.filter(**{'key': k, 'value': v}) |
|---|
| 46 | |
|---|
| 47 | pks = indexeds.distinct().values_list('fossil', flat=True) |
|---|
| 48 | |
|---|
| 49 | return qs.filter(pk__in=pks) |
|---|
| 50 | |
|---|
| 51 | class Fossil(models.Model): |
|---|
| 52 | objects = FossilManager() |
|---|
| 53 | |
|---|
| 54 | id = models.CharField(max_length=64, primary_key=True) |
|---|
| 55 | serialized = models.TextField(blank=True, default='') |
|---|
| 56 | display_text = models.TextField(blank=True, default='') |
|---|
| 57 | creation = models.DateTimeField(blank=True, default=datetime.datetime.now) |
|---|
| 58 | content_type = models.ForeignKey(ContentType) |
|---|
| 59 | object_id = models.TextField() |
|---|
| 60 | object = GenericForeignKey() |
|---|
| 61 | is_most_recent = models.BooleanField(blank=True, default=True, db_index=True) |
|---|
| 62 | previous_revision = models.ForeignKey('self', null=True, blank=True) |
|---|
| 63 | |
|---|
| 64 | def __unicode__(self): |
|---|
| 65 | return self.display_text |
|---|
| 66 | |
|---|
| 67 | def get_object_fossil(self): |
|---|
| 68 | """ |
|---|
| 69 | Returns the stored version of this object. |
|---|
| 70 | """ |
|---|
| 71 | |
|---|
| 72 | data = self.serialized |
|---|
| 73 | |
|---|
| 74 | if isinstance(data, unicode): |
|---|
| 75 | data = data.encode("utf8") |
|---|
| 76 | |
|---|
| 77 | manager = self.content_type.model_class().objects |
|---|
| 78 | if hasattr(manager, 'deserialize_for_fossil'): |
|---|
| 79 | return manager.deserialize_for_fossil(data) |
|---|
| 80 | else: |
|---|
| 81 | return list(deserialize('json', data))[0] |
|---|
| 82 | |
|---|
| 83 | def create_indexer(self, key, value): |
|---|
| 84 | """ |
|---|
| 85 | Creates a fossil index for this fossil + given key and value |
|---|
| 86 | """ |
|---|
| 87 | FossilIndexer.objects.get_or_create( |
|---|
| 88 | fossil=self, |
|---|
| 89 | key=key, |
|---|
| 90 | value=value, |
|---|
| 91 | ) |
|---|
| 92 | |
|---|
| 93 | class FossilIndexer(models.Model): |
|---|
| 94 | """ |
|---|
| 95 | Class used to index fossil by field values. This is a sollution for querying |
|---|
| 96 | fossils withouth use search in the field 'serialized'. Of course, this is |
|---|
| 97 | because index + join is faster than like. |
|---|
| 98 | """ |
|---|
| 99 | class Meta: |
|---|
| 100 | unique_together = ( |
|---|
| 101 | ('fossil','key','value'), |
|---|
| 102 | ) |
|---|
| 103 | |
|---|
| 104 | fossil = models.ForeignKey('Fossil', related_name='indexeds') |
|---|
| 105 | key = models.CharField(max_length=250) |
|---|
| 106 | value = models.CharField(max_length=250) |
|---|
| 107 | |
|---|
| 108 | # SIGNALS |
|---|
| 109 | from django.db.models import signals |
|---|
| 110 | |
|---|
| 111 | def fossil_post_save(sender, instance, signal, **kwargs): |
|---|
| 112 | # Updates old revisions setting them with "is_most_recent" as False |
|---|
| 113 | if instance.is_most_recent: |
|---|
| 114 | Fossil.objects.filter( |
|---|
| 115 | content_type=instance.content_type, |
|---|
| 116 | object_id=instance.object_id, |
|---|
| 117 | creation__lt=instance.creation, |
|---|
| 118 | is_most_recent=True, |
|---|
| 119 | ).exclude( |
|---|
| 120 | pk=instance.pk, |
|---|
| 121 | ).update( |
|---|
| 122 | is_most_recent=False, |
|---|
| 123 | ) |
|---|
| 124 | |
|---|
| 125 | # Gets "previous_revision" from last one |
|---|
| 126 | if not instance.previous_revision: |
|---|
| 127 | try: |
|---|
| 128 | instance.previous_revision = Fossil.objects.filter( |
|---|
| 129 | content_type=instance.content_type, |
|---|
| 130 | object_id=instance.object_id, |
|---|
| 131 | creation__lt=instance.creation, |
|---|
| 132 | ).exclude( |
|---|
| 133 | pk=instance.pk, |
|---|
| 134 | ).latest('creation') |
|---|
| 135 | instance.save() |
|---|
| 136 | except Fossil.DoesNotExist: |
|---|
| 137 | pass |
|---|
| 138 | |
|---|
| 139 | signals.post_save.connect(fossil_post_save, sender=Fossil) |
|---|