|  | @@ -24,7 +24,7 @@ from sqlalchemy.types import LargeBinary
 | 
	
		
			
			| 24 | 24 |  from sqlalchemy.types import Text
 | 
	
		
			
			| 25 | 25 |  from sqlalchemy.types import Unicode
 | 
	
		
			
			| 26 | 26 |  
 | 
	
		
			
			| 27 |  | -from tg.i18n import lazy_ugettext as l_
 | 
	
		
			
			|  | 27 | +from tg.i18n import lazy_ugettext as l_, ugettext as _
 | 
	
		
			
			| 28 | 28 |  
 | 
	
		
			
			| 29 | 29 |  from tracim.model import DeclarativeBase
 | 
	
		
			
			| 30 | 30 |  from tracim.model.auth import User
 | 
	
	
		
			
			|  | @@ -169,29 +169,32 @@ class ActionDescription(object):
 | 
	
		
			
			| 169 | 169 |      STATUS_UPDATE = 'status-update'
 | 
	
		
			
			| 170 | 170 |      UNARCHIVING = 'unarchiving'
 | 
	
		
			
			| 171 | 171 |      UNDELETION = 'undeletion'
 | 
	
		
			
			|  | 172 | +    MOVE = 'move'
 | 
	
		
			
			| 172 | 173 |  
 | 
	
		
			
			| 173 | 174 |      _ICONS = {
 | 
	
		
			
			| 174 | 175 |          'archiving': 'fa fa-archive',
 | 
	
		
			
			| 175 | 176 |          'content-comment': 'fa-comment-o',
 | 
	
		
			
			| 176 | 177 |          'creation': 'fa-magic',
 | 
	
		
			
			| 177 |  | -        'deletion': 'fa fa-trash',
 | 
	
		
			
			| 178 |  | -        'edition': 'fa fa-edit',
 | 
	
		
			
			|  | 178 | +        'deletion': 'fa-trash',
 | 
	
		
			
			|  | 179 | +        'edition': 'fa-edit',
 | 
	
		
			
			| 179 | 180 |          'revision': 'fa-history',
 | 
	
		
			
			| 180 | 181 |          'status-update': 'fa-random',
 | 
	
		
			
			| 181 |  | -        'unarchiving': 'fa fa-file-archive-o',
 | 
	
		
			
			| 182 |  | -        'undeletion': 'fa-trash-o'
 | 
	
		
			
			|  | 182 | +        'unarchiving': 'fa-file-archive-o',
 | 
	
		
			
			|  | 183 | +        'undeletion': 'fa-trash-o',
 | 
	
		
			
			|  | 184 | +        'move': 'fa-arrows'
 | 
	
		
			
			| 183 | 185 |      }
 | 
	
		
			
			| 184 | 186 |  
 | 
	
		
			
			| 185 | 187 |      _LABELS = {
 | 
	
		
			
			| 186 | 188 |          'archiving': l_('archive'),
 | 
	
		
			
			| 187 |  | -        'content-comment': l_('commente'),
 | 
	
		
			
			| 188 |  | -        'creation': l_('creation'),
 | 
	
		
			
			| 189 |  | -        'deletion': l_('deletion'),
 | 
	
		
			
			| 190 |  | -        'edition': l_('modified'),
 | 
	
		
			
			| 191 |  | -        'revision': l_('revision'),
 | 
	
		
			
			| 192 |  | -        'status-update': l_('statut'),
 | 
	
		
			
			| 193 |  | -        'unarchiving': l_('un-archived'),
 | 
	
		
			
			| 194 |  | -        'undeletion': l_('un-deleted'),
 | 
	
		
			
			|  | 189 | +        'content-comment': l_('Item commented'),
 | 
	
		
			
			|  | 190 | +        'creation': l_('Item created'),
 | 
	
		
			
			|  | 191 | +        'deletion': l_('Item deleted'),
 | 
	
		
			
			|  | 192 | +        'edition': l_('item modified'),
 | 
	
		
			
			|  | 193 | +        'revision': l_('New revision'),
 | 
	
		
			
			|  | 194 | +        'status-update': l_('New status'),
 | 
	
		
			
			|  | 195 | +        'unarchiving': l_('Item unarchived'),
 | 
	
		
			
			|  | 196 | +        'undeletion': l_('Item undeleted'),
 | 
	
		
			
			|  | 197 | +        'move': l_('Item moved')
 | 
	
		
			
			| 195 | 198 |      }
 | 
	
		
			
			| 196 | 199 |  
 | 
	
		
			
			| 197 | 200 |      def __init__(self, id):
 | 
	
	
		
			
			|  | @@ -199,6 +202,7 @@ class ActionDescription(object):
 | 
	
		
			
			| 199 | 202 |          self.id = id
 | 
	
		
			
			| 200 | 203 |          self.label = ActionDescription._LABELS[id]
 | 
	
		
			
			| 201 | 204 |          self.icon = ActionDescription._ICONS[id]
 | 
	
		
			
			|  | 205 | +        self.css = ''
 | 
	
		
			
			| 202 | 206 |  
 | 
	
		
			
			| 203 | 207 |      @classmethod
 | 
	
		
			
			| 204 | 208 |      def allowed_values(cls):
 | 
	
	
		
			
			|  | @@ -210,7 +214,8 @@ class ActionDescription(object):
 | 
	
		
			
			| 210 | 214 |                  cls.REVISION,
 | 
	
		
			
			| 211 | 215 |                  cls.STATUS_UPDATE,
 | 
	
		
			
			| 212 | 216 |                  cls.UNARCHIVING,
 | 
	
		
			
			| 213 |  | -                cls.UNDELETION]
 | 
	
		
			
			|  | 217 | +                cls.UNDELETION,
 | 
	
		
			
			|  | 218 | +                cls.MOVE]
 | 
	
		
			
			| 214 | 219 |  
 | 
	
		
			
			| 215 | 220 |  
 | 
	
		
			
			| 216 | 221 |  class ContentStatus(object):
 | 
	
	
		
			
			|  | @@ -406,10 +411,15 @@ class ContentType(object):
 | 
	
		
			
			| 406 | 411 |      def sorted(cls, types: ['ContentType']) -> ['ContentType']:
 | 
	
		
			
			| 407 | 412 |          return sorted(types, key=lambda content_type: content_type.priority)
 | 
	
		
			
			| 408 | 413 |  
 | 
	
		
			
			|  | 414 | +    @property
 | 
	
		
			
			|  | 415 | +    def type(self):
 | 
	
		
			
			|  | 416 | +        return self.id
 | 
	
		
			
			|  | 417 | +
 | 
	
		
			
			| 409 | 418 |      def __init__(self, type):
 | 
	
		
			
			| 410 |  | -        self.type = type
 | 
	
		
			
			|  | 419 | +        self.id = type
 | 
	
		
			
			| 411 | 420 |          self.icon = ContentType._CSS_ICONS[type]
 | 
	
		
			
			| 412 |  | -        self.color = ContentType._CSS_COLORS[type]
 | 
	
		
			
			|  | 421 | +        self.color = ContentType._CSS_COLORS[type]  # deprecated
 | 
	
		
			
			|  | 422 | +        self.css = ContentType._CSS_COLORS[type]
 | 
	
		
			
			| 413 | 423 |          self.label = ContentType._LABEL[type]
 | 
	
		
			
			| 414 | 424 |          self.priority = ContentType._ORDER_WEIGHT[type]
 | 
	
		
			
			| 415 | 425 |  
 | 
	
	
		
			
			|  | @@ -594,10 +604,21 @@ class Content(DeclarativeBase):
 | 
	
		
			
			| 594 | 604 |          except Exception as e:
 | 
	
		
			
			| 595 | 605 |              print(e.__str__())
 | 
	
		
			
			| 596 | 606 |              print('----- /*\ *****')
 | 
	
		
			
			| 597 |  | -            raise ValueError('No allowed content property')
 | 
	
		
			
			|  | 607 | +            raise ValueError('Not allowed content property')
 | 
	
		
			
			| 598 | 608 |  
 | 
	
		
			
			| 599 | 609 |          return ContentType.sorted(types)
 | 
	
		
			
			| 600 | 610 |  
 | 
	
		
			
			|  | 611 | +    def get_history(self) -> '[VirtualEvent]':
 | 
	
		
			
			|  | 612 | +        events = []
 | 
	
		
			
			|  | 613 | +        for comment in self.get_comments():
 | 
	
		
			
			|  | 614 | +            events.append(VirtualEvent.create_from_content(comment))
 | 
	
		
			
			|  | 615 | +        for revision in self.revisions:
 | 
	
		
			
			|  | 616 | +            events.append(VirtualEvent.create_from_content_revision(revision))
 | 
	
		
			
			|  | 617 | +
 | 
	
		
			
			|  | 618 | +        sorted_events = sorted(events,
 | 
	
		
			
			|  | 619 | +                               key=lambda event: event.created, reverse=True)
 | 
	
		
			
			|  | 620 | +        return sorted_events
 | 
	
		
			
			|  | 621 | +
 | 
	
		
			
			| 601 | 622 |  
 | 
	
		
			
			| 602 | 623 |  class ContentChecker(object):
 | 
	
		
			
			| 603 | 624 |  
 | 
	
	
		
			
			|  | @@ -649,6 +670,7 @@ class ContentRevisionRO(DeclarativeBase):
 | 
	
		
			
			| 649 | 670 |      file_mimetype = Column(Unicode(255),  unique=False, nullable=False, default='')
 | 
	
		
			
			| 650 | 671 |      file_content = deferred(Column(LargeBinary(), unique=False, nullable=False, default=None))
 | 
	
		
			
			| 651 | 672 |  
 | 
	
		
			
			|  | 673 | +    type = Column(Unicode(32), unique=False, nullable=False)
 | 
	
		
			
			| 652 | 674 |      status = Column(Unicode(32), unique=False, nullable=False)
 | 
	
		
			
			| 653 | 675 |      created = Column(DateTime, unique=False, nullable=False)
 | 
	
		
			
			| 654 | 676 |      updated = Column(DateTime, unique=False, nullable=False)
 | 
	
	
		
			
			|  | @@ -665,6 +687,9 @@ class ContentRevisionRO(DeclarativeBase):
 | 
	
		
			
			| 665 | 687 |      def get_status(self):
 | 
	
		
			
			| 666 | 688 |          return ContentStatus(self.status)
 | 
	
		
			
			| 667 | 689 |  
 | 
	
		
			
			|  | 690 | +    def get_label(self):
 | 
	
		
			
			|  | 691 | +        return self.label if self.label else self.file_name if self.file_name else ''
 | 
	
		
			
			|  | 692 | +
 | 
	
		
			
			| 668 | 693 |      def get_last_action(self) -> ActionDescription:
 | 
	
		
			
			| 669 | 694 |          return ActionDescription(self.revision_type)
 | 
	
		
			
			| 670 | 695 |  
 | 
	
	
		
			
			|  | @@ -678,3 +703,60 @@ class NodeTreeItem(object):
 | 
	
		
			
			| 678 | 703 |          self.node = node
 | 
	
		
			
			| 679 | 704 |          self.children = children
 | 
	
		
			
			| 680 | 705 |          self.is_selected = is_selected
 | 
	
		
			
			|  | 706 | +
 | 
	
		
			
			|  | 707 | +class VirtualEvent(object):
 | 
	
		
			
			|  | 708 | +    @classmethod
 | 
	
		
			
			|  | 709 | +    def create_from(cls, object):
 | 
	
		
			
			|  | 710 | +        if Content == object.__class__:
 | 
	
		
			
			|  | 711 | +            return cls.create_from_content(object)
 | 
	
		
			
			|  | 712 | +        elif ContentRevisionRO == object.__class__:
 | 
	
		
			
			|  | 713 | +            return cls.create_from_content_revision(object)
 | 
	
		
			
			|  | 714 | +
 | 
	
		
			
			|  | 715 | +    @classmethod
 | 
	
		
			
			|  | 716 | +    def create_from_content(cls, content: Content):
 | 
	
		
			
			|  | 717 | +        content_type = ContentType(content.type)
 | 
	
		
			
			|  | 718 | +
 | 
	
		
			
			|  | 719 | +        label = content.get_label()
 | 
	
		
			
			|  | 720 | +        if content.type==ContentType.Comment:
 | 
	
		
			
			|  | 721 | +            label = _('<strong>{}</strong> wrote:').format(content.owner.get_display_name())
 | 
	
		
			
			|  | 722 | +
 | 
	
		
			
			|  | 723 | +        return VirtualEvent(id=content.content_id,
 | 
	
		
			
			|  | 724 | +                            created=content.created,
 | 
	
		
			
			|  | 725 | +                            owner=content.owner,
 | 
	
		
			
			|  | 726 | +                            type=content_type,
 | 
	
		
			
			|  | 727 | +                            label=label,
 | 
	
		
			
			|  | 728 | +                            content=content.description,
 | 
	
		
			
			|  | 729 | +                            ref_object=content)
 | 
	
		
			
			|  | 730 | +
 | 
	
		
			
			|  | 731 | +    @classmethod
 | 
	
		
			
			|  | 732 | +    def create_from_content_revision(cls, revision: ContentRevisionRO):
 | 
	
		
			
			|  | 733 | +        action_description = ActionDescription(revision.revision_type)
 | 
	
		
			
			|  | 734 | +
 | 
	
		
			
			|  | 735 | +        return VirtualEvent(id=revision.revision_id,
 | 
	
		
			
			|  | 736 | +                            created=revision.created,
 | 
	
		
			
			|  | 737 | +                            owner=revision.owner,
 | 
	
		
			
			|  | 738 | +                            type=action_description,
 | 
	
		
			
			|  | 739 | +                            label=action_description.label,
 | 
	
		
			
			|  | 740 | +                            content='',
 | 
	
		
			
			|  | 741 | +                            ref_object=revision)
 | 
	
		
			
			|  | 742 | +
 | 
	
		
			
			|  | 743 | +    def __init__(self, id, created, owner, type, label, content, ref_object):
 | 
	
		
			
			|  | 744 | +        self.id = id
 | 
	
		
			
			|  | 745 | +        self.created = created
 | 
	
		
			
			|  | 746 | +        self.owner = owner
 | 
	
		
			
			|  | 747 | +        self.type = type
 | 
	
		
			
			|  | 748 | +        self.label = label
 | 
	
		
			
			|  | 749 | +        self.content = content
 | 
	
		
			
			|  | 750 | +        self.ref_object = ref_object
 | 
	
		
			
			|  | 751 | +
 | 
	
		
			
			|  | 752 | +        print(type)
 | 
	
		
			
			|  | 753 | +        assert hasattr(type, 'id')
 | 
	
		
			
			|  | 754 | +        assert hasattr(type, 'css')
 | 
	
		
			
			|  | 755 | +        assert hasattr(type, 'icon')
 | 
	
		
			
			|  | 756 | +        assert hasattr(type, 'label')
 | 
	
		
			
			|  | 757 | +
 | 
	
		
			
			|  | 758 | +    def created_as_delta(self, delta_from_datetime:datetime=None):
 | 
	
		
			
			|  | 759 | +        if not delta_from_datetime:
 | 
	
		
			
			|  | 760 | +            delta_from_datetime = datetime.now()
 | 
	
		
			
			|  | 761 | +        return format_timedelta(delta_from_datetime - self.created,
 | 
	
		
			
			|  | 762 | +                                locale=tg.i18n.get_lang()[0])
 |