import string from django.contrib.contenttypes.fields import GenericForeignKey from django.db import models from django.utils.translation import ugettext_lazy as _ from . import managers class Tag(models.Model): """Tag allows typing event and data linked to events. name: the string identifier of the tag """ objects = managers.TagManager() name = models.CharField(verbose_name=_('name'), max_length=32, unique=True, db_index=True) def __str__(self): return self.name def natural_key(self): return (self.name,) class Meta: ordering = ('name',) verbose_name = _('tag') class Template(models.Model): """Template for formatting an event. ex.: Template( content='{user1} gave group {group} to {user2}') """ objects = managers.TemplateManager() content = models.TextField(verbose_name=_('content'), unique=True, db_index=True) def __str__(self): return self.content def natural_key(self): return (self.content,) class Meta: ordering = ('content',) class Journal(models.Model): """One line of the journal. Each recorded event in the journal is a Journal instance. time - the time at which the event was recorded tag - the tag giving the type of event template - a format string to present the event message - a simple string representation of the event, computed using the template and associated datas. """ objects = managers.JournalManager() time = models.DateTimeField(verbose_name=_('time'), auto_now_add=True, db_index=True) tag = models.ForeignKey(Tag, verbose_name=_('tag'), on_delete=models.PROTECT) template = models.ForeignKey(Template, verbose_name=_('template'), on_delete=models.PROTECT) message = models.CharField(verbose_name=_('message'), max_length=128, db_index=True) class Meta: ordering = ('-id',) verbose_name = _('journal entry') verbose_name_plural = _('journal entries') def message_context(self): ctx = {} for data in self.objectdata_set.all(): if data.content_object is not None: ctx[data.tag.name] = data.content_object else: ctx[data.tag.name] = ''.format( content_type=data.content_type, object_id=data.object_id ) for data in self.stringdata_set.all(): ctx[data.tag.name] = data.content for text, field, format_spec, conversion in string.Formatter().parse(self.template.content): if not field: continue splitted = field.split('.') if splitted[0] not in ctx: ctx[splitted[0]] = None return ctx def add_object_tag(self, tag_name, obj): ObjectData(journal=self, tag=Tag.objects.get_cached(name=tag_name), content_object=obj).save() def __str__(self): ctx = self.message_context() return self.template.content.format(**ctx) def __repr__(self): return ''.format( self.pk, unicode(self.tag).encode('utf-8'), unicode(self.message).encode('utf-8') ) class StringData(models.Model): """String data associated to a recorded event. journal: the recorded event tag: the identifier for this data content: the string value of the data """ journal = models.ForeignKey(Journal, verbose_name=_('journal entry'), on_delete=models.CASCADE) tag = models.ForeignKey(Tag, verbose_name=_('tag'), on_delete=models.CASCADE) content = models.TextField(verbose_name=_('content')) class Meta: unique_together = (('journal', 'tag'),) verbose_name = _('linked text string') class ObjectData(models.Model): """Object data associated with a recorded event. journal: the recorded event tag: the identifier for this data content_object: the object value of the data """ journal = models.ForeignKey(Journal, verbose_name=_('journal entry'), on_delete=models.CASCADE) tag = models.ForeignKey(Tag, verbose_name=_('tag'), on_delete=models.CASCADE) content_type = models.ForeignKey( 'contenttypes.ContentType', on_delete=models.CASCADE, verbose_name=_('content type') ) object_id = models.PositiveIntegerField(db_index=True, verbose_name=_('object id')) content_object = GenericForeignKey('content_type', 'object_id') class Meta: unique_together = (('journal', 'tag'),) verbose_name = _('linked object') def __str__(self): return f'{self.journal.id}:{self.tag}:{self.content_object}'