zoo/zoo/zoo_data/models.py

198 lines
5.6 KiB
Python

from operator import __add__, __or__
from django.db import models, connection
from django.db.models import F, Value
from django.db.models.query import QuerySet, Q
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.search import TrigramDistance
from .search import Unaccent, Lower, JSONTextRef
from zoo.zoo_meta.validators import schema_validator
class Transaction(models.Model):
created = models.DateTimeField(
auto_now_add=True,
verbose_name=_('created'))
meta = JSONField(
verbose_name=_('meta'),
blank=True,
null=True)
content = JSONField(
verbose_name=_('content'),
blank=True,
null=True)
def __unicode__(self):
return unicode(self.id)
class Meta:
ordering = ('id',)
verbose_name = _('transaction')
verbose_name_plural = _('transactions')
class EntityQuerySet(QuerySet):
def content_search(self, schema, limit=0.3, **kwargs):
qs = self
qs = qs.filter(schema=schema)
filters = []
connection.cursor().execute('SELECT SET_LIMIT(%s)', (limit,))
for key, value in kwargs.iteritems():
filters.append(Q(**{
'content__' + key + '__unaccent__lower__trigram_similar':
Lower(Unaccent(Value(value))),
}))
qs = qs.filter(reduce(__or__, filters))
expressions = []
ordering = []
for key, value in kwargs.iteritems():
ordering.append(Lower(Unaccent(JSONTextRef(F('content'), *key.split('__')))))
expressions.append(TrigramDistance(
Lower(Unaccent(JSONTextRef(F('content'), *key.split('__')))),
Lower(Unaccent(Value(value)))))
expression = reduce(__add__, expressions)
qs = qs.annotate(similarity=expression / len(kwargs))
qs = qs.order_by('similarity', *ordering)
return qs
class CommonData(models.Model):
def clean(self):
if self.schema:
try:
schema_validator(self.schema.schema)(self.content)
except ValidationError, e:
raise ValidationError({'content': e})
def save(self, *args, **kwargs):
# ensure we have a strict serialization of transactions
# move elsewhere later
with connection.cursor() as cursor:
cursor.execute('LOCK TABLE %s' % Transaction._meta.db_table)
tr = Transaction.objects.create()
if self.id:
self.modified = tr
else:
self.created = tr
return super(CommonData, self).save(*args, **kwargs)
def __unicode__(self):
return unicode(self.id)
class Meta:
abstract = True
class Entity(CommonData):
schema = models.ForeignKey(
'zoo_meta.EntitySchema',
verbose_name=_('schema'))
created = models.ForeignKey(
Transaction,
blank=True,
null=True,
verbose_name=_('created'),
related_name='created_entities')
modified = models.ForeignKey(
Transaction,
blank=True,
null=True,
verbose_name=_('modified'),
related_name='modified_entities')
deleted = models.ForeignKey(
Transaction,
verbose_name=_('deleted'),
blank=True,
null=True,
related_name='deleted_entities')
meta = JSONField(
blank=True,
null=True,
verbose_name=_('meta'))
content = JSONField(
blank=True,
null=False,
verbose_name=_('content'))
objects = EntityQuerySet.as_manager()
class Meta:
ordering = ('created',)
verbose_name = _('entity')
verbose_name_plural = _('entities')
class Relation(CommonData):
schema = models.ForeignKey(
'zoo_meta.RelationSchema',
verbose_name=_('schema'))
left = models.ForeignKey(
'Entity',
verbose_name=_('left'),
related_name='left_relations')
right = models.ForeignKey(
'Entity',
verbose_name=_('right'),
related_name='right_relations')
created = models.ForeignKey(
Transaction,
blank=True,
null=True,
verbose_name=_('created'),
related_name='created_relations')
modified = models.ForeignKey(
Transaction,
blank=True,
null=True,
verbose_name=_('modified'),
related_name='modified_relations')
deleted = models.ForeignKey(
Transaction,
verbose_name=_('deleted'),
blank=True,
null=True,
related_name='deleted_relations')
meta = JSONField(
blank=True,
null=True,
verbose_name=_('meta'))
content = JSONField(
blank=True,
null=False,
verbose_name=_('content'))
class Meta:
ordering = ('created',)
verbose_name = _('relation')
verbose_name_plural = _('relations')
class Log(models.Model):
entity = models.ForeignKey(
'Entity',
verbose_name=_('entity'))
transaction = models.ForeignKey(
'Transaction',
verbose_name=_('transaction'))
timestamp = models.DateTimeField(
auto_now_add=True,
db_index=True,
verbose_name=_('timestamp'))
content = JSONField(
blank=True,
null=True,
verbose_name=_('content'))
url = models.URLField(
blank=True,
null=True,
verbose_name=_('url'))
class Meta:
ordering = ('timestamp',)
verbose_name = _('log')
verbose_name_plural = _('logs')