浏览代码

massive refactoring for better understanding of the code and sql schema. minor bugfixes + css improvments

Damien ACCORSI 10 年前
父节点
当前提交
f17975039f
共有 37 个文件被更改,包括 935 次插入1946 次删除
  1. 273 0
      doc/database/tracim-init-database.new.sql
  2. 1 1
      install/requirements.txt
  3. 19 3
      tracim/tracim/config/app_cfg.py
  4. 32 32
      tracim/tracim/controllers/__init__.py
  5. 4 4
      tracim/tracim/controllers/admin/user.py
  6. 9 9
      tracim/tracim/controllers/admin/workspace.py
  7. 46 46
      tracim/tracim/controllers/content.py
  8. 0 1
      tracim/tracim/controllers/help.py
  9. 5 5
      tracim/tracim/controllers/workspace.py
  10. 二进制
      tracim/tracim/i18n/fr/LC_MESSAGES/tracim.mo
  11. 150 187
      tracim/tracim/i18n/fr/LC_MESSAGES/tracim.po
  12. 3 0
      tracim/tracim/lib/__init__.py
  13. 0 65
      tracim/tracim/lib/auth.py
  14. 0 4
      tracim/tracim/lib/base.py
  15. 63 68
      tracim/tracim/lib/content.py
  16. 0 432
      tracim/tracim/lib/dbapi.py
  17. 13 70
      tracim/tracim/lib/helpers.py
  18. 6 144
      tracim/tracim/lib/user.py
  19. 1 1
      tracim/tracim/lib/userworkspace.py
  20. 3 3
      tracim/tracim/lib/workspace.py
  21. 1 1
      tracim/tracim/model/__init__.py
  22. 22 62
      tracim/tracim/model/auth.py
  23. 102 539
      tracim/tracim/model/data.py
  24. 134 229
      tracim/tracim/model/serializers.py
  25. 14 9
      tracim/tracim/public/assets/css/dashboard.css
  26. 1 1
      tracim/tracim/templates/debug/iconset-tango.mak
  27. 3 2
      tracim/tracim/templates/master_anonymous.mak
  28. 6 5
      tracim/tracim/templates/master_authenticated.mak
  29. 1 0
      tracim/tracim/templates/pod.mak
  30. 1 1
      tracim/tracim/templates/user_get_me.mak
  31. 6 6
      tracim/tracim/templates/user_workspace_folder_file_get_one.mak
  32. 1 1
      tracim/tracim/templates/user_workspace_folder_get_one.mak
  33. 2 2
      tracim/tracim/templates/user_workspace_folder_page_get_one.mak
  34. 8 8
      tracim/tracim/templates/user_workspace_folder_thread_get_one.mak
  35. 1 1
      tracim/tracim/templates/user_workspace_get_one.mak
  36. 2 2
      tracim/tracim/tests/models/test_auth.py
  37. 2 2
      tracim/tracim/websetup/bootstrap.py

+ 273 - 0
doc/database/tracim-init-database.new.sql 查看文件

1
+SET statement_timeout = 0;
2
+SET client_encoding = 'UTF8';
3
+SET standard_conforming_strings = on;
4
+SET check_function_bodies = false;
5
+SET client_min_messages = warning;
6
+
7
+CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
8
+COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
9
+SET search_path = public, pg_catalog;
10
+
11
+CREATE FUNCTION update_node() RETURNS trigger
12
+    LANGUAGE plpgsql
13
+    AS $$
14
+BEGIN
15
+INSERT INTO content_revisions (content_id, parent_id, type, created, updated, 
16
+       label, description, status, 
17
+       file_name, file_content, file_mimetype, parent_tree_path, 
18
+       node_depth, owner_id, revision_id, workspace_id, is_deleted, is_archived, properties, revision_type) VALUES (NEW.content_id, NEW.parent_id, NEW.type, NEW.created, NEW.updated, NEW.label, NEW.description, NEW.status, NEW.file_name, NEW.file_content, NEW.file_mimetype, NEW.parent_tree_path, NEW.node_depth, NEW.owner_id, nextval('seq__content_revisions__revision_id'), NEW.workspace_id, NEW.is_deleted, NEW.is_archived, NEW.properties, NEW.revision_type);
19
+return new;
20
+END;
21
+$$;
22
+
23
+CREATE FUNCTION set_created() RETURNS trigger
24
+    LANGUAGE plpgsql
25
+    AS $$
26
+BEGIN
27
+    NEW.created = CURRENT_TIMESTAMP;
28
+    NEW.updated = CURRENT_TIMESTAMP;
29
+    RETURN NEW;
30
+END;
31
+$$;
32
+
33
+CREATE FUNCTION set_updated() RETURNS trigger
34
+    LANGUAGE plpgsql
35
+    AS $$
36
+BEGIN
37
+    NEW.updated = CURRENT_TIMESTAMP;
38
+    RETURN NEW;
39
+END;
40
+$$;
41
+
42
+SET default_tablespace = '';
43
+SET default_with_oids = false;
44
+
45
+CREATE TABLE migrate_version (
46
+    version_num character varying(32) NOT NULL
47
+);
48
+
49
+CREATE TABLE groups (
50
+    group_id integer NOT NULL,
51
+    group_name character varying(16) NOT NULL,
52
+    display_name character varying(255),
53
+    created timestamp without time zone
54
+);
55
+
56
+CREATE SEQUENCE seq__groups__group_id
57
+    START WITH 1
58
+    INCREMENT BY 1
59
+    NO MINVALUE
60
+    NO MAXVALUE
61
+    CACHE 1;
62
+
63
+ALTER SEQUENCE seq__groups__group_id OWNED BY groups.group_id;
64
+
65
+CREATE TABLE group_permission (
66
+    group_id integer NOT NULL,
67
+    permission_id integer NOT NULL
68
+);
69
+
70
+CREATE SEQUENCE seq__content_revisions__revision_id
71
+    START WITH 1
72
+    INCREMENT BY 1
73
+    NO MINVALUE
74
+    NO MAXVALUE
75
+    CACHE 1;
76
+
77
+CREATE TABLE content_revisions (
78
+    content_id integer NOT NULL,
79
+    parent_id integer,
80
+    type character varying(16) DEFAULT 'data'::character varying NOT NULL,
81
+    created timestamp without time zone,
82
+    updated timestamp without time zone,
83
+    label character varying(1024),
84
+    description text DEFAULT ''::text NOT NULL,
85
+    status character varying(32) DEFAULT 'new'::character varying,
86
+    file_name character varying(255),
87
+    file_content bytea,
88
+    file_mimetype character varying(255),
89
+    parent_tree_path character varying(255),
90
+    node_depth integer DEFAULT 0 NOT NULL,
91
+    owner_id integer,
92
+    revision_id integer DEFAULT nextval('seq__content_revisions__revision_id'::regclass) NOT NULL,
93
+    workspace_id integer,
94
+    is_deleted boolean DEFAULT false NOT NULL,
95
+    is_archived boolean DEFAULT false NOT NULL,
96
+    properties text,
97
+    revision_type character varying(32)
98
+);
99
+
100
+COMMENT ON COLUMN content_revisions.properties IS 'This column contain properties specific to a given type. these properties are json encoded (so there is no structure "a priori")';
101
+
102
+CREATE VIEW contents AS
103
+    SELECT DISTINCT ON (content_revisions.content_id) content_revisions.content_id, content_revisions.parent_id, content_revisions.type, content_revisions.created, content_revisions.updated, content_revisions.label, content_revisions.description, content_revisions.status, content_revisions.file_name, content_revisions.file_content, content_revisions.file_mimetype, content_revisions.parent_tree_path, content_revisions.node_depth, content_revisions.owner_id, content_revisions.workspace_id, content_revisions.is_deleted, content_revisions.is_archived, content_revisions.properties, content_revisions.revision_type FROM content_revisions ORDER BY content_revisions.content_id, content_revisions.updated DESC, content_revisions.created DESC;
104
+
105
+CREATE SEQUENCE seq__contents__content_id
106
+    START WITH 1
107
+    INCREMENT BY 1
108
+    NO MINVALUE
109
+    NO MAXVALUE
110
+    CACHE 1;
111
+
112
+ALTER SEQUENCE seq__contents__content_id OWNED BY content_revisions.content_id;
113
+
114
+CREATE TABLE permissions (
115
+    permission_id integer NOT NULL,
116
+    permission_name character varying(63) NOT NULL,
117
+    description character varying(255)
118
+);
119
+
120
+CREATE SEQUENCE seq__permissions__permission_id
121
+    START WITH 1
122
+    INCREMENT BY 1
123
+    NO MINVALUE
124
+    NO MAXVALUE
125
+    CACHE 1;
126
+
127
+ALTER SEQUENCE seq__permissions__permission_id OWNED BY permissions.permission_id;
128
+
129
+CREATE TABLE users (
130
+    user_id integer NOT NULL,
131
+    email character varying(255) NOT NULL,
132
+    display_name character varying(255),
133
+    password character varying(128),
134
+    created timestamp without time zone,
135
+    is_active boolean DEFAULT true NOT NULL
136
+);
137
+
138
+CREATE TABLE user_group (
139
+    user_id integer NOT NULL,
140
+    group_id integer NOT NULL
141
+);
142
+
143
+CREATE SEQUENCE seq__users__user_id
144
+    START WITH 1
145
+    INCREMENT BY 1
146
+    NO MINVALUE
147
+    NO MAXVALUE
148
+    CACHE 1;
149
+
150
+ALTER SEQUENCE seq__users__user_id OWNED BY users.user_id;
151
+
152
+CREATE TABLE user_workspace (
153
+    user_id integer NOT NULL,
154
+    workspace_id integer NOT NULL,
155
+    role integer
156
+);
157
+
158
+CREATE TABLE workspaces (
159
+    workspace_id integer NOT NULL,
160
+    label character varying(1024),
161
+    description text,
162
+    created timestamp without time zone,
163
+    updated timestamp without time zone,
164
+    is_deleted boolean DEFAULT false NOT NULL
165
+);
166
+
167
+CREATE SEQUENCE seq__workspaces__workspace_id
168
+    START WITH 11
169
+    INCREMENT BY 1
170
+    NO MINVALUE
171
+    NO MAXVALUE
172
+    CACHE 1;
173
+
174
+ALTER TABLE ONLY groups ALTER COLUMN group_id SET DEFAULT nextval('seq__groups__group_id'::regclass);
175
+ALTER TABLE ONLY content_revisions ALTER COLUMN content_id SET DEFAULT nextval('seq__contents__content_id'::regclass);
176
+ALTER TABLE ONLY permissions ALTER COLUMN permission_id SET DEFAULT nextval('seq__permissions__permission_id'::regclass);
177
+ALTER TABLE ONLY users ALTER COLUMN user_id SET DEFAULT nextval('seq__users__user_id'::regclass);
178
+ALTER TABLE ONLY workspaces ALTER COLUMN workspace_id SET DEFAULT nextval('seq__workspaces__workspace_id'::regclass);
179
+
180
+-- COPY migrate_version (version_num) FROM stdin;
181
+
182
+INSERT INTO groups (group_id, group_name, display_name, created) VALUES
183
+(1, 'users',	'Users',	'2014-10-08 14:55:43.329136'),
184
+(2, 'managers',	'Global Managers',	'2014-10-08 14:55:43.329136'),
185
+(3, 'administrators',	'Administrators',	'2014-10-08 14:55:43.329136');
186
+
187
+SELECT pg_catalog.setval('seq__groups__group_id', 4, true);
188
+
189
+SELECT pg_catalog.setval('seq__contents__content_id', 1, true);
190
+
191
+SELECT pg_catalog.setval('seq__content_revisions__revision_id', 2568, true);
192
+
193
+SELECT pg_catalog.setval('seq__permissions__permission_id', 1, true);
194
+
195
+INSERT INTO users(user_id, email, display_name, password, created, is_active)
196
+VALUES(1, 'demo.michel@tracim.org', 'Michel', '1533a541f0f24746a21b622a88ee8a43ce0197fb73300f633f8860abdbd22e6b8ebb1542dc4bae072729f84b4f0020c37abc60dd769dec7951e4ab80d10ce39e', '2014-10-23 15:28:56.268502', 't');
197
+
198
+INSERT INTO user_group(user_id, group_id) VALUES (1,1), (1, 2), (1,3);
199
+
200
+SELECT pg_catalog.setval('seq__users__user_id', 2, true);
201
+
202
+SELECT pg_catalog.setval('seq__workspaces__workspace_id', 1, true);
203
+
204
+ALTER TABLE ONLY user_workspace
205
+    ADD CONSTRAINT pk__user_workspace__user_id__workspace_id PRIMARY KEY (user_id, workspace_id);
206
+
207
+ALTER TABLE ONLY workspaces
208
+    ADD CONSTRAINT pk__workspace__workspace_id PRIMARY KEY (workspace_id);
209
+
210
+ALTER TABLE ONLY groups
211
+    ADD CONSTRAINT uk__groups__group_name UNIQUE (group_name);
212
+
213
+ALTER TABLE ONLY group_permission
214
+    ADD CONSTRAINT pk__group_permission__group_id__permission_id PRIMARY KEY (group_id, permission_id);
215
+
216
+ALTER TABLE ONLY groups
217
+    ADD CONSTRAINT pk__groups__group_id PRIMARY KEY (group_id);
218
+
219
+ALTER TABLE ONLY content_revisions
220
+    ADD CONSTRAINT pk__content_revisions__revision_id PRIMARY KEY (revision_id);
221
+
222
+ALTER TABLE ONLY permissions
223
+    ADD CONSTRAINT uk__permissions__permission_name UNIQUE (permission_name);
224
+
225
+ALTER TABLE ONLY permissions
226
+    ADD CONSTRAINT pk__permissions__permission_id PRIMARY KEY (permission_id);
227
+
228
+ALTER TABLE ONLY users
229
+    ADD CONSTRAINT uk__users__email UNIQUE (email);
230
+
231
+ALTER TABLE ONLY user_group
232
+    ADD CONSTRAINT pk__user_group__user_id__group_id PRIMARY KEY (user_id, group_id);
233
+
234
+ALTER TABLE ONLY users
235
+    ADD CONSTRAINT pk__users__user_id PRIMARY KEY (user_id);
236
+
237
+CREATE INDEX idx__content_revisions__owner_id ON content_revisions USING btree (owner_id);
238
+
239
+CREATE INDEX idx__content_revisions__parent_id ON content_revisions USING btree (parent_id);
240
+
241
+CREATE INDEX idx__content_revisions__parent_tree_path ON content_revisions USING btree (parent_tree_path);
242
+
243
+CREATE RULE rul__insert__new_node AS ON INSERT TO contents DO INSTEAD INSERT INTO content_revisions (content_id, parent_id, type, created, updated, label, description, status, file_name, file_content, file_mimetype, parent_tree_path, node_depth, owner_id, revision_id, workspace_id, is_deleted, is_archived, properties, revision_type) VALUES (nextval('seq__contents__content_id'::regclass), new.parent_id, new.type, new.created, new.updated, new.label, new.description, new.status, new.file_name, new.file_content, new.file_mimetype, new.parent_tree_path, new.node_depth, new.owner_id, nextval('seq__content_revisions__revision_id'::regclass), new.workspace_id, new.is_deleted, new.is_archived, new.properties, new.revision_type) RETURNING content_revisions.content_id, content_revisions.parent_id, content_revisions.type, content_revisions.created, content_revisions.updated, content_revisions.label, content_revisions.description, content_revisions.status, content_revisions.file_name, content_revisions.file_content, content_revisions.file_mimetype, content_revisions.parent_tree_path, content_revisions.node_depth, content_revisions.owner_id, content_revisions.workspace_id, content_revisions.is_deleted, content_revisions.is_archived, content_revisions.properties, content_revisions.revision_type;
244
+
245
+CREATE TRIGGER trg__contents__on_insert__set_created BEFORE INSERT ON content_revisions FOR EACH ROW EXECUTE PROCEDURE set_created();
246
+CREATE TRIGGER trg__contents__on_update__set_updated BEFORE UPDATE ON content_revisions FOR EACH ROW EXECUTE PROCEDURE set_updated();
247
+
248
+CREATE TRIGGER trg__contents__on_update INSTEAD OF UPDATE ON contents FOR EACH ROW EXECUTE PROCEDURE update_node();
249
+CREATE TRIGGER trg__workspaces__on_insert__set_created BEFORE INSERT ON workspaces FOR EACH ROW EXECUTE PROCEDURE set_created();
250
+CREATE TRIGGER trg__workspaces__on_update__set_updated BEFORE UPDATE ON workspaces FOR EACH ROW EXECUTE PROCEDURE set_updated();
251
+
252
+ALTER TABLE ONLY user_workspace
253
+    ADD CONSTRAINT fk__user_workspace__user_id FOREIGN KEY (user_id) REFERENCES users(user_id) ON UPDATE CASCADE ON DELETE CASCADE;
254
+
255
+ALTER TABLE ONLY user_workspace
256
+    ADD CONSTRAINT fk__user_workspace__workspace_id FOREIGN KEY (workspace_id) REFERENCES workspaces(workspace_id) ON UPDATE CASCADE ON DELETE CASCADE;
257
+
258
+ALTER TABLE ONLY group_permission
259
+    ADD CONSTRAINT fk__group_permission__group_id FOREIGN KEY (group_id) REFERENCES groups(group_id) ON UPDATE CASCADE ON DELETE CASCADE;
260
+
261
+ALTER TABLE ONLY group_permission
262
+    ADD CONSTRAINT fk__group_permission__permission_id FOREIGN KEY (permission_id) REFERENCES permissions(permission_id) ON UPDATE CASCADE ON DELETE CASCADE;
263
+
264
+ALTER TABLE ONLY content_revisions
265
+    ADD CONSTRAINT fk__content_revisions__owner_id FOREIGN KEY (owner_id) REFERENCES users(user_id);
266
+
267
+ALTER TABLE ONLY user_group
268
+    ADD CONSTRAINT fk__user_group__group_id FOREIGN KEY (group_id) REFERENCES groups(group_id) ON UPDATE CASCADE ON DELETE CASCADE;
269
+
270
+ALTER TABLE ONLY user_group
271
+    ADD CONSTRAINT fk__user_group__user_id FOREIGN KEY (user_id) REFERENCES users(user_id) ON UPDATE CASCADE ON DELETE CASCADE;
272
+
273
+

+ 1 - 1
install/requirements.txt 查看文件

7
 Pillow==2.4.0
7
 Pillow==2.4.0
8
 SQLAlchemy==0.9.4
8
 SQLAlchemy==0.9.4
9
 Tempita==0.5.3dev
9
 Tempita==0.5.3dev
10
-TurboGears2==2.3.3
10
+TurboGears2==2.3.4
11
 WebOb==1.4
11
 WebOb==1.4
12
 WebTest==1.4.3
12
 WebTest==1.4.3
13
 alembic==0.6.5
13
 alembic==0.6.5

+ 19 - 3
tracim/tracim/config/app_cfg.py 查看文件

77
     def __init__(self, sa_auth):
77
     def __init__(self, sa_auth):
78
         self.sa_auth = sa_auth
78
         self.sa_auth = sa_auth
79
     def authenticate(self, environ, identity):
79
     def authenticate(self, environ, identity):
80
-        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(self.sa_auth.user_class.is_active==True, self.sa_auth.user_class.email_address==identity['login'])).first()
80
+        user = self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(self.sa_auth.user_class.is_active==True, self.sa_auth.user_class.email==identity['login'])).first()
81
         if user and user.validate_password(identity['password']):
81
         if user and user.validate_password(identity['password']):
82
             return identity['login']
82
             return identity['login']
83
     def get_user(self, identity, userid):
83
     def get_user(self, identity, userid):
84
-        return self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(self.sa_auth.user_class.is_active==True, self.sa_auth.user_class.email_address==userid)).first()
84
+        return self.sa_auth.dbsession.query(self.sa_auth.user_class).filter(and_(self.sa_auth.user_class.is_active==True, self.sa_auth.user_class.email==userid)).first()
85
     def get_groups(self, identity, userid):
85
     def get_groups(self, identity, userid):
86
         return [g.group_name for g in identity['user'].groups]
86
         return [g.group_name for g in identity['user'].groups]
87
     def get_permissions(self, identity, userid):
87
     def get_permissions(self, identity, userid):
116
 # INFO - This is the way to specialize the resetpassword email properties
116
 # INFO - This is the way to specialize the resetpassword email properties
117
 # plug(base_config, 'resetpassword', None, mail_subject=reset_password_email_subject)
117
 # plug(base_config, 'resetpassword', None, mail_subject=reset_password_email_subject)
118
 plug(base_config, 'resetpassword', 'reset_password')
118
 plug(base_config, 'resetpassword', 'reset_password')
119
+
120
+def fake_unicode(some_string_or_bytes):
121
+    return some_string_or_bytes
122
+    if isinstance(some_string_or_bytes, str):
123
+        print("IS STRING")
124
+        return some_string_or_bytes
125
+
126
+    print("IS ", some_string_or_bytes.__class__.__name__)
127
+    return str(some_string_or_bytes, 'utf-8')
128
+#     if isinstance(some_string_or_bytes, bytes):
129
+#         return some_string_or_bytes.encode('utf-8')
130
+#     return some_string_or_bytes
131
+#
132
+import resetpassword.lib as rplib
133
+# rplib.unicode = fake_unicode
134
+
119
 replace_template(base_config, 'resetpassword.templates.index', 'tracim.templates.reset_password_index')
135
 replace_template(base_config, 'resetpassword.templates.index', 'tracim.templates.reset_password_index')
120
-replace_template(base_config, 'resetpassword.templates.change_password', 'tracim.templates.reset_password_change_password')
136
+replace_template(base_config, 'resetpassword.templates.change_password', 'mako:tracim.templates.reset_password_change_password')
121
 
137
 
122
 # Note: here are fake translatable strings that allow to translate messages for reset password email content
138
 # Note: here are fake translatable strings that allow to translate messages for reset password email content
123
 duplicated_email_subject = l_('Password reset request')
139
 duplicated_email_subject = l_('Password reset request')

+ 32 - 32
tracim/tracim/controllers/__init__.py 查看文件

17
 from tracim.model.auth import User
17
 from tracim.model.auth import User
18
 from tracim.model.data import ActionDescription
18
 from tracim.model.data import ActionDescription
19
 from tracim.model.data import BreadcrumbItem
19
 from tracim.model.data import BreadcrumbItem
20
-from tracim.model.data import PBNode
21
-from tracim.model.data import PBNodeType
20
+from tracim.model.data import Content
21
+from tracim.model.data import ContentType
22
 from tracim.model.data import Workspace
22
 from tracim.model.data import Workspace
23
 
23
 
24
 from tracim.lib.content import ContentApi
24
 from tracim.lib.content import ContentApi
53
 
53
 
54
 
54
 
55
     @classmethod
55
     @classmethod
56
-    def current_folder(cls) -> PBNode:
56
+    def current_folder(cls) -> Content:
57
         content_api = ContentApi(tg.tmpl_context.current_user)
57
         content_api = ContentApi(tg.tmpl_context.current_user)
58
         folder_id = int(tg.request.controller_state.routing_args.get('folder_id'))
58
         folder_id = int(tg.request.controller_state.routing_args.get('folder_id'))
59
-        folder = content_api.get_one(folder_id, PBNodeType.Folder, tg.tmpl_context.workspace)
59
+        folder = content_api.get_one(folder_id, ContentType.Folder, tg.tmpl_context.workspace)
60
 
60
 
61
         tg.tmpl_context.folder_id = folder_id
61
         tg.tmpl_context.folder_id = folder_id
62
         tg.tmpl_context.folder = folder
62
         tg.tmpl_context.folder = folder
65
 
65
 
66
 
66
 
67
     @classmethod
67
     @classmethod
68
-    def current_folder(cls) -> PBNode:
68
+    def current_folder(cls) -> Content:
69
         content_api = ContentApi(tg.tmpl_context.current_user)
69
         content_api = ContentApi(tg.tmpl_context.current_user)
70
         folder_id = int(tg.request.controller_state.routing_args.get('folder_id'))
70
         folder_id = int(tg.request.controller_state.routing_args.get('folder_id'))
71
-        folder = content_api.get_one(folder_id, PBNodeType.Folder, tg.tmpl_context.workspace)
71
+        folder = content_api.get_one(folder_id, ContentType.Folder, tg.tmpl_context.workspace)
72
 
72
 
73
         tg.tmpl_context.folder_id = folder_id
73
         tg.tmpl_context.folder_id = folder_id
74
         tg.tmpl_context.folder = folder
74
         tg.tmpl_context.folder = folder
77
 
77
 
78
 
78
 
79
     @classmethod
79
     @classmethod
80
-    def _current_item_manually(cls, item_id: int, item_type: str) -> PBNode:
80
+    def _current_item_manually(cls, item_id: int, item_type: str) -> Content:
81
         # in case thread or page or other stuff is instanciated, then force
81
         # in case thread or page or other stuff is instanciated, then force
82
         # the associated item to be available through generic name tmpl_context.item to be available
82
         # the associated item to be available through generic name tmpl_context.item to be available
83
         content_api = ContentApi(tg.tmpl_context.current_user)
83
         content_api = ContentApi(tg.tmpl_context.current_user)
84
         item = content_api.get_one(item_id, item_type, tg.tmpl_context.workspace)
84
         item = content_api.get_one(item_id, item_type, tg.tmpl_context.workspace)
85
 
85
 
86
-        tg.tmpl_context.item_id = item.node_id
86
+        tg.tmpl_context.item_id = item.content_id
87
         tg.tmpl_context.item = item
87
         tg.tmpl_context.item = item
88
 
88
 
89
         return item
89
         return item
90
 
90
 
91
 
91
 
92
     @classmethod
92
     @classmethod
93
-    def current_thread(cls) -> PBNode:
93
+    def current_thread(cls) -> Content:
94
         thread_id = int(tg.request.controller_state.routing_args.get('thread_id'))
94
         thread_id = int(tg.request.controller_state.routing_args.get('thread_id'))
95
-        thread = cls._current_item_manually(thread_id, PBNodeType.Thread)
96
-        tg.tmpl_context.thread_id = thread.node_id
95
+        thread = cls._current_item_manually(thread_id, ContentType.Thread)
96
+        tg.tmpl_context.thread_id = thread.content_id
97
         tg.tmpl_context.thread = thread
97
         tg.tmpl_context.thread = thread
98
         return thread
98
         return thread
99
 
99
 
100
 
100
 
101
     @classmethod
101
     @classmethod
102
-    def current_page(cls) -> PBNode:
102
+    def current_page(cls) -> Content:
103
         page_id = int(tg.request.controller_state.routing_args.get('page_id'))
103
         page_id = int(tg.request.controller_state.routing_args.get('page_id'))
104
-        page = cls._current_item_manually(page_id, PBNodeType.Page)
105
-        tg.tmpl_context.page_id = page.node_id
104
+        page = cls._current_item_manually(page_id, ContentType.Page)
105
+        tg.tmpl_context.page_id = page.content_id
106
         tg.tmpl_context.page = page
106
         tg.tmpl_context.page = page
107
         return page
107
         return page
108
 
108
 
136
         workspace_id = tmpl_context.workspace_id
136
         workspace_id = tmpl_context.workspace_id
137
         breadcrumb = []
137
         breadcrumb = []
138
 
138
 
139
-        breadcrumb.append(BreadcrumbItem(PBNodeType.icon(PBNodeType.FAKE_Dashboard), _('Workspaces'), tg.url('/workspaces')))
140
-        breadcrumb.append(BreadcrumbItem(PBNodeType.icon(PBNodeType.FAKE_Workspace), workspace.data_label, tg.url('/workspaces/{}'.format(workspace.workspace_id))))
139
+        breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Dashboard), _('Workspaces'), tg.url('/workspaces')))
140
+        breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Workspace), workspace.label, tg.url('/workspaces/{}'.format(workspace.workspace_id))))
141
 
141
 
142
         content_api = ContentApi(tmpl_context.current_user)
142
         content_api = ContentApi(tmpl_context.current_user)
143
         if folder_id:
143
         if folder_id:
144
             breadcrumb_folder_items = []
144
             breadcrumb_folder_items = []
145
-            current_item = content_api.get_one(folder_id, PBNodeType.Any, workspace)
145
+            current_item = content_api.get_one(folder_id, ContentType.Any, workspace)
146
             is_active = True
146
             is_active = True
147
 
147
 
148
             while current_item:
148
             while current_item:
149
-                breadcrumb_item = BreadcrumbItem(PBNodeType.icon(current_item.node_type),
150
-                                                 current_item.data_label,
151
-                                                 tg.url('/workspaces/{}/folders/{}'.format(workspace_id, current_item.node_id)),
149
+                breadcrumb_item = BreadcrumbItem(ContentType.icon(current_item.type),
150
+                                                 current_item.label,
151
+                                                 tg.url('/workspaces/{}/folders/{}'.format(workspace_id, current_item.content_id)),
152
                                                  is_active)
152
                                                  is_active)
153
                 is_active = False # the first item is True, then all other are False => in the breadcrumb, only the last item is "active"
153
                 is_active = False # the first item is True, then all other are False => in the breadcrumb, only the last item is "active"
154
                 breadcrumb_folder_items.append(breadcrumb_item)
154
                 breadcrumb_folder_items.append(breadcrumb_item)
273
 
273
 
274
             msg = _('{} updated').format(self._item_type_label)
274
             msg = _('{} updated').format(self._item_type_label)
275
             tg.flash(msg, CST.STATUS_OK)
275
             tg.flash(msg, CST.STATUS_OK)
276
-            tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item.node_id))
276
+            tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item.content_id))
277
 
277
 
278
         except ValueError as e:
278
         except ValueError as e:
279
             msg = _('{} not updated - error: {}').format(self._item_type_label, str(e))
279
             msg = _('{} not updated - error: {}').format(self._item_type_label, str(e))
292
             content_api.save(item, ActionDescription.STATUS_UPDATE)
292
             content_api.save(item, ActionDescription.STATUS_UPDATE)
293
             msg = _('{} status updated').format(self._item_type_label)
293
             msg = _('{} status updated').format(self._item_type_label)
294
             tg.flash(msg, CST.STATUS_OK)
294
             tg.flash(msg, CST.STATUS_OK)
295
-            tg.redirect(self._std_url.format(item.workspace_id, item.parent_id, item.node_id))
295
+            tg.redirect(self._std_url.format(item.workspace_id, item.parent_id, item.content_id))
296
         except ValueError as e:
296
         except ValueError as e:
297
             msg = _('{} status not updated: {}').format(self._item_type_label, str(e))
297
             msg = _('{} status not updated: {}').format(self._item_type_label, str(e))
298
             tg.flash(msg, CST.STATUS_ERROR)
298
             tg.flash(msg, CST.STATUS_ERROR)
299
-            tg.redirect(self._err_url.format(item.workspace_id, item.parent_id, item.node_id))
299
+            tg.redirect(self._err_url.format(item.workspace_id, item.parent_id, item.content_id))
300
 
300
 
301
 
301
 
302
-    def get_all_fake(self, context_workspace: Workspace, context_folder: PBNode) -> [PBNode]:
302
+    def get_all_fake(self, context_workspace: Workspace, context_folder: Content) -> [Content]:
303
         """
303
         """
304
         fake methods are used in other controllers in order to simulate a client/server api.
304
         fake methods are used in other controllers in order to simulate a client/server api.
305
         the "client" controller method will include the result into its own fake_api object
305
         the "client" controller method will include the result into its own fake_api object
310
         """
310
         """
311
         workspace = context_workspace
311
         workspace = context_workspace
312
         content_api = ContentApi(tmpl_context.current_user)
312
         content_api = ContentApi(tmpl_context.current_user)
313
-        items = content_api.get_all(context_folder.node_id, self._item_type, workspace)
313
+        items = content_api.get_all(context_folder.content_id, self._item_type, workspace)
314
 
314
 
315
         dictified_items = Context(self._get_all_context).toDict(items)
315
         dictified_items = Context(self._get_all_context).toDict(items)
316
         return DictLikeClass(result = dictified_items)
316
         return DictLikeClass(result = dictified_items)
325
         item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
325
         item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
326
         try:
326
         try:
327
             next_url = self._parent_url.format(item.workspace_id, item.parent_id)
327
             next_url = self._parent_url.format(item.workspace_id, item.parent_id)
328
-            undo_url = self._std_url.format(item.workspace_id, item.parent_id, item.node_id)+'/put_archive_undo'
328
+            undo_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)+'/put_archive_undo'
329
             msg = _('{} archived. <a class="alert-link" href="{}">Cancel action</a>').format(self._item_type_label, undo_url)
329
             msg = _('{} archived. <a class="alert-link" href="{}">Cancel action</a>').format(self._item_type_label, undo_url)
330
 
330
 
331
             content_api.archive(item)
331
             content_api.archive(item)
334
             tg.flash(msg, CST.STATUS_OK, no_escape=True) # TODO allow to come back
334
             tg.flash(msg, CST.STATUS_OK, no_escape=True) # TODO allow to come back
335
             tg.redirect(next_url)
335
             tg.redirect(next_url)
336
         except ValueError as e:
336
         except ValueError as e:
337
-            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.node_id)
337
+            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
338
             msg = _('{} not archived: {}').format(self._item_type_label, str(e))
338
             msg = _('{} not archived: {}').format(self._item_type_label, str(e))
339
             tg.flash(msg, CST.STATUS_ERROR)
339
             tg.flash(msg, CST.STATUS_ERROR)
340
             tg.redirect(next_url)
340
             tg.redirect(next_url)
348
         content_api = ContentApi(tmpl_context.current_user, True) # Here we do not filter archived items
348
         content_api = ContentApi(tmpl_context.current_user, True) # Here we do not filter archived items
349
         item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
349
         item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
350
         try:
350
         try:
351
-            next_url = self._parent_url.format(item.workspace_id, item.parent_id)
351
+            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
352
             msg = _('{} unarchived.').format(self._item_type_label)
352
             msg = _('{} unarchived.').format(self._item_type_label)
353
             content_api.unarchive(item)
353
             content_api.unarchive(item)
354
             content_api.save(item, ActionDescription.UNARCHIVING)
354
             content_api.save(item, ActionDescription.UNARCHIVING)
358
 
358
 
359
         except ValueError as e:
359
         except ValueError as e:
360
             msg = _('{} not un-archived: {}').format(self._item_type_label, str(e))
360
             msg = _('{} not un-archived: {}').format(self._item_type_label, str(e))
361
-            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.node_id)
361
+            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
362
             # We still use std url because the item has not been archived
362
             # We still use std url because the item has not been archived
363
             tg.flash(msg, CST.STATUS_ERROR)
363
             tg.flash(msg, CST.STATUS_ERROR)
364
             tg.redirect(next_url)
364
             tg.redirect(next_url)
374
         try:
374
         try:
375
 
375
 
376
             next_url = self._parent_url.format(item.workspace_id, item.parent_id)
376
             next_url = self._parent_url.format(item.workspace_id, item.parent_id)
377
-            undo_url = self._std_url.format(item.workspace_id, item.parent_id, item.node_id)+'/put_delete_undo'
377
+            undo_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)+'/put_delete_undo'
378
             msg = _('{} deleted. <a class="alert-link" href="{}">Cancel action</a>').format(self._item_type_label, undo_url)
378
             msg = _('{} deleted. <a class="alert-link" href="{}">Cancel action</a>').format(self._item_type_label, undo_url)
379
             content_api.delete(item)
379
             content_api.delete(item)
380
             content_api.save(item, ActionDescription.DELETION)
380
             content_api.save(item, ActionDescription.DELETION)
383
             tg.redirect(next_url)
383
             tg.redirect(next_url)
384
 
384
 
385
         except ValueError as e:
385
         except ValueError as e:
386
-            back_url = self._std_url.format(item.workspace_id, item.parent_id, item.node_id)
386
+            back_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
387
             msg = _('{} not deleted: {}').format(self._item_type_label, str(e))
387
             msg = _('{} not deleted: {}').format(self._item_type_label, str(e))
388
             tg.flash(msg, CST.STATUS_ERROR)
388
             tg.flash(msg, CST.STATUS_ERROR)
389
             tg.redirect(back_url)
389
             tg.redirect(back_url)
398
         content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items
398
         content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items
399
         item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
399
         item = content_api.get_one(item_id, self._item_type, tmpl_context.workspace)
400
         try:
400
         try:
401
-            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.node_id)
401
+            next_url = self._std_url.format(item.workspace_id, item.parent_id, item.content_id)
402
             msg = _('{} undeleted.').format(self._item_type_label)
402
             msg = _('{} undeleted.').format(self._item_type_label)
403
             content_api.undelete(item)
403
             content_api.undelete(item)
404
             content_api.save(item, ActionDescription.UNDELETION)
404
             content_api.save(item, ActionDescription.UNDELETION)

+ 4 - 4
tracim/tracim/controllers/admin/user.py 查看文件

246
 
246
 
247
     @tg.require(predicates.in_group(Group.TIM_MANAGER_GROUPNAME))
247
     @tg.require(predicates.in_group(Group.TIM_MANAGER_GROUPNAME))
248
     @tg.expose()
248
     @tg.expose()
249
-    def post(self, name, email, password, is_tracim_manager='off', is_pod_admin='off'):
249
+    def post(self, name, email, password, is_tracim_manager='off', is_tracim_admin='off'):
250
         is_tracim_manager = h.on_off_to_boolean(is_tracim_manager)
250
         is_tracim_manager = h.on_off_to_boolean(is_tracim_manager)
251
-        is_tracim_admin = h.on_off_to_boolean(is_pod_admin)
251
+        is_tracim_admin = h.on_off_to_boolean(is_tracim_admin)
252
         current_user = tmpl_context.current_user
252
         current_user = tmpl_context.current_user
253
-        current_user = User()
253
+
254
         if current_user.profile.id < Group.TIM_ADMIN:
254
         if current_user.profile.id < Group.TIM_ADMIN:
255
             # A manager can't give large rights
255
             # A manager can't give large rights
256
             is_tracim_manager = False
256
             is_tracim_manager = False
264
             tg.redirect(self.url())
264
             tg.redirect(self.url())
265
 
265
 
266
         user = api.create_user()
266
         user = api.create_user()
267
-        user.email_address = email
267
+        user.email = email
268
         user.display_name = name
268
         user.display_name = name
269
         if password:
269
         if password:
270
             user.password = password
270
             user.password = password

+ 9 - 9
tracim/tracim/controllers/admin/workspace.py 查看文件

17
 
17
 
18
 from tracim.model.auth import Group
18
 from tracim.model.auth import Group
19
 from tracim.model.data import NodeTreeItem
19
 from tracim.model.data import NodeTreeItem
20
-from tracim.model.data import PBNode
21
-from tracim.model.data import PBNodeType
20
+from tracim.model.data import Content
21
+from tracim.model.data import ContentType
22
 from tracim.model.data import Workspace
22
 from tracim.model.data import Workspace
23
 from tracim.model.data import UserRoleInWorkspace
23
 from tracim.model.data import UserRoleInWorkspace
24
 
24
 
109
 
109
 
110
         tg.flash(flash_msg_template.format(
110
         tg.flash(flash_msg_template.format(
111
             role.user.get_display_name(),
111
             role.user.get_display_name(),
112
-            tg.tmpl_context.workspace.data_label,
112
+            tg.tmpl_context.workspace.label,
113
             role.role_as_label()), CST.STATUS_OK)
113
             role.role_as_label()), CST.STATUS_OK)
114
 
114
 
115
         tg.redirect(self.parent_controller.url(tg.tmpl_context.workspace_id))
115
         tg.redirect(self.parent_controller.url(tg.tmpl_context.workspace_id))
199
 
199
 
200
         workspace = workspace_api_controller.create_workspace(name, description)
200
         workspace = workspace_api_controller.create_workspace(name, description)
201
 
201
 
202
-        tg.flash(_('{} workspace created.').format(workspace.data_label), CST.STATUS_OK)
202
+        tg.flash(_('{} workspace created.').format(workspace.label), CST.STATUS_OK)
203
         tg.redirect(self.url())
203
         tg.redirect(self.url())
204
         return
204
         return
205
 
205
 
219
         workspace_api_controller = WorkspaceApi(user)
219
         workspace_api_controller = WorkspaceApi(user)
220
 
220
 
221
         workspace = workspace_api_controller.get_one(id)
221
         workspace = workspace_api_controller.get_one(id)
222
-        workspace.data_label = name
223
-        workspace.data_comment = description
222
+        workspace.label = name
223
+        workspace.description = description
224
         workspace_api_controller.save(workspace)
224
         workspace_api_controller.save(workspace)
225
 
225
 
226
-        tg.flash(_('{} workspace updated.').format(workspace.data_label), CST.STATUS_OK)
226
+        tg.flash(_('{} workspace updated.').format(workspace.label), CST.STATUS_OK)
227
         tg.redirect(self.url(workspace.workspace_id))
227
         tg.redirect(self.url(workspace.workspace_id))
228
         return
228
         return
229
 
229
 
245
         workspace = api.get_one(workspace_id)
245
         workspace = api.get_one(workspace_id)
246
         api.delete_one(workspace_id)
246
         api.delete_one(workspace_id)
247
 
247
 
248
-        workspace_label = workspace.data_label
248
+        workspace_label = workspace.label
249
         undo_url = self.url(workspace_id, self.restore.__name__)
249
         undo_url = self.url(workspace_id, self.restore.__name__)
250
 
250
 
251
         tg.flash(_('{} workspace deleted. In case of error, you can <a class="alert-link" href="{}">restore it</a>.').format(workspace_label, undo_url), CST.STATUS_OK, no_escape=True)
251
         tg.flash(_('{} workspace deleted. In case of error, you can <a class="alert-link" href="{}">restore it</a>.').format(workspace_label, undo_url), CST.STATUS_OK, no_escape=True)
258
         api = WorkspaceApi(tg.tmpl_context.current_user)
258
         api = WorkspaceApi(tg.tmpl_context.current_user)
259
         workspace = api.restore_one(workspace_id, True)
259
         workspace = api.restore_one(workspace_id, True)
260
 
260
 
261
-        workspace_label = workspace.data_label
261
+        workspace_label = workspace.label
262
         undo_url = self.url(workspace_id, 'delete')
262
         undo_url = self.url(workspace_id, 'delete')
263
 
263
 
264
         tg.flash(_('{} workspace restored.').format(workspace_label), CST.STATUS_OK)
264
         tg.flash(_('{} workspace restored.').format(workspace_label), CST.STATUS_OK)

+ 46 - 46
tracim/tracim/controllers/content.py 查看文件

24
 
24
 
25
 from tracim.model.serializers import Context, CTX, DictLikeClass
25
 from tracim.model.serializers import Context, CTX, DictLikeClass
26
 from tracim.model.data import ActionDescription
26
 from tracim.model.data import ActionDescription
27
-from tracim.model.data import PBNode
28
-from tracim.model.data import PBNodeType
27
+from tracim.model.data import Content
28
+from tracim.model.data import ContentType
29
 from tracim.model.data import Workspace
29
 from tracim.model.data import Workspace
30
 
30
 
31
 
31
 
71
 
71
 
72
     @property
72
     @property
73
     def _item_type(self):
73
     def _item_type(self):
74
-        return PBNodeType.File
74
+        return ContentType.File
75
 
75
 
76
     @property
76
     @property
77
     def _item_type_label(self):
77
     def _item_type_label(self):
135
                 if not revision_to_send:
135
                 if not revision_to_send:
136
                     revision_to_send = revision
136
                     revision_to_send = revision
137
 
137
 
138
-                if revision.version_id>revision_to_send.version_id:
138
+                if revision.revision_id>revision_to_send.revision_id:
139
                     revision_to_send = revision
139
                     revision_to_send = revision
140
         else:
140
         else:
141
             for revision in item.revisions:
141
             for revision in item.revisions:
142
-                if revision.version_id==item.revision_to_serialize:
142
+                if revision.revision_id==item.revision_to_serialize:
143
                     revision_to_send = revision
143
                     revision_to_send = revision
144
                     break
144
                     break
145
 
145
 
146
         content_type = 'application/x-download'
146
         content_type = 'application/x-download'
147
-        if revision_to_send.data_file_mime_type:
148
-            content_type = str(revision_to_send.data_file_mime_type)
149
-            tg.response.headers['Content-type'] = str(revision_to_send.data_file_mime_type)
147
+        if revision_to_send.file_mimetype:
148
+            content_type = str(revision_to_send.file_mimetype)
149
+            tg.response.headers['Content-type'] = str(revision_to_send.file_mimetype)
150
 
150
 
151
         tg.response.headers['Content-Type'] = content_type
151
         tg.response.headers['Content-Type'] = content_type
152
-        tg.response.headers['Content-Disposition'] = str('attachment; filename="{}"'.format(revision_to_send.data_file_name))
153
-        return revision_to_send.data_file_content
152
+        tg.response.headers['Content-Disposition'] = str('attachment; filename="{}"'.format(revision_to_send.file_name))
153
+        return revision_to_send.file_content
154
 
154
 
155
 
155
 
156
-    def get_all_fake(self, context_workspace: Workspace, context_folder: PBNode):
156
+    def get_all_fake(self, context_workspace: Workspace, context_folder: Content):
157
         """
157
         """
158
         fake methods are used in other controllers in order to simulate a client/server api.
158
         fake methods are used in other controllers in order to simulate a client/server api.
159
         the "client" controller method will include the result into its own fake_api object
159
         the "client" controller method will include the result into its own fake_api object
164
         """
164
         """
165
         workspace = context_workspace
165
         workspace = context_workspace
166
         content_api = ContentApi(tmpl_context.current_user)
166
         content_api = ContentApi(tmpl_context.current_user)
167
-        files = content_api.get_all(context_folder.node_id, PBNodeType.File, workspace)
167
+        files = content_api.get_all(context_folder.content_id, ContentType.File, workspace)
168
 
168
 
169
         dictified_files = Context(CTX.FILES).toDict(files)
169
         dictified_files = Context(CTX.FILES).toDict(files)
170
         return DictLikeClass(result = dictified_files)
170
         return DictLikeClass(result = dictified_files)
178
 
178
 
179
         api = ContentApi(tmpl_context.current_user)
179
         api = ContentApi(tmpl_context.current_user)
180
 
180
 
181
-        file = api.create(PBNodeType.File, workspace, tmpl_context.folder, label)
181
+        file = api.create(ContentType.File, workspace, tmpl_context.folder, label)
182
         api.update_file_data(file, file_data.filename, file_data.type, file_data.file.read())
182
         api.update_file_data(file, file_data.filename, file_data.type, file_data.file.read())
183
         api.save(file, ActionDescription.CREATION)
183
         api.save(file, ActionDescription.CREATION)
184
 
184
 
185
         tg.flash(_('File created'), CST.STATUS_OK)
185
         tg.flash(_('File created'), CST.STATUS_OK)
186
-        tg.redirect(tg.url('/workspaces/{}/folders/{}/files/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, file.node_id))
186
+        tg.redirect(tg.url('/workspaces/{}/folders/{}/files/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, file.content_id))
187
 
187
 
188
 
188
 
189
     @tg.require(current_user_is_contributor())
189
     @tg.require(current_user_is_contributor())
196
             api = ContentApi(tmpl_context.current_user)
196
             api = ContentApi(tmpl_context.current_user)
197
             item = api.get_one(int(item_id), self._item_type, workspace)
197
             item = api.get_one(int(item_id), self._item_type, workspace)
198
             if comment:
198
             if comment:
199
-                api.update_content(item, label if label else item.data_label, comment)
199
+                api.update_content(item, label if label else item.label, comment)
200
 
200
 
201
             if isinstance(file_data, FieldStorage):
201
             if isinstance(file_data, FieldStorage):
202
                 api.update_file_data(item, file_data.filename, file_data.type, file_data.file.read())
202
                 api.update_file_data(item, file_data.filename, file_data.type, file_data.file.read())
205
 
205
 
206
             msg = _('{} updated').format(self._item_type_label)
206
             msg = _('{} updated').format(self._item_type_label)
207
             tg.flash(msg, CST.STATUS_OK)
207
             tg.flash(msg, CST.STATUS_OK)
208
-            tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item.node_id))
208
+            tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, item.content_id))
209
 
209
 
210
         except ValueError as e:
210
         except ValueError as e:
211
             msg = _('{} not updated - error: {}').format(self._item_type_label, str(e))
211
             msg = _('{} not updated - error: {}').format(self._item_type_label, str(e))
229
 
229
 
230
     @property
230
     @property
231
     def _item_type(self):
231
     def _item_type(self):
232
-        return PBNodeType.Page
232
+        return ContentType.Page
233
 
233
 
234
     @property
234
     @property
235
     def _item_type_label(self):
235
     def _item_type_label(self):
261
 
261
 
262
         content_api = ContentApi(user)
262
         content_api = ContentApi(user)
263
         if revision_id:
263
         if revision_id:
264
-            page = content_api.get_one_from_revision(page_id, PBNodeType.Page, workspace, revision_id)
264
+            page = content_api.get_one_from_revision(page_id, ContentType.Page, workspace, revision_id)
265
         else:
265
         else:
266
-            page = content_api.get_one(page_id, PBNodeType.Page, workspace)
266
+            page = content_api.get_one(page_id, ContentType.Page, workspace)
267
 
267
 
268
         fake_api_breadcrumb = self.get_breadcrumb(page_id)
268
         fake_api_breadcrumb = self.get_breadcrumb(page_id)
269
         fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb, current_user=current_user_content)
269
         fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb, current_user=current_user_content)
273
         return DictLikeClass(result = dictified_page, fake_api=fake_api)
273
         return DictLikeClass(result = dictified_page, fake_api=fake_api)
274
 
274
 
275
 
275
 
276
-    def get_all_fake(self, context_workspace: Workspace, context_folder: PBNode):
276
+    def get_all_fake(self, context_workspace: Workspace, context_folder: Content):
277
         """
277
         """
278
         fake methods are used in other controllers in order to simulate a client/server api.
278
         fake methods are used in other controllers in order to simulate a client/server api.
279
         the "client" controller method will include the result into its own fake_api object
279
         the "client" controller method will include the result into its own fake_api object
284
         """
284
         """
285
         workspace = context_workspace
285
         workspace = context_workspace
286
         content_api = ContentApi(tmpl_context.current_user)
286
         content_api = ContentApi(tmpl_context.current_user)
287
-        pages = content_api.get_all(context_folder.node_id, PBNodeType.Page, workspace)
287
+        pages = content_api.get_all(context_folder.content_id, ContentType.Page, workspace)
288
 
288
 
289
         dictified_pages = Context(CTX.PAGES).toDict(pages)
289
         dictified_pages = Context(CTX.PAGES).toDict(pages)
290
         return DictLikeClass(result = dictified_pages)
290
         return DictLikeClass(result = dictified_pages)
298
 
298
 
299
         api = ContentApi(tmpl_context.current_user)
299
         api = ContentApi(tmpl_context.current_user)
300
 
300
 
301
-        page = api.create(PBNodeType.Page, workspace, tmpl_context.folder, label)
302
-        page.data_content = content
301
+        page = api.create(ContentType.Page, workspace, tmpl_context.folder, label)
302
+        page.description = content
303
         api.save(page, ActionDescription.CREATION)
303
         api.save(page, ActionDescription.CREATION)
304
 
304
 
305
         tg.flash(_('Page created'), CST.STATUS_OK)
305
         tg.flash(_('Page created'), CST.STATUS_OK)
306
-        tg.redirect(tg.url('/workspaces/{}/folders/{}/pages/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, page.node_id))
306
+        tg.redirect(tg.url('/workspaces/{}/folders/{}/pages/{}').format(tmpl_context.workspace_id, tmpl_context.folder_id, page.content_id))
307
 
307
 
308
 
308
 
309
 
309
 
337
 
337
 
338
     @property
338
     @property
339
     def _item_type(self):
339
     def _item_type(self):
340
-        return PBNodeType.Thread
340
+        return ContentType.Thread
341
 
341
 
342
 
342
 
343
     @property
343
     @property
374
 
374
 
375
         api = ContentApi(tmpl_context.current_user)
375
         api = ContentApi(tmpl_context.current_user)
376
 
376
 
377
-        thread = api.create(PBNodeType.Thread, workspace, tmpl_context.folder, label)
378
-        # FIXME - DO NOT DUPLCIATE FIRST MESSAGE thread.data_content = content
377
+        thread = api.create(ContentType.Thread, workspace, tmpl_context.folder, label)
378
+        # FIXME - DO NOT DUPLCIATE FIRST MESSAGE thread.description = content
379
         api.save(thread, ActionDescription.CREATION)
379
         api.save(thread, ActionDescription.CREATION)
380
 
380
 
381
-        comment = api.create(PBNodeType.Comment, workspace, thread, label)
382
-        comment.data_label = ''
383
-        comment.data_content = content
381
+        comment = api.create(ContentType.Comment, workspace, thread, label)
382
+        comment.label = ''
383
+        comment.description = content
384
         api.save(comment, ActionDescription.COMMENT)
384
         api.save(comment, ActionDescription.COMMENT)
385
 
385
 
386
         tg.flash(_('Thread created'), CST.STATUS_OK)
386
         tg.flash(_('Thread created'), CST.STATUS_OK)
387
-        tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, thread.node_id))
387
+        tg.redirect(self._std_url.format(tmpl_context.workspace_id, tmpl_context.folder_id, thread.content_id))
388
 
388
 
389
 
389
 
390
     @tg.require(current_user_is_reader())
390
     @tg.require(current_user_is_reader())
398
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
398
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
399
 
399
 
400
         content_api = ContentApi(user)
400
         content_api = ContentApi(user)
401
-        thread = content_api.get_one(thread_id, PBNodeType.Thread, workspace)
401
+        thread = content_api.get_one(thread_id, ContentType.Thread, workspace)
402
 
402
 
403
         fake_api_breadcrumb = self.get_breadcrumb(thread_id)
403
         fake_api_breadcrumb = self.get_breadcrumb(thread_id)
404
         fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb, current_user=current_user_content)
404
         fake_api_content = DictLikeClass(breadcrumb=fake_api_breadcrumb, current_user=current_user_content)
418
         user = tmpl_context.current_user
418
         user = tmpl_context.current_user
419
         workspace = tmpl_context.workspace
419
         workspace = tmpl_context.workspace
420
 
420
 
421
-        item = ContentApi(user).get_one(item_id, PBNodeType.Any, workspace)
421
+        item = ContentApi(user).get_one(item_id, ContentType.Any, workspace)
422
         raise NotImplementedError
422
         raise NotImplementedError
423
         return item
423
         return item
424
 
424
 
438
         workspace = tmpl_context.workspace
438
         workspace = tmpl_context.workspace
439
 
439
 
440
         content_api = ContentApi(user)
440
         content_api = ContentApi(user)
441
-        item = content_api.get_one(item_id, PBNodeType.Any, workspace)
441
+        item = content_api.get_one(item_id, ContentType.Any, workspace)
442
 
442
 
443
         dictified_item = Context(CTX.DEFAULT).toDict(item, 'item')
443
         dictified_item = Context(CTX.DEFAULT).toDict(item, 'item')
444
         return DictLikeClass(result = dictified_item)
444
         return DictLikeClass(result = dictified_item)
458
         new_workspace, new_parent = convert_id_into_instances(folder_id)
458
         new_workspace, new_parent = convert_id_into_instances(folder_id)
459
 
459
 
460
         api = ContentApi(tmpl_context.current_user)
460
         api = ContentApi(tmpl_context.current_user)
461
-        item = api.get_one(item_id, PBNodeType.Any, workspace)
461
+        item = api.get_one(item_id, ContentType.Any, workspace)
462
         api.move(item, new_parent)
462
         api.move(item, new_parent)
463
         next_url = self.parent_controller.url(item_id)
463
         next_url = self.parent_controller.url(item_id)
464
         if new_parent:
464
         if new_parent:
465
-            tg.flash(_('Item moved to {}').format(new_parent.data_label), CST.STATUS_OK)
465
+            tg.flash(_('Item moved to {}').format(new_parent.label), CST.STATUS_OK)
466
         else:
466
         else:
467
             tg.flash(_('Item moved to workspace root'))
467
             tg.flash(_('Item moved to workspace root'))
468
 
468
 
498
         workspace = tmpl_context.workspace
498
         workspace = tmpl_context.workspace
499
 
499
 
500
         content_api = ContentApi(user)
500
         content_api = ContentApi(user)
501
-        folder = content_api.get_one(folder_id, PBNodeType.Folder, workspace)
501
+        folder = content_api.get_one(folder_id, ContentType.Folder, workspace)
502
 
502
 
503
         dictified_folder = Context(CTX.FOLDER).toDict(folder, 'folder')
503
         dictified_folder = Context(CTX.FOLDER).toDict(folder, 'folder')
504
         return DictLikeClass(result = dictified_folder)
504
         return DictLikeClass(result = dictified_folder)
516
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
516
         current_user_content.roles.sort(key=lambda role: role.workspace.name)
517
 
517
 
518
         content_api = ContentApi(user)
518
         content_api = ContentApi(user)
519
-        folder = content_api.get_one(folder_id, PBNodeType.Folder, workspace)
519
+        folder = content_api.get_one(folder_id, ContentType.Folder, workspace)
520
 
520
 
521
         fake_api_breadcrumb = self.get_breadcrumb(folder_id)
521
         fake_api_breadcrumb = self.get_breadcrumb(folder_id)
522
-        fake_api_subfolders = self.get_all_fake(workspace, folder.node_id).result
522
+        fake_api_subfolders = self.get_all_fake(workspace, folder.content_id).result
523
         fake_api_pages = self.pages.get_all_fake(workspace, folder).result
523
         fake_api_pages = self.pages.get_all_fake(workspace, folder).result
524
         fake_api_files = self.files.get_all_fake(workspace, folder).result
524
         fake_api_files = self.files.get_all_fake(workspace, folder).result
525
         fake_api_threads = self.threads.get_all_fake(workspace, folder).result
525
         fake_api_threads = self.threads.get_all_fake(workspace, folder).result
550
         """
550
         """
551
         workspace = context_workspace
551
         workspace = context_workspace
552
         content_api = ContentApi(tmpl_context.current_user)
552
         content_api = ContentApi(tmpl_context.current_user)
553
-        parent_folder = content_api.get_one(parent_id, PBNodeType.Folder)
553
+        parent_folder = content_api.get_one(parent_id, ContentType.Folder)
554
         folders = content_api.get_child_folders(parent_folder, workspace)
554
         folders = content_api.get_child_folders(parent_folder, workspace)
555
 
555
 
556
         folders = Context(CTX.FOLDERS).toDict(folders)
556
         folders = Context(CTX.FOLDERS).toDict(folders)
572
         try:
572
         try:
573
             parent = None
573
             parent = None
574
             if parent_id:
574
             if parent_id:
575
-                parent = api.get_one(int(parent_id), PBNodeType.Folder, workspace)
576
-            folder = api.create(PBNodeType.Folder, workspace, parent, label)
575
+                parent = api.get_one(int(parent_id), ContentType.Folder, workspace)
576
+            folder = api.create(ContentType.Folder, workspace, parent, label)
577
 
577
 
578
             subcontent = dict(
578
             subcontent = dict(
579
                 folder = True if can_contain_folders=='on' else False,
579
                 folder = True if can_contain_folders=='on' else False,
585
             api.save(folder)
585
             api.save(folder)
586
 
586
 
587
             tg.flash(_('Folder created'), CST.STATUS_OK)
587
             tg.flash(_('Folder created'), CST.STATUS_OK)
588
-            redirect_url = redirect_url_tmpl.format(tmpl_context.workspace_id, folder.node_id)
588
+            redirect_url = redirect_url_tmpl.format(tmpl_context.workspace_id, folder.content_id)
589
         except Exception as e:
589
         except Exception as e:
590
             logger.error(self, 'An unexpected exception has been catched. Look at the traceback below.')
590
             logger.error(self, 'An unexpected exception has been catched. Look at the traceback below.')
591
             traceback.print_exc()
591
             traceback.print_exc()
614
         next_url = ''
614
         next_url = ''
615
 
615
 
616
         try:
616
         try:
617
-            folder = api.get_one(int(folder_id), PBNodeType.Folder, workspace)
617
+            folder = api.get_one(int(folder_id), ContentType.Folder, workspace)
618
             subcontent = dict(
618
             subcontent = dict(
619
                 folder = True if can_contain_folders=='on' else False,
619
                 folder = True if can_contain_folders=='on' else False,
620
                 thread = True if can_contain_threads=='on' else False,
620
                 thread = True if can_contain_threads=='on' else False,
621
                 file = True if can_contain_files=='on' else False,
621
                 file = True if can_contain_files=='on' else False,
622
                 page = True if can_contain_pages=='on' else False
622
                 page = True if can_contain_pages=='on' else False
623
             )
623
             )
624
-            api.update_content(folder, label, folder.data_content)
624
+            api.update_content(folder, label, folder.description)
625
             api.set_allowed_content(folder, subcontent)
625
             api.set_allowed_content(folder, subcontent)
626
             api.save(folder)
626
             api.save(folder)
627
 
627
 
628
             tg.flash(_('Folder updated'), CST.STATUS_OK)
628
             tg.flash(_('Folder updated'), CST.STATUS_OK)
629
 
629
 
630
-            next_url = self.url(folder.node_id)
630
+            next_url = self.url(folder.content_id)
631
 
631
 
632
         except Exception as e:
632
         except Exception as e:
633
             tg.flash(_('Folder not updated: {}').format(str(e)), CST.STATUS_ERROR)
633
             tg.flash(_('Folder not updated: {}').format(str(e)), CST.STATUS_ERROR)

+ 0 - 1
tracim/tracim/controllers/help.py 查看文件

25
         # FIXME - NOT REALLY SAFE BECAUSE SOME UNWANTED FILE MAY BE USED AS HELP PAGE
25
         # FIXME - NOT REALLY SAFE BECAUSE SOME UNWANTED FILE MAY BE USED AS HELP PAGE
26
         if help_page:
26
         if help_page:
27
             help_page_path = 'mako:tracim.templates.help.page-{}'.format(help_page)
27
             help_page_path = 'mako:tracim.templates.help.page-{}'.format(help_page)
28
-            print('TEMPLATE:', help_page_path)
29
             override_template(HelpController.page, help_page_path)
28
             override_template(HelpController.page, help_page_path)
30
 
29
 
31
         return dict(mode=mode)
30
         return dict(mode=mode)

+ 5 - 5
tracim/tracim/controllers/workspace.py 查看文件

17
 from tracim.lib.workspace import WorkspaceApi
17
 from tracim.lib.workspace import WorkspaceApi
18
 
18
 
19
 from tracim.model.data import NodeTreeItem
19
 from tracim.model.data import NodeTreeItem
20
-from tracim.model.data import PBNode
21
-from tracim.model.data import PBNodeType
20
+from tracim.model.data import Content
21
+from tracim.model.data import ContentType
22
 from tracim.model.data import Workspace
22
 from tracim.model.data import Workspace
23
 from tracim.model.data import UserRoleInWorkspace
23
 from tracim.model.data import UserRoleInWorkspace
24
 
24
 
85
             return dictified_workspaces
85
             return dictified_workspaces
86
 
86
 
87
 
87
 
88
-        allowed_content_types = PBNodeType.allowed_types_from_str(folder_allowed_content_types)
88
+        allowed_content_types = ContentType.allowed_types_from_str(folder_allowed_content_types)
89
         ignored_item_ids = [int(ignore_id)] if ignore_id else []
89
         ignored_item_ids = [int(ignore_id)] if ignore_id else []
90
 
90
 
91
         # Now complex case: we must return a structured tree
91
         # Now complex case: we must return a structured tree
147
 
147
 
148
     def _build_sibling_list_of_tree_items(self,
148
     def _build_sibling_list_of_tree_items(self,
149
                                           workspace: Workspace,
149
                                           workspace: Workspace,
150
-                                          content: PBNode,
150
+                                          content: Content,
151
                                           children: [NodeTreeItem],
151
                                           children: [NodeTreeItem],
152
                                           select_active_node = False,
152
                                           select_active_node = False,
153
                                           allowed_content_types: list = [],
153
                                           allowed_content_types: list = [],
154
-                                          ignored_item_ids: list = []) -> (PBNode, [NodeTreeItem]):
154
+                                          ignored_item_ids: list = []) -> (Content, [NodeTreeItem]):
155
         api = ContentApi(tmpl_context.current_user)
155
         api = ContentApi(tmpl_context.current_user)
156
         tree_items = []
156
         tree_items = []
157
 
157
 

二进制
tracim/tracim/i18n/fr/LC_MESSAGES/tracim.mo 查看文件


+ 150 - 187
tracim/tracim/i18n/fr/LC_MESSAGES/tracim.po 查看文件

7
 msgstr ""
7
 msgstr ""
8
 "Project-Id-Version: pod 0.1\n"
8
 "Project-Id-Version: pod 0.1\n"
9
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
9
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10
-"POT-Creation-Date: 2014-10-23 15:20+0200\n"
11
-"PO-Revision-Date: 2014-10-22 19:26+0100\n"
10
+"POT-Creation-Date: 2014-10-24 13:36+0200\n"
11
+"PO-Revision-Date: 2014-10-24 13:36+0100\n"
12
 "Last-Translator: Damien Accorsi <damien.accorsi@free.fr>\n"
12
 "Last-Translator: Damien Accorsi <damien.accorsi@free.fr>\n"
13
 "Language-Team: fr_FR <LL@li.org>\n"
13
 "Language-Team: fr_FR <LL@li.org>\n"
14
 "Plural-Forms: nplurals=2; plural=(n > 1)\n"
14
 "Plural-Forms: nplurals=2; plural=(n > 1)\n"
30
 "\n"
30
 "\n"
31
 "%(password_reset_link)s\n"
31
 "%(password_reset_link)s\n"
32
 "\n"
32
 "\n"
33
-"If you no longer wish to make the above change, or if you did not "
34
-"initiate this request, please disregard and/or delete this e-mail.\n"
33
+"If you no longer wish to make the above change, or if you did not initiate this request, please disregard and/or delete this e-mail.\n"
35
 msgstr ""
34
 msgstr ""
36
 "\n"
35
 "\n"
37
-"Nous avons reçu une requête de réinitialisation du mot de passe pour ce "
38
-"compte utilisateur.\n"
36
+"Nous avons reçu une requête de réinitialisation du mot de passe pour ce compte utilisateur.\n"
39
 "Cliquer sur le lien ci-dessous pour réinitialiser votre mot de passe :\n"
37
 "Cliquer sur le lien ci-dessous pour réinitialiser votre mot de passe :\n"
40
 "\n"
38
 "\n"
41
 "%(password_reset_link)s\n"
39
 "%(password_reset_link)s\n"
42
 "\n"
40
 "\n"
43
-"Si vous ne souhaitez plus procéder à ce changement, ou si vous n'êtes pas"
44
-" à l'originie de cette requête, merci d'ignorer et/ou supprimer cet "
45
-"e-mail.\n"
41
+"Si vous ne souhaitez plus procéder à ce changement, ou si vous n'êtes pas à l'originie de cette requête, merci d'ignorer et/ou supprimer cet e-mail.\n"
46
 
42
 
47
 #: tracim/controllers/__init__.py:139
43
 #: tracim/controllers/__init__.py:139
48
 #: tracim/templates/master_authenticated.mak:87
44
 #: tracim/templates/master_authenticated.mak:87
49
 #: tracim/templates/master_no_toolbar_no_login.mak:112
45
 #: tracim/templates/master_no_toolbar_no_login.mak:112
50
-#: tracim/templates/user_get_one.mak:39 tracim/templates/user_profile.mak:39
46
+#: tracim/templates/user_get_one.mak:39
47
+#: tracim/templates/user_profile.mak:39
51
 #: tracim/templates/user_workspace_folder_file_get_one.mak:10
48
 #: tracim/templates/user_workspace_folder_file_get_one.mak:10
52
 #: tracim/templates/user_workspace_folder_get_one.mak:11
49
 #: tracim/templates/user_workspace_folder_get_one.mak:11
53
 #: tracim/templates/user_workspace_folder_page_get_one.mak:11
50
 #: tracim/templates/user_workspace_folder_page_get_one.mak:11
58
 msgid "Workspaces"
55
 msgid "Workspaces"
59
 msgstr "Espaces de travail"
56
 msgstr "Espaces de travail"
60
 
57
 
61
-#: tracim/controllers/__init__.py:274 tracim/controllers/content.py:206
58
+#: tracim/controllers/__init__.py:274
59
+#: tracim/controllers/content.py:206
62
 msgid "{} updated"
60
 msgid "{} updated"
63
 msgstr "{} mis(e) à jour"
61
 msgstr "{} mis(e) à jour"
64
 
62
 
65
-#: tracim/controllers/__init__.py:279 tracim/controllers/content.py:211
63
+#: tracim/controllers/__init__.py:279
64
+#: tracim/controllers/content.py:211
66
 msgid "{} not updated - error: {}"
65
 msgid "{} not updated - error: {}"
67
 msgstr "{} pas mis(e) à jour - erreur : {}"
66
 msgstr "{} pas mis(e) à jour - erreur : {}"
68
 
67
 
92
 
91
 
93
 #: tracim/controllers/__init__.py:378
92
 #: tracim/controllers/__init__.py:378
94
 msgid "{} deleted. <a class=\"alert-link\" href=\"{}\">Cancel action</a>"
93
 msgid "{} deleted. <a class=\"alert-link\" href=\"{}\">Cancel action</a>"
95
-msgstr ""
96
-"{} supprimé(e). <a class=\"alert-link\" href=\"{}\">Annuler "
97
-"l'opération</a>"
94
+msgstr "{} supprimé(e). <a class=\"alert-link\" href=\"{}\">Annuler l'opération</a>"
98
 
95
 
99
 #: tracim/controllers/__init__.py:387
96
 #: tracim/controllers/__init__.py:387
100
 msgid "{} not deleted: {}"
97
 msgid "{} not deleted: {}"
173
 msgid "Successfully logged out. We hope to see you soon!"
170
 msgid "Successfully logged out. We hope to see you soon!"
174
 msgstr "Déconnexion réussie. Nous espérons vous revoir bientôt !"
171
 msgstr "Déconnexion réussie. Nous espérons vous revoir bientôt !"
175
 
172
 
176
-#: tracim/controllers/user.py:64 tracim/controllers/admin/user.py:186
173
+#: tracim/controllers/user.py:64
174
+#: tracim/controllers/admin/user.py:201
177
 msgid "Empty password is not allowed."
175
 msgid "Empty password is not allowed."
178
 msgstr "Le mot de passe ne doit pas être vide"
176
 msgstr "Le mot de passe ne doit pas être vide"
179
 
177
 
180
-#: tracim/controllers/user.py:68 tracim/controllers/admin/user.py:190
178
+#: tracim/controllers/user.py:68
179
+#: tracim/controllers/admin/user.py:205
181
 msgid "The current password you typed is wrong"
180
 msgid "The current password you typed is wrong"
182
 msgstr "Le mot de passe que vous avez tapé est erroné"
181
 msgstr "Le mot de passe que vous avez tapé est erroné"
183
 
182
 
184
-#: tracim/controllers/user.py:72 tracim/controllers/admin/user.py:194
183
+#: tracim/controllers/user.py:72
184
+#: tracim/controllers/admin/user.py:209
185
 msgid "New passwords do not match."
185
 msgid "New passwords do not match."
186
 msgstr "Les mots de passe ne concordent pas"
186
 msgstr "Les mots de passe ne concordent pas"
187
 
187
 
188
-#: tracim/controllers/user.py:78 tracim/controllers/admin/user.py:200
188
+#: tracim/controllers/user.py:78
189
+#: tracim/controllers/admin/user.py:215
189
 msgid "Your password has been changed"
190
 msgid "Your password has been changed"
190
 msgstr "Votre mot de passe a été changé"
191
 msgstr "Votre mot de passe a été changé"
191
 
192
 
193
 msgid "profile updated."
194
 msgid "profile updated."
194
 msgstr "Profil mis à jour"
195
 msgstr "Profil mis à jour"
195
 
196
 
196
-#: tracim/controllers/admin/user.py:73
197
+#: tracim/controllers/admin/user.py:84
197
 msgid "You can't change your own profile"
198
 msgid "You can't change your own profile"
198
 msgstr "Vous ne pouvez pas changer votre propre profil"
199
 msgstr "Vous ne pouvez pas changer votre propre profil"
199
 
200
 
200
-#: tracim/controllers/admin/user.py:80
201
+#: tracim/controllers/admin/user.py:91
202
+#: tracim/controllers/admin/user.py:142
201
 msgid "Unknown profile"
203
 msgid "Unknown profile"
202
 msgstr "Profil inconnu"
204
 msgstr "Profil inconnu"
203
 
205
 
204
-#: tracim/controllers/admin/user.py:87
206
+#: tracim/controllers/admin/user.py:98
205
 msgid "User updated."
207
 msgid "User updated."
206
 msgstr "Utilisateur mis à jour"
208
 msgstr "Utilisateur mis à jour"
207
 
209
 
208
-#: tracim/controllers/admin/user.py:103
210
+#: tracim/controllers/admin/user.py:114
209
 msgid "User {} is now a basic user"
211
 msgid "User {} is now a basic user"
210
 msgstr "L'utilisateur {} est désormais un utilisateur standard"
212
 msgstr "L'utilisateur {} est désormais un utilisateur standard"
211
 
213
 
212
-#: tracim/controllers/admin/user.py:116
214
+#: tracim/controllers/admin/user.py:127
213
 msgid "User {} can now workspaces"
215
 msgid "User {} can now workspaces"
214
 msgstr "L'utilisateur {} peut désormais créer des espaces de travail"
216
 msgstr "L'utilisateur {} peut désormais créer des espaces de travail"
215
 
217
 
216
-#: tracim/controllers/admin/user.py:127
218
+#: tracim/controllers/admin/user.py:138
217
 msgid "User {} is now an administrator"
219
 msgid "User {} is now an administrator"
218
 msgstr "L'utilisateur {} est désormais administrateur"
220
 msgstr "L'utilisateur {} est désormais administrateur"
219
 
221
 
220
-#: tracim/controllers/admin/user.py:248
222
+#: tracim/controllers/admin/user.py:263
221
 msgid "A user with email address \"{}\" already exists."
223
 msgid "A user with email address \"{}\" already exists."
222
 msgstr "Un utilisateur avec l'adresse email \"{}\" existe déjà."
224
 msgstr "Un utilisateur avec l'adresse email \"{}\" existe déjà."
223
 
225
 
224
-#: tracim/controllers/admin/user.py:268
226
+#: tracim/controllers/admin/user.py:283
225
 msgid "User {} created."
227
 msgid "User {} created."
226
 msgstr "Utilisateur {} créé."
228
 msgstr "Utilisateur {} créé."
227
 
229
 
228
-#: tracim/controllers/admin/user.py:307
230
+#: tracim/controllers/admin/user.py:322
229
 msgid "User {} updated."
231
 msgid "User {} updated."
230
 msgstr "Utilisateur {} mis à jour."
232
 msgstr "Utilisateur {} mis à jour."
231
 
233
 
232
-#: tracim/controllers/admin/user.py:321
234
+#: tracim/controllers/admin/user.py:336
233
 msgid "User {} activated."
235
 msgid "User {} activated."
234
 msgstr "Utilisateur {} activé."
236
 msgstr "Utilisateur {} activé."
235
 
237
 
236
-#: tracim/controllers/admin/user.py:332
238
+#: tracim/controllers/admin/user.py:347
237
 msgid "You can't de-activate your own account"
239
 msgid "You can't de-activate your own account"
238
 msgstr "Vous ne pouvez pas désactiver votre propre compte"
240
 msgstr "Vous ne pouvez pas désactiver votre propre compte"
239
 
241
 
240
-#: tracim/controllers/admin/user.py:337
242
+#: tracim/controllers/admin/user.py:352
241
 msgid "User {} desactivated"
243
 msgid "User {} desactivated"
242
 msgstr "Utilisateur {} désactivé."
244
 msgstr "Utilisateur {} désactivé."
243
 
245
 
246
 msgstr "Vous ne pouvez pas vous retirer de cet espace de travail."
248
 msgstr "Vous ne pouvez pas vous retirer de cet espace de travail."
247
 
249
 
248
 #: tracim/controllers/admin/workspace.py:85
250
 #: tracim/controllers/admin/workspace.py:85
249
-msgid ""
250
-"User {} removed. You can <a class=\"alert-link\" href=\"{}\">restore "
251
-"it</a>"
251
+msgid "User {} removed. You can <a class=\"alert-link\" href=\"{}\">restore it</a>"
252
 msgstr "{} archivé(e). <a class=\"alert-link\" href=\"{}\">Annuler l'opération</a>"
252
 msgstr "{} archivé(e). <a class=\"alert-link\" href=\"{}\">Annuler l'opération</a>"
253
 
253
 
254
 #: tracim/controllers/admin/workspace.py:93
254
 #: tracim/controllers/admin/workspace.py:93
280
 msgstr "Espace de travail {} mis à jour."
280
 msgstr "Espace de travail {} mis à jour."
281
 
281
 
282
 #: tracim/controllers/admin/workspace.py:251
282
 #: tracim/controllers/admin/workspace.py:251
283
-msgid ""
284
-"{} workspace deleted. In case of error, you can <a class=\"alert-link\" "
285
-"href=\"{}\">restore it</a>."
286
-msgstr ""
287
-"Espace de travail {} supprimé. Vous pouvez <a class=\"alert-link\" "
288
-"href=\"{}\">annuler l'opération</a>."
283
+msgid "{} workspace deleted. In case of error, you can <a class=\"alert-link\" href=\"{}\">restore it</a>."
284
+msgstr "Espace de travail {} supprimé. Vous pouvez <a class=\"alert-link\" href=\"{}\">annuler l'opération</a>."
289
 
285
 
290
 #: tracim/controllers/admin/workspace.py:264
286
 #: tracim/controllers/admin/workspace.py:264
291
 msgid "{} workspace restored."
287
 msgid "{} workspace restored."
296
 msgid "%B %d at %I:%M%p"
292
 msgid "%B %d at %I:%M%p"
297
 msgstr "le %d %B à %H:%M"
293
 msgstr "le %d %B à %H:%M"
298
 
294
 
299
-#: tracim/lib/helpers.py:71
300
-msgid "This is the current status."
301
-msgstr "C'est le statut actuel"
302
-
303
-#: tracim/lib/helpers.py:74
304
-msgid "The item is a normal document, like a howto or a text document."
305
-msgstr "L'élément est un document normal, comme un howto ou un document texte"
306
-
307
-#: tracim/lib/helpers.py:76
308
-msgid ""
309
-"The item will be automatically computed as \"in progress\" or \"done\" "
310
-"according to its children status."
311
-msgstr ""
312
-"L'élément sera automatiquement mis en \"En cours\" ou \"Fait\" suivant le"
313
-" statut de ses enfants."
314
-
315
-#: tracim/lib/helpers.py:78
316
-msgid "No action done on the item."
317
-msgstr "Aucun changement sur l'élément"
318
-
319
-#: tracim/lib/helpers.py:80
320
-msgid "The item is being worked on."
321
-msgstr "L'élément est en cours d'utilisation."
322
-
323
-#: tracim/lib/helpers.py:82
324
-msgid "Waiting for some external actions."
325
-msgstr "En attente d'actions externes."
326
-
327
-#: tracim/lib/helpers.py:84
328
-msgid "The work associated with the item is finished."
329
-msgstr "L'action associée à l'élément n'est pas terminée."
330
-
331
-#: tracim/lib/helpers.py:86
332
-msgid ""
333
-"Close the item if you want not to see it anymore. The data won't be "
334
-"deleted"
335
-msgstr ""
336
-"Fermez l'élément si vous ne voulez plus le voir. Les données ne seront "
337
-"pas supprimées"
338
-
339
-#: tracim/lib/helpers.py:88
340
-msgid "This status tells that the item has been deleted."
341
-msgstr "Ce status indique que l'élément a été supprimé."
342
-
343
 #: tracim/lib/predicates.py:12
295
 #: tracim/lib/predicates.py:12
344
 msgid "You are not authorized to access this resource"
296
 msgid "You are not authorized to access this resource"
345
 msgstr "Vous n'êtes pas autorisé à accéder à cette ressource"
297
 msgstr "Vous n'êtes pas autorisé à accéder à cette ressource"
346
 
298
 
347
-#: tracim/model/auth.py:131
299
+#: tracim/model/auth.py:85
348
 msgid "Nobody"
300
 msgid "Nobody"
349
 msgstr "Personne"
301
 msgstr "Personne"
350
 
302
 
351
-#: tracim/model/auth.py:132 tracim/templates/master_authenticated.mak:86
303
+#: tracim/model/auth.py:86
304
+#: tracim/templates/master_authenticated.mak:86
352
 #: tracim/templates/master_no_toolbar_no_login.mak:111
305
 #: tracim/templates/master_no_toolbar_no_login.mak:111
353
 #: tracim/templates/user_get_all.mak:13
306
 #: tracim/templates/user_get_all.mak:13
354
 msgid "Users"
307
 msgid "Users"
355
 msgstr "Utilisateurs"
308
 msgstr "Utilisateurs"
356
 
309
 
357
-#: tracim/model/auth.py:133
310
+#: tracim/model/auth.py:87
358
 msgid "Global managers"
311
 msgid "Global managers"
359
 msgstr "Managers globaux"
312
 msgstr "Managers globaux"
360
 
313
 
361
-#: tracim/model/auth.py:134
314
+#: tracim/model/auth.py:88
362
 msgid "Administrators"
315
 msgid "Administrators"
363
 msgstr "Administrateurs"
316
 msgstr "Administrateurs"
364
 
317
 
365
-#: tracim/model/data.py:88
318
+#: tracim/model/data.py:79
366
 msgid "N/A"
319
 msgid "N/A"
367
 msgstr "N/A"
320
 msgstr "N/A"
368
 
321
 
369
-#: tracim/model/data.py:89
322
+#: tracim/model/data.py:80
370
 #: tracim/templates/help/page-user-role-definition.mak:15
323
 #: tracim/templates/help/page-user-role-definition.mak:15
371
 msgid "Reader"
324
 msgid "Reader"
372
 msgstr "Lecteur"
325
 msgstr "Lecteur"
373
 
326
 
374
-#: tracim/model/data.py:90
327
+#: tracim/model/data.py:81
375
 #: tracim/templates/help/page-user-role-definition.mak:19
328
 #: tracim/templates/help/page-user-role-definition.mak:19
376
 msgid "Contributor"
329
 msgid "Contributor"
377
 msgstr "Contributeurs"
330
 msgstr "Contributeurs"
378
 
331
 
379
-#: tracim/model/data.py:91
332
+#: tracim/model/data.py:82
380
 #: tracim/templates/help/page-user-role-definition.mak:23
333
 #: tracim/templates/help/page-user-role-definition.mak:23
381
 msgid "Content Manager"
334
 msgid "Content Manager"
382
 msgstr "Gestionnaire de contenu"
335
 msgstr "Gestionnaire de contenu"
383
 
336
 
384
-#: tracim/model/data.py:92
337
+#: tracim/model/data.py:83
385
 #: tracim/templates/help/page-user-role-definition.mak:27
338
 #: tracim/templates/help/page-user-role-definition.mak:27
386
 msgid "Workspace Manager"
339
 msgid "Workspace Manager"
387
 msgstr "Responsable"
340
 msgstr "Responsable"
388
 
341
 
389
-#: tracim/model/data.py:162
342
+#: tracim/model/data.py:153
390
 msgid "Item archived"
343
 msgid "Item archived"
391
 msgstr "Element archivé"
344
 msgstr "Element archivé"
392
 
345
 
393
-#: tracim/model/data.py:163
346
+#: tracim/model/data.py:154
394
 msgid "Item commented"
347
 msgid "Item commented"
395
 msgstr "Element commenté"
348
 msgstr "Element commenté"
396
 
349
 
397
-#: tracim/model/data.py:164
350
+#: tracim/model/data.py:155
398
 msgid "Item created"
351
 msgid "Item created"
399
 msgstr "Elément créé"
352
 msgstr "Elément créé"
400
 
353
 
401
-#: tracim/model/data.py:165
354
+#: tracim/model/data.py:156
402
 msgid "Item deleted"
355
 msgid "Item deleted"
403
 msgstr "Element supprimé"
356
 msgstr "Element supprimé"
404
 
357
 
405
-#: tracim/model/data.py:166
358
+#: tracim/model/data.py:157
406
 msgid "Item modified"
359
 msgid "Item modified"
407
 msgstr "Elément modifié"
360
 msgstr "Elément modifié"
408
 
361
 
409
-#: tracim/model/data.py:167
362
+#: tracim/model/data.py:158
410
 msgid "New revision"
363
 msgid "New revision"
411
 msgstr "Nouvelle version"
364
 msgstr "Nouvelle version"
412
 
365
 
413
-#: tracim/model/data.py:168
366
+#: tracim/model/data.py:159
414
 msgid "Status modified"
367
 msgid "Status modified"
415
 msgstr "Statut modifié"
368
 msgstr "Statut modifié"
416
 
369
 
417
-#: tracim/model/data.py:169
370
+#: tracim/model/data.py:160
418
 msgid "Item un-archived"
371
 msgid "Item un-archived"
419
 msgstr "Elément désarchivé"
372
 msgstr "Elément désarchivé"
420
 
373
 
421
-#: tracim/model/data.py:170
374
+#: tracim/model/data.py:161
422
 msgid "Item undeleted"
375
 msgid "Item undeleted"
423
 msgstr "Elément restauré"
376
 msgstr "Elément restauré"
424
 
377
 
425
-#: tracim/model/data.py:207 tracim/model/data.py:217
378
+#: tracim/model/data.py:198
379
+#: tracim/model/data.py:208
426
 msgid "work in progress"
380
 msgid "work in progress"
427
 msgstr "travail en cours"
381
 msgstr "travail en cours"
428
 
382
 
429
-#: tracim/model/data.py:208 tracim/model/data.py:218
383
+#: tracim/model/data.py:199
384
+#: tracim/model/data.py:209
430
 msgid "closed — validated"
385
 msgid "closed — validated"
431
 msgstr "clos(e) — validé(e)"
386
 msgstr "clos(e) — validé(e)"
432
 
387
 
433
-#: tracim/model/data.py:209 tracim/model/data.py:219
388
+#: tracim/model/data.py:200
389
+#: tracim/model/data.py:210
434
 msgid "closed — cancelled"
390
 msgid "closed — cancelled"
435
 msgstr "clos(e) — annulé(e)"
391
 msgstr "clos(e) — annulé(e)"
436
 
392
 
437
-#: tracim/model/data.py:210 tracim/model/data.py:215 tracim/model/data.py:220
393
+#: tracim/model/data.py:201
394
+#: tracim/model/data.py:206
395
+#: tracim/model/data.py:211
438
 msgid "deprecated"
396
 msgid "deprecated"
439
 msgstr "obsolète"
397
 msgstr "obsolète"
440
 
398
 
441
-#: tracim/model/data.py:212
399
+#: tracim/model/data.py:203
442
 msgid "subject in progress"
400
 msgid "subject in progress"
443
 msgstr "discussion en cours"
401
 msgstr "discussion en cours"
444
 
402
 
445
-#: tracim/model/data.py:213
403
+#: tracim/model/data.py:204
446
 msgid "subject closed — resolved"
404
 msgid "subject closed — resolved"
447
 msgstr "discussion close — résolue"
405
 msgstr "discussion close — résolue"
448
 
406
 
449
-#: tracim/model/data.py:214
407
+#: tracim/model/data.py:205
450
 msgid "subject closed — cancelled"
408
 msgid "subject closed — cancelled"
451
 msgstr "discussion close — annulée"
409
 msgstr "discussion close — annulée"
452
 
410
 
453
-#: tracim/model/data.py:711
454
-msgid "Titleless Document"
455
-msgstr "Document sans titre"
456
-
457
 #: tracim/templates/create_account.mak:5
411
 #: tracim/templates/create_account.mak:5
458
 msgid "Create account"
412
 msgid "Create account"
459
 msgstr "Créer un compte"
413
 msgstr "Créer un compte"
480
 msgid "Go to my profile"
434
 msgid "Go to my profile"
481
 msgstr "Consulter mon profil"
435
 msgstr "Consulter mon profil"
482
 
436
 
483
-#: tracim/templates/error.mak:23 tracim/templates/error_authenticated.mak:20
437
+#: tracim/templates/error.mak:23
438
+#: tracim/templates/error_authenticated.mak:20
484
 msgid "Something went wrong!"
439
 msgid "Something went wrong!"
485
 msgstr "Quelque chose s'est mal passé"
440
 msgstr "Quelque chose s'est mal passé"
486
 
441
 
496
 msgid "to contact the support"
451
 msgid "to contact the support"
497
 msgstr "de contacter le support"
452
 msgstr "de contacter le support"
498
 
453
 
499
-#: tracim/templates/error.mak:44 tracim/templates/error_authenticated.mak:42
454
+#: tracim/templates/error.mak:44
455
+#: tracim/templates/error_authenticated.mak:42
500
 msgid "The error was: error {code}"
456
 msgid "The error was: error {code}"
501
 msgstr "L'erreur était : erreur {code}"
457
 msgstr "L'erreur était : erreur {code}"
502
 
458
 
540
 msgid "Move current folder"
496
 msgid "Move current folder"
541
 msgstr "Déplacer le dossier courant"
497
 msgstr "Déplacer le dossier courant"
542
 
498
 
543
-#: tracim/templates/index.mak:25 tracim/templates/index.mak:46
499
+#: tracim/templates/index.mak:25
500
+#: tracim/templates/index.mak:46
544
 #: tracim/templates/master_authenticated.mak:138
501
 #: tracim/templates/master_authenticated.mak:138
545
 msgid "Login"
502
 msgid "Login"
546
 msgstr "Login"
503
 msgstr "Login"
568
 #: tracim/templates/master_anonymous.mak:67
525
 #: tracim/templates/master_anonymous.mak:67
569
 #: tracim/templates/master_authenticated.mak:48
526
 #: tracim/templates/master_authenticated.mak:48
570
 msgid "Create your own email-ready collaborative workspace on trac.im"
527
 msgid "Create your own email-ready collaborative workspace on trac.im"
571
-msgstr ""
572
-"Créez votre propre espace de travail collaboratif compatible avec l'email"
573
-" sur trac.im"
528
+msgstr "Créez votre propre espace de travail collaboratif compatible avec l'email sur trac.im"
574
 
529
 
575
 #: tracim/templates/master_authenticated.mak:80
530
 #: tracim/templates/master_authenticated.mak:80
576
 #: tracim/templates/master_no_toolbar_no_login.mak:106
531
 #: tracim/templates/master_no_toolbar_no_login.mak:106
673
 msgid "Create a user account..."
628
 msgid "Create a user account..."
674
 msgstr "Créer un compte utilisateur"
629
 msgstr "Créer un compte utilisateur"
675
 
630
 
676
-#: tracim/templates/user_get_all.mak:29 tracim/templates/user_get_all.mak:30
631
+#: tracim/templates/user_get_all.mak:29
632
+#: tracim/templates/user_get_all.mak:30
677
 #: tracim/templates/user_workspace_folder_file_get_one.mak:70
633
 #: tracim/templates/user_workspace_folder_file_get_one.mak:70
678
 #: tracim/templates/user_workspace_forms.mak:43
634
 #: tracim/templates/user_workspace_forms.mak:43
679
 #: tracim/templates/user_workspace_forms.mak:44
635
 #: tracim/templates/user_workspace_forms.mak:44
687
 msgid "Name"
643
 msgid "Name"
688
 msgstr "Nom"
644
 msgstr "Nom"
689
 
645
 
690
-#: tracim/templates/user_get_all.mak:33 tracim/templates/user_get_all.mak:93
646
+#: tracim/templates/user_get_all.mak:33
647
+#: tracim/templates/user_get_all.mak:93
691
 #: tracim/templates/user_workspace_forms.mak:227
648
 #: tracim/templates/user_workspace_forms.mak:227
692
 #: tracim/templates/workspace_get_one.mak:87
649
 #: tracim/templates/workspace_get_one.mak:87
693
 msgid "Email"
650
 msgid "Email"
734
 
691
 
735
 #: tracim/templates/user_get_all.mak:86
692
 #: tracim/templates/user_get_all.mak:86
736
 #: tracim/templates/workspace_get_all.mak:58
693
 #: tracim/templates/workspace_get_all.mak:58
737
-msgid ""
738
-"There are no workspace yet. Start by <a class=\"alert-link\" data-"
739
-"toggle=\"collapse\" data-target=\"#create-workspace-form\">creating a "
740
-"workspace</a>."
741
-msgstr ""
742
-"Il n'y a pas encore d'espaces de travail. Commencez en <a class=\"alert-"
743
-"link\" data-toggle=\"collapse\" data-target=\"#create-workspace-"
744
-"form\">créant un espace de travail</a>."
694
+msgid "There are no workspace yet. Start by <a class=\"alert-link\" data-toggle=\"collapse\" data-target=\"#create-workspace-form\">creating a workspace</a>."
695
+msgstr "Il n'y a pas encore d'espaces de travail. Commencez en <a class=\"alert-link\" data-toggle=\"collapse\" data-target=\"#create-workspace-form\">créant un espace de travail</a>."
745
 
696
 
746
 #: tracim/templates/user_get_all.mak:92
697
 #: tracim/templates/user_get_all.mak:92
747
 #: tracim/templates/workspace_get_one.mak:42
698
 #: tracim/templates/workspace_get_one.mak:42
765
 msgid "User disabled. Click to enable this user"
716
 msgid "User disabled. Click to enable this user"
766
 msgstr "Utilisateur désactivé. Cliquez pour l'activer"
717
 msgstr "Utilisateur désactivé. Cliquez pour l'activer"
767
 
718
 
768
-#: tracim/templates/user_get_me.mak:5 tracim/templates/user_get_one.mak:5
719
+#: tracim/templates/user_get_me.mak:5
720
+#: tracim/templates/user_get_one.mak:5
769
 #: tracim/templates/user_profile.mak:5
721
 #: tracim/templates/user_profile.mak:5
770
 msgid "My profile"
722
 msgid "My profile"
771
 msgstr "Mon profil"
723
 msgstr "Mon profil"
772
 
724
 
773
-#: tracim/templates/user_get_me.mak:26 tracim/templates/user_get_one.mak:26
725
+#: tracim/templates/user_get_me.mak:26
726
+#: tracim/templates/user_get_one.mak:26
774
 msgid "This user can create workspaces."
727
 msgid "This user can create workspaces."
775
 msgstr "Cet utilisateur peut créer des espaces de travail."
728
 msgstr "Cet utilisateur peut créer des espaces de travail."
776
 
729
 
777
-#: tracim/templates/user_get_me.mak:29 tracim/templates/user_get_one.mak:29
730
+#: tracim/templates/user_get_me.mak:29
731
+#: tracim/templates/user_get_one.mak:29
778
 msgid "This user is an administrator."
732
 msgid "This user is an administrator."
779
 msgstr "Cet utilisateur est un administrateur"
733
 msgstr "Cet utilisateur est un administrateur"
780
 
734
 
784
 msgid "My workspaces"
738
 msgid "My workspaces"
785
 msgstr "Mes espaces de travail"
739
 msgstr "Mes espaces de travail"
786
 
740
 
787
-#: tracim/templates/user_get_me.mak:42 tracim/templates/user_get_one.mak:42
741
+#: tracim/templates/user_get_me.mak:42
742
+#| msgid "This user is not member of any workspace."
743
+msgid "You are not member of any workspace."
744
+msgstr "Vous n'êtes membre d'aucun espace de travail"
745
+
746
+#: tracim/templates/user_get_one.mak:42
788
 #: tracim/templates/user_profile.mak:42
747
 #: tracim/templates/user_profile.mak:42
789
 msgid "This user is not member of any workspace."
748
 msgid "This user is not member of any workspace."
790
 msgstr "Cet utilisateur n'est membre d'aucun espace de travail"
749
 msgstr "Cet utilisateur n'est membre d'aucun espace de travail"
848
 msgstr "Indiquez le contenu du fichier"
807
 msgstr "Indiquez le contenu du fichier"
849
 
808
 
850
 #: tracim/templates/user_workspace_folder_file_get_one.mak:33
809
 #: tracim/templates/user_workspace_folder_file_get_one.mak:33
851
-msgid ""
852
-"You are reading <b>an old revision</b> of the current file. (the shown "
853
-"revision is r{})."
854
-msgstr ""
855
-"Vous consultez <b>une ancienne version</b> du fichier courant. (la "
856
-"version affichée est v{})."
810
+msgid "You are reading <b>an old revision</b> of the current file. (the shown revision is r{})."
811
+msgstr "Vous consultez <b>une ancienne version</b> du fichier courant. (la version affichée est v{})."
857
 
812
 
858
 #: tracim/templates/user_workspace_folder_file_get_one.mak:34
813
 #: tracim/templates/user_workspace_folder_file_get_one.mak:34
859
 #: tracim/templates/user_workspace_folder_page_get_one.mak:35
814
 #: tracim/templates/user_workspace_folder_page_get_one.mak:35
906
 msgstr "Description"
861
 msgstr "Description"
907
 
862
 
908
 #: tracim/templates/user_workspace_folder_file_get_one.mak:99
863
 #: tracim/templates/user_workspace_folder_file_get_one.mak:99
909
-msgid ""
910
-"<b>Note</b>: You need to change status in case you want to upload a new "
911
-"version"
912
-msgstr ""
913
-"<b>Note</b>: Vous devez changer le statut du fichier avant de pouvoir "
914
-"télécharger une nouvelle version"
864
+msgid "<b>Note</b>: You need to change status in case you want to upload a new version"
865
+msgstr "<b>Note</b>: Vous devez changer le statut du fichier avant de pouvoir télécharger une nouvelle version"
915
 
866
 
916
 #: tracim/templates/user_workspace_folder_file_get_one.mak:104
867
 #: tracim/templates/user_workspace_folder_file_get_one.mak:104
917
 #: tracim/templates/user_workspace_folder_file_get_one.mak:108
868
 #: tracim/templates/user_workspace_folder_file_get_one.mak:108
1011
 msgstr "Rédigez le contenu de la page"
962
 msgstr "Rédigez le contenu de la page"
1012
 
963
 
1013
 #: tracim/templates/user_workspace_folder_page_get_one.mak:34
964
 #: tracim/templates/user_workspace_folder_page_get_one.mak:34
1014
-msgid ""
1015
-"You are reading <b>an old revision</b> of the current page. (the shown "
1016
-"revision is r{})."
1017
-msgstr ""
1018
-"Vous consultez <b>une ancienne version</b> de al page courante. (la "
1019
-"version affichée est la v{})."
965
+msgid "You are reading <b>an old revision</b> of the current page. (the shown revision is r{})."
966
+msgstr "Vous consultez <b>une ancienne version</b> de al page courante. (la version affichée est la v{})."
1020
 
967
 
1021
 #: tracim/templates/user_workspace_folder_page_get_one.mak:65
968
 #: tracim/templates/user_workspace_folder_page_get_one.mak:65
1022
 msgid "Links extracted from the page"
969
 msgid "Links extracted from the page"
1045
 msgstr "Vous pouvez décrire le sujet (facultatif)"
992
 msgstr "Vous pouvez décrire le sujet (facultatif)"
1046
 
993
 
1047
 #: tracim/templates/user_workspace_folder_thread_get_one.mak:64
994
 #: tracim/templates/user_workspace_folder_thread_get_one.mak:64
1048
-msgid ""
1049
-"<b>Note</b>: In case you'd like to post a reply, you must first open "
1050
-"again the thread"
1051
-msgstr ""
1052
-"<b>Note</b> : si vous souhaitez commenter cette discussion, vous devez "
1053
-"tout d'abord en modifier le statut pour la ré-ouvrir"
995
+msgid "<b>Note</b>: In case you'd like to post a reply, you must first open again the thread"
996
+msgstr "<b>Note</b> : si vous souhaitez commenter cette discussion, vous devez tout d'abord en modifier le statut pour la ré-ouvrir"
1054
 
997
 
1055
 #: tracim/templates/user_workspace_folder_thread_get_one.mak:69
998
 #: tracim/templates/user_workspace_folder_thread_get_one.mak:69
1056
 msgid "Post a reply..."
999
 msgid "Post a reply..."
1262
 msgstr "Rôle"
1205
 msgstr "Rôle"
1263
 
1206
 
1264
 #: tracim/templates/workspace_get_one.mak:80
1207
 #: tracim/templates/workspace_get_one.mak:80
1265
-msgid ""
1266
-"There are no user associated to the current workspace. Start by <a class"
1267
-"=\"alert-link\" data-toggle=\"collapse\" data-target=\"#add-role-from-"
1268
-"existing-user-form\">adding members</a>  to the workspace."
1269
-msgstr ""
1270
-"Il n'y a aucun membre dans cet espace de travail. Commencez par <a class"
1271
-"=\"alert-link\" data-toggle=\"collapse\" data-target=\"#add-role-from-"
1272
-"existing-user-form\">ajouter des membres</a> à cet espace."
1208
+msgid "There are no user associated to the current workspace. Start by <a class=\"alert-link\" data-toggle=\"collapse\" data-target=\"#add-role-from-existing-user-form\">adding members</a>  to the workspace."
1209
+msgstr "Il n'y a aucun membre dans cet espace de travail. Commencez par <a class=\"alert-link\" data-toggle=\"collapse\" data-target=\"#add-role-from-existing-user-form\">ajouter des membres</a> à cet espace."
1273
 
1210
 
1274
 #: tracim/templates/workspace_get_one.mak:107
1211
 #: tracim/templates/workspace_get_one.mak:107
1275
 msgid "Change role to..."
1212
 msgid "Change role to..."
1325
 
1262
 
1326
 #: tracim/templates/help/page-user-role-definition.mak:11
1263
 #: tracim/templates/help/page-user-role-definition.mak:11
1327
 msgid "Roles give progressive rights on what the user can or can't do."
1264
 msgid "Roles give progressive rights on what the user can or can't do."
1328
-msgstr ""
1329
-"Les rôles donnent des droits progressifs qui déterminent ce que "
1330
-"l'utilisateur peut ou ne peut pas faire."
1265
+msgstr "Les rôles donnent des droits progressifs qui déterminent ce que l'utilisateur peut ou ne peut pas faire."
1331
 
1266
 
1332
 #: tracim/templates/help/page-user-role-definition.mak:16
1267
 #: tracim/templates/help/page-user-role-definition.mak:16
1333
 msgid "This role gives read-only access to workspace resources."
1268
 msgid "This role gives read-only access to workspace resources."
1334
 msgstr "Ce rôle donne accès en lecteur aux informations de l'espace de travail."
1269
 msgstr "Ce rôle donne accès en lecteur aux informations de l'espace de travail."
1335
 
1270
 
1336
 #: tracim/templates/help/page-user-role-definition.mak:20
1271
 #: tracim/templates/help/page-user-role-definition.mak:20
1337
-msgid ""
1338
-"Same as <span style=\"color: #1fdb11;\">reader</span> + contribution "
1339
-"rights: edit, comments, status update."
1340
-msgstr ""
1341
-"Comme <span style=\"color: #1fdb11;\">lecteur</span> + contribution : "
1342
-"modifications, commentaires, mise à jour du statut."
1272
+msgid "Same as <span style=\"color: #1fdb11;\">reader</span> + contribution rights: edit, comments, status update."
1273
+msgstr "Comme <span style=\"color: #1fdb11;\">lecteur</span> + contribution : modifications, commentaires, mise à jour du statut."
1343
 
1274
 
1344
 #: tracim/templates/help/page-user-role-definition.mak:24
1275
 #: tracim/templates/help/page-user-role-definition.mak:24
1345
-msgid ""
1346
-"Same as <span style=\"color: #759ac5;\">contributor</span> + content "
1347
-"management rights: move content, folders management, delete archive "
1348
-"operations."
1349
-msgstr ""
1350
-"Comme <span style=\"color: #759ac5;\">contributeur</span> + gestion du "
1351
-"contenu : déplacer le contenu, les dossiers"
1276
+msgid "Same as <span style=\"color: #759ac5;\">contributor</span> + content management rights: move content, folders management, delete archive operations."
1277
+msgstr "Comme <span style=\"color: #759ac5;\">contributeur</span> + gestion du contenu : déplacer le contenu, les dossiers"
1352
 
1278
 
1353
 #: tracim/templates/help/page-user-role-definition.mak:28
1279
 #: tracim/templates/help/page-user-role-definition.mak:28
1354
-msgid ""
1355
-"Same as <span style=\"color: #ea983d;\">content manager</span> + "
1356
-"workspace management rights: edit workspace, invite users, revoke them."
1357
-msgstr ""
1358
-"Comme <span style=\"color: #ea983d;\">gestionnaire de contenu</span> + "
1359
-"modification de l'espace de travail."
1280
+msgid "Same as <span style=\"color: #ea983d;\">content manager</span> + workspace management rights: edit workspace, invite users, revoke them."
1281
+msgstr "Comme <span style=\"color: #ea983d;\">gestionnaire de contenu</span> + modification de l'espace de travail."
1360
 
1282
 
1361
 #~ msgid "You have no document yet."
1283
 #~ msgid "You have no document yet."
1362
 #~ msgstr "Vous n'avez pas de document pour le moment."
1284
 #~ msgstr "Vous n'avez pas de document pour le moment."
1877
 #~ msgid "page"
1799
 #~ msgid "page"
1878
 #~ msgstr "page"
1800
 #~ msgstr "page"
1879
 
1801
 
1802
+#~ msgid "This is the current status."
1803
+#~ msgstr "C'est le statut actuel"
1804
+
1805
+#~ msgid "The item is a normal document, like a howto or a text document."
1806
+#~ msgstr "L'élément est un document normal, comme un howto ou un document texte"
1807
+
1808
+#~ msgid ""
1809
+#~ "The item will be automatically computed"
1810
+#~ " as \"in progress\" or \"done\" "
1811
+#~ "according to its children status."
1812
+#~ msgstr ""
1813
+#~ "L'élément sera automatiquement mis en "
1814
+#~ "\"En cours\" ou \"Fait\" suivant le "
1815
+#~ "statut de ses enfants."
1816
+
1817
+#~ msgid "No action done on the item."
1818
+#~ msgstr "Aucun changement sur l'élément"
1819
+
1820
+#~ msgid "The item is being worked on."
1821
+#~ msgstr "L'élément est en cours d'utilisation."
1822
+
1823
+#~ msgid "Waiting for some external actions."
1824
+#~ msgstr "En attente d'actions externes."
1825
+
1826
+#~ msgid "The work associated with the item is finished."
1827
+#~ msgstr "L'action associée à l'élément n'est pas terminée."
1828
+
1829
+#~ msgid ""
1830
+#~ "Close the item if you want not "
1831
+#~ "to see it anymore. The data won't"
1832
+#~ " be deleted"
1833
+#~ msgstr ""
1834
+#~ "Fermez l'élément si vous ne voulez "
1835
+#~ "plus le voir. Les données ne "
1836
+#~ "seront pas supprimées"
1837
+
1838
+#~ msgid "This status tells that the item has been deleted."
1839
+#~ msgstr "Ce status indique que l'élément a été supprimé."
1840
+
1841
+#~ msgid "Titleless Document"
1842
+#~ msgstr "Document sans titre"

+ 3 - 0
tracim/tracim/lib/__init__.py 查看文件

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 from tg.i18n import lazy_ugettext as l_
2
 from tg.i18n import lazy_ugettext as l_
3
 
3
 
4
+class NotFoundError(Exception):
5
+    pass
6
+
4
 class CST(object):
7
 class CST(object):
5
     STATUS_ERROR = 'error'
8
     STATUS_ERROR = 'error'
6
     STATUS_OK = 'ok'
9
     STATUS_OK = 'ok'

+ 0 - 65
tracim/tracim/lib/auth.py 查看文件

1
-# -*- coding: utf-8 -*-
2
-"""Predicates for authorizations"""
3
-from tg.predicates import Predicate
4
-from tracim.model import DBSession as session
5
-from tracim.model.auth import Permission, User
6
-import logging as l
7
-
8
-DIRTY_canReadOrCanWriteSqlQuery = """
9
-SELECT
10
-    pgn.node_id
11
-FROM
12
-    pod_group_node AS pgn
13
-    JOIN pod_nodes AS pn ON pn.node_id = pgn.node_id AND pn.is_shared = 't'
14
-    JOIN pod_user_group AS pug ON pug.group_id = pgn.group_id
15
-    JOIN pod_user AS pu ON pug.user_id = pu.user_id
16
-WHERE
17
-    rights > :excluded_right_low_level
18
-    AND email_address = :email
19
-    AND pgn.node_id = :node_id
20
-UNION
21
-    SELECT
22
-        pnn.node_id
23
-    FROM
24
-        pod_nodes AS pnn,
25
-        pod_user AS puu
26
-    WHERE
27
-        pnn.node_id = :node_id
28
-        AND pnn.owner_id = puu.user_id
29
-        AND puu.email_address = :email
30
-"""
31
-
32
-class can_read(Predicate):
33
-    message = ""
34
-
35
-    def __init__(self, **kwargs):
36
-        pass
37
-
38
-    def evaluate(self, environ, credentials):
39
-        if 'node_id' in environ['webob.adhoc_attrs']['validation']['values']:
40
-            node_id = environ['webob.adhoc_attrs']['validation']['values']['node_id']
41
-            if node_id!=0:
42
-                has_right = session.execute(
43
-                    DIRTY_canReadOrCanWriteSqlQuery,
44
-                    {"email":credentials["repoze.who.userid"], "node_id":node_id, "excluded_right_low_level": 0}
45
-                )
46
-                if has_right.rowcount == 0 :
47
-                    l.info("User {} don't have read right on node {}".format(credentials["repoze.who.userid"], node_id))
48
-                    self.unmet()
49
-
50
-class can_write(Predicate):
51
-    message = ""
52
-
53
-    def __init__(self, **kwargs):
54
-        pass
55
-
56
-    def evaluate(self, environ, credentials):
57
-        node_id = environ['webob.adhoc_attrs']['validation']['values']['node_id']
58
-        if node_id!=0:
59
-            has_right = session.execute(
60
-                DIRTY_canReadOrCanWriteSqlQuery,
61
-                {"email":credentials["repoze.who.userid"], "node_id":node_id, "excluded_right_low_level": 1}
62
-            )
63
-            if has_right.rowcount == 0 :
64
-                self.unmet()
65
-

+ 0 - 4
tracim/tracim/lib/base.py 查看文件

96
         :param id:
96
         :param id:
97
         :return:
97
         :return:
98
         """
98
         """
99
-        print('***********************************')
100
-        print("searching for an id")
101
-        print(cls)
102
-        print(cls)
103
         return getattr(tmpl_context, cls.current_item_id_key_in_context(), '')
99
         return getattr(tmpl_context, cls.current_item_id_key_in_context(), '')
104
 
100
 
105
     def back_with_error(self, message):
101
     def back_with_error(self, message):

+ 63 - 68
tracim/tracim/lib/content.py 查看文件

8
 from tracim.model import DBSession
8
 from tracim.model import DBSession
9
 from tracim.model.auth import User
9
 from tracim.model.auth import User
10
 from tracim.model.data import ContentStatus, ContentRevisionRO, ActionDescription
10
 from tracim.model.data import ContentStatus, ContentRevisionRO, ActionDescription
11
-from tracim.model.data import PBNode
12
-from tracim.model.data import PBNodeType
11
+from tracim.model.data import Content
12
+from tracim.model.data import ContentType
13
 from tracim.model.data import Workspace
13
 from tracim.model.data import Workspace
14
 
14
 
15
 class ContentApi(object):
15
 class ContentApi(object):
20
         self._show_deleted = show_deleted
20
         self._show_deleted = show_deleted
21
 
21
 
22
     def _base_query(self, workspace: Workspace=None):
22
     def _base_query(self, workspace: Workspace=None):
23
-        result = DBSession.query(PBNode)
23
+        result = DBSession.query(Content)
24
         if workspace:
24
         if workspace:
25
-            result = result.filter(PBNode.workspace_id==workspace.workspace_id)
25
+            result = result.filter(Content.workspace_id==workspace.workspace_id)
26
 
26
 
27
         if not self._show_deleted:
27
         if not self._show_deleted:
28
-            result = result.filter(PBNode.is_deleted==False)
28
+            result = result.filter(Content.is_deleted==False)
29
 
29
 
30
         if not self._show_archived:
30
         if not self._show_archived:
31
-            result = result.filter(PBNode.is_archived==False)
31
+            result = result.filter(Content.is_archived==False)
32
 
32
 
33
         return result
33
         return result
34
 
34
 
35
-    def get_child_folders(self, parent: PBNode=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[]) -> [PBNode]:
36
-        # assert parent is None or isinstance(parent, PBNode) # DYN_REMOVE
37
-        # assert workspace is None or isinstance(workspace, Workspace) # DYN_REMOVE
35
+    def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[]) -> [Content]:
38
 
36
 
39
-        parent_id = parent.node_id if parent else None
37
+        parent_id = parent.content_id if parent else None
40
         folders = self._base_query(workspace).\
38
         folders = self._base_query(workspace).\
41
-            filter(PBNode.parent_id==parent_id).\
42
-            filter(PBNode.node_type==PBNodeType.Folder).\
43
-            filter(PBNode.node_id.notin_(removed_item_ids)).\
39
+            filter(Content.parent_id==parent_id).\
40
+            filter(Content.type==ContentType.Folder).\
41
+            filter(Content.content_id.notin_(removed_item_ids)).\
44
             all()
42
             all()
45
 
43
 
46
         if not filter_by_allowed_content_types or len(filter_by_allowed_content_types)<=0:
44
         if not filter_by_allowed_content_types or len(filter_by_allowed_content_types)<=0:
50
         result = []
48
         result = []
51
         for folder in folders:
49
         for folder in folders:
52
             for allowed_content_type in filter_by_allowed_content_types:
50
             for allowed_content_type in filter_by_allowed_content_types:
53
-                print('ALLOWED = ', filter_by_allowed_content_types)
54
-                print('CONTENT = ', folder.properties['allowed_content'])
55
-                # exit()
56
                 if folder.properties['allowed_content'][allowed_content_type]==True:
51
                 if folder.properties['allowed_content'][allowed_content_type]==True:
57
                     result.append(folder)
52
                     result.append(folder)
58
 
53
 
59
         return result
54
         return result
60
 
55
 
61
-    def create(self, content_type: str, workspace: Workspace=None, parent: PBNode=None, label:str ='', do_save=False) -> PBNode:
62
-        assert content_type in PBNodeType.allowed_types()
63
-        content = PBNode()
56
+    def create(self, content_type: str, workspace: Workspace=None, parent: Content=None, label:str ='', do_save=False) -> Content:
57
+        assert content_type in ContentType.allowed_types()
58
+        content = Content()
64
         content.owner = self._user
59
         content.owner = self._user
65
         content.parent = parent
60
         content.parent = parent
66
         content.workspace = workspace
61
         content.workspace = workspace
67
-        content.node_type = content_type
68
-        content.data_label = label
69
-        content.last_action = ActionDescription.CREATION
62
+        content.type = content_type
63
+        content.label = label
64
+        content.revision_type = ActionDescription.CREATION
70
 
65
 
71
         if do_save:
66
         if do_save:
72
             self.save(content)
67
             self.save(content)
73
         return content
68
         return content
74
 
69
 
75
 
70
 
76
-    def create_comment(self, workspace: Workspace=None, parent: PBNode=None, content:str ='', do_save=False) -> PBNode:
77
-        assert parent  and parent.node_type!=PBNodeType.Folder
78
-        item = PBNode()
71
+    def create_comment(self, workspace: Workspace=None, parent: Content=None, content:str ='', do_save=False) -> Content:
72
+        assert parent  and parent.type!=ContentType.Folder
73
+        item = Content()
79
         item.owner = self._user
74
         item.owner = self._user
80
         item.parent = parent
75
         item.parent = parent
81
         item.workspace = workspace
76
         item.workspace = workspace
82
-        item.node_type = PBNodeType.Comment
83
-        item.data_content = content
84
-        item.data_label = ''
85
-        item.last_action = ActionDescription.COMMENT
77
+        item.type = ContentType.Comment
78
+        item.description = content
79
+        item.label = ''
80
+        item.revision_type = ActionDescription.COMMENT
86
 
81
 
87
         if do_save:
82
         if do_save:
88
             self.save(item)
83
             self.save(item)
89
         return content
84
         return content
90
 
85
 
91
 
86
 
92
-    def get_one_from_revision(self, content_id: int, content_type: str, workspace: Workspace=None, revision_id=None) -> PBNode:
87
+    def get_one_from_revision(self, content_id: int, content_type: str, workspace: Workspace=None, revision_id=None) -> Content:
93
         """
88
         """
94
         This method is a hack to convert a node revision item into a node
89
         This method is a hack to convert a node revision item into a node
95
         :param content_id:
90
         :param content_id:
100
         """
95
         """
101
 
96
 
102
         content = self.get_one(content_id, content_type, workspace)
97
         content = self.get_one(content_id, content_type, workspace)
103
-        revision = DBSession.query(ContentRevisionRO).filter(ContentRevisionRO.version_id==revision_id).one()
98
+        revision = DBSession.query(ContentRevisionRO).filter(ContentRevisionRO.revision_id==revision_id).one()
104
 
99
 
105
-        if revision.node_id==content.node_id:
106
-            content.revision_to_serialize = revision.version_id
100
+        if revision.content_id==content.content_id:
101
+            content.revision_to_serialize = revision.revision_id
107
         else:
102
         else:
108
             raise ValueError('Revision not found for given content')
103
             raise ValueError('Revision not found for given content')
109
 
104
 
110
         return content
105
         return content
111
 
106
 
112
-    def get_one(self, content_id: int, content_type: str, workspace: Workspace=None) -> PBNode:
107
+    def get_one(self, content_id: int, content_type: str, workspace: Workspace=None) -> Content:
113
         assert content_id is None or isinstance(content_id, int) # DYN_REMOVE
108
         assert content_id is None or isinstance(content_id, int) # DYN_REMOVE
114
         assert content_type is not None # DYN_REMOVE
109
         assert content_type is not None # DYN_REMOVE
115
         assert isinstance(content_type, str) # DYN_REMOVE
110
         assert isinstance(content_type, str) # DYN_REMOVE
117
         if not content_id:
112
         if not content_id:
118
             return
113
             return
119
 
114
 
120
-        if content_type==PBNodeType.Any:
115
+        if content_type==ContentType.Any:
121
             return self._base_query(workspace).\
116
             return self._base_query(workspace).\
122
-                filter(PBNode.node_id==content_id).\
117
+                filter(Content.content_id==content_id).\
123
                 one()
118
                 one()
124
 
119
 
125
         return self._base_query(workspace).\
120
         return self._base_query(workspace).\
126
-            filter(PBNode.node_id==content_id).\
127
-            filter(PBNode.node_type==content_type).\
121
+            filter(Content.content_id==content_id).\
122
+            filter(Content.type==content_type).\
128
             one()
123
             one()
129
 
124
 
130
-    def get_all(self, parent_id: int, content_type: str, workspace: Workspace=None) -> PBNode:
125
+    def get_all(self, parent_id: int, content_type: str, workspace: Workspace=None) -> Content:
131
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
126
         assert parent_id is None or isinstance(parent_id, int) # DYN_REMOVE
132
         assert content_type is not None # DYN_REMOVE
127
         assert content_type is not None # DYN_REMOVE
133
         assert isinstance(content_type, str) # DYN_REMOVE
128
         assert isinstance(content_type, str) # DYN_REMOVE
136
             return
131
             return
137
 
132
 
138
         return self._base_query(workspace).\
133
         return self._base_query(workspace).\
139
-            filter(PBNode.parent_id==parent_id).\
140
-            filter(PBNode.node_type==content_type).\
134
+            filter(Content.parent_id==parent_id).\
135
+            filter(Content.type==content_type).\
141
             all()
136
             all()
142
 
137
 
143
-    def set_allowed_content(self, folder: PBNode, allowed_content_dict:dict):
138
+    def set_allowed_content(self, folder: Content, allowed_content_dict:dict):
144
         """
139
         """
145
         :param folder: the given folder instance
140
         :param folder: the given folder instance
146
         :param allowed_content_dict: must be something like this:
141
         :param allowed_content_dict: must be something like this:
152
             )
147
             )
153
         :return:
148
         :return:
154
         """
149
         """
155
-        assert folder.node_type==PBNodeType.Folder
150
+        assert folder.type==ContentType.Folder
156
         assert 'file' in allowed_content_dict.keys()
151
         assert 'file' in allowed_content_dict.keys()
157
         assert 'folder' in allowed_content_dict.keys()
152
         assert 'folder' in allowed_content_dict.keys()
158
         assert 'page' in allowed_content_dict.keys()
153
         assert 'page' in allowed_content_dict.keys()
162
         folder.properties = properties
157
         folder.properties = properties
163
 
158
 
164
 
159
 
165
-    def set_status(self, content: PBNode, new_status: str):
160
+    def set_status(self, content: Content, new_status: str):
166
         if new_status in ContentStatus.allowed_values():
161
         if new_status in ContentStatus.allowed_values():
167
-            content.node_status = new_status
168
-            content.last_action = ActionDescription.STATUS_UPDATE
162
+            content.status = new_status
163
+            content.revision_type = ActionDescription.STATUS_UPDATE
169
         else:
164
         else:
170
             raise ValueError('The given value {} is not allowed'.format(new_status))
165
             raise ValueError('The given value {} is not allowed'.format(new_status))
171
 
166
 
172
 
167
 
173
-    def move(self, item: PBNode, new_parent: PBNode, must_stay_in_same_workspace:bool=True):
168
+    def move(self, item: Content, new_parent: Content, must_stay_in_same_workspace:bool=True):
174
         if must_stay_in_same_workspace:
169
         if must_stay_in_same_workspace:
175
             if new_parent and new_parent.workspace_id!=item.workspace_id:
170
             if new_parent and new_parent.workspace_id!=item.workspace_id:
176
                 raise ValueError('the item should stay in the same workspace')
171
                 raise ValueError('the item should stay in the same workspace')
177
 
172
 
178
         item.parent = new_parent
173
         item.parent = new_parent
179
-        item.last_action = ActionDescription.EDITION
174
+        item.revision_type = ActionDescription.EDITION
180
 
175
 
181
 
176
 
182
-    def update_content(self, item: PBNode, new_label: str, new_content: str) -> PBNode:
183
-        item.data_label = new_label
184
-        item.data_content = new_content # TODO: convert urls into links
185
-        item.last_action = ActionDescription.EDITION
177
+    def update_content(self, item: Content, new_label: str, new_content: str) -> Content:
178
+        item.label = new_label
179
+        item.description = new_content # TODO: convert urls into links
180
+        item.revision_type = ActionDescription.EDITION
186
         return item
181
         return item
187
 
182
 
188
-    def update_file_data(self, item: PBNode, new_filename: str, new_mimetype: str, new_file_content) -> PBNode:
189
-        item.data_file_name = new_filename
190
-        item.data_file_mime_type = new_mimetype
191
-        item.data_file_content = new_file_content
183
+    def update_file_data(self, item: Content, new_filename: str, new_mimetype: str, new_file_content) -> Content:
184
+        item.file_name = new_filename
185
+        item.file_mimetype = new_mimetype
186
+        item.file_content = new_file_content
192
         return item
187
         return item
193
 
188
 
194
-    def archive(self, content: PBNode):
189
+    def archive(self, content: Content):
195
         content.is_archived = True
190
         content.is_archived = True
196
-        content.last_action = ActionDescription.ARCHIVING
191
+        content.revision_type = ActionDescription.ARCHIVING
197
 
192
 
198
-    def unarchive(self, content: PBNode):
193
+    def unarchive(self, content: Content):
199
         content.is_archived = False
194
         content.is_archived = False
200
-        content.last_action = ActionDescription.UNARCHIVING
195
+        content.revision_type = ActionDescription.UNARCHIVING
201
 
196
 
202
 
197
 
203
-    def delete(self, content: PBNode):
198
+    def delete(self, content: Content):
204
         content.is_deleted = True
199
         content.is_deleted = True
205
-        content.last_action = ActionDescription.DELETION
200
+        content.revision_type = ActionDescription.DELETION
206
 
201
 
207
-    def undelete(self, content: PBNode):
202
+    def undelete(self, content: Content):
208
         content.is_deleted = False
203
         content.is_deleted = False
209
-        content.last_action = ActionDescription.UNDELETION
204
+        content.revision_type = ActionDescription.UNDELETION
210
 
205
 
211
-    def save(self, content: PBNode, action_description: str=None, do_flush=True):
206
+    def save(self, content: Content, action_description: str=None, do_flush=True):
212
         """
207
         """
213
-        Save an object, flush the session and set the last_action property
208
+        Save an object, flush the session and set the revision_type property
214
         :param content:
209
         :param content:
215
         :param action_description:
210
         :param action_description:
216
         :return:
211
         :return:
219
 
214
 
220
         if not action_description:
215
         if not action_description:
221
             # See if the last action has been modified
216
             # See if the last action has been modified
222
-            if content.last_action==None or len(get_history(content, 'last_action'))<=0:
217
+            if content.revision_type==None or len(get_history(content, 'revision_type'))<=0:
223
                 # The action has not been modified, so we set it to default edition
218
                 # The action has not been modified, so we set it to default edition
224
                 action_description = ActionDescription.EDITION
219
                 action_description = ActionDescription.EDITION
225
 
220
 
226
-        content.last_action = action_description
221
+        content.revision_type = action_description
227
 
222
 
228
         if do_flush:
223
         if do_flush:
229
             DBSession.flush()
224
             DBSession.flush()

+ 0 - 432
tracim/tracim/lib/dbapi.py 查看文件

1
-# -*- coding: utf-8 -*-
2
-"""
3
-"""
4
-import os
5
-from datetime import datetime
6
-from hashlib import sha256
7
-
8
-from sqlalchemy import Table, ForeignKey, Column
9
-from sqlalchemy.types import Unicode, Integer, DateTime, Text
10
-from sqlalchemy.orm import relation, synonym
11
-from sqlalchemy.orm import joinedload_all
12
-import sqlalchemy.orm as sqlao
13
-import sqlalchemy as sqla
14
-
15
-from tracim.model import data as pbmd
16
-from tracim.model import auth as pbma
17
-import tracim.model as pbm
18
-
19
-from tracim.model.auth import User, Group
20
-
21
-import tg
22
-
23
-FIXME_ERROR_CODE=-1
24
-
25
-class PODUserFilteredApiController(object):
26
-  
27
-  def __init__(self, piUserId, piExtraUserIdList=[]):
28
-    self._iCurrentUserId       = piUserId
29
-    self._iExtraUserIdList     = piExtraUserIdList
30
-    self._iUserIdFilteringList = None
31
-    self._cache_allowed_nodes = None
32
-
33
-  def _getUserIdListForFiltering(self):
34
-    if self._iUserIdFilteringList==None:
35
-      self._iUserIdFilteringList = list()
36
-      self._iUserIdFilteringList.append(self._iCurrentUserId)
37
-      for liUserId in self._iExtraUserIdList:
38
-        self._iUserIdFilteringList.append(liUserId)
39
-    return self._iUserIdFilteringList
40
-
41
-
42
-  def createNode(self, parent_id=0, inherit_rights=True):
43
-    loNode          = pbmd.PBNode()
44
-    loNode.owner_id = self._iCurrentUserId
45
-    if int(parent_id)!=0:
46
-        loNode.parent_id = parent_id
47
-
48
-    if inherit_rights:
49
-        parent_node = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_id==parent_id).one()
50
-        self.copy_rights(parent_node, loNode)
51
-
52
-    pbm.DBSession.add(loNode)
53
-
54
-    return loNode
55
-
56
-  def copy_rights(self, from_node: pbmd.PBNode, to_node: pbmd.PBNode, copy_also_is_shared=True):
57
-    """
58
-    copy rights from first node to second one
59
-    """
60
-    for parent_right in from_node._lRights:
61
-        new_right = self.createRight()
62
-        new_right.group_id = parent_right.group_id
63
-        new_right.rights = parent_right.rights
64
-        to_node._lRights.append(new_right)
65
-
66
-    if copy_also_is_shared:
67
-        to_node.is_shared = from_node.is_shared
68
-
69
-  def createDummyNode(self, parent_id, inherit_rights=True):
70
-    loNewNode = self.createNode(parent_id, inherit_rights)
71
-    loNewNode.data_label   = ''
72
-    loNewNode.data_content = ''
73
-    return loNewNode
74
-
75
-
76
-  def getNode(self, liNodeId: int) -> pbmd.PBNode:
77
-
78
-    lsSqlSelectQuery = """pod_nodes.node_id IN
79
-        (SELECT
80
-            pgn.node_id
81
-        FROM
82
-            pod_group_node AS pgn
83
-            join pod_user_group AS pug ON pug.group_id = pgn.group_id
84
-            join pod_user AS pu ON pug.user_id = pu.user_id
85
-        WHERE
86
-            rights > 0
87
-            AND pu.user_id = %s)
88
-    """
89
-    lsNodeIdFiltering = lsSqlSelectQuery % (str(self._iCurrentUserId))
90
-
91
-    if liNodeId!=None and liNodeId!=0:
92
-      return pbm.DBSession.query(pbmd.PBNode).options(sqlao.joinedload_all("_oParent"), sqlao.joinedload_all("_lAllChildren"))\
93
-        .filter(pbmd.PBNode.node_id==liNodeId)\
94
-        .filter(
95
-          sqla.or_(
96
-            pbmd.PBNode.owner_id==self._iCurrentUserId,
97
-            lsNodeIdFiltering
98
-          )
99
-        )\
100
-        .one()
101
-    return None
102
-
103
-  def getLastModifiedNodes(self, piMaxNodeNb: int):
104
-    """
105
-    Returns a list of nodes order by modification time and limited to piMaxNodeNb nodes
106
-    """
107
-    liOwnerIdList = self._getUserIdListForFiltering()
108
-    return pbm.DBSession.query(pbmd.PBNode)\
109
-        .outerjoin(pbma.Rights)\
110
-        .outerjoin(pbma.user_group_table, pbma.Rights.group_id==pbma.user_group_table.columns['group_id'])\
111
-        .options(joinedload_all("_lAllChildren"))\
112
-        .filter((pbmd.PBNode.owner_id.in_(liOwnerIdList)) | ((pbma.user_group_table.c.user_id.in_(liOwnerIdList)) & (pbmd.PBNode.is_shared == True)))\
113
-        .order_by(pbmd.PBNode.updated_at.desc())\
114
-        .limit(piMaxNodeNb).all()
115
-
116
-
117
-  def getListOfAllowedNodes(self, reset_cache=False) -> pbmd.PBNode:
118
-      if self._cache_allowed_nodes==None or reset_cache==True:
119
-        lsSqlQuery = """
120
-            SELECT
121
-                pn.node_id,
122
-                pn.parent_id,
123
-                pn.node_order,
124
-                pn.node_type,
125
-                pn.created_at,
126
-                pn.updated_at,
127
-                pn.data_label,
128
-                pn.data_content,
129
-                pn.data_datetime,
130
-                pn.node_status,
131
-                pn.data_reminder_datetime,
132
-                pn.parent_tree_path,
133
-                pn.node_depth,
134
-                pn.owner_id,
135
-                pn.is_shared,
136
-                pn.is_public,
137
-                pn.public_url_key
138
-            FROM
139
-                pod_group_node AS pgn
140
-                join pod_user_group AS pug ON pug.group_id = pgn.group_id
141
-                join pod_nodes AS pn ON pgn.node_id=pn.node_id AND pn.is_shared='t'
142
-            WHERE
143
-                pn.node_type='data'
144
-                AND pn.node_status NOT IN ('deleted', 'closed')
145
-                AND pn.node_id=pgn.node_id
146
-                AND pgn.rights > 0
147
-                AND pug.user_id = :owner_id
148
-
149
-            UNION
150
-                SELECT
151
-                    pn.node_id,
152
-                    pn.parent_id,
153
-                    pn.node_order,
154
-                    pn.node_type,
155
-                    pn.created_at,
156
-                    pn.updated_at,
157
-                    pn.data_label,
158
-                    pn.data_content,
159
-                    pn.data_datetime,
160
-                    pn.node_status,
161
-                    pn.data_reminder_datetime,
162
-                    pn.parent_tree_path,
163
-                    pn.node_depth,
164
-                    pn.owner_id,
165
-                    pn.is_shared,
166
-                    pn.is_public,
167
-                    pn.public_url_key
168
-                FROM
169
-                    pod_nodes AS pn
170
-                WHERE
171
-                    pn.node_type = 'data'
172
-                    AND pn.node_status NOT IN ('deleted', 'closed')
173
-                    AND pn.owner_id = :owner_id
174
-
175
-            ORDER BY node_id ASC
176
-        """
177
-
178
-        loNodeListResult = pbm.DBSession.query(pbmd.PBNode).\
179
-            from_statement(lsSqlQuery).\
180
-            params(owner_id=self._iCurrentUserId)
181
-        allowed_nodes = loNodeListResult.all()
182
-
183
-        self._cache_allowed_nodes = allowed_nodes
184
-
185
-      return self._cache_allowed_nodes
186
-
187
-
188
-  def searchNodesByText(self, plKeywordList: [str], piMaxNodeNb=100):
189
-    """
190
-    Returns a list of nodes order by type, nodes which contain at least one of the keywords
191
-    """
192
-    liOwnerIdList = self._getUserIdListForFiltering()
193
-    loKeywordFilteringClauses = []
194
-    for keyword in plKeywordList:
195
-        loKeywordFilteringClauses.append(pbmd.PBNode.data_label.ilike('%'+keyword+'%'))
196
-        loKeywordFilteringClauses.append(pbmd.PBNode.data_content.ilike('%'+keyword+'%'))
197
-        loKeywordFilteringClauses.append(pbmd.PBNode.data_file_name.ilike('%'+keyword+'%'))
198
-
199
-    loKeywordFilteringClausesAsOr = sqla.or_(*loKeywordFilteringClauses) # Combine them with or to a BooleanClauseList
200
-
201
-    loResultsForSomeKeywords = pbm.DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).outerjoin(pbma.Rights).outerjoin(pbma.user_group_table, pbma.Rights.group_id==pbma.user_group_table.columns['group_id'])\
202
-        .filter(loKeywordFilteringClausesAsOr)\
203
-        .filter((pbmd.PBNode.owner_id.in_(liOwnerIdList)) | (pbma.user_group_table.c.user_id.in_(liOwnerIdList) & pbmd.PBNode.is_shared))\
204
-        .order_by(sqla.desc(pbmd.PBNode.node_type))\
205
-        .limit(piMaxNodeNb)\
206
-        .all()
207
-
208
-    return loResultsForSomeKeywords
209
-
210
-  def getNodesByStatus(self, psNodeStatus, piMaxNodeNb=5):
211
-    liOwnerIdList = self._getUserIdListForFiltering()
212
-    return pbm.DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_status==psNodeStatus).order_by(pbmd.PBNode.updated_at).limit(piMaxNodeNb).all()
213
-
214
-
215
-  def getChildNodesForMenu(self, poParentNode: pbmd.PBNode, allowed_nodes: [pbmd.PBNode]) -> [pbmd.PBNode]:
216
-    visible_child_nodes = []
217
-
218
-    if poParentNode!=None:
219
-        # standard case
220
-        print(" -------------- BEFORE @@@@@@@@@@@@@@@@ ")
221
-        all_child_nodes = poParentNode.getChildren()
222
-        print("@@@@@@@@@@@@@@@@ AFTER @@@@@@@@@@@@@@@@ ")
223
-        for child_node in all_child_nodes:
224
-          if child_node in allowed_nodes:
225
-            visible_child_nodes.append(child_node)
226
-    else:
227
-        # Root case
228
-        parent_nodes = pbm.DBSession
229
-        for allowed_node in allowed_nodes:
230
-            print("     @@@@@@@@@@@@@@@@ BEFORE @@@@@@@@@@@@@@@@ ")
231
-            # loParent = allowed_node._oParent
232
-            # print("@@@@@@@@@@@@@@@@ AFTER @@@@@@@@@@@@@@@@ {0}".format(allowed_node._oParent.node_id if allowed_node._oParent!=None else '0'))
233
-            # print("==== EXTRA {0}".format(allowed_node._oParent.node_id if allowed_node._oParent!=None else '0'))
234
-            print("====> ")
235
-
236
-            if allowed_node._oParent is None or allowed_node._oParent==allowed_node:
237
-                # D.A. - HACK - 2014-05-27
238
-                # I don't know why, but as from now (with use of sqlao.contains_eager in getListOfAllowedNodes()
239
-                # the root items have at first iteration itself as parent
240
-                print("==== EXTRA END")
241
-                # this is a root item => add it
242
-                visible_child_nodes.append(allowed_node)
243
-            else:
244
-                if allowed_node._oParent not in allowed_nodes:
245
-                    # the node is visible but not the parent => put it at the root
246
-                    visible_child_nodes.append(allowed_node)
247
-                else:
248
-                    print("==== EXTRA END 2 {0}".format(allowed_node._oParent.node_id))
249
-
250
-    print(" @@@@@@@@@@@@@@@@ PRE FAILURE @@@@@@@@@@@@@@@@ ")
251
-    return visible_child_nodes
252
-
253
-
254
-
255
-
256
-  def buildTreeListForMenu(self, current_node: pbmd.PBNode, allowed_nodes: [pbmd.PBNode]) -> [pbmd.NodeTreeItem]:
257
-    # The algorithm is:
258
-    # 1. build an intermediate tree containing only current node and parent path
259
-    #    + complete it with sibling at each level (except root)
260
-    # 2. add sibling nodes at root level
261
-    # 3. complete it with shared documents (which are not at root but shared with current user)
262
-
263
-    node_tree = []
264
-
265
-    previous_tree_item = None
266
-    tmp_children_nodes = []
267
-
268
-    if current_node is not None:
269
-        breadcrumb_nodes = current_node.getBreadCrumbNodes()
270
-        breadcrumb_nodes.append(current_node) # by default the current node is not included
271
-
272
-        for breadcrumb_node in reversed(breadcrumb_nodes):
273
-            if previous_tree_item is None:
274
-                # First iteration. We add all current_node children
275
-                for child_node in breadcrumb_node.getChildren():
276
-                    if child_node in allowed_nodes:
277
-                        child_item = pbmd.NodeTreeItem(child_node, [])
278
-                        tmp_children_nodes.append(child_item)
279
-
280
-                previous_tree_item = pbmd.NodeTreeItem(breadcrumb_node, tmp_children_nodes)
281
-
282
-            else:
283
-                tmp_children_nodes = []
284
-                for child_node in breadcrumb_node.getChildren():
285
-                    if child_node in allowed_nodes:
286
-                        if child_node == previous_tree_item.node:
287
-                            tmp_children_nodes.append(previous_tree_item)
288
-                        else:
289
-                            sibling_node = pbmd.NodeTreeItem(child_node, [])
290
-                            tmp_children_nodes.append(sibling_node)
291
-
292
-                previous_tree_item = pbmd.NodeTreeItem(breadcrumb_node, tmp_children_nodes)
293
-
294
-    for node in allowed_nodes:
295
-        # This part of the algorithm insert root items
296
-        if node.parent_id==0 or node.parent_id is None:
297
-            if previous_tree_item is not None and node == previous_tree_item.node:
298
-                assert(isinstance(previous_tree_item, pbmd.NodeTreeItem))
299
-                node_tree.append(previous_tree_item)
300
-            else:
301
-                node_tree.append(pbmd.NodeTreeItem(node, []))
302
-        else:
303
-            # Try to add nodes shared with me but with a parent which is not shared
304
-            if node.owner_id!=self._iCurrentUserId:
305
-                # this is a node shared with me
306
-                if node._oParent!=None and node._oParent not in allowed_nodes:
307
-                    # the node is shared but not the parent => put it in the root
308
-                    node_tree.append(pbmd.NodeTreeItem(node, []))
309
-
310
-
311
-    return node_tree
312
-
313
-
314
-
315
-  def DIRTY_OLDbuildTreeListForMenu(self, plViewableStatusId: []) -> [pbmd.PBNode]:
316
-
317
-    liOwnerIdList = self._getUserIdListForFiltering()
318
-    
319
-    # loNodeList = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_type==pbmd.PBNodeType.Data).filter(pbmd.PBNode.node_status.in_(plViewableStatusId)).order_by(pbmd.PBNode.parent_tree_path).order_by(pbmd.PBNode.node_order).order_by(pbmd.PBNode.node_id).all()
320
-    loNodeListNotFiltered = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.node_type==pbmd.PBNodeType.Data).filter(pbmd.PBNode.node_status.in_(plViewableStatusId)).order_by(pbmd.PBNode.parent_tree_path).order_by(pbmd.PBNode.node_order).order_by(pbmd.PBNode.node_id).all()
321
-
322
-    loNodeList = []
323
-    for loNode in loNodeListNotFiltered:
324
-      if loNode.owner_id in self._getUserIdListForFiltering():
325
-        loNodeList.append(loNode)
326
-      else:
327
-        for loRight in loNode._lRights:
328
-          for loUser in loRight._oGroup.users:
329
-            if loUser.user_id in self._getUserIdListForFiltering():
330
-              loNodeList.append(loNode)
331
-
332
-    loTreeList = []
333
-    loTmpDict = {}
334
-    for loNode in loNodeList:
335
-      loTmpDict[loNode.node_id] = loNode
336
-  
337
-      if loNode.parent_id==None or loNode.parent_id==0:
338
-        loTreeList.append(loNode)
339
-      else:
340
-        # append the node to the parent list
341
-        # FIXME - D.A - 2013-10-08
342
-        # The following line may raise an exception
343
-        # We suppose that the parent node has already been added
344
-        # this *should* be the case, but the code does not check it
345
-        if loNode.parent_id not in loTmpDict.keys():
346
-          try:
347
-
348
-            loTmpDict[loNode.parent_id] = self.getNode(loNode.parent_id)
349
-          except Exception as e:
350
-            # loTreeList.append(
351
-            # FIXME - D.A. - 2014-05-22 This may be wrong code:
352
-            # we are in the case when the node parent is not shared with the current user
353
-            # So the node should be added at the root
354
-            pass
355
-        if loNode.parent_id in loTmpDict.keys():
356
-          # HACK- D.A. - 2014-05-22 - See FIXME upper
357
-          loTmpDict[loNode.parent_id].appendStaticChild(loNode)
358
-  
359
-    return loTreeList
360
-
361
-  def getParentNode(self, loNode):
362
-    liOwnerIdList = self._getUserIdListForFiltering()
363
-    return pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_id==loNode.parent_id).one()
364
-
365
-  def getSiblingNodes(self, poNode, pbReverseOrder=False):
366
-    liOwnerIdList = self._getUserIdListForFiltering()
367
-    
368
-    if pbReverseOrder:
369
-      return pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.parent_id==poNode.parent_id).order_by(pbmd.PBNode.node_order.desc()).all()
370
-    else:
371
-      return pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.parent_id==poNode.parent_id).order_by(pbmd.PBNode.node_order).all()
372
-
373
-  def resetNodeOrderOfSiblingNodes(self, loSiblingNodes):
374
-    liNewWeight = 0
375
-    for loNode in loSiblingNodes:
376
-      liNewWeight = liNewWeight + 1
377
-      loNode.node_order = liNewWeight
378
-    # DBSession.save()
379
-
380
-  def moveNodeUpper(self, loNode):
381
-    # FIXME - manage errors and logging
382
-    
383
-    loSiblingNodes = self.getSiblingNodes(loNode)
384
-    self.resetNodeOrderOfSiblingNodes(loSiblingNodes)
385
-  
386
-    loPreviousItem = None
387
-    for loItem in loSiblingNodes:
388
-      if loItem==loNode:
389
-        if loPreviousItem==None:
390
-          return FIXME_ERROR_CODE # FIXME - D.A. Do not use hard-coded error codes
391
-          print("No previous node")
392
-        else:
393
-          liPreviousItemOrder       = loPreviousItem.node_order
394
-          loPreviousItem.node_order = loNode.node_order
395
-          loNode.node_order         = liPreviousItemOrder
396
-          # DBSession.save()
397
-          break
398
-      loPreviousItem = loItem
399
-
400
-  def moveNodeLower(self, loNode):
401
-    # FIXME - manage errors and logging
402
-    
403
-    loSiblingNodes = self.getSiblingNodes(loNode)
404
-    self.resetNodeOrderOfSiblingNodes(loSiblingNodes)
405
-  
406
-    loPreviousItem = None
407
-    for loItem in reversed(loSiblingNodes):
408
-      if loItem==loNode:
409
-        if loPreviousItem==None:
410
-          return FIXME_ERROR_CODE # FIXME - D.A. Do not use hard-coded error codes
411
-          # FIXME
412
-          print("No previous node")
413
-        else:
414
-          liPreviousItemOrder       = loPreviousItem.node_order
415
-          loPreviousItem.node_order = loNode.node_order
416
-          loNode.node_order         = liPreviousItemOrder
417
-          # DBSession.save()
418
-          break
419
-      loPreviousItem = loItem
420
-
421
-  def getNodeFileContent(self, liNodeId):
422
-    liOwnerIdList = self._getUserIdListForFiltering()
423
-    return pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_id==liNodeId).one().data_file_content
424
-
425
-  def deleteNode(loNode):
426
-    # INFO - D.A. - 2013-11-07 - should be save as getNode should return only accessible nodes
427
-    pbm.DBSession.delete(loNode)
428
-    return
429
-
430
-  def createRight(self):
431
-    loRight = pbma.Rights()
432
-    return loRight

+ 13 - 70
tracim/tracim/lib/helpers.py 查看文件

15
 from tracim.lib.workspace import WorkspaceApi
15
 from tracim.lib.workspace import WorkspaceApi
16
 
16
 
17
 from tracim.model.data import ContentStatus
17
 from tracim.model.data import ContentStatus
18
-from tracim.model.data import PBNode
19
-from tracim.model.data import PBNodeType
18
+from tracim.model.data import Content
19
+from tracim.model.data import ContentType
20
 from tracim.model.data import UserRoleInWorkspace
20
 from tracim.model.data import UserRoleInWorkspace
21
 from tracim.model.data import Workspace
21
 from tracim.model.data import Workspace
22
 
22
 
64
     else:
64
     else:
65
         return Markup('<i class="icon-%s"></i>' % icon_name)
65
         return Markup('<i class="icon-%s"></i>' % icon_name)
66
 
66
 
67
-
68
-def getExplanationAboutStatus(psStatusId, psCurrentStatusId):
69
-  lsMsg = ""
70
-  if psStatusId==psCurrentStatusId:
71
-    return _("This is the current status.")
72
-  else:
73
-    if psStatusId=='information':
74
-      return _("The item is a normal document, like a howto or a text document.")
75
-    if psStatusId=='automatic':
76
-      return _("The item will be automatically computed as \"in progress\" or \"done\" according to its children status.")
77
-    if psStatusId=='new':
78
-      return _("No action done on the item.")
79
-    if psStatusId=='inprogress':
80
-      return _("The item is being worked on.")
81
-    if psStatusId=='standby':
82
-      return _("Waiting for some external actions.")
83
-    if psStatusId=='done':
84
-      return _("The work associated with the item is finished.")
85
-    if psStatusId=='closed':
86
-      return _("Close the item if you want not to see it anymore. The data won't be deleted")
87
-    if psStatusId=='deleted':
88
-      return _("This status tells that the item has been deleted.")
89
-
90
-class ID(object):
91
-  """ Helper class that will manage html items ids that need to be shared"""
92
-
93
-  @classmethod
94
-  def AddDocumentModalForm(cls, poNode=None):
95
-    if poNode:
96
-      return 'add-document-modal-form-%d'%poNode.node_id
97
-    else:
98
-      return 'add-document-modal-form'
99
-
100
-  @classmethod
101
-  def AddContactModalForm(cls, poNode=None):
102
-    if poNode:
103
-      return 'add-contact-modal-form-%d'%poNode.node_id
104
-    else:
105
-      return 'add-contact-modal-form'
106
-
107
-  @classmethod
108
-  def AddFileModalForm(cls, poNode=None):
109
-    if poNode:
110
-      return 'add-file-modal-form-%d'%poNode.node_id
111
-    else:
112
-      return 'add-file-modal-form'
113
-
114
-  @classmethod
115
-  def MoveDocumentModalForm(cls, poNode):
116
-      return 'move-document-modal-form-{0}'.format(poNode.node_id)
117
-
118
-  @classmethod
119
-  def AddEventModalForm(cls, poNode=None):
120
-    if poNode:
121
-      return 'add-event-modal-form-%d'%poNode.node_id
122
-    else:
123
-      return 'add-event-modal-form'
124
-    ## Original id is 'current-document-add-event-form'
125
-
126
-  @classmethod
127
-  def AddCommentInlineForm(cls):
128
-    return 'current-document-add-comment-form'
129
-
130
 class ICON(object):
67
 class ICON(object):
131
   Shared = '<i class="fa fa-group"></i>'
68
   Shared = '<i class="fa fa-group"></i>'
132
   Private = '<i class="fa fa-key"></i>'
69
   Private = '<i class="fa fa-key"></i>'
158
     elif role_level==UserRoleInWorkspace.WORKSPACE_MANAGER:
95
     elif role_level==UserRoleInWorkspace.WORKSPACE_MANAGER:
159
         return '#F00'
96
         return '#F00'
160
 
97
 
161
-def AllStatus(node_type=''):
162
-    return ContentStatus.all(node_type)
98
+def AllStatus(type=''):
99
+    return ContentStatus.all(type)
163
 
100
 
164
 
101
 
165
 def is_debug_mode():
102
 def is_debug_mode():
169
 def on_off_to_boolean(on_or_off: str) -> bool:
106
 def on_off_to_boolean(on_or_off: str) -> bool:
170
     return True if on_or_off=='on' else False
107
     return True if on_or_off=='on' else False
171
 
108
 
172
-def convert_id_into_instances(id: str) -> (Workspace, PBNode):
109
+def convert_id_into_instances(id: str) -> (Workspace, Content):
173
     """
110
     """
174
     TODO - D.A. - 2014-10-18 - Refactor and move this function where it must be placed
111
     TODO - D.A. - 2014-10-18 - Refactor and move this function where it must be placed
175
     convert an id like 'workspace_<workspace_id>|content_<content_id>'
112
     convert an id like 'workspace_<workspace_id>|content_<content_id>'
179
     if id=='#':
116
     if id=='#':
180
         return None, None
117
         return None, None
181
 
118
 
182
-    workspace_str, content_str = id.split(CST.TREEVIEW_MENU.ITEM_SEPARATOR)
119
+    workspace_str = ''
120
+    content_str = ''
121
+    try:
122
+        workspace_str, content_str = id.split(CST.TREEVIEW_MENU.ITEM_SEPARATOR)
123
+    except:
124
+        pass
125
+
183
     workspace = None
126
     workspace = None
184
     content = None
127
     content = None
185
 
128
 
193
     try:
136
     try:
194
         content_data = content_str.split(CST.TREEVIEW_MENU.ID_SEPARATOR)
137
         content_data = content_str.split(CST.TREEVIEW_MENU.ID_SEPARATOR)
195
         content_id = int(content_data[1])
138
         content_id = int(content_data[1])
196
-        content = ContentApi(tg.tmpl_context.current_user).get_one(content_id, PBNodeType.Folder)
139
+        content = ContentApi(tg.tmpl_context.current_user).get_one(content_id, ContentType.Folder)
197
     except (IndexError, ValueError) as e:
140
     except (IndexError, ValueError) as e:
198
         content = None
141
         content = None
199
 
142
 

+ 6 - 144
tracim/tracim/lib/user.py 查看文件

9
 from tracim.model import auth as pbma
9
 from tracim.model import auth as pbma
10
 from tracim.model import DBSession
10
 from tracim.model import DBSession
11
 import tracim.model.data as pmd
11
 import tracim.model.data as pmd
12
-# import DIRTY_GroupRightsOnNode, DIRTY_UserDedicatedGroupRightOnNodeSqlQuery, DIRTY_RealGroupRightOnNodeSqlQuery, PBNode
13
 
12
 
14
 class UserApi(object):
13
 class UserApi(object):
15
 
14
 
26
         return self._base_query().filter(User.user_id==user_id).one()
25
         return self._base_query().filter(User.user_id==user_id).one()
27
 
26
 
28
     def get_one_by_email(self, email: str):
27
     def get_one_by_email(self, email: str):
29
-        return self._base_query().filter(User.email_address==email).one()
28
+        return self._base_query().filter(User.email==email).one()
30
 
29
 
31
     def update(self, user: User, name: str, email: str, do_save):
30
     def update(self, user: User, name: str, email: str, do_save):
32
         user.display_name = name
31
         user.display_name = name
33
-        user.email_address = email
32
+        user.email = email
34
         if do_save:
33
         if do_save:
35
             self.save(user)
34
             self.save(user)
36
 
35
 
36
+        if user.user_id==self._user.user_id:
37
+            # this is required for the session to keep on being up-to-date
38
+            tg.request.identity['repoze.who.userid'] = email
39
+
37
     def user_with_email_exists(self, email: str):
40
     def user_with_email_exists(self, email: str):
38
         try:
41
         try:
39
             self.get_one_by_email(email)
42
             self.get_one_by_email(email)
58
   @classmethod
61
   @classmethod
59
   def get_current_user(cls) -> User:
62
   def get_current_user(cls) -> User:
60
     identity = tg.request.identity
63
     identity = tg.request.identity
61
-    print (tg.request.identity)
62
 
64
 
63
     if tg.request.identity:
65
     if tg.request.identity:
64
         return pbma.User.by_email_address(tg.request.identity['repoze.who.userid'])
66
         return pbma.User.by_email_address(tg.request.identity['repoze.who.userid'])
65
     return None
67
     return None
66
-
67
-  @classmethod
68
-  def getUserByEmailAddress(cls, psEmailAddress):
69
-    loUser = pbma.User.by_email_address(psEmailAddress)
70
-    return loUser
71
-
72
-  @classmethod
73
-  def createNewUser(cls, real_name, email_address, password, groups):
74
-    loUser = pbma.User()
75
-    new_user = pbma.User()
76
-    new_user.email_address = email_address
77
-    new_user.display_name  = real_name if real_name!='' else email_address
78
-    new_user.password      = password
79
-
80
-    public_group = cls.getGroupById(pbma.Group.TIM_USER)
81
-    public_group.users.append(new_user)
82
-
83
-    DBSession.add(new_user)
84
-    DBSession.flush()
85
-    DBSession.refresh(new_user)
86
-
87
-    user_dedicated_group = cls.createGroup()
88
-    user_dedicated_group.group_id = 0-new_user.user_id # group id of a given user is the opposite of the user id
89
-    user_dedicated_group.group_name = 'user_%d' % new_user.user_id
90
-    user_dedicated_group.personnal_group = True
91
-    user_dedicated_group.users.append(new_user)
92
-
93
-    for group_id in groups:
94
-        selected_group = cls.getGroupById(group_id)
95
-        selected_group.users.append(new_user)
96
-
97
-    DBSession.flush()
98
-
99
-    return new_user
100
-
101
-  @classmethod
102
-  def updateUser(cls, user_id, real_name, email, group_ids):
103
-
104
-      group_ids = list(map(int, group_ids))
105
-      group_ids.append(pbma.Group.TIM_USER)
106
-      print('new group ids:', group_ids)
107
-      user_to_update = DBSession.query(pbma.User).filter(pbma.User.user_id==user_id).one()
108
-      user_to_update.display_name = real_name
109
-      user_to_update.email_address = email
110
-
111
-      merged_new_groups = []
112
-
113
-      for group in user_to_update.groups:
114
-          if group.group_id==pbma.Group.TIM_ADMIN:
115
-              print('adding group (3)', group.group_id)
116
-              merged_new_groups.append(group)
117
-
118
-          elif group.group_id==pbma.Group.TIM_USER:
119
-              print('adding group (2)', group.group_id)
120
-              merged_new_groups.append(group)
121
-
122
-          elif group.group_id in group_ids:
123
-              print('adding group', group.group_id)
124
-              merged_new_groups.append(group)
125
-
126
-          else:
127
-              print('remove group', group.group_id)
128
-              user_to_update.groups.remove(group)
129
-
130
-      for group_id in group_ids:
131
-          group = cls.getGroupById(group_id)
132
-
133
-          if group not in merged_new_groups:
134
-              merged_new_groups.append(group)
135
-
136
-      user_to_update.groups = merged_new_groups
137
-
138
-      for group in merged_new_groups:
139
-          print("group => ", group.group_id)
140
-      DBSession.flush()
141
-
142
-  @classmethod
143
-  def deleteUser(cls, user_id):
144
-      user_to_delete = DBSession.query(pbma.User).filter(pbma.User.user_id==user_id).one()
145
-      user_dedicated_group = DBSession.query(pbma.Group).filter(pbma.Group.group_id==-user_id).one()
146
-      DBSession.delete(user_to_delete)
147
-      DBSession.delete(user_dedicated_group)
148
-      DBSession.flush()
149
-
150
-  @classmethod
151
-  def getGroup(cls, psGroupName):
152
-    loGroup = pbma.Group.by_group_name(psGroupName)
153
-    return loGroup
154
-
155
-  @classmethod
156
-  def getGroupById(cls, group_id):
157
-    return DBSession.query(pbma.Group).filter(pbma.Group.group_id==group_id).one()
158
-
159
-  @classmethod
160
-  def createGroup(cls):
161
-    loGroup = pbma.Group()
162
-    return loGroup
163
-
164
-  @classmethod
165
-  def getGroups(cls):
166
-    loGroups = pbma.Group.real_groups_first()
167
-    return loGroups
168
-
169
-  @classmethod
170
-  def getRealGroupRightsOnNode(cls, piNodeId: int) -> pmd.DIRTY_GroupRightsOnNode:
171
-
172
-    groupRightsOnNodeCustomSelect = DBSession\
173
-        .query(pmd.DIRTY_GroupRightsOnNode)\
174
-        .from_statement(pmd.DIRTY_RealGroupRightOnNodeSqlQuery)\
175
-        .params(node_id=piNodeId)\
176
-        .all()
177
-
178
-    return groupRightsOnNodeCustomSelect
179
-
180
-  @classmethod
181
-  def getUserDedicatedGroupRightsOnNode(cls, node: pmd.PBNode) -> pmd.DIRTY_GroupRightsOnNode:
182
-
183
-    group_rights_on_node = []
184
-    if node:
185
-        group_rights_on_node = DBSession\
186
-            .query(pmd.DIRTY_GroupRightsOnNode)\
187
-            .from_statement(pmd.DIRTY_UserDedicatedGroupRightOnNodeSqlQuery)\
188
-            .params(node_id=node.node_id)\
189
-            .all()
190
-
191
-    return group_rights_on_node
192
-
193
-  @classmethod
194
-  def DIRTY_get_rights_on_node(self, user_id, node_id):
195
-      rights = DBSession\
196
-              .execute("""select max(rights) as rights
197
-                      from pod_user_group
198
-                      natural join pod_group_node
199
-                      where node_id=:node_id
200
-                      and user_id=:user_id""", {"node_id":node_id, "user_id":user_id})\
201
-              .fetchone()
202
-      r = pmd.DIRTY_GroupRightsOnNode()
203
-      r.rights = rights[0]
204
-      return r
205
-

+ 1 - 1
tracim/tracim/lib/userworkspace.py 查看文件

71
         return self._get_all_for_user(user_id).all()
71
         return self._get_all_for_user(user_id).all()
72
 
72
 
73
     def get_all_for_user_order_by_workspace(self, user_id: int) -> UserRoleInWorkspace:
73
     def get_all_for_user_order_by_workspace(self, user_id: int) -> UserRoleInWorkspace:
74
-        return self._get_all_for_user(user_id).join(UserRoleInWorkspace.workspace).order_by(Workspace.data_label).all()
74
+        return self._get_all_for_user(user_id).join(UserRoleInWorkspace.workspace).order_by(Workspace.label).all()
75
 
75
 
76
     def get_all_for_workspace(self, workspace_id):
76
     def get_all_for_workspace(self, workspace_id):
77
         return DBSession.query(UserRoleInWorkspace).filter(UserRoleInWorkspace.workspace_id==workspace_id).all()
77
         return DBSession.query(UserRoleInWorkspace).filter(UserRoleInWorkspace.workspace_id==workspace_id).all()

+ 3 - 3
tracim/tracim/lib/workspace.py 查看文件

41
 
41
 
42
     def create_workspace(self, label: str, description: str='', save_now:bool=False) -> Workspace:
42
     def create_workspace(self, label: str, description: str='', save_now:bool=False) -> Workspace:
43
         workspace = Workspace()
43
         workspace = Workspace()
44
-        workspace.data_label = label
45
-        workspace.data_comment = description
44
+        workspace.label = label
45
+        workspace.description = description
46
 
46
 
47
         # By default, we force the current user to be the workspace manager
47
         # By default, we force the current user to be the workspace manager
48
         role = RoleApi(self._user).create_one(self._user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER)
48
         role = RoleApi(self._user).create_one(self._user, workspace, UserRoleInWorkspace.WORKSPACE_MANAGER)
70
 
70
 
71
     def get_all_for_user(self, user: User):
71
     def get_all_for_user(self, user: User):
72
         workspaces = [role.workspace for role in user.roles]
72
         workspaces = [role.workspace for role in user.roles]
73
-        workspaces.sort(key=lambda workspace: workspace.data_label.lower())
73
+        workspaces.sort(key=lambda workspace: workspace.label.lower())
74
         return workspaces
74
         return workspaces
75
 
75
 
76
     def save(self, workspace: Workspace):
76
     def save(self, workspace: Workspace):

+ 1 - 1
tracim/tracim/model/__init__.py 查看文件

60
 
60
 
61
 # Import your model modules here.
61
 # Import your model modules here.
62
 from tracim.model.auth import User, Group, Permission
62
 from tracim.model.auth import User, Group, Permission
63
-from tracim.model.data import PBNode
63
+from tracim.model.data import Content

+ 22 - 62
tracim/tracim/model/auth.py 查看文件

11
 import os
11
 import os
12
 from datetime import datetime
12
 from datetime import datetime
13
 from hashlib import sha256
13
 from hashlib import sha256
14
+from sqlalchemy.ext.hybrid import hybrid_property
14
 from tg.i18n import lazy_ugettext as l_
15
 from tg.i18n import lazy_ugettext as l_
15
 
16
 
16
 __all__ = ['User', 'Group', 'Permission']
17
 __all__ = ['User', 'Group', 'Permission']
23
 
24
 
24
 # This is the association table for the many-to-many relationship between
25
 # This is the association table for the many-to-many relationship between
25
 # groups and permissions.
26
 # groups and permissions.
26
-group_permission_table = Table('pod_group_permission', metadata,
27
-    Column('group_id', Integer, ForeignKey('pod_group.group_id',
27
+group_permission_table = Table('group_permission', metadata,
28
+    Column('group_id', Integer, ForeignKey('groups.group_id',
28
         onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
29
         onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
29
-    Column('permission_id', Integer, ForeignKey('pod_permission.permission_id',
30
+    Column('permission_id', Integer, ForeignKey('permissions.permission_id',
30
         onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
31
         onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
31
 )
32
 )
32
 
33
 
33
 # This is the association table for the many-to-many relationship between
34
 # This is the association table for the many-to-many relationship between
34
 # groups and members - this is, the memberships.
35
 # groups and members - this is, the memberships.
35
-user_group_table = Table('pod_user_group', metadata,
36
-    Column('user_id', Integer, ForeignKey('pod_user.user_id',
36
+user_group_table = Table('user_group', metadata,
37
+    Column('user_id', Integer, ForeignKey('users.user_id',
37
         onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
38
         onupdate="CASCADE", ondelete="CASCADE"), primary_key=True),
38
-    Column('group_id', Integer, ForeignKey('pod_group.group_id',
39
+    Column('group_id', Integer, ForeignKey('groups.group_id',
39
         onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
40
         onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
40
 )
41
 )
41
 
42
 
42
-
43
-
44
-
45
-class Rights(DeclarativeBase):
46
-
47
-    READ_ACCESS = 1
48
-    WRITE_ACCESS = 2
49
-
50
-    __tablename__ = 'pod_group_node'
51
-
52
-    group_id = Column(Integer, ForeignKey('pod_group.group_id'), primary_key=True)
53
-    node_id = Column(Integer, ForeignKey('pod_nodes.node_id'), primary_key=True)
54
-    rights = Column(Integer)
55
-
56
-    def hasReadAccess(self):
57
-        return self.rights & Rights.READ_ACCESS
58
-
59
-    def hasWriteAccess(self):
60
-        return self.rights & Rights.WRITE_ACCESS
61
-
62
-
63
 class Group(DeclarativeBase):
43
 class Group(DeclarativeBase):
64
 
44
 
65
     TIM_NOBODY = 0
45
     TIM_NOBODY = 0
72
     TIM_MANAGER_GROUPNAME = 'managers'
52
     TIM_MANAGER_GROUPNAME = 'managers'
73
     TIM_ADMIN_GROUPNAME = 'administrators'
53
     TIM_ADMIN_GROUPNAME = 'administrators'
74
 
54
 
75
-    __tablename__ = 'pod_group'
55
+    __tablename__ = 'groups'
76
 
56
 
77
     group_id = Column(Integer, autoincrement=True, primary_key=True)
57
     group_id = Column(Integer, autoincrement=True, primary_key=True)
78
     group_name = Column(Unicode(16), unique=True, nullable=False)
58
     group_name = Column(Unicode(16), unique=True, nullable=False)
81
 
61
 
82
     users = relationship('User', secondary=user_group_table, backref='groups')
62
     users = relationship('User', secondary=user_group_table, backref='groups')
83
 
63
 
84
-    _lRights = relationship('Rights', backref='_oGroup', cascade = "all, delete-orphan")
85
-
86
-
87
-
88
     def __repr__(self):
64
     def __repr__(self):
89
         return '<Group: name=%s>' % repr(self.group_name)
65
         return '<Group: name=%s>' % repr(self.group_name)
90
 
66
 
96
         """Return the user object whose email address is ``email``."""
72
         """Return the user object whose email address is ``email``."""
97
         return DBSession.query(cls).filter_by(group_name=group_name).first()
73
         return DBSession.query(cls).filter_by(group_name=group_name).first()
98
 
74
 
99
-    def getDisplayName(self) -> str:
100
-        if self.group_id<0:
101
-            # FIXME - D.A. - 2014-05-19 - MAKE THIS CODE CLEANER,
102
-            try:
103
-                return self.users[0].get_display_name()
104
-            except:
105
-                print('ERROR GROUP =>', self.group_id)
106
-
107
-
108
-        return self.display_name
109
-
110
-    @property
111
-    def rights(self):
112
-        return self._lRights
113
-
114
-    def hasSomeAccess(self, poNode):
115
-        for loRight in self._lRights:
116
-            if loRight.node_id == poNode.node_id and loRight.rights>0:
117
-                return True
118
-        return False
119
-
120
 
75
 
121
 
76
 
122
 class Profile(object):
77
 class Profile(object):
146
     User definition.
101
     User definition.
147
 
102
 
148
     This is the user definition used by :mod:`repoze.who`, which requires at
103
     This is the user definition used by :mod:`repoze.who`, which requires at
149
-    least the ``email_address`` column.
104
+    least the ``email`` column.
150
 
105
 
151
     """
106
     """
152
-    __tablename__ = 'pod_user'
107
+    __tablename__ = 'users'
153
 
108
 
154
     user_id = Column(Integer, autoincrement=True, primary_key=True)
109
     user_id = Column(Integer, autoincrement=True, primary_key=True)
155
-    email_address = Column(Unicode(255), unique=True, nullable=False)
110
+    email = Column(Unicode(255), unique=True, nullable=False)
156
     display_name = Column(Unicode(255))
111
     display_name = Column(Unicode(255))
157
     _password = Column('password', Unicode(128))
112
     _password = Column('password', Unicode(128))
158
     created = Column(DateTime, default=datetime.now)
113
     created = Column(DateTime, default=datetime.now)
159
     is_active = Column(Boolean, default=True, nullable=False)
114
     is_active = Column(Boolean, default=True, nullable=False)
115
+
116
+    @hybrid_property
117
+    def email_address(self):
118
+        return self.email
119
+
160
     def __repr__(self):
120
     def __repr__(self):
161
         return '<User: email=%s, display=%s>' % (
121
         return '<User: email=%s, display=%s>' % (
162
-                repr(self.email_address), repr(self.display_name))
122
+                repr(self.email), repr(self.display_name))
163
 
123
 
164
     def __unicode__(self):
124
     def __unicode__(self):
165
-        return self.display_name or self.email_address
125
+        return self.display_name or self.email
166
 
126
 
167
     @property
127
     @property
168
     def permissions(self):
128
     def permissions(self):
182
     @classmethod
142
     @classmethod
183
     def by_email_address(cls, email):
143
     def by_email_address(cls, email):
184
         """Return the user object whose email address is ``email``."""
144
         """Return the user object whose email address is ``email``."""
185
-        return DBSession.query(cls).filter_by(email_address=email).first()
145
+        return DBSession.query(cls).filter_by(email=email).first()
186
 
146
 
187
     @classmethod
147
     @classmethod
188
     def by_user_name(cls, username):
148
     def by_user_name(cls, username):
189
         """Return the user object whose user name is ``username``."""
149
         """Return the user object whose user name is ``username``."""
190
-        return DBSession.query(cls).filter_by(email_address=username).first()
150
+        return DBSession.query(cls).filter_by(email=username).first()
191
 
151
 
192
     @classmethod
152
     @classmethod
193
     def _hash_password(cls, password):
153
     def _hash_password(cls, password):
240
         if self.display_name!=None and self.display_name!='':
200
         if self.display_name!=None and self.display_name!='':
241
             return self.display_name
201
             return self.display_name
242
         else:
202
         else:
243
-            return self.email_address
203
+            return self.email
244
 
204
 
245
 
205
 
246
 class Permission(DeclarativeBase):
206
 class Permission(DeclarativeBase):
251
 
211
 
252
     """
212
     """
253
 
213
 
254
-    __tablename__ = 'pod_permission'
214
+    __tablename__ = 'permissions'
255
 
215
 
256
 
216
 
257
     permission_id = Column(Integer, autoincrement=True, primary_key=True)
217
     permission_id = Column(Integer, autoincrement=True, primary_key=True)

+ 102 - 539
tracim/tracim/model/data.py 查看文件

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 
2
 
3
 from bs4 import BeautifulSoup
3
 from bs4 import BeautifulSoup
4
-
4
+import datetime as datetime_root
5
 import json
5
 import json
6
-import re
7
-import datetime as datetimeroot
8
-from datetime import datetime
9
-from hashlib import sha256
10
-
11
-from sqlalchemy import Table, ForeignKey, Column, Sequence
12
-import sqlalchemy as sqla
13
-from sqlalchemy.sql.sqltypes import Boolean
14
-from sqlalchemy.types import Unicode, Integer, DateTime, Text, LargeBinary
15
-import sqlalchemy.types as sqlat
16
-from sqlalchemy.ext.orderinglist import ordering_list
17
-from sqlalchemy.orm import relation, synonym, relationship
18
-from sqlalchemy.orm import backref
19
-import sqlalchemy.orm as sqlao
20
-import sqlalchemy.orm.query as sqlaoq
21
-from sqlalchemy import orm as sqlao
6
+
7
+from sqlalchemy import Column
8
+from sqlalchemy import ForeignKey
9
+
22
 from sqlalchemy.ext.hybrid import hybrid_property
10
 from sqlalchemy.ext.hybrid import hybrid_property
23
 
11
 
24
-from tg.i18n import ugettext as _, lazy_ugettext as l_
12
+from sqlalchemy.orm import relationship
13
+from sqlalchemy.orm import deferred
25
 
14
 
26
-import tg
27
-from tracim.model import DeclarativeBase, metadata, DBSession
28
-# from tracim.model import auth as pma
15
+from sqlalchemy.types import Boolean
16
+from sqlalchemy.types import DateTime
17
+from sqlalchemy.types import Integer
18
+from sqlalchemy.types import LargeBinary
19
+from sqlalchemy.types import Text
20
+from sqlalchemy.types import Unicode
29
 
21
 
30
-from tracim.model.auth import User
31
-from tracim.model.auth import Rights
32
-from tracim.model.auth import user_group_table
22
+from tg.i18n import lazy_ugettext as l_
33
 
23
 
34
-from tracim.lib.base import current_user
24
+from tracim.model import DeclarativeBase
25
+from tracim.model.auth import User
35
 
26
 
36
 class BreadcrumbItem(object):
27
 class BreadcrumbItem(object):
37
 
28
 
47
 
38
 
48
 class Workspace(DeclarativeBase):
39
 class Workspace(DeclarativeBase):
49
 
40
 
50
-    __tablename__ = 'pod_workspaces'
41
+    __tablename__ = 'workspaces'
51
 
42
 
52
-    workspace_id = Column(Integer, Sequence('pod_workspaces__workspace_id__sequence'), primary_key=True)
43
+    workspace_id = Column(Integer, autoincrement=True, primary_key=True)
53
 
44
 
54
-    data_label   = Column(Unicode(1024), unique=False, nullable=False, default='')
55
-    data_comment = Column(Text(),        unique=False, nullable=False, default='')
45
+    label   = Column(Unicode(1024), unique=False, nullable=False, default='')
46
+    description = Column(Text(), unique=False, nullable=False, default='')
56
 
47
 
57
-    created_at = Column(DateTime, unique=False, nullable=False)
58
-    updated_at = Column(DateTime, unique=False, nullable=False)
48
+    created = Column(DateTime, unique=False, nullable=False)
49
+    updated = Column(DateTime, unique=False, nullable=False)
59
 
50
 
60
-    is_deleted = Column(sqlat.Boolean, unique=False, nullable=False, default=False)
51
+    is_deleted = Column(Boolean, unique=False, nullable=False, default=False)
61
 
52
 
62
     def get_user_role(self, user: User) -> int:
53
     def get_user_role(self, user: User) -> int:
63
         for role in user.roles:
54
         for role in user.roles:
69
 
60
 
70
 class UserRoleInWorkspace(DeclarativeBase):
61
 class UserRoleInWorkspace(DeclarativeBase):
71
 
62
 
72
-    __tablename__ = 'pod_user_workspace'
63
+    __tablename__ = 'user_workspace'
73
 
64
 
74
-    user_id = Column(Integer, ForeignKey('pod_user.user_id'), nullable=False, default=None, primary_key=True)
75
-    workspace_id = Column(Integer, ForeignKey('pod_workspaces.workspace_id'), nullable=False, default=None, primary_key=True)
65
+    user_id = Column(Integer, ForeignKey('users.user_id'), nullable=False, default=None, primary_key=True)
66
+    workspace_id = Column(Integer, ForeignKey('workspaces.workspace_id'), nullable=False, default=None, primary_key=True)
76
     role = Column(Integer, nullable=False, default=0, primary_key=False)
67
     role = Column(Integer, nullable=False, default=0, primary_key=False)
77
 
68
 
78
     workspace = relationship('Workspace', remote_side=[Workspace.workspace_id], backref='roles', lazy='joined')
69
     workspace = relationship('Workspace', remote_side=[Workspace.workspace_id], backref='roles', lazy='joined')
149
     _ICONS = {
140
     _ICONS = {
150
         'archiving': 'mimetypes/package-x-generic',
141
         'archiving': 'mimetypes/package-x-generic',
151
         'content-comment': 'apps/internet-group-chat',
142
         'content-comment': 'apps/internet-group-chat',
152
-        'creation': 'apps/accessories-text-editor',
143
+        'creation': 'actions/document-new',
153
         'deletion': 'status/user-trash-full',
144
         'deletion': 'status/user-trash-full',
154
         'edition': 'apps/accessories-text-editor',
145
         'edition': 'apps/accessories-text-editor',
155
         'revision': 'apps/accessories-text-editor',
146
         'revision': 'apps/accessories-text-editor',
174
     def __init__(self, id):
165
     def __init__(self, id):
175
         assert id in ActionDescription.allowed_values()
166
         assert id in ActionDescription.allowed_values()
176
         self.id = id
167
         self.id = id
177
-        self.label = ''
168
+        self.label = ActionDescription._LABELS[id]
178
         self.icon = ActionDescription._ICONS[id]
169
         self.icon = ActionDescription._ICONS[id]
179
 
170
 
180
     @classmethod
171
     @classmethod
233
         'closed-deprecated': 'tracim-status-closed-deprecated',
224
         'closed-deprecated': 'tracim-status-closed-deprecated',
234
     }
225
     }
235
 
226
 
236
-    def __init__(self, id, node_type=''):
227
+    def __init__(self, id, type=''):
237
         self.id = id
228
         self.id = id
238
-        print('ID', id)
239
         self.icon = ContentStatus._ICONS[id]
229
         self.icon = ContentStatus._ICONS[id]
240
         self.css = ContentStatus._CSS[id]
230
         self.css = ContentStatus._CSS[id]
241
 
231
 
242
-        if node_type==PBNodeType.Thread:
232
+        if type==ContentType.Thread:
243
             self.label = ContentStatus._LABELS_THREAD[id]
233
             self.label = ContentStatus._LABELS_THREAD[id]
244
-        elif node_type==PBNodeType.File:
234
+        elif type==ContentType.File:
245
             self.label = ContentStatus._LABELS_FILE[id]
235
             self.label = ContentStatus._LABELS_FILE[id]
246
         else:
236
         else:
247
             self.label = ContentStatus._LABELS[id]
237
             self.label = ContentStatus._LABELS[id]
248
 
238
 
249
 
239
 
250
     @classmethod
240
     @classmethod
251
-    def all(cls, node_type='') -> ['ContentStatus']:
241
+    def all(cls, type='') -> ['ContentStatus']:
252
         all = []
242
         all = []
253
-        all.append(ContentStatus('open', node_type))
254
-        all.append(ContentStatus('closed-validated', node_type))
255
-        all.append(ContentStatus('closed-unvalidated', node_type))
256
-        all.append(ContentStatus('closed-deprecated', node_type))
243
+        all.append(ContentStatus('open', type))
244
+        all.append(ContentStatus('closed-validated', type))
245
+        all.append(ContentStatus('closed-unvalidated', type))
246
+        all.append(ContentStatus('closed-deprecated', type))
257
         return all
247
         return all
258
 
248
 
259
     @classmethod
249
     @classmethod
260
     def allowed_values(cls):
250
     def allowed_values(cls):
261
         return ContentStatus._LABELS.keys()
251
         return ContentStatus._LABELS.keys()
262
 
252
 
263
-class PBNodeStatusItem(object):
264
-  def __init__(self, psStatusId, psStatusLabel, psStatusFamily, psIconId, psCssClass): #, psBackgroundColor):
265
-    self._sStatusId     = psStatusId
266
-    self._sStatusLabel  = psStatusLabel
267
-    self._sStatusFamily = psStatusFamily
268
-    self._sIconId   = psIconId
269
-    self._sCssClass = psCssClass
270
-    # self._sBackgroundColor = psBackgroundColor
271
-  
272
-  def getLabel(self):
273
-    return self._sStatusLabel
274
-    
275
-  @property
276
-  def status_family(self):
277
-    return self._sStatusFamily
278
-    
279
-  @property
280
-  def icon(self):
281
-    return self._sIconId
282
-    
283
-  def getId(self):
284
-    return self._sStatusId
285
-
286
-  @property
287
-  def css(self):
288
-    return self._sCssClass
289
-
290
-  @property
291
-  def status_id(self):
292
-    return self._sStatusId
293
-    
294
-  @property
295
-  def icon_id(self):
296
-    return self._sIconId
297
-
298
-  @property
299
-  def label(self):
300
-    return self._sStatusLabel
301
-
302
-class PBNodeStatus(object):
303
-    
304
-  StatusList = dict()
305
-  StatusList['information'] = PBNodeStatusItem('information', 'Information',         'normal', 'fa fa-info-circle',            'tracim-status-grey-light')
306
-  StatusList['automatic']   = PBNodeStatusItem('automatic',   'Automatic',           'open',   'fa fa-flash',                  'tracim-status-grey-light')
307
-  StatusList['new']         = PBNodeStatusItem('new',         'New',                 'open',   'fa fa-lightbulb-o fa-inverse', 'btn-success')
308
-  StatusList['inprogress']  = PBNodeStatusItem('inprogress',  'In progress',         'open',   'fa fa-gears fa-inverse',       'btn-info')
309
-  StatusList['standby']     = PBNodeStatusItem('standby',     'In standby',          'open',   'fa fa-spinner fa-inverse',     'btn-warning')
310
-  StatusList['done']        = PBNodeStatusItem('done',        'Done',                'closed', 'fa fa-check-square-o',         'tracim-status-grey-light')
311
-  StatusList['closed']      = PBNodeStatusItem('closed',      'Closed',              'closed', 'fa fa-lightbulb-o',            'tracim-status-grey-middle')
312
-  StatusList['deleted']     = PBNodeStatusItem('deleted',     'Deleted',             'closed', 'fa fa-trash-o',                'tracim-status-grey-dark')
313
-
314
-  @classmethod
315
-  def getChoosableList(cls):
316
-    return [
317
-      PBNodeStatus.StatusList['information'],
318
-      PBNodeStatus.StatusList['automatic'],
319
-      PBNodeStatus.StatusList['new'],
320
-      PBNodeStatus.StatusList['inprogress'],
321
-      PBNodeStatus.StatusList['standby'],
322
-      PBNodeStatus.StatusList['done'],
323
-      PBNodeStatus.StatusList['closed'],
324
-    ]
325
-
326
-  @classmethod
327
-  def getVisibleIdsList(cls):
328
-    return ['information', 'automatic', 'new', 'inprogress', 'standby', 'done' ]
329
-
330
-  @classmethod
331
-  def getVisibleList(cls):
332
-    return [
333
-      PBNodeStatus.StatusList['information'],
334
-      PBNodeStatus.StatusList['automatic'],
335
-      PBNodeStatus.StatusList['new'],
336
-      PBNodeStatus.StatusList['inprogress'],
337
-      PBNodeStatus.StatusList['standby'],
338
-      PBNodeStatus.StatusList['done'],
339
-    ]
340
-
341
-  @classmethod
342
-  def getList(cls):
343
-    return [
344
-      PBNodeStatus.StatusList['information'],
345
-      PBNodeStatus.StatusList['automatic'],
346
-      PBNodeStatus.StatusList['new'],
347
-      PBNodeStatus.StatusList['inprogress'],
348
-      PBNodeStatus.StatusList['standby'],
349
-      PBNodeStatus.StatusList['done'],
350
-      PBNodeStatus.StatusList['closed'],
351
-      PBNodeStatus.StatusList['deleted']
352
-    ]
353
-
354
-    
355
-  @classmethod
356
-  def getStatusItem(cls, psStatusId):
357
-    return PBNodeStatus.StatusList[psStatusId]
358
-
359
-class PBNodeType(object):
253
+class ContentType(object):
360
     Any = 'any'
254
     Any = 'any'
361
 
255
 
362
     Folder  = 'folder'
256
     Folder  = 'folder'
365
     Thread = 'thread'
259
     Thread = 'thread'
366
     Page = 'page'
260
     Page = 'page'
367
 
261
 
368
-    # Obsolete ones - to be removed
369
-    Node    = 'node'
370
-    Data    = 'data'
371
-    Event   = 'event'
372
-    Contact = 'contact'
373
-
374
     # Fake types, used for breadcrumb only
262
     # Fake types, used for breadcrumb only
375
     FAKE_Dashboard = 'dashboard'
263
     FAKE_Dashboard = 'dashboard'
376
     FAKE_Workspace = 'workspace'
264
     FAKE_Workspace = 'workspace'
389
 
277
 
390
     @classmethod
278
     @classmethod
391
     def icon(cls, type: str):
279
     def icon(cls, type: str):
392
-        assert(type in PBNodeType._ICONS) # DYN_REMOVE
393
-        return PBNodeType._ICONS[type]
280
+        assert(type in ContentType._ICONS) # DYN_REMOVE
281
+        return ContentType._ICONS[type]
394
 
282
 
395
     @classmethod
283
     @classmethod
396
     def allowed_types(cls):
284
     def allowed_types(cls):
400
     def allowed_types_from_str(cls, allowed_types_as_string: str):
288
     def allowed_types_from_str(cls, allowed_types_as_string: str):
401
         allowed_types = []
289
         allowed_types = []
402
         # HACK - THIS
290
         # HACK - THIS
403
-        for item in allowed_types_as_string.split(PBNodeType._STRING_LIST_SEPARATOR):
404
-            if item and item in PBNodeType.allowed_types():
291
+        for item in allowed_types_as_string.split(ContentType._STRING_LIST_SEPARATOR):
292
+            if item and item in ContentType.allowed_types():
405
                 allowed_types.append(item)
293
                 allowed_types.append(item)
406
         return allowed_types
294
         return allowed_types
407
 
295
 
408
-MINIMUM_DATE = datetimeroot.date(datetimeroot.MINYEAR, 1, 1)
409
-
410
-class PBNode(DeclarativeBase):
411
-
412
-    #def __init__(self):
413
-    #  self._lStaticChildList = []
414
-
415
-    @sqlao.reconstructor
416
-    def init_on_load(self):
417
-      self._lStaticChildList = []
418
-
419
-    def appendStaticChild(self, loNode):
420
-        print("%s has child %s" % (self.node_id, loNode.node_id))
421
-        self._lStaticChildList.append(loNode)
422
-
423
-    def getStaticChildList(self):
424
-        return self._lStaticChildList
425
-
426
-    def getStaticChildNb(self):
427
-        return len(self._lStaticChildList)
428
-
429
-    __tablename__ = 'pod_nodes'
296
+class Content(DeclarativeBase):
297
+    __tablename__ = 'contents'
430
 
298
 
431
     revision_to_serialize = -0  # This flag allow to serialize a given revision if required by the user
299
     revision_to_serialize = -0  # This flag allow to serialize a given revision if required by the user
432
 
300
 
433
-    node_id          = Column(Integer, Sequence('pod_nodes__node_id__sequence'), primary_key=True)
434
-    parent_id        = Column(Integer, ForeignKey('pod_nodes.node_id'), nullable=True, default=None)
435
-    node_depth       = Column(Integer, unique=False, nullable=False, default=0)
301
+    content_id = Column(Integer, autoincrement=True, primary_key=True)
302
+    parent_id = Column(Integer, ForeignKey('contents.content_id'), nullable=True, default=None)
303
+    node_depth = Column(Integer, unique=False, nullable=False, default=0)
436
     parent_tree_path = Column(Unicode(255), unique=False, nullable=False, default='')
304
     parent_tree_path = Column(Unicode(255), unique=False, nullable=False, default='')
437
-    owner_id         = Column(Integer, ForeignKey('pod_user.user_id'), nullable=True, default=None)
305
+    owner_id = Column(Integer, ForeignKey('users.user_id'), nullable=True, default=None)
438
 
306
 
439
-    node_order   = Column(Integer, nullable=True, default=1)
440
-    node_type    = Column(Unicode(32), unique=False, nullable=False)
441
-    node_status = Column(Unicode(32), unique=False, nullable=False, default=ContentStatus.OPEN)
307
+    type = Column(Unicode(32), unique=False, nullable=False)
308
+    status = Column(Unicode(32), unique=False, nullable=False, default=ContentStatus.OPEN)
442
 
309
 
443
-    created_at = Column(DateTime, unique=False, nullable=False)
444
-    updated_at = Column(DateTime, unique=False, nullable=False)
310
+    created = Column(DateTime, unique=False, nullable=False)
311
+    updated = Column(DateTime, unique=False, nullable=False)
445
 
312
 
446
-    workspace_id = Column(Integer, ForeignKey('pod_workspaces.workspace_id'), unique=False, nullable=True)
313
+    workspace_id = Column(Integer, ForeignKey('workspaces.workspace_id'), unique=False, nullable=True)
447
 
314
 
448
     workspace = relationship('Workspace', remote_side=[Workspace.workspace_id], backref='contents')
315
     workspace = relationship('Workspace', remote_side=[Workspace.workspace_id], backref='contents')
449
 
316
 
450
 
317
 
451
-    is_deleted = Column(sqlat.Boolean, unique=False, nullable=False, default=False)
452
-    is_archived = Column(sqlat.Boolean, unique=False, nullable=False, default=False)
453
-
454
-    is_shared = Column(sqlat.Boolean, unique=False, nullable=False, default=False)
455
-    is_public = Column(sqlat.Boolean, unique=False, nullable=False, default=False)
456
-    public_url_key = Column(Unicode(1024), unique=False, nullable=False, default='')
318
+    is_deleted = Column(Boolean, unique=False, nullable=False, default=False)
319
+    is_archived = Column(Boolean, unique=False, nullable=False, default=False)
457
 
320
 
458
-    data_label = Column(Unicode(1024), unique=False, nullable=False, default='')
459
-    data_content = Column(Text(), unique=False, nullable=False, default='')
321
+    label = Column(Unicode(1024), unique=False, nullable=False, default='')
322
+    description = Column(Text(), unique=False, nullable=False, default='')
460
     _properties = Column('properties', Text(), unique=False, nullable=False, default='')
323
     _properties = Column('properties', Text(), unique=False, nullable=False, default='')
461
 
324
 
462
-    data_datetime = Column(DateTime, unique=False, nullable=False)
463
-    data_reminder_datetime = Column(DateTime, unique=False, nullable=True)
325
+    file_name = Column(Unicode(255),  unique=False, nullable=False, default='')
326
+    file_mimetype = Column(Unicode(255),  unique=False, nullable=False, default='')
327
+    file_content = deferred(Column(LargeBinary(), unique=False, nullable=False, default=None))
464
 
328
 
465
-    data_file_name = Column(Unicode(255),  unique=False, nullable=False, default='')
466
-    data_file_mime_type = Column(Unicode(255),  unique=False, nullable=False, default='')
467
-    data_file_content = sqlao.deferred(Column(LargeBinary(), unique=False, nullable=False, default=None))
329
+    revision_type = Column(Unicode(32), unique=False, nullable=False, default='')
468
 
330
 
469
-    last_action = Column(Unicode(32), unique=False, nullable=False, default='')
470
-
471
-    _lRights = relationship('Rights', backref='_oNode', cascade = "all, delete-orphan")
472
-
473
-    parent = relationship('PBNode', remote_side=[node_id], backref='children')
474
-    owner = relationship('User', remote_side=[User.user_id], backref='_lAllNodes')
475
-
476
-    @hybrid_property
477
-    def _lAllChildren(self):
478
-        # for backward compatibility method
479
-        return self.children
480
-
481
-    @property
482
-    def _oOwner(self):
483
-        # for backward compatibility method
484
-        return self.owner
485
-
486
-    @property
487
-    def _oParent(self):
488
-        # for backward compatibility method
489
-        return self.parent
331
+    parent = relationship('Content', remote_side=[content_id], backref='children')
332
+    owner = relationship('User', remote_side=[User.user_id])
490
 
333
 
491
     @hybrid_property
334
     @hybrid_property
492
     def properties(self):
335
     def properties(self):
503
 
346
 
504
     def extract_links_from_content(self, other_content: str=None) -> [LinkItem]:
347
     def extract_links_from_content(self, other_content: str=None) -> [LinkItem]:
505
         """
348
         """
506
-        parse html content and extract links. By default, it works on the data_content property
507
-        :param other_content: if not empty, then parse the given html content instead of data_content
349
+        parse html content and extract links. By default, it works on the description property
350
+        :param other_content: if not empty, then parse the given html content instead of description
508
         :return: a list of LinkItem
351
         :return: a list of LinkItem
509
         """
352
         """
510
         links = []
353
         links = []
511
-        soup = BeautifulSoup(self.data_content if not other_content else other_content)
354
+        soup = BeautifulSoup(self.description if not other_content else other_content)
512
         for link in soup.findAll('a'):
355
         for link in soup.findAll('a'):
513
             href = link.get('href')
356
             href = link.get('href')
514
-            print(href)
515
             label = link.contents
357
             label = link.contents
516
             links.append(LinkItem(href, label))
358
             links.append(LinkItem(href, label))
517
         links.sort(key=lambda link: link.href if link.href else '')
359
         links.sort(key=lambda link: link.href if link.href else '')
521
         return sorted_links
363
         return sorted_links
522
 
364
 
523
 
365
 
524
-    def getChildrenOfType(self, plNodeTypeList, poKeySortingMethod=None, pbDoReverseSorting=False):
525
-        """return all children nodes of type 'data' or 'node' or 'folder'"""
526
-        llChildren = []
527
-        user_id = current_user().user_id
528
-        llChildren = DBSession.query(PBNode).outerjoin(Rights)\
529
-                .outerjoin(user_group_table, Rights.group_id==user_group_table.columns['group_id'])\
530
-                .filter(PBNode.parent_id==self.node_id)\
531
-                .filter((PBNode.owner_id==user_id) | ((user_group_table.c.user_id==user_id) & (PBNode.is_shared == True)))\
532
-                .filter(PBNode.node_type.in_(plNodeTypeList))\
533
-                .all()
534
-        if poKeySortingMethod!=None:
535
-          llChildren = sorted(llChildren, key=poKeySortingMethod, reverse=pbDoReverseSorting)
536
-        return llChildren
537
-
538
-    def get_child_nb(self, content_type: PBNodeType, content_status = ''):
539
-        # V2 method - to keep
366
+    def get_child_nb(self, content_type: ContentType, content_status = ''):
540
         child_nb = 0
367
         child_nb = 0
541
-        for child in self._lAllChildren:
542
-            if child.node_type==content_type:
368
+        for child in self.children:
369
+            if child.type==content_type:
543
                 if not content_status:
370
                 if not content_status:
544
                     child_nb = child_nb+1
371
                     child_nb = child_nb+1
545
-                elif content_status==child.node_status:
372
+                elif content_status==child.status:
546
                     child_nb = child_nb+1
373
                     child_nb = child_nb+1
547
         return child_nb
374
         return child_nb
548
 
375
 
549
     def get_status(self) -> ContentStatus:
376
     def get_status(self) -> ContentStatus:
550
-        return ContentStatus(self.node_status, self.node_type.__str__())
377
+        return ContentStatus(self.status, self.type.__str__())
378
+
551
 
379
 
552
     def get_last_action(self) -> ActionDescription:
380
     def get_last_action(self) -> ActionDescription:
553
-        return ActionDescription(self.last_action)
381
+        return ActionDescription(self.revision_type)
382
+
554
 
383
 
555
     def get_comments(self):
384
     def get_comments(self):
556
         children = []
385
         children = []
557
         for child in self.children:
386
         for child in self.children:
558
-            if child.node_type==PBNodeType.Comment:
387
+            if child.type==ContentType.Comment:
559
                 children.append(child)
388
                 children.append(child)
560
         return children
389
         return children
561
 
390
 
562
 
391
 
563
 
392
 
564
-
565
-
566
-    def getChildNb(self):
567
-        return self.getChildNbOfType([PBNodeType.Data])
568
-
569
-    def getGroupsWithSomeAccess(self):
570
-        llRights = []
571
-        for loRight in self._lRights:
572
-            if loRight.rights>0:
573
-                llRights.append(loRight)
574
-        return llRights
575
-
576
-    def getChildren(self, pbIncludeDeleted=False):
577
-        """return all children nodes of type 'data' or 'node' or 'folder'"""
578
-        # return self.getChildrenOfType([PBNodeType.Node, PBNodeType.Folder, PBNodeType.Data])
579
-        items = self.getChildrenOfType([PBNodeType.Node, PBNodeType.Folder, PBNodeType.Data])
580
-        items2 = list()
581
-        for item in items:
582
-            if pbIncludeDeleted==True or item.node_status!='deleted':
583
-                items2.append(item)
584
-        return items2
585
-
586
-    def getContacts(self):
587
-        """return all children nodes of type 'data' or 'node' or 'folder'"""
588
-        return self.getChildrenOfType([PBNodeType.Contact], PBNode.getSortingKeyForContact)
589
-
590
-    def getContactNb(self):
591
-        """return all children nodes of type 'data' or 'node' or 'folder'"""
592
-        return self.getChildNbOfType([PBNodeType.Contact])
593
-
594
-    @classmethod
595
-    def getSortingKeyBasedOnDataDatetime(cls, poDataNode):
596
-        return poDataNode.data_datetime or MINIMUM_DATE
597
-    
598
-    @classmethod
599
-    def getSortingKeyForContact(cls, poDataNode):
600
-        return poDataNode.data_label or ''
601
-
602
-    @classmethod
603
-    def getSortingKeyForComment(cls, poDataNode):
604
-        return poDataNode.data_datetime or ''
605
-
606
-    def getEvents(self):
607
-        return self.getChildrenOfType([PBNodeType.Event], PBNode.getSortingKeyBasedOnDataDatetime, True)
608
-    
609
-    def getFiles(self):
610
-        return self.getChildrenOfType([PBNodeType.File])
611
-
612
-    def getIconClass(self):
613
-        if self.node_type==PBNodeType.Data and self.getStaticChildNb()>0:
614
-            return PBNode.getIconClassForNodeType('folder')
615
-        else:
616
-            return PBNode.getIconClassForNodeType(self.node_type)
617
-
618
-    def getBreadCrumbNodes(self) -> list('PBNode'):
619
-        loNodes = []
620
-        if self._oParent!=None:
621
-            loNodes = self._oParent.getBreadCrumbNodes()
622
-            loNodes.append(self._oParent)
623
-        return loNodes
624
-
625
-    def getContentWithHighlightedKeywords(self, plKeywords, psPlainText):
626
-        if len(plKeywords)<=0:
627
-            return psPlainText
628
-
629
-        lsPlainText = psPlainText
630
-
631
-        for lsKeyword in plKeywords:
632
-            lsPlainText = re.sub('(?i)(%s)' % lsKeyword, '<strong>\\1</strong>', lsPlainText)
633
-
634
-        return lsPlainText
635
-
636
-
637
-    @classmethod
638
-    def getIconClassForNodeType(cls, psIconType):
639
-        laIconClass = dict()
640
-        laIconClass['node']   = 'fa fa-folder-open'
641
-        laIconClass['folder'] = 'fa fa-folder-open'
642
-        laIconClass['data']   = 'fa fa-file-text-o'
643
-
644
-        laIconClass['file']   = 'fa fa-paperclip'
645
-        laIconClass['event']  = 'fa fa-calendar'
646
-        laIconClass['contact'] = 'fa fa-user'
647
-        laIconClass['comment'] = 'fa fa-comments-o'
648
-        return laIconClass[psIconType]
649
-
650
-
651
-    def getUserFriendlyNodeType(self):
652
-        laNodeTypesLng = dict()
653
-        laNodeTypesLng['node']   = 'Document' # FIXME - D.A. - 2013-11-14 - Make text translatable
654
-        laNodeTypesLng['folder'] = 'Document'
655
-        laNodeTypesLng['data']   = 'Document'
656
-
657
-        laNodeTypesLng['file']   = 'File'
658
-        laNodeTypesLng['event']  = 'Event'
659
-        laNodeTypesLng['contact'] = 'Contact'
660
-        laNodeTypesLng['comment'] = 'Comment'
661
-
662
-        if self.node_type==PBNodeType.Data and self.getStaticChildNb()>0:
663
-            return laNodeTypesLng['folder']
664
-        else:
665
-            return laNodeTypesLng[self.node_type]
666
-
667
-    
668
-    def getFormattedDateTime(self, poDateTime, psDateTimeFormat = '%d/%m/%Y ~ %H:%M'):
669
-        return poDateTime.strftime(psDateTimeFormat)
670
-
671
-    def getFormattedDate(self, poDateTime, psDateTimeFormat = '%d/%m/%Y'):
672
-        return poDateTime.strftime(psDateTimeFormat)
673
-
674
-    def getFormattedTime(self, poDateTime, psDateTimeFormat = '%H:%M'):
675
-        return poDateTime.strftime(psDateTimeFormat)
676
-
677
-    def getStatus(self) -> PBNodeStatusItem:
678
-        loStatus = PBNodeStatus.getStatusItem(self.node_status)
679
-        if loStatus.status_id!='automatic':
680
-            return loStatus
681
-
682
-        # default case
683
-        # Compute the status:
684
-        # - if at least one child is 'new' or 'in progress' or 'in standby' => status is inprogress
685
-        # - else if all status are 'done', 'closed' or 'deleted' => 'done'
686
-        lsRealStatusId = 'done'
687
-        for loChild in self.getChildren():
688
-            if loChild.getStatus().status_id in ('new', 'inprogress', 'standby'):
689
-                lsRealStatusId = 'inprogress'
690
-                break
691
-        return PBNodeStatus.getStatusItem(lsRealStatusId)
692
-
693
-    def getTruncatedLabel(self, piCharNb: int):
694
-        """
695
-        return a truncated version of the data_label property.
696
-        if piCharNb is not > 0, then the full data_label is returned
697
-        note: if the node is a file and the data_label is empty, the file name is returned
698
-        """
699
-        lsTruncatedLabel = self.data_label
700
-
701
-        # 2014-05-06 - D.A. - HACK
702
-        # if the node is a file and label empty, then use the filename as data_label
703
-        if self.node_type==PBNodeType.File and lsTruncatedLabel=='':
704
-            lsTruncatedLabel = self.data_file_name
705
-
706
-        liMaxLength = int(piCharNb)
707
-        if liMaxLength>0 and len(lsTruncatedLabel)>liMaxLength:
708
-            lsTruncatedLabel = lsTruncatedLabel[0:liMaxLength-1]+'…'
709
-
710
-        if lsTruncatedLabel=='':
711
-            lsTruncatedLabel = _('Titleless Document')
712
-
713
-        return lsTruncatedLabel
714
-
715
-    def getTruncatedContentAsText(self, piCharNb):
716
-        lsPlainText = ''.join(BeautifulSoup(self.data_content).findAll(text=True))
717
-        lsTruncatedContent = ''
718
-
719
-        liMaxLength = int(piCharNb)
720
-        if len(lsPlainText)>liMaxLength:
721
-            lsTruncatedContent = lsPlainText[0:liMaxLength-1]+'…'
722
-        else:
723
-            lsTruncatedContent = lsPlainText
724
-        return lsTruncatedContent
725
-
726
-    def getTagList(self):
727
-        loPattern = re.compile('(^|\s|@)@(\w+)')
728
-        loResults = re.findall(loPattern, self.data_content)
729
-        lsResultList = []
730
-        for loResult in loResults:
731
-            lsResultList.append(loResult[1].replace('@', '').replace('_', ' '))
732
-        return lsResultList
733
-
734
-    @classmethod
735
-    def addTagReplacement(cls, matchobj):
736
-        return " <span class='badge'>%s</span> " %(matchobj.group(0).replace('@', '').replace('_', ' '))
737
-
738
-    @classmethod
739
-    def addDocLinkReplacement(cls, matchobj):
740
-        return " <a href='%s'>%s</a> " %(tg.url('/dashboard?node=%s')%(matchobj.group(1)), matchobj.group(0))
741
-
742
-    def getContentWithTags(self):
743
-        lsTemporaryResult = re.sub('(^|\s)@@(\w+)', '', self.data_content) # tags with @@ are explicitly removed from the body
744
-        lsTemporaryResult = re.sub('#([0-9]*)', PBNode.addDocLinkReplacement, lsTemporaryResult) # tags with @@ are explicitly removed from the body
745
-        return re.sub('(^|\s)@(\w+)', PBNode.addTagReplacement, lsTemporaryResult) # then, 'normal tags are transformed as labels'
746
-        # FIXME - D.A. - 2013-09-12
747
-        # Does not match @@ at end of content.
748
-  
749
-    def getHistory(self):
750
-        return DBSession.execute("select node_id, version_id, created_at from pod_nodes_history where node_id = :node_id order by created_at desc", {"node_id":self.node_id}).fetchall()
751
-
752
-
753
 class ContentChecker(object):
393
 class ContentChecker(object):
754
 
394
 
755
     @classmethod
395
     @classmethod
756
-    def check_properties(cls, item: PBNode):
757
-        if item.node_type==PBNodeType.Folder:
396
+    def check_properties(cls, item: Content):
397
+        if item.type==ContentType.Folder:
758
             properties = item.properties
398
             properties = item.properties
759
             if 'allowed_content' not in properties.keys():
399
             if 'allowed_content' not in properties.keys():
760
                 return False
400
                 return False
772
         raise NotImplementedError
412
         raise NotImplementedError
773
 
413
 
774
     @classmethod
414
     @classmethod
775
-    def reset_properties(cls, item: PBNode):
776
-        if item.node_type==PBNodeType.Folder:
415
+    def reset_properties(cls, item: Content):
416
+        if item.type==ContentType.Folder:
777
             item.properties = dict(
417
             item.properties = dict(
778
                 allowed_content = dict (
418
                 allowed_content = dict (
779
                     folder = True,
419
                     folder = True,
784
             )
424
             )
785
             return
425
             return
786
 
426
 
787
-        print('NODE TYPE', item.node_type)
788
         raise NotImplementedError
427
         raise NotImplementedError
789
 
428
 
429
+
790
 class ContentRevisionRO(DeclarativeBase):
430
 class ContentRevisionRO(DeclarativeBase):
791
 
431
 
792
-    __tablename__ = 'pod_nodes_history'
793
-
794
-    version_id = Column(Integer, primary_key=True)
795
-    node_id = Column(Integer, ForeignKey('pod_nodes.node_id'))
796
-    # parent_id = Column(Integer, ForeignKey('pod_nodes.node_id'), nullable=True)
797
-    owner_id = Column(Integer, ForeignKey('pod_user.user_id'), nullable=True)
798
-    data_label = Column(Unicode(1024), unique=False, nullable=False)
799
-    data_content = Column(Text(), unique=False, nullable=False, default='')
800
-    data_file_name = Column(Unicode(255),  unique=False, nullable=False, default='')
801
-    data_file_mime_type = Column(Unicode(255),  unique=False, nullable=False, default='')
802
-    data_file_content = sqlao.deferred(Column(LargeBinary(), unique=False, nullable=False, default=None))
803
-
804
-    node_status = Column(Unicode(32), unique=False, nullable=False)
805
-    created_at = Column(DateTime, unique=False, nullable=False)
806
-    updated_at = Column(DateTime, unique=False, nullable=False)
807
-    is_deleted = Column(sqlat.Boolean, unique=False, nullable=False)
808
-    is_archived = Column(sqlat.Boolean, unique=False, nullable=False)
809
-    last_action = Column(Unicode(32), unique=False, nullable=False, default='')
810
-
811
-    workspace_id = Column(Integer, ForeignKey('pod_workspaces.workspace_id'), unique=False, nullable=True)
432
+    __tablename__ = 'content_revisions'
433
+
434
+    revision_id = Column(Integer, primary_key=True)
435
+    content_id = Column(Integer, ForeignKey('contents.content_id'))
436
+    owner_id = Column(Integer, ForeignKey('users.user_id'), nullable=True)
437
+    label = Column(Unicode(1024), unique=False, nullable=False)
438
+    description = Column(Text(), unique=False, nullable=False, default='')
439
+    file_name = Column(Unicode(255),  unique=False, nullable=False, default='')
440
+    file_mimetype = Column(Unicode(255),  unique=False, nullable=False, default='')
441
+    file_content = deferred(Column(LargeBinary(), unique=False, nullable=False, default=None))
442
+
443
+    status = Column(Unicode(32), unique=False, nullable=False)
444
+    created = Column(DateTime, unique=False, nullable=False)
445
+    updated = Column(DateTime, unique=False, nullable=False)
446
+    is_deleted = Column(Boolean, unique=False, nullable=False)
447
+    is_archived = Column(Boolean, unique=False, nullable=False)
448
+    revision_type = Column(Unicode(32), unique=False, nullable=False, default='')
449
+
450
+    workspace_id = Column(Integer, ForeignKey('workspaces.workspace_id'), unique=False, nullable=True)
812
     workspace = relationship('Workspace', remote_side=[Workspace.workspace_id])
451
     workspace = relationship('Workspace', remote_side=[Workspace.workspace_id])
813
 
452
 
814
-    node = relationship('PBNode', remote_side=[PBNode.node_id], backref='revisions')
453
+    node = relationship('Content', remote_side=[Content.content_id], backref='revisions')
815
     owner = relationship('User', remote_side=[User.user_id])
454
     owner = relationship('User', remote_side=[User.user_id])
816
-    # parent = relationship('PBNode', remote_side=[PBNode.node_id])
817
 
455
 
818
     def get_status(self):
456
     def get_status(self):
819
-        return ContentStatus(self.node_status)
457
+        return ContentStatus(self.status)
820
 
458
 
821
     def get_last_action(self) -> ActionDescription:
459
     def get_last_action(self) -> ActionDescription:
822
-        return ActionDescription(self.last_action)
460
+        return ActionDescription(self.revision_type)
823
 
461
 
824
 
462
 
825
 class NodeTreeItem(object):
463
 class NodeTreeItem(object):
827
         This class implements a model that allow to simply represents the left-panel menu items
465
         This class implements a model that allow to simply represents the left-panel menu items
828
          This model is used by dbapi but is not directly related to sqlalchemy and database
466
          This model is used by dbapi but is not directly related to sqlalchemy and database
829
     """
467
     """
830
-    def __init__(self, node: PBNode, children: list('NodeTreeItem'), is_selected = False):
468
+    def __init__(self, node: Content, children: list('NodeTreeItem'), is_selected = False):
831
         self.node = node
469
         self.node = node
832
         self.children = children
470
         self.children = children
833
         self.is_selected = is_selected
471
         self.is_selected = is_selected
834
-
835
-
836
-
837
-
838
-#####
839
-#
840
-# HACK - 2014-05-21 - D.A
841
-#
842
-# The following hack is a horrible piece of code that allow to map a raw SQL select to a mapped class
843
-#
844
-class DIRTY_GroupRightsOnNode(object):
845
-    def hasSomeAccess(self):
846
-        return self.rights >= Rights.READ_ACCESS
847
-
848
-    def hasReadAccess(self):
849
-        return self.rights & Rights.READ_ACCESS
850
-
851
-    def hasWriteAccess(self):
852
-        return self.rights & Rights.WRITE_ACCESS
853
-
854
-DIRTY_group_rights_on_node_query = Table('fake_table', metadata,
855
-    Column('group_id', Integer, primary_key=True),
856
-    Column('node_id', Integer, primary_key=True),
857
-
858
-    Column('display_name', Unicode(255)),
859
-    Column('personnal_group', Boolean),
860
-    Column('rights', Integer, primary_key=True)
861
-)
862
-
863
-DIRTY_UserDedicatedGroupRightOnNodeSqlQuery = """
864
-SELECT
865
-    COALESCE(NULLIF(pg.display_name, ''), pu.display_name) AS display_name,
866
-    pg.personnal_group,
867
-    pg.group_id,
868
-    :node_id AS node_id,
869
-    COALESCE(pgn.rights, 0) AS rights
870
-FROM
871
-    pod_group AS pg
872
-    LEFT JOIN
873
-        pod_group_node AS pgn
874
-    ON
875
-        pg.group_id=pgn.group_id
876
-        AND pgn.node_id=:node_id
877
-    LEFT JOIN
878
-        pod_user AS pu
879
-    ON
880
-        pu.user_id=-pg.group_id
881
-WHERE
882
-    pg.personnal_group='t'
883
-ORDER BY
884
-    display_name
885
-;"""
886
-
887
-DIRTY_RealGroupRightOnNodeSqlQuery = """
888
-SELECT
889
-    pg.display_name AS display_name,
890
-    pg.personnal_group,
891
-    pg.group_id,
892
-    :node_id AS node_id,
893
-    COALESCE(pgn.rights, 0) AS rights
894
-FROM
895
-    pod_group AS pg
896
-    LEFT JOIN
897
-        pod_group_node AS pgn
898
-    ON
899
-        pg.group_id=pgn.group_id
900
-        AND pgn.node_id=:node_id
901
-WHERE
902
-    pg.personnal_group!='t'
903
-ORDER BY
904
-    display_name
905
-;"""
906
-
907
-sqlao.mapper(DIRTY_GroupRightsOnNode, DIRTY_group_rights_on_node_query)
908
-

+ 134 - 229
tracim/tracim/model/serializers.py 查看文件

11
 from tracim.model.data import ContentRevisionRO
11
 from tracim.model.data import ContentRevisionRO
12
 from tracim.model.data import LinkItem
12
 from tracim.model.data import LinkItem
13
 from tracim.model.data import NodeTreeItem
13
 from tracim.model.data import NodeTreeItem
14
-from tracim.model.data import PBNode
15
-from tracim.model.data import PBNodeType
14
+from tracim.model.data import Content
15
+from tracim.model.data import ContentType
16
 from tracim.model.data import RoleType
16
 from tracim.model.data import RoleType
17
 from tracim.model.data import UserRoleInWorkspace
17
 from tracim.model.data import UserRoleInWorkspace
18
 from tracim.model.data import Workspace
18
 from tracim.model.data import Workspace
20
 from tracim.model import data as pmd
20
 from tracim.model import data as pmd
21
 from tracim.lib import CST
21
 from tracim.lib import CST
22
 
22
 
23
-def node_to_dict(node: pmd.PBNode, children_content, new_item_state):
24
-    """
25
-    DEPRECATED - TODO - REMOVE
26
-        children_content may be boolean or a list containing json values
27
-    """
28
-    url = tg.url('/document/', dict(node_id=node.node_id)) ## FIXME - 2014-05-27 - Make this more flexible
29
-
30
-    return dict(
31
-        id = node.node_id,
32
-        children = children_content,
33
-        text = node.data_label,
34
-        a_attr = { "href" : url },
35
-        li_attr = { "title": node.data_label },
36
-        type = node.node_type, # this property is understandable by jstree (through "types" plugin)
37
-        state = new_item_state,
38
-        node_status = node.getStatus().getId() # this is not jstree understandable data. This requires a JS 'success' callback
39
-    )
40
-
41
-
42
-def PBNodeForMenu(func):
43
-
44
-    def process_item(item: pmd.PBNode):
45
-        """ convert given item into a dictionnary """
46
-        return node_to_dict(item, item.getChildNb()>0, None)
47
-
48
-    def pre_serialize(*args, **kws):
49
-        initial_result = func(*args, **kws)
50
-        real_result = None
51
-
52
-        if isinstance(initial_result, list):
53
-            real_result = list()
54
-            for value_item in initial_result:
55
-                real_result.append(process_item(value_item))
56
-        else:
57
-            # We suppose here that we have an object only
58
-            real_result = process_item(initial_result)
59
-
60
-        return dict(d = real_result)
61
-
62
-    return pre_serialize
63
-
64
-
65
-def NodeTreeItemForMenu(func):
66
-    """ works with structure NodeTreeItem """
67
-    def process_item(structure_item: pmd.NodeTreeItem, current_node_id=None):
68
-        """ convert given item into a dictionnary """
69
-
70
-        item = structure_item.node
71
-        children = []
72
-
73
-        for child_item in structure_item.children:
74
-            children.append(process_item(child_item, current_node_id))
75
-
76
-        children_field_value = None
77
-        if len(children)>0:
78
-            children_field_value = children
79
-        elif item.getChildNb()>0:
80
-            children_field_value = True
81
-        else:
82
-            children_field_value = False
83
-
84
-        new_item_state = dict(
85
-            opened = item.getChildNb()<=0 or len(children)>0,
86
-            selected = current_node_id!=None and item.node_id==current_node_id,
87
-        )
88
-
89
-        return node_to_dict(item, children_field_value, new_item_state)
90
-
91
-    def pre_serialize(*args, **kws):
92
-        initial_result = func(*args, **kws)
93
-        real_result = None
94
-
95
-        current_node_id = None
96
-        if "current_node_id" in kws:
97
-            current_node_id = int(kws['current_node_id'])
98
-
99
-        if isinstance(initial_result, list):
100
-            real_result = list()
101
-            for value_item in initial_result:
102
-                real_result.append(process_item(value_item, current_node_id))
103
-        else:
104
-            # We suppose here that we have an object only
105
-            real_result = process_item(initial_result, current_node_id)
106
-
107
-        return dict(d = real_result)
108
-
109
-    return pre_serialize
110
-
111
 #############################################################"
23
 #############################################################"
112
 ##
24
 ##
113
 ## HERE COMES THE SERIALIZATION CLASSES
25
 ## HERE COMES THE SERIALIZATION CLASSES
324
 @pod_serializer(ContentRevisionRO, CTX.FILE)
236
 @pod_serializer(ContentRevisionRO, CTX.FILE)
325
 def serialize_version_for_page_or_file(version: ContentRevisionRO, context: Context):
237
 def serialize_version_for_page_or_file(version: ContentRevisionRO, context: Context):
326
     return DictLikeClass(
238
     return DictLikeClass(
327
-        id = version.version_id,
328
-        node_id = version.node_id,
329
-        label = version.data_label if version.data_label else version.data_file_name,
239
+        id = version.revision_id,
240
+        label = version.label if version.label else version.file_name,
330
         owner = context.toDict(version.owner),
241
         owner = context.toDict(version.owner),
331
-        created = version.created_at,
242
+        created = version.created,
332
         action = context.toDict(version.get_last_action())
243
         action = context.toDict(version.get_last_action())
333
     )
244
     )
334
 
245
 
335
 
246
 
336
-@pod_serializer(PBNode, CTX.DEFAULT)
337
-def serialize_breadcrumb_item(content: PBNode, context: Context):
247
+@pod_serializer(Content, CTX.DEFAULT)
248
+def serialize_breadcrumb_item(content: Content, context: Context):
338
     return DictLikeClass(
249
     return DictLikeClass(
339
-        id = content.node_id,
340
-        label = content.data_label,
341
-        folder = context.toDict(DictLikeClass(id = content.parent.node_id if content.parent else None)),
342
-        # folder = None if not content.parent else context.toDict(DictLikeClass(id = content.parent.node_id)),
250
+        id = content.content_id,
251
+        label = content.label,
252
+        folder = context.toDict(DictLikeClass(id = content.parent.content_id if content.parent else None)),
343
         workspace = context.toDict(content.workspace)
253
         workspace = context.toDict(content.workspace)
344
     )
254
     )
345
 
255
 
346
 
256
 
347
-@pod_serializer(PBNode, CTX.MENU_API)
348
-def serialize_content_for_menu_api(content: PBNode, context: Context):
349
-    content_id = content.node_id
257
+@pod_serializer(Content, CTX.MENU_API)
258
+def serialize_content_for_menu_api(content: Content, context: Context):
259
+    content_id = content.content_id
350
     workspace_id = content.workspace_id
260
     workspace_id = content.workspace_id
351
 
261
 
352
     result = DictLikeClass(
262
     result = DictLikeClass(
353
         id = CST.TREEVIEW_MENU.ID_TEMPLATE__FULL.format(workspace_id, content_id),
263
         id = CST.TREEVIEW_MENU.ID_TEMPLATE__FULL.format(workspace_id, content_id),
354
         children = True, # TODO: make this dynamic
264
         children = True, # TODO: make this dynamic
355
-        text = content.data_label,
265
+        text = content.label,
356
         a_attr = { 'href' : tg.url('/workspaces/{}/folders/{}'.format(workspace_id, content_id)) },
266
         a_attr = { 'href' : tg.url('/workspaces/{}/folders/{}'.format(workspace_id, content_id)) },
357
-        li_attr = { 'title': content.data_label, 'class': 'tracim-tree-item-is-a-folder' },
358
-        type = content.node_type,
267
+        li_attr = { 'title': content.label, 'class': 'tracim-tree-item-is-a-folder' },
268
+        type = content.type,
359
         state = { 'opened': False, 'selected': False }
269
         state = { 'opened': False, 'selected': False }
360
     )
270
     )
361
     return result
271
     return result
362
 
272
 
363
 
273
 
364
-@pod_serializer(PBNode, CTX.FILES)
365
-@pod_serializer(PBNode, CTX.PAGES)
366
-def serialize_node_for_page_list(content: PBNode, context: Context):
274
+@pod_serializer(Content, CTX.FILES)
275
+@pod_serializer(Content, CTX.PAGES)
276
+def serialize_node_for_page_list(content: Content, context: Context):
367
 
277
 
368
-    if content.node_type==PBNodeType.Page:
278
+    if content.type==ContentType.Page:
369
         if not content.parent:
279
         if not content.parent:
370
             folder = None
280
             folder = None
371
         else:
281
         else:
372
-            print('FOLDER PARENT IS', content.parent)
373
             folder = Context(CTX.DEFAULT).toDict(content.parent)
282
             folder = Context(CTX.DEFAULT).toDict(content.parent)
374
-        print('FOLDER IS', folder)
283
+
375
         result = DictLikeClass(
284
         result = DictLikeClass(
376
-            id = content.node_id,
377
-            label = content.data_label,
285
+            id = content.content_id,
286
+            label = content.label,
378
             status = context.toDict(content.get_status()),
287
             status = context.toDict(content.get_status()),
379
             folder = folder
288
             folder = folder
380
         )
289
         )
381
         return result
290
         return result
382
 
291
 
383
-    if content.node_type==PBNodeType.File:
292
+    if content.type==ContentType.File:
384
         result = DictLikeClass(
293
         result = DictLikeClass(
385
-            id = content.node_id,
386
-            label = content.data_label if content.data_label else content.data_file_name,
294
+            id = content.content_id,
295
+            label = content.label if content.label else content.file_name,
387
             status = context.toDict(content.get_status()),
296
             status = context.toDict(content.get_status()),
388
             folder = Context(CTX.DEFAULT).toDict(content.parent)
297
             folder = Context(CTX.DEFAULT).toDict(content.parent)
389
         )
298
         )
392
 
301
 
393
     # TODO - DA - 2014-10-16 - THE FOLLOWING CODE SHOULD BE REMOVED
302
     # TODO - DA - 2014-10-16 - THE FOLLOWING CODE SHOULD BE REMOVED
394
     #
303
     #
395
-    # if content.node_type==PBNodeType.Folder:
304
+    # if content.type==ContentType.Folder:
396
     #     return DictLikeClass(
305
     #     return DictLikeClass(
397
-    #         id = content.node_id,
398
-    #         label = content.data_label,
306
+    #         id = content.content_id,
307
+    #         label = content.label,
399
     #     )
308
     #     )
400
 
309
 
401
-    raise NotImplementedError('node type / context not implemented: {} {}'. format(content.node_type, context.context_string))
310
+    raise NotImplementedError('node type / context not implemented: {} {}'. format(content.type, context.context_string))
402
 
311
 
403
 
312
 
404
-@pod_serializer(PBNode, CTX.PAGE)
405
-@pod_serializer(PBNode, CTX.FILE)
406
-def serialize_node_for_page(content: PBNode, context: Context):
407
-    if content.node_type in (PBNodeType.Page, PBNodeType.File) :
313
+@pod_serializer(Content, CTX.PAGE)
314
+@pod_serializer(Content, CTX.FILE)
315
+def serialize_node_for_page(content: Content, context: Context):
316
+    if content.type in (ContentType.Page, ContentType.File) :
408
         data_container = content
317
         data_container = content
409
 
318
 
410
 
319
 
412
         # The following properties are overriden by revision values
321
         # The following properties are overriden by revision values
413
         if content.revision_to_serialize>0:
322
         if content.revision_to_serialize>0:
414
             for revision in content.revisions:
323
             for revision in content.revisions:
415
-                if revision.version_id==content.revision_to_serialize:
324
+                if revision.revision_id==content.revision_to_serialize:
416
                     data_container = revision
325
                     data_container = revision
417
                     break
326
                     break
418
 
327
 
419
         result = DictLikeClass(
328
         result = DictLikeClass(
420
-            id = content.node_id,
329
+            id = content.content_id,
421
             parent = context.toDict(content.parent),
330
             parent = context.toDict(content.parent),
422
             workspace = context.toDict(content.workspace),
331
             workspace = context.toDict(content.workspace),
423
-            type = content.node_type,
332
+            type = content.type,
424
 
333
 
425
-            content = data_container.data_content,
426
-            created = data_container.created_at,
427
-            label = data_container.data_label,
428
-            icon = PBNodeType.icon(content.node_type),
334
+            content = data_container.description,
335
+            created = data_container.created,
336
+            label = data_container.label,
337
+            icon = ContentType.icon(content.type),
429
             owner = context.toDict(data_container.owner),
338
             owner = context.toDict(data_container.owner),
430
             status = context.toDict(data_container.get_status()),
339
             status = context.toDict(data_container.get_status()),
431
-            links = context.toDict(content.extract_links_from_content(data_container.data_content)),
432
-            revisions = context.toDict(sorted(content.revisions, key=lambda v: v.created_at, reverse=True)),
340
+            links = context.toDict(content.extract_links_from_content(data_container.description)),
341
+            revisions = context.toDict(sorted(content.revisions, key=lambda v: v.created, reverse=True)),
433
             selected_revision = 'latest' if content.revision_to_serialize<=0 else content.revision_to_serialize
342
             selected_revision = 'latest' if content.revision_to_serialize<=0 else content.revision_to_serialize
434
         )
343
         )
435
 
344
 
436
-        if content.node_type==PBNodeType.File:
437
-            result.label = content.data_label if content.data_label else content.data_file_name
345
+        if content.type==ContentType.File:
346
+            result.label = content.label if content.label else content.file_name
438
             result['file'] = DictLikeClass(
347
             result['file'] = DictLikeClass(
439
-                name = data_container.data_file_name,
440
-                size = len(data_container.data_file_content),
441
-                mimetype = data_container.data_file_mime_type)
348
+                name = data_container.file_name,
349
+                size = len(data_container.file_content),
350
+                mimetype = data_container.file_mimetype)
442
         return result
351
         return result
443
 
352
 
444
-    if content.node_type==PBNodeType.Folder:
353
+    if content.type==ContentType.Folder:
445
         value = DictLikeClass(
354
         value = DictLikeClass(
446
-            id = content.node_id,
447
-            label = content.data_label,
355
+            id = content.content_id,
356
+            label = content.label,
448
         )
357
         )
449
         return value
358
         return value
450
 
359
 
451
     raise NotImplementedError
360
     raise NotImplementedError
452
 
361
 
453
 
362
 
454
-@pod_serializer(PBNode, CTX.THREAD)
455
-def serialize_node_for_page(item: PBNode, context: Context):
456
-    if item.node_type==PBNodeType.Thread:
363
+@pod_serializer(Content, CTX.THREAD)
364
+def serialize_node_for_page(item: Content, context: Context):
365
+    if item.type==ContentType.Thread:
457
         return DictLikeClass(
366
         return DictLikeClass(
458
-            content = item.data_content,
459
-            created = item.created_at,
460
-            icon = PBNodeType.icon(item.node_type),
461
-            id = item.node_id,
462
-            label = item.data_label,
463
-            links = context.toDict(item.extract_links_from_content(item.data_content)),
367
+            content = item.description,
368
+            created = item.created,
369
+            icon = ContentType.icon(item.type),
370
+            id = item.content_id,
371
+            label = item.label,
372
+            links = context.toDict(item.extract_links_from_content(item.description)),
464
             owner = context.toDict(item.owner),
373
             owner = context.toDict(item.owner),
465
             parent = context.toDict(item.parent),
374
             parent = context.toDict(item.parent),
466
             selected_revision = 'latest',
375
             selected_revision = 'latest',
467
             status = context.toDict(item.get_status()),
376
             status = context.toDict(item.get_status()),
468
-            type = item.node_type,
377
+            type = item.type,
469
             workspace = context.toDict(item.workspace),
378
             workspace = context.toDict(item.workspace),
470
             comments = reversed(context.toDict(item.get_comments()))
379
             comments = reversed(context.toDict(item.get_comments()))
471
         )
380
         )
472
 
381
 
473
-    if item.node_type==PBNodeType.Comment:
382
+    if item.type==ContentType.Comment:
474
         return DictLikeClass(
383
         return DictLikeClass(
475
-            content = item.data_content,
476
-            created = item.created_at,
477
-            icon = PBNodeType.icon(item.node_type),
478
-            id = item.node_id,
479
-            label = item.data_label,
384
+            content = item.description,
385
+            created = item.created,
386
+            icon = ContentType.icon(item.type),
387
+            id = item.content_id,
388
+            label = item.label,
480
             owner = context.toDict(item.owner),
389
             owner = context.toDict(item.owner),
481
             # REMOVE parent = context.toDict(item.parent),
390
             # REMOVE parent = context.toDict(item.parent),
482
-            type = item.node_type,
391
+            type = item.type,
483
         )
392
         )
484
 
393
 
485
-    if item.node_type==PBNodeType.Folder:
394
+    if item.type==ContentType.Folder:
486
         return Context(CTX.DEFAULT).toDict(item)
395
         return Context(CTX.DEFAULT).toDict(item)
487
     ### CODE BELOW IS REPLACED BY THE TWO LINES UP ^^
396
     ### CODE BELOW IS REPLACED BY THE TWO LINES UP ^^
488
     # 2014-10-08 - IF YOU FIND THIS COMMENT, YOU CAn REMOVE THE CODE
397
     # 2014-10-08 - IF YOU FIND THIS COMMENT, YOU CAn REMOVE THE CODE
489
     #
398
     #
490
-    #if item.node_type==PBNodeType.Folder:
399
+    #if item.type==ContentType.Folder:
491
     #    value = DictLikeClass(
400
     #    value = DictLikeClass(
492
-    #        id = item.node_id,
493
-    #        label = item.data_label,
401
+    #        id = item.content_id,
402
+    #        label = item.label,
494
     #    )
403
     #    )
495
     #    return value
404
     #    return value
496
 
405
 
497
     raise NotImplementedError
406
     raise NotImplementedError
498
 
407
 
499
 
408
 
500
-@pod_serializer(PBNode, CTX.THREADS)
501
-def serialize_node_for_thread_list(content: PBNode, context: Context):
502
-    if content.node_type==PBNodeType.Thread:
409
+@pod_serializer(Content, CTX.THREADS)
410
+def serialize_node_for_thread_list(content: Content, context: Context):
411
+    if content.type==ContentType.Thread:
503
         return DictLikeClass(
412
         return DictLikeClass(
504
-            id = content.node_id,
505
-            label = content.data_label,
413
+            id = content.content_id,
414
+            label = content.label,
506
             status = context.toDict(content.get_status()),
415
             status = context.toDict(content.get_status()),
507
             folder = context.toDict(content.parent),
416
             folder = context.toDict(content.parent),
508
             comment_nb = len(content.get_comments())
417
             comment_nb = len(content.get_comments())
509
         )
418
         )
510
 
419
 
511
-    if content.node_type==PBNodeType.Folder:
420
+    if content.type==ContentType.Folder:
512
         return Context(CTX.DEFAULT).toDict(content)
421
         return Context(CTX.DEFAULT).toDict(content)
513
 
422
 
514
     raise NotImplementedError
423
     raise NotImplementedError
515
 
424
 
516
-@pod_serializer(PBNode, CTX.WORKSPACE)
517
-@pod_serializer(PBNode, CTX.FOLDERS)
518
-def serialize_content_for_workspace(content: PBNode, context: Context):
519
-    content_id = content.node_id
520
-    workspace_id = content.workspace_id
521
-
522
-    thread_nb_all  = content.get_child_nb(PBNodeType.Thread)
523
-    thread_nb_open = content.get_child_nb(PBNodeType.Thread)
524
-    file_nb_all  = content.get_child_nb(PBNodeType.File)
525
-    file_nb_open = content.get_child_nb(PBNodeType.File)
526
-    folder_nb_all  = content.get_child_nb(PBNodeType.Folder)
527
-    folder_nb_open = content.get_child_nb(PBNodeType.Folder)
528
-    page_nb_all  = content.get_child_nb(PBNodeType.Data)
529
-    page_nb_open = content.get_child_nb(PBNodeType.Data)
425
+@pod_serializer(Content, CTX.WORKSPACE)
426
+@pod_serializer(Content, CTX.FOLDERS)
427
+def serialize_content_for_workspace(content: Content, context: Context):
428
+    thread_nb_all  = content.get_child_nb(ContentType.Thread)
429
+    thread_nb_open = content.get_child_nb(ContentType.Thread)
430
+    file_nb_all  = content.get_child_nb(ContentType.File)
431
+    file_nb_open = content.get_child_nb(ContentType.File)
432
+    folder_nb_all  = content.get_child_nb(ContentType.Folder)
433
+    folder_nb_open = content.get_child_nb(ContentType.Folder)
434
+    page_nb_all  = content.get_child_nb(ContentType.Page)
435
+    page_nb_open = content.get_child_nb(ContentType.Page)
530
 
436
 
531
     content_nb_all = thread_nb_all +\
437
     content_nb_all = thread_nb_all +\
532
                      thread_nb_open +\
438
                      thread_nb_open +\
538
                      page_nb_open
444
                      page_nb_open
539
 
445
 
540
 
446
 
541
-    if content.node_type==PBNodeType.Folder:
447
+    result = None
448
+    if content.type==ContentType.Folder:
542
         result = DictLikeClass(
449
         result = DictLikeClass(
543
-            id = content.node_id,
544
-            label = content.data_label,
450
+            id = content.content_id,
451
+            label = content.label,
545
             thread_nb = DictLikeClass(
452
             thread_nb = DictLikeClass(
546
                 all = thread_nb_all,
453
                 all = thread_nb_all,
547
                 open = thread_nb_open,
454
                 open = thread_nb_open,
563
 
470
 
564
     return result
471
     return result
565
 
472
 
566
-@pod_serializer(PBNode, CTX.FOLDER)
567
-def serialize_content_for_workspace_and_folder(content: PBNode, context: Context):
568
-    content_id = content.node_id
569
-    workspace_id = content.workspace_id
570
-
571
-    thread_nb_all  = content.get_child_nb(PBNodeType.Thread)
572
-    thread_nb_open = content.get_child_nb(PBNodeType.Thread)
573
-    file_nb_all  = content.get_child_nb(PBNodeType.File)
574
-    file_nb_open = content.get_child_nb(PBNodeType.File)
575
-    folder_nb_all  = content.get_child_nb(PBNodeType.Folder)
576
-    folder_nb_open = content.get_child_nb(PBNodeType.Folder)
577
-    page_nb_all  = content.get_child_nb(PBNodeType.Data)
578
-    page_nb_open = content.get_child_nb(PBNodeType.Data)
473
+@pod_serializer(Content, CTX.FOLDER)
474
+def serialize_content_for_workspace_and_folder(content: Content, context: Context):
475
+    thread_nb_all  = content.get_child_nb(ContentType.Thread)
476
+    thread_nb_open = content.get_child_nb(ContentType.Thread)
477
+    file_nb_all  = content.get_child_nb(ContentType.File)
478
+    file_nb_open = content.get_child_nb(ContentType.File)
479
+    folder_nb_all  = content.get_child_nb(ContentType.Folder)
480
+    folder_nb_open = content.get_child_nb(ContentType.Folder)
481
+    page_nb_all  = content.get_child_nb(ContentType.Page)
482
+    page_nb_open = content.get_child_nb(ContentType.Page)
579
 
483
 
580
     content_nb_all = thread_nb_all +\
484
     content_nb_all = thread_nb_all +\
581
                      thread_nb_open +\
485
                      thread_nb_open +\
587
                      page_nb_open
491
                      page_nb_open
588
 
492
 
589
 
493
 
590
-    if content.node_type==PBNodeType.Folder:
494
+    result = None
495
+    if content.type==ContentType.Folder:
591
         result = DictLikeClass(
496
         result = DictLikeClass(
592
-            id = content.node_id,
593
-            label = content.data_label,
594
-            created = content.created_at,
497
+            id = content.content_id,
498
+            label = content.label,
499
+            created = content.created,
595
             workspace = context.toDict(content.workspace),
500
             workspace = context.toDict(content.workspace),
596
             allowed_content = DictLikeClass(content.properties['allowed_content']),
501
             allowed_content = DictLikeClass(content.properties['allowed_content']),
597
             selected_revision = 'latest',
502
             selected_revision = 'latest',
616
             content_nb = DictLikeClass(all = content_nb_all)
521
             content_nb = DictLikeClass(all = content_nb_all)
617
         )
522
         )
618
 
523
 
619
-    elif content.node_type==PBNodeType.Page:
524
+    elif content.type==ContentType.Page:
620
         result = DictLikeClass(
525
         result = DictLikeClass(
621
-            id = content.node_id,
622
-            label = content.data_label,
623
-            created = content.created_at,
526
+            id = content.content_id,
527
+            label = content.label,
528
+            created = content.created,
624
             workspace = context.toDict(content.workspace),
529
             workspace = context.toDict(content.workspace),
625
             owner = DictLikeClass(
530
             owner = DictLikeClass(
626
-                id = content._oOwner.user_id,
627
-                name = content._oOwner.display_name
531
+                id = content.owner.user_id,
532
+                name = content.owner.get_display_name()
628
             ),
533
             ),
629
             status = DictLikeClass(id='', label=''), #FIXME - EXPORT DATA
534
             status = DictLikeClass(id='', label=''), #FIXME - EXPORT DATA
630
         )
535
         )
712
     result = DictLikeClass()
617
     result = DictLikeClass()
713
     result['id'] = user.user_id
618
     result['id'] = user.user_id
714
     result['name'] = user.get_display_name()
619
     result['name'] = user.get_display_name()
715
-    result['email'] = user.email_address
620
+    result['email'] = user.email
716
     result['enabled'] = user.is_active
621
     result['enabled'] = user.is_active
717
     result['profile'] = user.profile
622
     result['profile'] = user.profile
718
     return result
623
     return result
731
     result = DictLikeClass()
636
     result = DictLikeClass()
732
     result['id'] = user.user_id
637
     result['id'] = user.user_id
733
     result['name'] = user.get_display_name()
638
     result['name'] = user.get_display_name()
734
-    result['email'] = user.email_address
639
+    result['email'] = user.email
735
     result['roles'] = context.toDict(user.roles)
640
     result['roles'] = context.toDict(user.roles)
736
     result['enabled'] = user.is_active
641
     result['enabled'] = user.is_active
737
     result['profile'] = user.profile
642
     result['profile'] = user.profile
756
     result['role'] = role.role
661
     result['role'] = role.role
757
     result['style'] = role.style
662
     result['style'] = role.style
758
     result['role_description'] = role.role_as_label()
663
     result['role_description'] = role.role_as_label()
759
-    result['email'] = role.user.email_address
664
+    result['email'] = role.user.email
760
     return result
665
     return result
761
 
666
 
762
 
667
 
774
     result['label'] = role.role_as_label()
679
     result['label'] = role.role_as_label()
775
     result['style'] = RoleType(role.role).css_style
680
     result['style'] = RoleType(role.role).css_style
776
     result['workspace'] =  context.toDict(role.workspace)
681
     result['workspace'] =  context.toDict(role.workspace)
777
-    # result['workspace_name'] = role.workspace.data_label
682
+    # result['workspace_name'] = role.workspace.label
778
 
683
 
779
     return result
684
     return result
780
 
685
 
785
 def serialize_workspace_default(workspace: Workspace, context: Context):
690
 def serialize_workspace_default(workspace: Workspace, context: Context):
786
     result = DictLikeClass(
691
     result = DictLikeClass(
787
         id = workspace.workspace_id,
692
         id = workspace.workspace_id,
788
-        label = workspace.data_label
693
+        label = workspace.label
789
     )
694
     )
790
     return result
695
     return result
791
 
696
 
800
     """
705
     """
801
     result = DictLikeClass()
706
     result = DictLikeClass()
802
     result['id'] = workspace.workspace_id
707
     result['id'] = workspace.workspace_id
803
-    result['name'] = workspace.data_label
708
+    result['name'] = workspace.label
804
 
709
 
805
     return result
710
     return result
806
 
711
 
808
 def serialize_workspace_in_list(workspace: pmd.Workspace, context: Context):
713
 def serialize_workspace_in_list(workspace: pmd.Workspace, context: Context):
809
     result = DictLikeClass()
714
     result = DictLikeClass()
810
     result['id'] = workspace.workspace_id
715
     result['id'] = workspace.workspace_id
811
-    result['label'] = workspace.data_label
812
-    result['description'] = workspace.data_comment
716
+    result['label'] = workspace.label
717
+    result['description'] = workspace.description
813
     result['member_nb'] = len(workspace.roles)
718
     result['member_nb'] = len(workspace.roles)
814
 
719
 
815
     #    roles = serializableObject.roles
720
     #    roles = serializableObject.roles
822
 def serialize_workspace_complete(workspace: pmd.Workspace, context: Context):
727
 def serialize_workspace_complete(workspace: pmd.Workspace, context: Context):
823
     result = DictLikeClass()
728
     result = DictLikeClass()
824
     result['id'] = workspace.workspace_id
729
     result['id'] = workspace.workspace_id
825
-    result['label'] = workspace.data_label
826
-    result['description'] = workspace.data_comment
827
-    result['created'] = workspace.created_at
730
+    result['label'] = workspace.label
731
+    result['description'] = workspace.description
732
+    result['created'] = workspace.created
828
     result['members'] = context.toDict(workspace.roles)
733
     result['members'] = context.toDict(workspace.roles)
829
     result['member_nb'] = len(workspace.roles)
734
     result['member_nb'] = len(workspace.roles)
830
 
735
 
835
     result = DictLikeClass(
740
     result = DictLikeClass(
836
         id = CST.TREEVIEW_MENU.ID_TEMPLATE__WORKSPACE_ONLY.format(workspace.workspace_id),
741
         id = CST.TREEVIEW_MENU.ID_TEMPLATE__WORKSPACE_ONLY.format(workspace.workspace_id),
837
         children = True, # TODO: make this dynamic
742
         children = True, # TODO: make this dynamic
838
-        text = workspace.data_label,
743
+        text = workspace.label,
839
         a_attr = { 'href' : tg.url('/workspaces/{}'.format(workspace.workspace_id)) },
744
         a_attr = { 'href' : tg.url('/workspaces/{}'.format(workspace.workspace_id)) },
840
-        li_attr = { 'title': workspace.data_label, 'class': 'tracim-tree-item-is-a-workspace' },
745
+        li_attr = { 'title': workspace.label, 'class': 'tracim-tree-item-is-a-workspace' },
841
         type = 'workspace',
746
         type = 'workspace',
842
         state = { 'opened': False, 'selected': False }
747
         state = { 'opened': False, 'selected': False }
843
     )
748
     )
845
 
750
 
846
 @pod_serializer(NodeTreeItem, CTX.MENU_API_BUILD_FROM_TREE_ITEM)
751
 @pod_serializer(NodeTreeItem, CTX.MENU_API_BUILD_FROM_TREE_ITEM)
847
 def serialize_node_tree_item_for_menu_api_tree(item: NodeTreeItem, context: Context):
752
 def serialize_node_tree_item_for_menu_api_tree(item: NodeTreeItem, context: Context):
848
-    if isinstance(item.node, PBNode):
753
+    if isinstance(item.node, Content):
849
         return DictLikeClass(
754
         return DictLikeClass(
850
-            id=CST.TREEVIEW_MENU.ID_TEMPLATE__FULL.format(item.node.workspace_id, item.node.node_id),
755
+            id=CST.TREEVIEW_MENU.ID_TEMPLATE__FULL.format(item.node.workspace_id, item.node.content_id),
851
             children=True if len(item.children)<=0 else context.toDict(item.children),
756
             children=True if len(item.children)<=0 else context.toDict(item.children),
852
-            text=item.node.data_label,
853
-            a_attr={'href': tg.url('/workspaces/{}/folders/{}'.format(item.node.workspace_id, item.node.node_id)) },
854
-            li_attr={'title': item.node.data_label, 'class': 'tracim-tree-item-is-a-folder'},
757
+            text=item.node.label,
758
+            a_attr={'href': tg.url('/workspaces/{}/folders/{}'.format(item.node.workspace_id, item.node.content_id)) },
759
+            li_attr={'title': item.node.label, 'class': 'tracim-tree-item-is-a-folder'},
855
             type='folder',
760
             type='folder',
856
             state={'opened': True if len(item.children)>0 else False, 'selected': item.is_selected}
761
             state={'opened': True if len(item.children)>0 else False, 'selected': item.is_selected}
857
         )
762
         )
859
         return DictLikeClass(
764
         return DictLikeClass(
860
             id=CST.TREEVIEW_MENU.ID_TEMPLATE__WORKSPACE_ONLY.format(item.node.workspace_id),
765
             id=CST.TREEVIEW_MENU.ID_TEMPLATE__WORKSPACE_ONLY.format(item.node.workspace_id),
861
             children=True if len(item.children)<=0 else context.toDict(item.children),
766
             children=True if len(item.children)<=0 else context.toDict(item.children),
862
-            text=item.node.data_label,
767
+            text=item.node.label,
863
             a_attr={'href': tg.url('/workspaces/{}'.format(item.node.workspace_id))},
768
             a_attr={'href': tg.url('/workspaces/{}'.format(item.node.workspace_id))},
864
-            li_attr={'title': item.node.data_label, 'class': 'tracim-tree-item-is-a-workspace'},
769
+            li_attr={'title': item.node.label, 'class': 'tracim-tree-item-is-a-workspace'},
865
             type='workspace',
770
             type='workspace',
866
             state={'opened': True if len(item.children)>0 else False, 'selected': item.is_selected}
771
             state={'opened': True if len(item.children)>0 else False, 'selected': item.is_selected}
867
         )
772
         )

+ 14 - 9
tracim/tracim/public/assets/css/dashboard.css 查看文件

146
 .pod-tree-item-is-a-folder > a { }
146
 .pod-tree-item-is-a-folder > a { }
147
 
147
 
148
 
148
 
149
-.pod-less-visible { color: #999; }
149
+.tracim-less-visible { color: #999; }
150
 
150
 
151
-.pod-status-open { font-weight: bold; color: #759ac5; }
152
-.pod-status-closed-validated { font-weight: bold; color: #1fdb11;}
153
-.pod-status-closed-unvalidated { font-weight: bold; color: #F00;}
154
-.pod-status-closed-deprecated { font-weight: bold; color: #ea983d;}
155
-.pod-status-selected { background-color: #EEE; }
156
-.pod-panel-separator { border-width: 12px 0 0 0; }
151
+.tracim-status-open { font-weight: bold; color: #759ac5; }
152
+.tracim-status-closed-validated { font-weight: bold; color: #1fdb11;}
153
+.tracim-status-closed-unvalidated { font-weight: bold; color: #F00;}
154
+.tracim-status-closed-deprecated { font-weight: bold; color: #ea983d;}
155
+.tracim-status-selected { background-color: #EEE; }
156
+.tracim-panel-separator { border-width: 12px 0 0 0; }
157
 
157
 
158
-.pod-thread-item {}
159
-.pod-thread-item-content { margin-left: 12px; border-left: 8px solid #EEE; padding: 0 0.5em; }
158
+.tracim-thread-item {}
159
+.tracim-thread-item-content { margin-left: 12px; border-left: 8px solid #EEE; padding: 0 0.5em; }
160
 
160
 
161
+#tracim-footer-separator { margin-bottom: 30px; }
161
 .pod-footer {
162
 .pod-footer {
162
     position: fixed;
163
     position: fixed;
163
     bottom: 0;
164
     bottom: 0;
164
     width: 100%;
165
     width: 100%;
166
+    background-color: #F5F5F5;
167
+    border-top: 1px solid #CCC;
168
+    z-index: 1001;
165
 }
169
 }
170
+.pod-footer p { margin: auto; }
166
 
171
 
167
 
172
 

+ 1 - 1
tracim/tracim/templates/debug/iconset-tango.mak 查看文件

23
     import sys
23
     import sys
24
 
24
 
25
     icon_files = dict()
25
     icon_files = dict()
26
-    rootdir = './pod/public/assets/icons/'
26
+    rootdir = './tracim/public/assets/icons/'
27
     
27
     
28
     for root, subFolders, files in os.walk(rootdir):
28
     for root, subFolders, files in os.walk(rootdir):
29
         for foundFile in files:
29
         for foundFile in files:

+ 3 - 2
tracim/tracim/templates/master_anonymous.mak 查看文件

28
         <div class="container-fluid">
28
         <div class="container-fluid">
29
             ${self.main_menu()}
29
             ${self.main_menu()}
30
             ${self.content_wrapper()}
30
             ${self.content_wrapper()}
31
-            ${self.footer()}
31
+            <div id="tracim-footer-separator"></div>
32
         </div>
32
         </div>
33
+        ${self.footer()}
33
 
34
 
34
         <script src="${tg.url('/assets/js/bootstrap.min.js')}"></script>
35
         <script src="${tg.url('/assets/js/bootstrap.min.js')}"></script>
35
         ## HACK - D.A. - 2014-10-21
36
         ## HACK - D.A. - 2014-10-21
64
 <%def name="footer()">
65
 <%def name="footer()">
65
     <div class="pod-footer footer hidden-tablet hidden-phone text-center">
66
     <div class="pod-footer footer hidden-tablet hidden-phone text-center">
66
         <p>
67
         <p>
67
-            <a href="http://trac.im">${_('Create your own email-ready collaborative workspace on trac.im')}</a> &mdash;
68
+            <a href="http://trac.im">${_('Create your own collaborative workspace on trac.im')}</a> &mdash;
68
             copyright &copy; 2013 - ${h.current_year()} tracim project.
69
             copyright &copy; 2013 - ${h.current_year()} tracim project.
69
         </p>
70
         </p>
70
     </div>
71
     </div>

+ 6 - 5
tracim/tracim/templates/master_authenticated.mak 查看文件

21
         <div class="container-fluid">
21
         <div class="container-fluid">
22
             ${self.main_menu()}
22
             ${self.main_menu()}
23
             ${self.content_wrapper()}
23
             ${self.content_wrapper()}
24
-            ${self.footer()}
25
-        </div>
26
-
24
+            <div id="tracim-footer-separator"></div>
25
+        </div>
26
+        ${self.footer()}
27
+
27
         <script src="${tg.url('/assets/js/bootstrap.min.js')}"></script>
28
         <script src="${tg.url('/assets/js/bootstrap.min.js')}"></script>
28
         ${h.tracker_js()|n}
29
         ${h.tracker_js()|n}
29
     </body>
30
     </body>
45
 <%def name="footer()">
46
 <%def name="footer()">
46
     <div class="pod-footer footer hidden-tablet hidden-phone text-center">
47
     <div class="pod-footer footer hidden-tablet hidden-phone text-center">
47
         <p>
48
         <p>
48
-            <a href="http://trac.im">${_('Create your own email-ready collaborative workspace on trac.im')}</a> &mdash;
49
+            <a href="http://trac.im">${_('Create your own collaborative workspace on trac.im')}</a> &mdash;
49
             copyright &copy; 2013 - ${h.current_year()} tracim project.
50
             copyright &copy; 2013 - ${h.current_year()} tracim project.
50
         </p>
51
         </p>
51
     </div>
52
     </div>
121
 ##                        </li>
122
 ##                        </li>
122
                         <li class="dropdown">
123
                         <li class="dropdown">
123
                             <a href="#" class="dropdown-toggle" data-toggle="dropdown">
124
                             <a href="#" class="dropdown-toggle" data-toggle="dropdown">
124
-                              ${request.identity['user'].display_name}
125
+                              ${TIM.ICO(16, 'categories/applications-system')} ${request.identity['user'].display_name}
125
                             </a>
126
                             </a>
126
                             <ul class="dropdown-menu pull-right">
127
                             <ul class="dropdown-menu pull-right">
127
                                 <li>
128
                                 <li>

+ 1 - 0
tracim/tracim/templates/pod.mak 查看文件

1
 <%def name="ICO_URL(icon_size, icon_path)">${h.IconPath(icon_size, icon_path)|n}</%def>
1
 <%def name="ICO_URL(icon_size, icon_path)">${h.IconPath(icon_size, icon_path)|n}</%def>
2
 <%def name="ICO(icon_size, icon_path, title='')"><img src="${h.IconPath(icon_size, icon_path)|n}" alt="" title="${title}"/></%def>
2
 <%def name="ICO(icon_size, icon_path, title='')"><img src="${h.IconPath(icon_size, icon_path)|n}" alt="" title="${title}"/></%def>
3
+<%def name="ICO_TOOLTIP(icon_size, icon_path, title='')"><span rel="tooltip" data-toggle="tooltip" data-placement="bottom" title="${title}">${ICO(icon_size, icon_path, title)}</span></%def>
3
 <%def name="ICO_BADGED(icon_size, icon_path, title='', css_class='badge')"><span class="${css_class}" rel="tooltip" data-toggle="tooltip" data-placement="bottom" title="${title}">${ICO(icon_size, icon_path, title)}</span></%def>
4
 <%def name="ICO_BADGED(icon_size, icon_path, title='', css_class='badge')"><span class="${css_class}" rel="tooltip" data-toggle="tooltip" data-placement="bottom" title="${title}">${ICO(icon_size, icon_path, title)}</span></%def>
4
 <%def name="ICO_FA_BADGED(fa_class='fa fa-flag', title='', css_style='')"><i style="${css_style}" class="${fa_class}" rel="tooltip" data-toggle="tooltip" data-placement="bottom" title="${title}"></i></%def>
5
 <%def name="ICO_FA_BADGED(fa_class='fa fa-flag', title='', css_style='')"><i style="${css_style}" class="${fa_class}" rel="tooltip" data-toggle="tooltip" data-placement="bottom" title="${title}"></i></%def>
5
 
6
 

+ 1 - 1
tracim/tracim/templates/user_get_me.mak 查看文件

39
                             ${_('My workspaces')}
39
                             ${_('My workspaces')}
40
                         </h3>
40
                         </h3>
41
                         % if len(result.user.roles)<=0:
41
                         % if len(result.user.roles)<=0:
42
-                            ${WIDGETS.EMPTY_CONTENT(_('This user is not member of any workspace.'))}
42
+                            ${WIDGETS.EMPTY_CONTENT(_('You are not member of any workspace.'))}
43
                         % else:
43
                         % else:
44
                             <table class="table">
44
                             <table class="table">
45
                                 % for role in result.user.roles:
45
                                 % for role in result.user.roles:

+ 6 - 6
tracim/tracim/templates/user_workspace_folder_file_get_one.mak 查看文件

96
 </div>
96
 </div>
97
 
97
 
98
 % if result.file.status.id!='open':
98
 % if result.file.status.id!='open':
99
-    <p class="pod-less-visible">${_('<b>Note</b>: You need to change status in case you want to upload a new version')|n}</p>
100
-    <hr class="pod-panel-separator"/>
99
+    <p class="tracim-less-visible">${_('<b>Note</b>: You need to change status in case you want to upload a new version')|n}</p>
100
+    <hr class="tracim-panel-separator"/>
101
 % else:
101
 % else:
102
     % if h.user_role(fake_api.current_user, result.file.workspace)<=1: # User must be a contributor to be allowed to upload files
102
     % if h.user_role(fake_api.current_user, result.file.workspace)<=1: # User must be a contributor to be allowed to upload files
103
-        <hr class="pod-panel-separator"/>
103
+        <hr class="tracim-panel-separator"/>
104
         ${WIDGETS.SECURED_SECTION_TITLE(fake_api.current_user, result.file.workspace, 'file-revisions', _('File revisions'))}
104
         ${WIDGETS.SECURED_SECTION_TITLE(fake_api.current_user, result.file.workspace, 'file-revisions', _('File revisions'))}
105
         <p>${_('This file contains {} revision(s)').format(sum(1 for revision in result.file.revisions if revision.action.id=='revision'))}</p>
105
         <p>${_('This file contains {} revision(s)').format(sum(1 for revision in result.file.revisions if revision.action.id=='revision'))}</p>
106
     % else:
106
     % else:
107
-        <hr class="pod-panel-separator"/>
107
+        <hr class="tracim-panel-separator"/>
108
         ${WIDGETS.SECURED_SECTION_TITLE(fake_api.current_user, result.file.workspace, 'file-revisions', _('File revisions'), 'new-file-revision', _('upload a new revision and/or comment...'))}
108
         ${WIDGETS.SECURED_SECTION_TITLE(fake_api.current_user, result.file.workspace, 'file-revisions', _('File revisions'), 'new-file-revision', _('upload a new revision and/or comment...'))}
109
         <p>${_('This file contains {} revision(s)').format(sum(1 for revision in result.file.revisions if revision.action.id=='revision'))}</p>
109
         <p>${_('This file contains {} revision(s)').format(sum(1 for revision in result.file.revisions if revision.action.id=='revision'))}</p>
110
         ${FORMS.NEW_FILE_REVISION_WITH_COMMENT_FORM('new-file-revision', result.file.workspace.id, result.file.parent.id, result.file.id)}
110
         ${FORMS.NEW_FILE_REVISION_WITH_COMMENT_FORM('new-file-revision', result.file.workspace.id, result.file.parent.id, result.file.id)}
114
 <div>
114
 <div>
115
     <table class="table table-striped table-hover">
115
     <table class="table table-striped table-hover">
116
         % for revid, revision in reversed(list(enumerate(reversed(result.file.revisions)))):
116
         % for revid, revision in reversed(list(enumerate(reversed(result.file.revisions)))):
117
-            % if revision.action.id=='revision':
117
+            % if revision.action.id in ('creation', 'revision'):
118
                 ## INFO - D.A. - 2014-10-22
118
                 ## INFO - D.A. - 2014-10-22
119
                 ## We do not show status update and other editions that are not revisions
119
                 ## We do not show status update and other editions that are not revisions
120
                 ## (at least in this revision list table)
120
                 ## (at least in this revision list table)
126
                     <% rev_download_url = tg.url('/workspaces/{}/folders/{}/files/{}/download?revision_id={}'.format(result.file.workspace.id, result.file.parent.id, result.file.id, revision.id)) %>
126
                     <% rev_download_url = tg.url('/workspaces/{}/folders/{}/files/{}/download?revision_id={}'.format(result.file.workspace.id, result.file.parent.id, result.file.id, revision.id)) %>
127
                     <td><a href="${rev_download_url}">${TIM.ICO(16, 'actions/go-bottom', _('Download this particular revision'))}</a></td>
127
                     <td><a href="${rev_download_url}">${TIM.ICO(16, 'actions/go-bottom', _('Download this particular revision'))}</a></td>
128
                     <td>${h.date_time_in_long_format(revision.created, _('%Y-%m-%d at %H:%M'))}</td>
128
                     <td>${h.date_time_in_long_format(revision.created, _('%Y-%m-%d at %H:%M'))}</td>
129
-                    <td>${TIM.ICO(16, revision.action.icon, revision.action.label)}</td>
129
+                    <td>${TIM.ICO_TOOLTIP(16, revision.action.icon, revision.action.label)}</td>
130
                     <td>
130
                     <td>
131
                         % if warning_or_not:
131
                         % if warning_or_not:
132
                             ${TIM.ICO(16, 'actions/go-previous')} <strong>${_('Revision r{}').format(result.file.selected_revision)}</strong>
132
                             ${TIM.ICO(16, 'actions/go-previous')} <strong>${_('Revision r{}').format(result.file.selected_revision)}</strong>

+ 1 - 1
tracim/tracim/templates/user_workspace_folder_get_one.mak 查看文件

54
         ${TIM.ICO_BADGED(16, 'mimetypes/text-html', _('pages'))}
54
         ${TIM.ICO_BADGED(16, 'mimetypes/text-html', _('pages'))}
55
     % endif
55
     % endif
56
 </p>
56
 </p>
57
-<hr class="pod-panel-separator"/>
57
+<hr class="tracim-panel-separator"/>
58
 
58
 
59
 % if result.folder.allowed_content.folder:
59
 % if result.folder.allowed_content.folder:
60
     % if h.user_role(fake_api.current_user, result.folder.workspace)<=2: # User must be a content manager to be allowed to create folders
60
     % if h.user_role(fake_api.current_user, result.folder.workspace)<=2: # User must be a content manager to be allowed to create folders

+ 2 - 2
tracim/tracim/templates/user_workspace_folder_page_get_one.mak 查看文件

60
     ${result.page.content|n}
60
     ${result.page.content|n}
61
 </div>
61
 </div>
62
 
62
 
63
-<hr class="pod-panel-separator"/>
63
+<hr class="tracim-panel-separator"/>
64
 <div>
64
 <div>
65
     <h4 id="associated-links" class="anchored-title" >${_('Links extracted from the page')}</h4>
65
     <h4 id="associated-links" class="anchored-title" >${_('Links extracted from the page')}</h4>
66
     <div>
66
     <div>
74
             </ul>
74
             </ul>
75
         % endif
75
         % endif
76
     </div>
76
     </div>
77
-    <hr/>
77
+    <hr class="tracim-panel-separator"/>
78
 
78
 
79
 
79
 
80
     <h4 id="associated-links" class="anchored-title" >${_('Page revisions')}</h4>
80
     <h4 id="associated-links" class="anchored-title" >${_('Page revisions')}</h4>

+ 8 - 8
tracim/tracim/templates/user_workspace_folder_thread_get_one.mak 查看文件

58
 
58
 
59
 % if h.user_role(fake_api.current_user, result.thread.workspace)<=1:
59
 % if h.user_role(fake_api.current_user, result.thread.workspace)<=1:
60
     ## READONLY USER
60
     ## READONLY USER
61
-    <hr class="pod-panel-separator"/>
61
+    <hr class="tracim-panel-separator"/>
62
 % else:
62
 % else:
63
     % if result.thread.status.id!='open':
63
     % if result.thread.status.id!='open':
64
-        <p class="pod-less-visible">${_('<b>Note</b>: In case you\'d like to post a reply, you must first open again the thread')|n}</p>
65
-        <hr class="pod-panel-separator"/>
64
+        <p class="tracim-less-visible">${_('<b>Note</b>: In case you\'d like to post a reply, you must first open again the thread')|n}</p>
65
+        <hr class="tracim-panel-separator"/>
66
     % else:
66
     % else:
67
-        <hr class="pod-panel-separator"/>
67
+        <hr class="tracim-panel-separator"/>
68
         <p>    
68
         <p>    
69
             ${WIDGETS.DATA_TARGET_BUTTON('new-comment', _('Post a reply...'))}
69
             ${WIDGETS.DATA_TARGET_BUTTON('new-comment', _('Post a reply...'))}
70
             ${FORMS.NEW_COMMENT_FORM_IN_THREAD('new-comment', result.thread.workspace.id, result.thread.parent.id, result.thread.id)}
70
             ${FORMS.NEW_COMMENT_FORM_IN_THREAD('new-comment', result.thread.workspace.id, result.thread.parent.id, result.thread.id)}
73
 % endif 
73
 % endif 
74
 
74
 
75
 % for comment in result.thread.comments:
75
 % for comment in result.thread.comments:
76
-    <div class="pod-thread-item">
76
+    <div class="tracim-thread-item">
77
         <h5 style="margin: 0;">
77
         <h5 style="margin: 0;">
78
             <div class="pull-right text-right">
78
             <div class="pull-right text-right">
79
                 <div class="label" style="font-size: 10px; border: 1px solid #CCC; color: #777; ">
79
                 <div class="label" style="font-size: 10px; border: 1px solid #CCC; color: #777; ">
85
             </div>
85
             </div>
86
    
86
    
87
             ${TIM.ICO(32, comment.icon)}
87
             ${TIM.ICO(32, comment.icon)}
88
-            <span class="pod-less-visible">${_('<strong>{}</strong> wrote:').format(comment.owner.name)|n}</span>
88
+            <span class="tracim-less-visible">${_('<strong>{}</strong> wrote:').format(comment.owner.name)|n}</span>
89
         </h5>
89
         </h5>
90
-        <div class="pod-thread-item-content">
90
+        <div class="tracim-thread-item-content">
91
             <div>${comment.content|n}</div>
91
             <div>${comment.content|n}</div>
92
             <br/>
92
             <br/>
93
         </div>
93
         </div>
95
 
95
 
96
 % endfor
96
 % endfor
97
 
97
 
98
-## <hr class="pod-panel-separator"/>
98
+## <hr class="tracim-panel-separator"/>
99
 ## <div>
99
 ## <div>
100
 ##     <h4 id="associated-links" class="anchored-title" >${_('Links extracted from the thread')}</h4>
100
 ##     <h4 id="associated-links" class="anchored-title" >${_('Links extracted from the thread')}</h4>
101
 ##     <div>
101
 ##     <div>

+ 1 - 1
tracim/tracim/templates/user_workspace_get_one.mak 查看文件

46
         % endfor
46
         % endfor
47
     % endif
47
     % endif
48
 </p>
48
 </p>
49
-<hr class="pod-panel-separator"/>
49
+<hr class="tracim-panel-separator"/>
50
 
50
 
51
 
51
 
52
 % if h.user_role(fake_api.current_user, result.workspace)<=2: # User must be a content manager to be allowed to create folders
52
 % if h.user_role(fake_api.current_user, result.workspace)<=2: # User must be a content manager to be allowed to create folders

+ 2 - 2
tracim/tracim/tests/models/test_auth.py 查看文件

21
     klass = model.User
21
     klass = model.User
22
     attrs = dict(
22
     attrs = dict(
23
         user_name = "ignucius",
23
         user_name = "ignucius",
24
-        email_address = "ignucius@example.org"
24
+        email = "ignucius@example.org"
25
         )
25
         )
26
 
26
 
27
     def test_obj_creation_username(self):
27
     def test_obj_creation_username(self):
30
 
30
 
31
     def test_obj_creation_email(self):
31
     def test_obj_creation_email(self):
32
         """The obj constructor must set the email right"""
32
         """The obj constructor must set the email right"""
33
-        eq_(self.obj.email_address, "ignucius@example.org")
33
+        eq_(self.obj.email, "ignucius@example.org")
34
 
34
 
35
     def test_no_permissions_by_default(self):
35
     def test_no_permissions_by_default(self):
36
         """User objects should have no permission by default."""
36
         """User objects should have no permission by default."""

+ 2 - 2
tracim/tracim/websetup/bootstrap.py 查看文件

16
         u = model.User()
16
         u = model.User()
17
         u.user_name = 'manager'
17
         u.user_name = 'manager'
18
         u.display_name = 'Example manager'
18
         u.display_name = 'Example manager'
19
-        u.email_address = 'manager@somedomain.com'
19
+        u.email = 'manager@somedomain.com'
20
         u.password = 'managepass'
20
         u.password = 'managepass'
21
     
21
     
22
         model.DBSession.add(u)
22
         model.DBSession.add(u)
39
         u1 = model.User()
39
         u1 = model.User()
40
         u1.user_name = 'editor'
40
         u1.user_name = 'editor'
41
         u1.display_name = 'Example editor'
41
         u1.display_name = 'Example editor'
42
-        u1.email_address = 'editor@somedomain.com'
42
+        u1.email = 'editor@somedomain.com'
43
         u1.password = 'editpass'
43
         u1.password = 'editpass'
44
     
44
     
45
         model.DBSession.add(u1)
45
         model.DBSession.add(u1)