|
@@ -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])
|