Przeglądaj źródła

Model integration into tracim v2 (uncomplete)

Guénaël Muller 7 lat temu
rodzic
commit
a000568f5c

+ 0 - 2
.gitignore Wyświetl plik

64
 # binary translation files
64
 # binary translation files
65
 *.mo
65
 *.mo
66
 
66
 
67
-# Virtualenv
68
-/venv/

+ 1 - 1
tracim/__init__.py Wyświetl plik

1
 from pyramid.config import Configurator
1
 from pyramid.config import Configurator
2
 
2
 
3
-from config import RequestWithCFG
3
+from tracim.config import RequestWithCFG
4
 
4
 
5
 
5
 
6
 def main(global_config, **settings):
6
 def main(global_config, **settings):

+ 57 - 0
tracim/exceptions.py Wyświetl plik

1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+class TracimError(Exception):
5
+    pass
6
+
7
+class TracimException(Exception):
8
+    pass
9
+
10
+class RunTimeError(TracimError):
11
+    pass
12
+
13
+
14
+class ContentRevisionUpdateError(RuntimeError):
15
+    pass
16
+
17
+
18
+class ContentRevisionDeleteError(ContentRevisionUpdateError):
19
+    pass
20
+
21
+
22
+class ConfigurationError(TracimError):
23
+    pass
24
+
25
+
26
+class AlreadyExistError(TracimError):
27
+    pass
28
+
29
+
30
+class CommandError(TracimError):
31
+    pass
32
+
33
+
34
+class CommandAbortedError(CommandError):
35
+    pass
36
+
37
+class DaemonException(TracimException):
38
+    pass
39
+
40
+
41
+class AlreadyRunningDaemon(DaemonException):
42
+    pass
43
+
44
+
45
+class CalendarException(TracimException):
46
+    pass
47
+
48
+
49
+class UnknownCalendarType(CalendarException):
50
+    pass
51
+
52
+
53
+class NotFound(TracimException):
54
+    pass
55
+
56
+class SameValueError(ValueError):
57
+    pass

+ 6 - 1
tracim/models/__init__.py Wyświetl plik

1
 from sqlalchemy import engine_from_config
1
 from sqlalchemy import engine_from_config
2
+from sqlalchemy.event import listen
2
 from sqlalchemy.orm import sessionmaker
3
 from sqlalchemy.orm import sessionmaker
3
 from sqlalchemy.orm import configure_mappers
4
 from sqlalchemy.orm import configure_mappers
4
 import zope.sqlalchemy
5
 import zope.sqlalchemy
5
-
6
+from .meta import DeclarativeBase
7
+from .revision_protection import prevent_content_revision_delete
6
 # import or define all models here to ensure they are attached to the
8
 # import or define all models here to ensure they are attached to the
7
 # Base.metadata prior to any initialization routines
9
 # Base.metadata prior to any initialization routines
8
 from .mymodel import MyModel  # flake8: noqa
10
 from .mymodel import MyModel  # flake8: noqa
11
+from .auth import User, Group, Permission
12
+from .data import Content, ContentRevisionRO
9
 
13
 
10
 # run configure_mappers after defining all of the models to ensure
14
 # run configure_mappers after defining all of the models to ensure
11
 # all relationships can be setup
15
 # all relationships can be setup
46
     dbsession = session_factory()
50
     dbsession = session_factory()
47
     zope.sqlalchemy.register(
51
     zope.sqlalchemy.register(
48
         dbsession, transaction_manager=transaction_manager)
52
         dbsession, transaction_manager=transaction_manager)
53
+    listen(dbsession, 'before_flush', prevent_content_revision_delete)
49
     return dbsession
54
     return dbsession
50
 
55
 
51
 
56
 

+ 21 - 24
tracim/models/auth.py Wyświetl plik

29
 from sqlalchemy.types import Integer
29
 from sqlalchemy.types import Integer
30
 from sqlalchemy.types import Unicode
30
 from sqlalchemy.types import Unicode
31
 
31
 
32
-from tracim.lib.utils import lazy_ugettext as l_
33
-from tracim.model import DBSession
34
-from tracim.model import DeclarativeBase
35
-from tracim.model import metadata
32
+from tracim.translation import fake_translator as l_
33
+from tracim.models.meta import DeclarativeBase
34
+from tracim.models.meta import metadata
36
 if TYPE_CHECKING:
35
 if TYPE_CHECKING:
37
-    from tracim.model.data import Workspace
38
-
36
+    from tracim.models.data import Workspace
37
+    from tracim.models.data import UserRoleInWorkspace
39
 __all__ = ['User', 'Group', 'Permission']
38
 __all__ = ['User', 'Group', 'Permission']
40
 
39
 
41
 # This is the association table for the many-to-many relationship between
40
 # This is the association table for the many-to-many relationship between
85
         return self.group_name
84
         return self.group_name
86
 
85
 
87
     @classmethod
86
     @classmethod
88
-    def by_group_name(cls, group_name):
87
+    def by_group_name(cls, group_name, dbsession):
89
         """Return the user object whose email address is ``email``."""
88
         """Return the user object whose email address is ``email``."""
90
-        return DBSession.query(cls).filter_by(group_name=group_name).first()
89
+        return dbsession.query(cls).filter_by(group_name=group_name).first()
91
 
90
 
92
 
91
 
93
 class Profile(object):
92
 class Profile(object):
157
             profile_id = max(group.group_id for group in self.groups)
156
             profile_id = max(group.group_id for group in self.groups)
158
         return Profile(profile_id)
157
         return Profile(profile_id)
159
 
158
 
160
-    @property
161
-    def calendar_url(self) -> str:
162
-        # TODO - 20160531 - Bastien: Cyclic import if import in top of file
163
-        from tracim.lib.calendar import CalendarManager
164
-        calendar_manager = CalendarManager(None)
165
-
166
-        return calendar_manager.get_user_calendar_url(self.user_id)
159
+    # TODO - G-M - 27-03-2018 - Check about calendar code
160
+    # @property
161
+    # def calendar_url(self) -> str:
162
+    #     # TODO - 20160531 - Bastien: Cyclic import if import in top of file
163
+    #     from tracim.lib.calendar import CalendarManager
164
+    #     calendar_manager = CalendarManager(None)
165
+    #
166
+    #     return calendar_manager.get_user_calendar_url(self.user_id)
167
 
167
 
168
     @classmethod
168
     @classmethod
169
-    def by_email_address(cls, email):
169
+    def by_email_address(cls, email, dbsession):
170
         """Return the user object whose email address is ``email``."""
170
         """Return the user object whose email address is ``email``."""
171
-        return DBSession.query(cls).filter_by(email=email).first()
171
+        return dbsession.query(cls).filter_by(email=email).first()
172
 
172
 
173
     @classmethod
173
     @classmethod
174
-    def by_user_name(cls, username):
174
+    def by_user_name(cls, username, dbsession):
175
         """Return the user object whose user name is ``username``."""
175
         """Return the user object whose user name is ``username``."""
176
-        return DBSession.query(cls).filter_by(email=username).first()
176
+        return dbsession.query(cls).filter_by(email=username).first()
177
 
177
 
178
     @classmethod
178
     @classmethod
179
     def _hash_password(cls, cleartext_password: str) -> str:
179
     def _hash_password(cls, cleartext_password: str) -> str:
252
             if role.workspace == workspace:
252
             if role.workspace == workspace:
253
                 return role.role
253
                 return role.role
254
 
254
 
255
-        from tracim.model.data import UserRoleInWorkspace
256
         return UserRoleInWorkspace.NOT_APPLICABLE
255
         return UserRoleInWorkspace.NOT_APPLICABLE
257
 
256
 
258
     def get_active_roles(self) -> ['UserRoleInWorkspace']:
257
     def get_active_roles(self) -> ['UserRoleInWorkspace']:
265
                 roles.append(role)
264
                 roles.append(role)
266
         return roles
265
         return roles
267
 
266
 
268
-    def ensure_auth_token(self) -> None:
267
+    def ensure_auth_token(self, validity_seconds, dbsession) -> None:
269
         """
268
         """
270
         Create auth_token if None, regenerate auth_token if too much old.
269
         Create auth_token if None, regenerate auth_token if too much old.
271
 
270
 
272
         auth_token validity is set in
271
         auth_token validity is set in
273
         :return:
272
         :return:
274
         """
273
         """
275
-        from tracim.config.app_cfg import CFG
276
-        validity_seconds = CFG.get_instance().USER_AUTH_TOKEN_VALIDITY
277
 
274
 
278
         if not self.auth_token or not self.auth_token_created:
275
         if not self.auth_token or not self.auth_token_created:
279
             self.auth_token = str(uuid.uuid4())
276
             self.auth_token = str(uuid.uuid4())
280
             self.auth_token_created = datetime.utcnow()
277
             self.auth_token_created = datetime.utcnow()
281
-            DBSession.flush()
278
+            dbsession.flush()
282
             return
279
             return
283
 
280
 
284
         now_seconds = time.mktime(datetime.utcnow().timetuple())
281
         now_seconds = time.mktime(datetime.utcnow().timetuple())

+ 13 - 12
tracim/models/data.py Wyświetl plik

5
 import os
5
 import os
6
 from datetime import datetime
6
 from datetime import datetime
7
 
7
 
8
-import tg
9
 from babel.dates import format_timedelta
8
 from babel.dates import format_timedelta
10
 from bs4 import BeautifulSoup
9
 from bs4 import BeautifulSoup
11
 from sqlalchemy import Column, inspect, Index
10
 from sqlalchemy import Column, inspect, Index
28
 from depot.fields.upload import UploadedFile
27
 from depot.fields.upload import UploadedFile
29
 from depot.io.utils import FileIntent
28
 from depot.io.utils import FileIntent
30
 
29
 
31
-from tracim.lib.utils import lazy_ugettext as l_
32
-from tracim.lib.exception import ContentRevisionUpdateError
33
-from tracim.model import DeclarativeBase, RevisionsIntegrity
34
-from tracim.model.auth import User
30
+from tracim.translation import fake_translator as l_
31
+from tracim.exceptions import ContentRevisionUpdateError
32
+from tracim.models.meta import DeclarativeBase
33
+from tracim.models.auth import User
35
 
34
 
36
 DEFAULT_PROPERTIES = dict(
35
 DEFAULT_PROPERTIES = dict(
37
     allowed_content=dict(
36
     allowed_content=dict(
86
 
85
 
87
         return contents
86
         return contents
88
 
87
 
89
-    @property
90
-    def calendar_url(self) -> str:
91
-        # TODO - 20160531 - Bastien: Cyclic import if import in top of file
92
-        from tracim.lib.calendar import CalendarManager
93
-        calendar_manager = CalendarManager(None)
94
-
95
-        return calendar_manager.get_workspace_calendar_url(self.workspace_id)
88
+    # TODO - G-M - 27-03-2018 - Check about calendar code
89
+    # @property
90
+    # def calendar_url(self) -> str:
91
+    #     # TODO - 20160531 - Bastien: Cyclic import if import in top of file
92
+    #     from tracim.lib.calendar import CalendarManager
93
+    #     calendar_manager = CalendarManager(None)
94
+    #
95
+    #     return calendar_manager.get_workspace_calendar_url(self.workspace_id)
96
 
96
 
97
     def get_user_role(self, user: User) -> int:
97
     def get_user_role(self, user: User) -> int:
98
         for role in user.roles:
98
         for role in user.roles:
522
         # TODO - G.M - 15-03-2018 - Choose only correct Content-type for origin
522
         # TODO - G.M - 15-03-2018 - Choose only correct Content-type for origin
523
         # Only content who can be copied need this
523
         # Only content who can be copied need this
524
         if item.type == ContentType.Any:
524
         if item.type == ContentType.Any:
525
+            properties = item.properties
525
             if 'origin' in properties.keys():
526
             if 'origin' in properties.keys():
526
                 return True
527
                 return True
527
         raise NotImplementedError
528
         raise NotImplementedError

+ 4 - 1
tracim/models/meta.py Wyświetl plik

1
 from sqlalchemy.ext.declarative import declarative_base
1
 from sqlalchemy.ext.declarative import declarative_base
2
 from sqlalchemy.schema import MetaData
2
 from sqlalchemy.schema import MetaData
3
+from sqlalchemy import inspect
4
+
5
+from tracim.exceptions import ContentRevisionUpdateError
3
 
6
 
4
 # Recommended naming convention used by Alembic, as various different database
7
 # Recommended naming convention used by Alembic, as various different database
5
 # providers will autogenerate vastly different names making migrations more
8
 # providers will autogenerate vastly different names making migrations more
13
 }
16
 }
14
 
17
 
15
 metadata = MetaData(naming_convention=NAMING_CONVENTION)
18
 metadata = MetaData(naming_convention=NAMING_CONVENTION)
16
-Base = declarative_base(metadata=metadata)
19
+DeclarativeBase = declarative_base(metadata=metadata)

+ 2 - 2
tracim/models/mymodel.py Wyświetl plik

5
     Text,
5
     Text,
6
 )
6
 )
7
 
7
 
8
-from .meta import Base
8
+from .meta import DeclarativeBase
9
 
9
 
10
 
10
 
11
-class MyModel(Base):
11
+class MyModel(DeclarativeBase):
12
     __tablename__ = 'models'
12
     __tablename__ = 'models'
13
     id = Column(Integer, primary_key=True)
13
     id = Column(Integer, primary_key=True)
14
     name = Column(Text)
14
     name = Column(Text)

+ 2 - 2
tracim/models/organisational.py Wyświetl plik

1
-from tracim.model import User
2
-from tracim.model.data import UserRoleInWorkspace
1
+from tracim.models import User
2
+from tracim.models.data import UserRoleInWorkspace
3
 
3
 
4
 
4
 
5
 CALENDAR_PERMISSION_READ = 'r'
5
 CALENDAR_PERMISSION_READ = 'r'

+ 96 - 0
tracim/models/revision_protection.py Wyświetl plik

1
+from sqlalchemy.orm import Session
2
+from sqlalchemy import inspect
3
+from sqlalchemy.orm.unitofwork import UOWTransaction
4
+from transaction import TransactionManager
5
+from contextlib import contextmanager
6
+
7
+from tracim.exceptions import ContentRevisionDeleteError
8
+from tracim.exceptions import ContentRevisionUpdateError
9
+from tracim.exceptions import SameValueError
10
+
11
+from .data import ContentRevisionRO
12
+from .data import Content
13
+from .meta import DeclarativeBase
14
+
15
+
16
+def prevent_content_revision_delete(
17
+        session: Session,
18
+        flush_context: UOWTransaction,
19
+        instances: [DeclarativeBase]
20
+) -> None:
21
+    for instance in session.deleted:
22
+        if isinstance(instance, ContentRevisionRO) \
23
+                and instance.revision_id is not None:
24
+            raise ContentRevisionDeleteError(
25
+                "ContentRevision is not deletable. " +
26
+                "You must make a new revision with" +
27
+                "is_deleted set to True. Look at " +
28
+                "tracim.model.new_revision context " +
29
+                "manager to make a new revision"
30
+            )
31
+
32
+
33
+class RevisionsIntegrity(object):
34
+    """
35
+    Simple static used class to manage a list with list of ContentRevisionRO
36
+    who are allowed to be updated.
37
+
38
+    When modify an already existing (understood have an identity in databse)
39
+    ContentRevisionRO, if it's not in RevisionsIntegrity._updatable_revisions
40
+    list, a ContentRevisionUpdateError thrown.
41
+
42
+    This class is used by tracim.model.new_revision context manager.
43
+    """
44
+    _updatable_revisions = []
45
+
46
+    @classmethod
47
+    def add_to_updatable(cls, revision: 'ContentRevisionRO') -> None:
48
+        if inspect(revision).has_identity:
49
+            raise ContentRevisionUpdateError("ContentRevision is not updatable. %s already have identity." % revision)  # nopep8
50
+
51
+        if revision not in cls._updatable_revisions:
52
+            cls._updatable_revisions.append(revision)
53
+
54
+    @classmethod
55
+    def remove_from_updatable(cls, revision: 'ContentRevisionRO') -> None:
56
+        if revision in cls._updatable_revisions:
57
+            cls._updatable_revisions.remove(revision)
58
+
59
+    @classmethod
60
+    def is_updatable(cls, revision: 'ContentRevisionRO') -> bool:
61
+        return revision in cls._updatable_revisions
62
+
63
+
64
+@contextmanager
65
+def new_revision(
66
+        dbsession: Session,
67
+        tm: TransactionManager,
68
+        content: Content,
69
+        force_create_new_revision: bool=False,
70
+) -> Content:
71
+    """
72
+    Prepare context to update a Content. It will add a new updatable revision
73
+    to the content.
74
+    :param dbsession: Database session
75
+    :param tm: TransactionManager
76
+    :param content: Content instance to update
77
+    :param force_create_new_revision: Decide if new_rev should or should not
78
+    be forced.
79
+    :return:
80
+    """
81
+    with dbsession.no_autoflush:
82
+        try:
83
+            if force_create_new_revision \
84
+                    or inspect(content.revision).has_identity:
85
+                content.new_revision()
86
+            RevisionsIntegrity.add_to_updatable(content.revision)
87
+            yield content
88
+        except SameValueError or ValueError as e:
89
+            # INFO - 20-03-2018 - renew transaction when error happened
90
+            # This avoid bad session data like new "temporary" revision
91
+            # to be add when problem happen.
92
+            tm.abort()
93
+            tm.begin()
94
+            raise e
95
+        finally:
96
+            RevisionsIntegrity.remove_from_updatable(content.revision)

+ 2 - 0
tracim/routes.py Wyświetl plik

1
 def includeme(config):
1
 def includeme(config):
2
     config.add_static_view('static', 'static', cache_max_age=3600)
2
     config.add_static_view('static', 'static', cache_max_age=3600)
3
     config.add_route('home', '/')
3
     config.add_route('home', '/')
4
+    config.add_route('test_config', '/test_config')
5
+    config.add_route('test_model', '/test_model')

+ 2 - 2
tracim/scripts/initializedb.py Wyświetl plik

9
 
9
 
10
 from pyramid.scripts.common import parse_vars
10
 from pyramid.scripts.common import parse_vars
11
 
11
 
12
-from ..models.meta import Base
12
+from ..models.meta import DeclarativeBase
13
 from ..models import (
13
 from ..models import (
14
     get_engine,
14
     get_engine,
15
     get_session_factory,
15
     get_session_factory,
34
     settings = get_appsettings(config_uri, options=options)
34
     settings = get_appsettings(config_uri, options=options)
35
 
35
 
36
     engine = get_engine(settings)
36
     engine = get_engine(settings)
37
-    Base.metadata.create_all(engine)
37
+    DeclarativeBase.metadata.create_all(engine)
38
 
38
 
39
     session_factory = get_session_factory(engine)
39
     session_factory = get_session_factory(engine)
40
 
40
 

+ 1 - 1
tracim/templates/mytemplate.jinja2 Wyświetl plik

3
 {% block content %}
3
 {% block content %}
4
 <div class="content">
4
 <div class="content">
5
   <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1>
5
   <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Alchemy project</span></h1>
6
-  <p class="lead">Welcome to <span class="font-normal">tracim</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
6
+  <p class="lead">Welcome to <span class="font-normal">{{ project }}</span>, a&nbsp;Pyramid application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
7
 </div>
7
 </div>
8
 {% endblock content %}
8
 {% endblock content %}

+ 4 - 0
tracim/translation.py Wyświetl plik

1
+
2
+# TODO - G.M - 27-03-2018 - Reconnect true internationalization
3
+def fake_translator(text: str):
4
+    return text

+ 18 - 1
tracim/views/default.py Wyświetl plik

4
 from sqlalchemy.exc import DBAPIError
4
 from sqlalchemy.exc import DBAPIError
5
 
5
 
6
 from ..models import MyModel
6
 from ..models import MyModel
7
+from ..models import Content
8
+
9
+
10
+@view_config(route_name='test_config', renderer='../templates/mytemplate.jinja2')
11
+def test_config(request):
12
+    try:
13
+        project = request.config().WEBSITE_TITLE
14
+    except Exception as e:
15
+        return Response(e, content_type='text/plain', status=500)
16
+    return {'project': project}
17
+
18
+@view_config(route_name='test_model', renderer='../templates/mytemplate.jinja2')
19
+def test_model(request):
20
+    try:
21
+        # project = request.dbsession.query(MyModel)
22
+    except Exception as e:
23
+        return Response(e, content_type='text/plain', status=500)
24
+    return {'project': project}
7
 
25
 
8
 
26
 
9
 @view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
27
 @view_config(route_name='home', renderer='../templates/mytemplate.jinja2')
10
 def my_view(request):
28
 def my_view(request):
11
-    request.config()
12
     try:
29
     try:
13
         query = request.dbsession.query(MyModel)
30
         query = request.dbsession.query(MyModel)
14
         one = query.filter(MyModel.name == 'one').first()
31
         one = query.filter(MyModel.name == 'one').first()