##// END OF EJS Templates
Added grouping by days in journal
marcink -
r994:7f9d23f6 beta
parent child Browse files
Show More
@@ -1,98 +1,112
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.journal
3 rhodecode.controllers.journal
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Journal controller for pylons
6 Journal controller for pylons
7
7
8 :created_on: Nov 21, 2010
8 :created_on: Nov 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29 from sqlalchemy import or_
29 from sqlalchemy import or_
30
30
31 from pylons import request, response, session, tmpl_context as c, url
31 from pylons import request, response, session, tmpl_context as c, url
32
32
33 from rhodecode.lib.auth import LoginRequired, NotAnonymous
33 from rhodecode.lib.auth import LoginRequired, NotAnonymous
34 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.helpers import get_token
35 from rhodecode.lib.helpers import get_token
36 from rhodecode.lib.utils import OrderedDict
36 from rhodecode.model.db import UserLog, UserFollowing
37 from rhodecode.model.db import UserLog, UserFollowing
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
38
39
39 from paste.httpexceptions import HTTPInternalServerError
40 from paste.httpexceptions import HTTPInternalServerError
40
41
41 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
42
43
43 class JournalController(BaseController):
44 class JournalController(BaseController):
44
45
45
46
46 @LoginRequired()
47 @LoginRequired()
47 @NotAnonymous()
48 @NotAnonymous()
48 def __before__(self):
49 def __before__(self):
49 super(JournalController, self).__before__()
50 super(JournalController, self).__before__()
50
51
51 def index(self):
52 def index(self):
52 # Return a rendered template
53 # Return a rendered template
53
54
54 c.following = self.sa.query(UserFollowing)\
55 c.following = self.sa.query(UserFollowing)\
55 .filter(UserFollowing.user_id == c.rhodecode_user.user_id).all()
56 .filter(UserFollowing.user_id == c.rhodecode_user.user_id).all()
56
57
57 repo_ids = [x.follows_repository.repo_id for x in c.following
58 repo_ids = [x.follows_repository.repo_id for x in c.following
58 if x.follows_repository is not None]
59 if x.follows_repository is not None]
59 user_ids = [x.follows_user.user_id for x in c.following
60 user_ids = [x.follows_user.user_id for x in c.following
60 if x.follows_user is not None]
61 if x.follows_user is not None]
61
62
62 c.journal = self.sa.query(UserLog)\
63 journal = self.sa.query(UserLog)\
63 .filter(or_(
64 .filter(or_(
64 UserLog.repository_id.in_(repo_ids),
65 UserLog.repository_id.in_(repo_ids),
65 UserLog.user_id.in_(user_ids),
66 UserLog.user_id.in_(user_ids),
66 ))\
67 ))\
67 .order_by(UserLog.action_date.desc())\
68 .order_by(UserLog.action_date.desc())\
68 .limit(20)\
69 .limit(30)\
69 .all()
70 .all()
71
72 c.journal_day_aggreagate = self._get_daily_aggregate(journal)
73
70 return render('/journal.html')
74 return render('/journal.html')
71
75
76
77 def _get_daily_aggregate(self, journal):
78 from itertools import groupby
79 groups = []
80 for k, g in groupby(journal, lambda x:x.action_as_day):
81 groups.append((k, list(g),)) # Store group iterator as a list
82
83 return groups
84
85
72 def toggle_following(self):
86 def toggle_following(self):
73 cur_token = request.POST.get('auth_token')
87 cur_token = request.POST.get('auth_token')
74 token = get_token()
88 token = get_token()
75 if cur_token == token:
89 if cur_token == token:
76 scm_model = ScmModel()
90 scm_model = ScmModel()
77
91
78 user_id = request.POST.get('follows_user_id')
92 user_id = request.POST.get('follows_user_id')
79 if user_id:
93 if user_id:
80 try:
94 try:
81 scm_model.toggle_following_user(user_id,
95 scm_model.toggle_following_user(user_id,
82 c.rhodecode_user.user_id)
96 c.rhodecode_user.user_id)
83 return 'ok'
97 return 'ok'
84 except:
98 except:
85 raise HTTPInternalServerError()
99 raise HTTPInternalServerError()
86
100
87 repo_id = request.POST.get('follows_repo_id')
101 repo_id = request.POST.get('follows_repo_id')
88 if repo_id:
102 if repo_id:
89 try:
103 try:
90 scm_model.toggle_following_repo(repo_id,
104 scm_model.toggle_following_repo(repo_id,
91 c.rhodecode_user.user_id)
105 c.rhodecode_user.user_id)
92 return 'ok'
106 return 'ok'
93 except:
107 except:
94 raise HTTPInternalServerError()
108 raise HTTPInternalServerError()
95
109
96
110
97 log.debug('token mismatch %s vs %s', cur_token, token)
111 log.debug('token mismatch %s vs %s', cur_token, token)
98 raise HTTPInternalServerError()
112 raise HTTPInternalServerError()
@@ -1,337 +1,342
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import logging
27 import logging
28 import datetime
28 import datetime
29 from datetime import date
29
30
30 from sqlalchemy import *
31 from sqlalchemy import *
31 from sqlalchemy.exc import DatabaseError
32 from sqlalchemy.exc import DatabaseError
32 from sqlalchemy.orm import relationship, backref, class_mapper
33 from sqlalchemy.orm import relationship, backref, class_mapper
33 from sqlalchemy.orm.session import Session
34 from sqlalchemy.orm.session import Session
34
35
35 from rhodecode.model.meta import Base
36 from rhodecode.model.meta import Base
36
37
37 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
38
39
39 class BaseModel(object):
40 class BaseModel(object):
40
41
41 @classmethod
42 @classmethod
42 def _get_keys(cls):
43 def _get_keys(cls):
43 """return column names for this model """
44 """return column names for this model """
44 return class_mapper(cls).c.keys()
45 return class_mapper(cls).c.keys()
45
46
46 def get_dict(self):
47 def get_dict(self):
47 """return dict with keys and values corresponding
48 """return dict with keys and values corresponding
48 to this model data """
49 to this model data """
49
50
50 d = {}
51 d = {}
51 for k in self._get_keys():
52 for k in self._get_keys():
52 d[k] = getattr(self, k)
53 d[k] = getattr(self, k)
53 return d
54 return d
54
55
55 def get_appstruct(self):
56 def get_appstruct(self):
56 """return list with keys and values tupples corresponding
57 """return list with keys and values tupples corresponding
57 to this model data """
58 to this model data """
58
59
59 l = []
60 l = []
60 for k in self._get_keys():
61 for k in self._get_keys():
61 l.append((k, getattr(self, k),))
62 l.append((k, getattr(self, k),))
62 return l
63 return l
63
64
64 def populate_obj(self, populate_dict):
65 def populate_obj(self, populate_dict):
65 """populate model with data from given populate_dict"""
66 """populate model with data from given populate_dict"""
66
67
67 for k in self._get_keys():
68 for k in self._get_keys():
68 if k in populate_dict:
69 if k in populate_dict:
69 setattr(self, k, populate_dict[k])
70 setattr(self, k, populate_dict[k])
70
71
71 class RhodeCodeSettings(Base, BaseModel):
72 class RhodeCodeSettings(Base, BaseModel):
72 __tablename__ = 'rhodecode_settings'
73 __tablename__ = 'rhodecode_settings'
73 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
74 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
74 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
75 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
75 app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
76 app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
76 app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
77 app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
77
78
78 def __init__(self, k='', v=''):
79 def __init__(self, k='', v=''):
79 self.app_settings_name = k
80 self.app_settings_name = k
80 self.app_settings_value = v
81 self.app_settings_value = v
81
82
82 def __repr__(self):
83 def __repr__(self):
83 return "<%s('%s:%s')>" % (self.__class__.__name__,
84 return "<%s('%s:%s')>" % (self.__class__.__name__,
84 self.app_settings_name, self.app_settings_value)
85 self.app_settings_name, self.app_settings_value)
85
86
86 class RhodeCodeUi(Base, BaseModel):
87 class RhodeCodeUi(Base, BaseModel):
87 __tablename__ = 'rhodecode_ui'
88 __tablename__ = 'rhodecode_ui'
88 __table_args__ = {'useexisting':True}
89 __table_args__ = {'useexisting':True}
89 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
90 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
90 ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
91 ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
91 ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
92 ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
92 ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
93 ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
93 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
94 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
94
95
95
96
96 class User(Base, BaseModel):
97 class User(Base, BaseModel):
97 __tablename__ = 'users'
98 __tablename__ = 'users'
98 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
99 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
99 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
100 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
100 username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
101 username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
101 password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
102 password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
102 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
103 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
103 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
104 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
104 name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
105 name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
105 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
108 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
108 ldap_dn = Column("ldap_dn", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
109 ldap_dn = Column("ldap_dn", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
109
110
110 user_log = relationship('UserLog', cascade='all')
111 user_log = relationship('UserLog', cascade='all')
111 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
112 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
112
113
113 repositories = relationship('Repository')
114 repositories = relationship('Repository')
114 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
115 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
115
116
116 @property
117 @property
117 def full_contact(self):
118 def full_contact(self):
118 return '%s %s <%s>' % (self.name, self.lastname, self.email)
119 return '%s %s <%s>' % (self.name, self.lastname, self.email)
119
120
120
121
121 @property
122 @property
122 def is_admin(self):
123 def is_admin(self):
123 return self.admin
124 return self.admin
124
125
125 def __repr__(self):
126 def __repr__(self):
126 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
127 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
127 self.user_id, self.username)
128 self.user_id, self.username)
128
129
129 def update_lastlogin(self):
130 def update_lastlogin(self):
130 """Update user lastlogin"""
131 """Update user lastlogin"""
131
132
132 try:
133 try:
133 session = Session.object_session(self)
134 session = Session.object_session(self)
134 self.last_login = datetime.datetime.now()
135 self.last_login = datetime.datetime.now()
135 session.add(self)
136 session.add(self)
136 session.commit()
137 session.commit()
137 log.debug('updated user %s lastlogin', self.username)
138 log.debug('updated user %s lastlogin', self.username)
138 except (DatabaseError,):
139 except (DatabaseError,):
139 session.rollback()
140 session.rollback()
140
141
141
142
142 class UserLog(Base, BaseModel):
143 class UserLog(Base, BaseModel):
143 __tablename__ = 'user_logs'
144 __tablename__ = 'user_logs'
144 __table_args__ = {'useexisting':True}
145 __table_args__ = {'useexisting':True}
145 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
146 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
147 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
147 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
148 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
148 repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
149 repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
149 user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
150 user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
150 action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
152 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
152
153
154 @property
155 def action_as_day(self):
156 return date(*self.action_date.timetuple()[:3])
157
153 user = relationship('User')
158 user = relationship('User')
154 repository = relationship('Repository')
159 repository = relationship('Repository')
155
160
156
161
157 class UsersGroup(Base, BaseModel):
162 class UsersGroup(Base, BaseModel):
158 __tablename__ = 'users_groups'
163 __tablename__ = 'users_groups'
159 __table_args__ = {'useexisting':True}
164 __table_args__ = {'useexisting':True}
160
165
161 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
166 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
162 users_group_name = Column("users_group_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
167 users_group_name = Column("users_group_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
163 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
168 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
164
169
165 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
170 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
166
171
167 class UsersGroupMember(Base, BaseModel):
172 class UsersGroupMember(Base, BaseModel):
168 __tablename__ = 'users_groups_members'
173 __tablename__ = 'users_groups_members'
169 __table_args__ = {'useexisting':True}
174 __table_args__ = {'useexisting':True}
170
175
171 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
176 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
172 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
177 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
178 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
174
179
175 user = relationship('User', lazy='joined')
180 user = relationship('User', lazy='joined')
176 users_group = relationship('UsersGroup')
181 users_group = relationship('UsersGroup')
177
182
178 def __init__(self, gr_id, u_id):
183 def __init__(self, gr_id, u_id):
179 self.users_group_id = gr_id
184 self.users_group_id = gr_id
180 self.user_id = u_id
185 self.user_id = u_id
181
186
182 class Repository(Base, BaseModel):
187 class Repository(Base, BaseModel):
183 __tablename__ = 'repositories'
188 __tablename__ = 'repositories'
184 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
189 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
185 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
190 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
186 repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
191 repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
187 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
192 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
188 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
193 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
189 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
194 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
190 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
195 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
191 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
196 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
192 description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
197 description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
193 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
198 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
194 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
199 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
195
200
196 user = relationship('User')
201 user = relationship('User')
197 fork = relationship('Repository', remote_side=repo_id)
202 fork = relationship('Repository', remote_side=repo_id)
198 group = relationship('Group')
203 group = relationship('Group')
199 repo_to_perm = relationship('RepoToPerm', cascade='all')
204 repo_to_perm = relationship('RepoToPerm', cascade='all')
200 stats = relationship('Statistics', cascade='all', uselist=False)
205 stats = relationship('Statistics', cascade='all', uselist=False)
201
206
202 repo_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
207 repo_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
203
208
204 logs = relationship('UserLog', cascade='all')
209 logs = relationship('UserLog', cascade='all')
205
210
206 def __repr__(self):
211 def __repr__(self):
207 return "<%s('%s:%s')>" % (self.__class__.__name__,
212 return "<%s('%s:%s')>" % (self.__class__.__name__,
208 self.repo_id, self.repo_name)
213 self.repo_id, self.repo_name)
209
214
210 class Group(Base, BaseModel):
215 class Group(Base, BaseModel):
211 __tablename__ = 'groups'
216 __tablename__ = 'groups'
212 __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
217 __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
213
218
214 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
219 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
215 group_name = Column("group_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
220 group_name = Column("group_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
216 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
221 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
217
222
218 parent_group = relationship('Group', remote_side=group_id)
223 parent_group = relationship('Group', remote_side=group_id)
219
224
220
225
221 def __init__(self, group_name='', parent_group=None):
226 def __init__(self, group_name='', parent_group=None):
222 self.group_name = group_name
227 self.group_name = group_name
223 self.parent_group = parent_group
228 self.parent_group = parent_group
224
229
225 def __repr__(self):
230 def __repr__(self):
226 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
231 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
227 self.group_name)
232 self.group_name)
228
233
229 class Permission(Base, BaseModel):
234 class Permission(Base, BaseModel):
230 __tablename__ = 'permissions'
235 __tablename__ = 'permissions'
231 __table_args__ = {'useexisting':True}
236 __table_args__ = {'useexisting':True}
232 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
237 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
233 permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
238 permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
239 permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
235
240
236 def __repr__(self):
241 def __repr__(self):
237 return "<%s('%s:%s')>" % (self.__class__.__name__,
242 return "<%s('%s:%s')>" % (self.__class__.__name__,
238 self.permission_id, self.permission_name)
243 self.permission_id, self.permission_name)
239
244
240 class RepoToPerm(Base, BaseModel):
245 class RepoToPerm(Base, BaseModel):
241 __tablename__ = 'repo_to_perm'
246 __tablename__ = 'repo_to_perm'
242 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
247 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
243 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
248 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
249 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
250 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
246 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
251 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
247
252
248 user = relationship('User')
253 user = relationship('User')
249 permission = relationship('Permission')
254 permission = relationship('Permission')
250 repository = relationship('Repository')
255 repository = relationship('Repository')
251
256
252 class UserToPerm(Base, BaseModel):
257 class UserToPerm(Base, BaseModel):
253 __tablename__ = 'user_to_perm'
258 __tablename__ = 'user_to_perm'
254 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
259 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
255 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
260 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
256 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
261 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
257 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
262 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
258
263
259 user = relationship('User')
264 user = relationship('User')
260 permission = relationship('Permission')
265 permission = relationship('Permission')
261
266
262 class UsersGroupToPerm(Base, BaseModel):
267 class UsersGroupToPerm(Base, BaseModel):
263 __tablename__ = 'users_group_to_perm'
268 __tablename__ = 'users_group_to_perm'
264 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
269 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
265 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
270 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
271 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
267 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
272 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
268
273
269 users_group = relationship('UsersGroup')
274 users_group = relationship('UsersGroup')
270 permission = relationship('Permission')
275 permission = relationship('Permission')
271
276
272 class GroupToPerm(Base, BaseModel):
277 class GroupToPerm(Base, BaseModel):
273 __tablename__ = 'group_to_perm'
278 __tablename__ = 'group_to_perm'
274 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
279 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
275
280
276 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
281 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
282 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
278 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
283 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
279 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
284 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
280
285
281 user = relationship('User')
286 user = relationship('User')
282 permission = relationship('Permission')
287 permission = relationship('Permission')
283 group = relationship('Group')
288 group = relationship('Group')
284
289
285 class Statistics(Base, BaseModel):
290 class Statistics(Base, BaseModel):
286 __tablename__ = 'statistics'
291 __tablename__ = 'statistics'
287 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
292 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
288 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
293 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
289 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
294 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
290 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
295 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
291 commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data
296 commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data
292 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
297 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
293 languages = Column("languages", LargeBinary(), nullable=False)#JSON data
298 languages = Column("languages", LargeBinary(), nullable=False)#JSON data
294
299
295 repository = relationship('Repository', single_parent=True)
300 repository = relationship('Repository', single_parent=True)
296
301
297 class UserFollowing(Base, BaseModel):
302 class UserFollowing(Base, BaseModel):
298 __tablename__ = 'user_followings'
303 __tablename__ = 'user_followings'
299 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
304 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
300 UniqueConstraint('user_id', 'follows_user_id')
305 UniqueConstraint('user_id', 'follows_user_id')
301 , {'useexisting':True})
306 , {'useexisting':True})
302
307
303 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
308 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
304 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
309 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
305 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
310 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
306 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
311 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
307
312
308 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
313 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
309
314
310 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
315 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
311 follows_repository = relationship('Repository')
316 follows_repository = relationship('Repository')
312
317
313 class CacheInvalidation(Base, BaseModel):
318 class CacheInvalidation(Base, BaseModel):
314 __tablename__ = 'cache_invalidation'
319 __tablename__ = 'cache_invalidation'
315 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
320 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
316 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
321 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
317 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
322 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
318 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
319 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
324 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
320
325
321
326
322 def __init__(self, cache_key, cache_args=''):
327 def __init__(self, cache_key, cache_args=''):
323 self.cache_key = cache_key
328 self.cache_key = cache_key
324 self.cache_args = cache_args
329 self.cache_args = cache_args
325 self.cache_active = False
330 self.cache_active = False
326
331
327 def __repr__(self):
332 def __repr__(self):
328 return "<%s('%s:%s')>" % (self.__class__.__name__,
333 return "<%s('%s:%s')>" % (self.__class__.__name__,
329 self.cache_id, self.cache_key)
334 self.cache_id, self.cache_key)
330
335
331 class DbMigrateVersion(Base, BaseModel):
336 class DbMigrateVersion(Base, BaseModel):
332 __tablename__ = 'db_migrate_version'
337 __tablename__ = 'db_migrate_version'
333 __table_args__ = {'useexisting':True}
338 __table_args__ = {'useexisting':True}
334 repository_id = Column('repository_id', String(250), primary_key=True)
339 repository_id = Column('repository_id', String(250), primary_key=True)
335 repository_path = Column('repository_path', Text)
340 repository_path = Column('repository_path', Text)
336 version = Column('version', Integer)
341 version = Column('version', Integer)
337
342
@@ -1,92 +1,95
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/base.html"/>
2 <%inherit file="base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Journal')} - ${c.rhodecode_name}
4 ${_('Journal')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6 <%def name="breadcrumbs()">
6 <%def name="breadcrumbs()">
7 ${c.rhodecode_name}
7 ${c.rhodecode_name}
8 </%def>
8 </%def>
9 <%def name="page_nav()">
9 <%def name="page_nav()">
10 ${self.menu('home')}
10 ${self.menu('home')}
11 </%def>
11 </%def>
12 <%def name="main()">
12 <%def name="main()">
13
13
14 <div class="box box-left">
14 <div class="box box-left">
15 <!-- box / title -->
15 <!-- box / title -->
16 <div class="title">
16 <div class="title">
17 <h5>${_('Journal')}</h5>
17 <h5>${_('Journal')}</h5>
18 </div>
18 </div>
19 <div>
19 <div>
20 %if c.journal:
20 %if c.journal_day_aggreagate:
21 %for entry in c.journal:
21 %for day,items in c.journal_day_aggreagate:
22 <div style="font-size:20px;padding:10px 5px">${day}</div>
23 % for entry in items:
22 <div style="padding:10px">
24 <div style="padding:10px">
23 <div class="gravatar">
25 <div class="gravatar">
24 <img alt="gravatar" src="${h.gravatar_url(entry.user.email)}"/>
26 <img alt="gravatar" src="${h.gravatar_url(entry.user.email)}"/>
25 </div>
27 </div>
26 <div>${entry.user.name} ${entry.user.lastname}</div>
28 <div>${entry.user.name} ${entry.user.lastname}</div>
27 <div style="padding-left: 45px;padding-top:5px;min-height:20px">${h.action_parser(entry)}</div>
29 <div style="padding-left: 45px;padding-top:5px;min-height:20px">${h.action_parser(entry)}</div>
28 <div style="float: left; padding-top: 8px;padding-left:18px">
30 <div style="float: left; padding-top: 8px;padding-left:18px">
29 ${h.action_parser_icon(entry)}
31 ${h.action_parser_icon(entry)}
30 </div>
32 </div>
31 <div style="margin-left: 45px;padding-top: 10px">
33 <div style="margin-left: 45px;padding-top: 10px">
32 <span style="font-weight: bold;font-size: 1.1em">
34 <span style="font-weight: bold;font-size: 1.1em">
33 %if entry.repository:
35 %if entry.repository:
34 ${h.link_to(entry.repository.repo_name,
36 ${h.link_to(entry.repository.repo_name,
35 h.url('summary_home',repo_name=entry.repository.repo_name))}
37 h.url('summary_home',repo_name=entry.repository.repo_name))}
36 %else:
38 %else:
37 ${entry.repository_name}
39 ${entry.repository_name}
38 %endif
40 %endif
39 </span> - <span title="${entry.action_date}">${h.age(entry.action_date)}</span>
41 </span> - <span title="${entry.action_date}">${h.age(entry.action_date)}</span>
40 </div>
42 </div>
41 </div>
43 </div>
42 <div style="clear:both;border-bottom:1px dashed #DDD;padding:3px 3px;margin:0px 10px 0px 10px"></div>
44 <div style="clear:both;border-bottom:1px dashed #DDD;padding:3px 3px;margin:0px 10px 0px 10px"></div>
43 %endfor
45 %endfor
46 %endfor
44 %else:
47 %else:
45 ${_('No entries yet')}
48 ${_('No entries yet')}
46 %endif
49 %endif
47 </div>
50 </div>
48 </div>
51 </div>
49
52
50 <div class="box box-right">
53 <div class="box box-right">
51 <!-- box / title -->
54 <!-- box / title -->
52 <div class="title">
55 <div class="title">
53 <h5>${_('Following')}</h5>
56 <h5>${_('Following')}</h5>
54 </div>
57 </div>
55 <div>
58 <div>
56 %if c.following:
59 %if c.following:
57 %for entry in c.following:
60 %for entry in c.following:
58 <div class="currently_following">
61 <div class="currently_following">
59 %if entry.follows_user_id:
62 %if entry.follows_user_id:
60 <img title="${_('following user')}" alt="${_('user')}" src="/images/icons/user.png"/>
63 <img title="${_('following user')}" alt="${_('user')}" src="/images/icons/user.png"/>
61 ${entry.follows_user.full_contact}
64 ${entry.follows_user.full_contact}
62 %endif
65 %endif
63
66
64 %if entry.follows_repo_id:
67 %if entry.follows_repo_id:
65
68
66 %if entry.follows_repository.private:
69 %if entry.follows_repository.private:
67 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
70 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
68 %else:
71 %else:
69 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
72 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
70 %endif
73 %endif
71
74
72 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',
75 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',
73 repo_name=entry.follows_repository.repo_name))}
76 repo_name=entry.follows_repository.repo_name))}
74
77
75 %endif
78 %endif
76 </div>
79 </div>
77 %endfor
80 %endfor
78 %else:
81 %else:
79 ${_('You are not following any users or repositories')}
82 ${_('You are not following any users or repositories')}
80 %endif
83 %endif
81 </div>
84 </div>
82 </div>
85 </div>
83
86
84 <script type="text/javascript">
87 <script type="text/javascript">
85 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
88 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
86 var el = e.target;
89 var el = e.target;
87 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
90 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
88 YUD.setStyle(el.parentNode,'display','none');
91 YUD.setStyle(el.parentNode,'display','none');
89 });
92 });
90 </script>
93 </script>
91
94
92 </%def>
95 </%def>
General Comments 0
You need to be logged in to leave comments. Login now