| 3 | | |
|---|
| 4 | | class Forum(ISecurityContext,IModel,IUIModel): |
|---|
| 5 | | def __init__(self,db): |
|---|
| 6 | | self.db = db |
|---|
| 7 | | self.instance = {} |
|---|
| 8 | | self.moderators = [] |
|---|
| 9 | | self.targetForum = None |
|---|
| 10 | | |
|---|
| 11 | | def _getTargetForum(self,context): |
|---|
| 12 | | if not self.targetForum: |
|---|
| 13 | | self.targetForum = self.getById(context.getArgs()["targetForumId"],context) |
|---|
| 14 | | |
|---|
| 15 | | return self.targetForum |
|---|
| 16 | | |
|---|
| 17 | | def _getDisplayAssets(self,context): |
|---|
| 18 | | assets = {} |
|---|
| 19 | | |
|---|
| 20 | | assets['thisProjectId'] = context.getProjectId() |
|---|
| 21 | | |
|---|
| 22 | | self.instance['descriptionhtml'] = context.wikiToOneLiner(self.instance['description']) |
|---|
| 23 | | self.instance['hiddenhtml'] = context.boolToHTML(self.instance['hidden']) |
|---|
| 24 | | self.instance['lockedhtml'] = context.boolToHTML(self.instance['locked']) |
|---|
| 25 | | self.instance['canAppend'] = self.canAppend(context) |
|---|
| 26 | | self.instance['canModify'] = self.canModify(context) |
|---|
| 27 | | assets['forum'] = self.instance |
|---|
| 28 | | |
|---|
| 29 | | return assets |
|---|
| 30 | | |
|---|
| 31 | | ### IUIModel Methods ### |
|---|
| 32 | | |
|---|
| 33 | | def getViewTemplate(self,context): |
|---|
| 34 | | assets = self._getDisplayAssets(context) |
|---|
| 35 | | |
|---|
| 36 | | from tracforums.models.topic import Topic |
|---|
| 37 | | assets['topics'] = Topic(self.db).getList(self.instance['id'],context) |
|---|
| 38 | | |
|---|
| 39 | | if self.canModify(context): |
|---|
| 40 | | #get forum listing |
|---|
| 41 | | from tracforums.models.forum import Forum |
|---|
| 42 | | assets['thisForumId'] = self.instance['id'] |
|---|
| 43 | | assets['forums'] = self.getList(context) |
|---|
| 44 | | |
|---|
| 45 | | #get project listing |
|---|
| 46 | | from tracforums.models.project import Project |
|---|
| 47 | | projectObj = Project(self.db) |
|---|
| 48 | | assets['projects'] = projectObj.getSimpleList(context) |
|---|
| 49 | | |
|---|
| 50 | | #get permissions |
|---|
| 51 | | assets['canMoveTopics'] = self._canModifyContents(self.instance,context) |
|---|
| 52 | | assets['canDeleteTopics'] = self._canModifyContents(self.instance,context) |
|---|
| 53 | | |
|---|
| 54 | | return "forum/view.cs",assets |
|---|
| 55 | | |
|---|
| 56 | | def getEditTemplate(self,context): |
|---|
| 57 | | assets = self._getDisplayAssets(context) |
|---|
| 58 | | assets['forum'] = self.instance |
|---|
| 59 | | |
|---|
| 60 | | assets['forum_description_rows'] = context.getSessionVar('forum_description_rows',8) |
|---|
| 61 | | assets['categories'] = self._getCategories(context) |
|---|
| 62 | | |
|---|
| 63 | | return "forum/edit.cs",assets |
|---|
| 64 | | |
|---|
| 65 | | def getSaveTemplate(self,context): |
|---|
| 66 | | from tracforums.models.profile import Profile |
|---|
| 67 | | profile = Profile(self.db).getById(context.getAuthname(),context) |
|---|
| 68 | | |
|---|
| 69 | | if profile['isexpert']: |
|---|
| 70 | | context.forumRedirect("/forum/" + str(self.instance['id'])); |
|---|
| 71 | | else: |
|---|
| 72 | | assets = self._getDisplayAssets(context) |
|---|
| 73 | | self.instance['descriptionhtml'] = context.wikiToOneLiner(self.instance['description']) |
|---|
| 74 | | self.instance['hiddenhtml'] = context.boolToHTML(self.instance['hidden']) |
|---|
| 75 | | self.instance['lockedhtml'] = context.boolToHTML(self.instance['locked']) |
|---|
| 76 | | assets['forum'] = self.instance |
|---|
| 77 | | return "forum/save.cs",assets |
|---|
| 78 | | |
|---|
| 79 | | def getNewTemplate(self,context): |
|---|
| 80 | | return self.getEditTemplate(context) |
|---|
| 81 | | |
|---|
| 82 | | def getCreateTemplate(self,context): |
|---|
| 83 | | return self.getSaveTemplate(context) |
|---|
| 84 | | |
|---|
| 85 | | def getDeleteTemplate(self,context): |
|---|
| 86 | | args = context.getArgs() |
|---|
| 87 | | assets = self._getDisplayAssets(context) |
|---|
| 88 | | |
|---|
| 89 | | # forward form data |
|---|
| 90 | | selectedTopics = args['selectedTopics'] |
|---|
| 91 | | assets['selectedTopics'] = selectedTopics |
|---|
| 92 | | |
|---|
| 93 | | # load the set for display |
|---|
| 94 | | from tracforums.models.topic import Topic |
|---|
| 95 | | assets['selection'] = Topic(self.db).getSelectionList(selectedTopics.split(','),context) |
|---|
| 96 | | |
|---|
| 97 | | return "forum/delete.cs", assets |
|---|
| 98 | | |
|---|
| 99 | | def getDeletedTemplate(self,context): |
|---|
| 100 | | from tracforums.models.profile import Profile |
|---|
| 101 | | profile = Profile(self.db).getById(context.getAuthname(),context) |
|---|
| 102 | | |
|---|
| 103 | | if profile['isexpert']: |
|---|
| 104 | | context.forumRedirect("/forum/" + str(self.instance["id"])); |
|---|
| 105 | | else: |
|---|
| 106 | | args = context.getArgs() |
|---|
| 107 | | assets = self._getDisplayAssets(context) |
|---|
| 108 | | |
|---|
| 109 | | # forward form data |
|---|
| 110 | | selectedTopics = args['selectedTopics'] |
|---|
| 111 | | assets['selectedTopics'] = selectedTopics |
|---|
| 112 | | |
|---|
| 113 | | return "forum/deleted.cs", assets |
|---|
| 114 | | |
|---|
| 115 | | def getMoveTemplate(self,context): |
|---|
| 116 | | assets = self._getDisplayAssets(context) |
|---|
| 117 | | args = context.getArgs() |
|---|
| 118 | | |
|---|
| 119 | | # forward form data |
|---|
| 120 | | selectedTopics = args['selectedTopics'] |
|---|
| 121 | | targetForumId = args['targetForumId'] |
|---|
| 122 | | assets['selectedTopics'] = selectedTopics |
|---|
| 123 | | assets['targetForumId'] = targetForumId |
|---|
| 124 | | |
|---|
| 125 | | # load the set for display |
|---|
| 126 | | from tracforums.models.topic import Topic |
|---|
| 127 | | assets['selection'] = Topic(self.db).getSelectionList(selectedTopics.split(','),context) |
|---|
| 128 | | |
|---|
| 129 | | # get the target forum |
|---|
| 130 | | assets['targetForum'] = self.getById(targetForumId,context) |
|---|
| 131 | | |
|---|
| 132 | | return "forum/move.cs", assets |
|---|
| 133 | | |
|---|
| 134 | | def getMovedTemplate(self,context): |
|---|
| 135 | | assets = self._getDisplayAssets(context) |
|---|
| 136 | | args = context.getArgs() |
|---|
| 137 | | |
|---|
| 138 | | # forward form data |
|---|
| 139 | | selectedTopics = args['selectedTopics'] |
|---|
| 140 | | targetForumId = args['targetForumId'] |
|---|
| 141 | | assets['selectedTopics'] = selectedTopics |
|---|
| 142 | | assets['targetForumId'] = targetForumId |
|---|
| 143 | | |
|---|
| 144 | | # get the target forum |
|---|
| 145 | | assets['targetForum'] = self.getById(targetForumId,context) |
|---|
| 146 | | |
|---|
| 147 | | return "forum/moved.cs", assets |
|---|
| 148 | | |
|---|
| 149 | | ### ISecurityContext Methods ### |
|---|
| 150 | | |
|---|
| 151 | | def canView(self,context): |
|---|
| 152 | | # viewing a forum is restricted to: guest |
|---|
| 153 | | # viewing a hidden forum is restricted to: admin, moderators |
|---|
| 154 | | if self.instance["hidden"]: |
|---|
| 155 | | if context.isForumAdmin(): |
|---|
| 156 | | return True |
|---|
| 157 | | if context.isForumUser() and (context.getAuthname() in self.instance["moderatorsArr"]): |
|---|
| 158 | | return True |
|---|
| 159 | | else: |
|---|
| 160 | | return False |
|---|
| 161 | | else: |
|---|
| 162 | | return context.isForumGuest() |
|---|
| 163 | | |
|---|
| 164 | | def canCreate(self,context): |
|---|
| 165 | | # creating a forum is restricted to: admin |
|---|
| 166 | | return context.isForumAdmin() |
|---|
| 167 | | |
|---|
| 168 | | def canModify(self,context): |
|---|
| 169 | | # modifying a forum is restricted to: admin, moderators |
|---|
| 170 | | if context.isForumAdmin(): |
|---|
| 171 | | return True |
|---|
| 172 | | if context.isForumUser() and (context.getAuthname() in self.instance["moderatorsArr"]): |
|---|
| 173 | | return True |
|---|
| 174 | | else: |
|---|
| 175 | | return False |
|---|
| 176 | | |
|---|
| 177 | | def canAppend(self,context): |
|---|
| 178 | | # adding a topic to a locked forum is restricted to: admin, moderators |
|---|
| 179 | | # adding a topic to a forum is restricted to: forum users |
|---|
| 180 | | if self.instance["locked"]: |
|---|
| 181 | | if context.isForumAdmin(): |
|---|
| 182 | | return True |
|---|
| 183 | | elif context.isForumUser() and (context.getAuthname() in self.instance["moderatorsArr"]): |
|---|
| 184 | | return True |
|---|
| 185 | | else: |
|---|
| 186 | | return False |
|---|
| 187 | | elif context.isForumUser(): |
|---|
| 188 | | return True |
|---|
| 189 | | else: |
|---|
| 190 | | return False |
|---|
| 191 | | |
|---|
| 192 | | def canDelete(self,context): |
|---|
| 193 | | # deleting portions of this forum: admins, moderators |
|---|
| 194 | | return self._canModifyContents(self.instance,context) |
|---|
| 195 | | |
|---|
| 196 | | def canMove(self,context): |
|---|
| 197 | | # moving portions of this forum around: admins, moderators |
|---|
| 198 | | # - conditional on canMove() being valid on both this and target |
|---|
| 199 | | self._getTargetForum(context) |
|---|
| 200 | | |
|---|
| 201 | | return self._canModifyContents(self.instance,context) and self._canModifyContents(self.targetForum,context) |
|---|
| 202 | | |
|---|
| 203 | | def _canModifyContents(self,forum,context): |
|---|
| 204 | | if context.isForumAdmin(): |
|---|
| 205 | | return True |
|---|
| 206 | | if context.isForumUser() and (context.getAuthname() in forum["moderatorsArr"]): |
|---|
| 207 | | return True |
|---|
| 208 | | else: |
|---|
| 209 | | return False |
|---|
| 210 | | |
|---|
| 211 | | ### IModel Methods ### |
|---|
| 212 | | |
|---|
| 213 | | def _getCategories(self,context): |
|---|
| 214 | | cursor = self.db.cursor() |
|---|
| 215 | | cursor.execute(""" |
|---|
| 216 | | SELECT DISTINCT categoryid FROM forum |
|---|
| 217 | | WHERE projectid = %(projectid)s AND categoryid > 0 |
|---|
| 218 | | """,{ |
|---|
| 219 | | 'projectid': context.getProjectId() |
|---|
| 220 | | }) |
|---|
| 221 | | |
|---|
| 222 | | return cursor.fetchall() |
|---|
| 223 | | |
|---|
| 224 | | def _resetModerators(self): |
|---|
| 225 | | # flush and rebuild moderators |
|---|
| 226 | | cursor = self.db.cursor() |
|---|
| 227 | | cursor.execute(""" |
|---|
| 228 | | DELETE from moderators WHERE forumid = %(id)s |
|---|
| 229 | | """,{ |
|---|
| 230 | | 'id': self.instance['id'] |
|---|
| 231 | | }) |
|---|
| 232 | | for mod in self.instance['moderatorsArr']: |
|---|
| 233 | | if len(mod) > 0: |
|---|
| 234 | | cursor.execute(""" |
|---|
| 235 | | INSERT into moderators (forumid,username) values (%(id)s,%(mod)s) |
|---|
| 236 | | """,{ |
|---|
| 237 | | 'id': self.instance['id'], |
|---|
| 238 | | 'mod': mod.strip() |
|---|
| 239 | | }) |
|---|
| 240 | | |
|---|
| 241 | | def load(self,context): |
|---|
| 242 | | args = context.getArgs() |
|---|
| 243 | | |
|---|
| 244 | | # name-based load |
|---|
| 245 | | if args["forum_args"] != '': |
|---|
| 246 | | self.instance = self.getById(args["forum_args"],context) |
|---|
| 247 | | |
|---|
| 248 | | # id-based load |
|---|
| 249 | | elif "id" in args: |
|---|
| 250 | | self.instance = self.getById(args["id"],context) |
|---|
| 251 | | |
|---|
| 252 | | # failed |
|---|
| 253 | | else: |
|---|
| 254 | | raise TracError("cannot load forum") |
|---|
| 255 | | |
|---|
| 256 | | |
|---|
| 257 | | def set(self,context): |
|---|
| 258 | | args = context.getArgs() |
|---|
| 259 | | modified = int(time.time()) |
|---|
| 260 | | |
|---|
| 261 | | if 'forum_description_rows' in args: |
|---|
| 262 | | context.setSessionVar('forum_description_rows',args['forum_description_rows']) |
|---|
| 263 | | |
|---|
| 264 | | self.instance = { |
|---|
| 265 | | 'id': self.getDefault((args,self.instance),'id',''), |
|---|
| 266 | | 'name': self.getDefault((args,self.instance),'name',''), |
|---|
| 267 | | 'description': self.getDefault((args,self.instance),'description',''), |
|---|
| 268 | | 'modified': self.getDefault((args,self.instance),'modified',modified), |
|---|
| 269 | | 'created': self.getDefault((args,self.instance),'created',modified), |
|---|
| 270 | | 'hidden': self.getDefault((args,self.instance),'hidden',False), |
|---|
| 271 | | 'locked': self.getDefault((args,self.instance),'locked',False), |
|---|
| 272 | | 'moderators': self.getDefault((args,self.instance),'moderators',''), |
|---|
| 273 | | 'categoryid': self.getDefault((args,self.instance),'categoryid',''), |
|---|
| 274 | | 'projectid': args['projectid'] |
|---|
| 275 | | } |
|---|
| 276 | | |
|---|
| 277 | | if self.instance['moderators'] != '': |
|---|
| 278 | | self.instance['moderatorsArr'] = self.instance['moderators'].split(',') |
|---|
| 279 | | else: |
|---|
| 280 | | self.instance['moderatorsArr'] = [] |
|---|
| 281 | | |
|---|
| 282 | | def create(self,context): |
|---|
| 283 | | # create instance |
|---|
| 284 | | cursor = self.db.cursor() |
|---|
| 285 | | cursor.execute(""" |
|---|
| 286 | | INSERT into forum (created,modified,name,description,projectid,hidden,locked,categoryid) |
|---|
| 287 | | values (%(modified)s,%(modified)s,%(name)s,%(description)s,%(projectid)s,%(hidden)s,%(locked)s,%(categoryid)s) |
|---|
| 288 | | """,{ |
|---|
| 289 | | 'modified': self.instance['modified'], |
|---|
| 290 | | 'name': self.instance['name'], |
|---|
| 291 | | 'description': self.instance['description'], |
|---|
| 292 | | 'locked': self.instance['locked'], |
|---|
| 293 | | 'hidden': self.instance['hidden'], |
|---|
| 294 | | 'id': self.instance['id'], |
|---|
| 295 | | 'categoryid': self.instance['categoryid'], |
|---|
| 296 | | 'projectid': context.getProjectId(), |
|---|
| 297 | | }) |
|---|
| 298 | | self.instance['id'] = self.db.get_last_id(cursor, 'forum') |
|---|
| 299 | | |
|---|
| 300 | | self._resetModerators() |
|---|
| 301 | | |
|---|
| 302 | | def save(self,context): |
|---|
| 303 | | # save instance |
|---|
| 304 | | cursor = self.db.cursor() |
|---|
| 305 | | |
|---|
| 306 | | cursor.execute(""" |
|---|
| 307 | | UPDATE forum SET |
|---|
| 308 | | modified=%(modified)s, name=%(name)s, description=%(description)s, locked=%(locked)s, hidden=%(hidden)s, categoryid=%(categoryid)s |
|---|
| 309 | | WHERE id = %(id)s AND projectid=%(projectid)s |
|---|
| 310 | | """,{ |
|---|
| 311 | | 'modified': self.instance['modified'], |
|---|
| 312 | | 'name': self.instance['name'], |
|---|
| 313 | | 'description': self.instance['description'], |
|---|
| 314 | | 'locked': self.instance['locked'], |
|---|
| 315 | | 'hidden': self.instance['hidden'], |
|---|
| 316 | | 'id': self.instance['id'], |
|---|
| 317 | | 'categoryid': self.instance['categoryid'], |
|---|
| 318 | | 'projectid': context.getProjectId(), |
|---|
| 319 | | }) |
|---|
| 320 | | |
|---|
| 321 | | self._resetModerators() |
|---|
| 322 | | |
|---|
| 323 | | def delete(self,context): |
|---|
| 324 | | args = context.getArgs() |
|---|
| 325 | | selectedTopics = args["selectedTopics"].split(",") |
|---|
| 326 | | self.deleteTopics(selectedTopics,context) |
|---|
| 327 | | |
|---|
| 328 | | def move(self,context): |
|---|
| 329 | | args = context.getArgs() |
|---|
| 330 | | selectedTopics = args["selectedTopics"].split(",") |
|---|
| 331 | | targetForumId = args["targetForumId"] |
|---|
| 332 | | self.moveTopicsTo(selectedTopics,targetForumId,context) |
|---|
| 333 | | |
|---|
| 334 | | def _validate(self,context): |
|---|
| | 4 | from tracforums.orm import * |
|---|
| | 5 | from tracforums.models.category import CategoryModel |
|---|
| | 6 | |
|---|
| | 7 | """ |
|---|
| | 8 | Forum ORM |
|---|
| | 9 | """ |
|---|
| | 10 | |
|---|
| | 11 | class ForumModel(ORMSchema( |
|---|
| | 12 | tablename="forum", |
|---|
| | 13 | orderby={"rank":"asc"}, |
|---|
| | 14 | columns={ |
|---|
| | 15 | # normal columns |
|---|
| | 16 | "id": ORMKey(type="int", auto_increment = True, unique = True), |
|---|
| | 17 | "projectid": ORMKey(type="str", force_insert = True), |
|---|
| | 18 | "name": ORMColumn(type="str", unique = True, required = True), |
|---|
| | 19 | "created": ORMColumn(type="int"), |
|---|
| | 20 | "modified": ORMColumn(type="int"), |
|---|
| | 21 | "description": ORMColumn(type="str"), |
|---|
| | 22 | "locked": ORMColumn(type="bool"), |
|---|
| | 23 | "hidden": ORMColumn(type="bool"), |
|---|
| | 24 | "categoryid": ORMColumn(type="int"), |
|---|
| | 25 | "rank": ORMColumn(type="int", auto_increment = True, force_update = True), |
|---|
| | 26 | |
|---|
| | 27 | # aliases |
|---|
| | 28 | "forumid": ORMAlias(sql="forum.id"), |
|---|
| | 29 | "topicCount": ORMAlias(sql="coalesce((select count(id) from topic as t where forum.id = t.forumid),0)"), |
|---|
| | 30 | "viewCount": ORMAlias(sql="coalesce((select sum(views) from topic as t where t.forumid=forum.id),0)"), |
|---|
| | 31 | "replyCount": ORMAlias(sql=""" |
|---|
| | 32 | coalesce( |
|---|
| | 33 | (select (select count(m.id) |
|---|
| | 34 | from topic as t join message as m on m.topicid = t.id |
|---|
| | 35 | where t.forumid = forum.id |
|---|
| | 36 | ) - (select count(id) from topic as t where t.forumid=forum.id) |
|---|
| | 37 | ),0) |
|---|
| | 38 | """), |
|---|
| | 39 | "recentPostId": ORMAlias(sql=""" |
|---|
| | 40 | select id from message as m where modified = ( |
|---|
| | 41 | select max(modified) |
|---|
| | 42 | from message as m join topic as t on m.topicid = t.id |
|---|
| | 43 | where t.forumid = forum.id |
|---|
| | 44 | ) |
|---|
| | 45 | """), |
|---|
| | 46 | |
|---|
| | 47 | "descriptionhtml": ORMAlias( |
|---|
| | 48 | name = "description", |
|---|
| | 49 | type = "oneliner" |
|---|
| | 50 | ), |
|---|
| | 51 | |
|---|
| | 52 | # relations |
|---|
| | 53 | "moderators": ORMRelation( |
|---|
| | 54 | model = ORMImportModel("tracforums.models.moderator","ModeratorModel"), |
|---|
| | 55 | relationship = {"forumid":"forumid"}, |
|---|
| | 56 | ), |
|---|
| | 57 | })): |
|---|
| | 58 | |
|---|
| | 59 | def format(self): |
|---|
| | 60 | print """Custom Format""",self |
|---|
| | 61 | self.canModify = toBool(self._canModify()) |
|---|
| | 62 | self.canAppend = toBool(self._canAppend()) |
|---|
| | 63 | self.canView = toBool(self._canView()) |
|---|
| | 64 | self.moderatorsList = ", ".join(map(lambda x: x.username,self.moderators)) |
|---|
| | 65 | self.userIsModerator = self.formatContext.getAuthname() in self.moderators |
|---|
| | 66 | |
|---|
| | 67 | def _canView(self): |
|---|
| | 68 | if not self.formatContext.isForumUser(): return False |
|---|
| | 69 | elif self.formatContext.isForumAdmin(): return True |
|---|
| | 70 | elif self.formatContext.getAuthname() in self.moderators: return True |
|---|
| | 71 | else: return not self.hidden |
|---|
| | 72 | |
|---|
| | 73 | def _canModify(self): |
|---|
| | 74 | if not self.formatContext.isForumUser(): return False |
|---|
| | 75 | elif self.formatContext.isForumAdmin(): return True |
|---|
| | 76 | else: return self.formatContext.getAuthname() in self.moderators |
|---|
| | 77 | |
|---|
| | 78 | def _canAppend(self): |
|---|
| | 79 | if not self.formatContext.isForumUser(): return False |
|---|
| | 80 | elif self.formatContext.isForumAdmin(): return True |
|---|
| | 81 | elif self.formatContext.getAuthname() in self.moderators: return True |
|---|
| | 82 | else: return not self.locked |
|---|
| | 83 | |
|---|
| | 84 | def validate(self): |
|---|
| 342 | | if self.instance['id'] == "": |
|---|
| 343 | | cursor.execute( |
|---|
| 344 | | "SELECT id from forum WHERE name=%(name)s AND projectid=%(projectid)s", |
|---|
| 345 | | { |
|---|
| 346 | | 'projectid': context.getProjectId(), |
|---|
| 347 | | 'name': self.instance['name'], |
|---|
| | 92 | if self.id == '0': |
|---|
| | 93 | nameTest = self.getMany({ |
|---|
| | 94 | "projectid": self.formatContext.getProjectId(), |
|---|
| | 95 | "name": self.name, |
|---|
| | 96 | }) |
|---|
| | 97 | else: |
|---|
| | 98 | nameTest = self.getMany({ |
|---|
| | 99 | "projectid": self.formatContext.getProjectId(), |
|---|
| | 100 | "name": self.name, |
|---|
| | 101 | "id": ("<>",self.id), #get everything but this instance |
|---|
| | 102 | }) |
|---|
| | 103 | if len(nameTest) > 0: |
|---|
| | 104 | reasons.append("The forum name, '" + self.name + "', is already taken.") |
|---|
| | 105 | return reasons |
|---|
| | 106 | |
|---|
| | 107 | def validateCreate(self): |
|---|
| | 108 | return self.validate() |
|---|
| | 109 | |
|---|
| | 110 | def validateSave(self): |
|---|
| | 111 | reasons = self.validate() |
|---|
| | 112 | |
|---|
| | 113 | if len(self.getMany({ |
|---|
| | 114 | "id": self.id, |
|---|
| | 115 | "projectid": self.formatContext.getProjectId() |
|---|
| | 116 | })) == 0: |
|---|
| | 117 | raise TracError('Invalid Forum Id %s' % self.id) |
|---|
| | 118 | |
|---|
| | 119 | return reasons |
|---|
| | 120 | |
|---|
| | 121 | def saveModerators(self): |
|---|
| | 122 | print map(lambda x: x.username,self.moderators) |
|---|
| | 123 | if self.moderators and len(self.moderators) > 0: |
|---|
| | 124 | from tracforums.models.moderator import ModeratorModel |
|---|
| | 125 | ModeratorModel(self.db,self.formatContext).deleteMany({"forumid":self.id,"username":map(lambda x: x.username,self.moderators)}) |
|---|
| | 126 | for username in self.moderators: |
|---|
| | 127 | ModeratorModel(self.db,self.formatContext).save({"username":username,"forumid":self.id}) |
|---|
| | 128 | |
|---|
| | 129 | def save(self,data={}): |
|---|
| | 130 | self.getBase().save(self,data) |
|---|
| | 131 | self.saveModerators() |
|---|
| | 132 | from tracforums.models.watch import notifyForumChanged |
|---|
| | 133 | notifyForumChanged(self.db,self.formatContext,self) |
|---|
| | 134 | |
|---|
| | 135 | def create(self,data={}): |
|---|
| | 136 | self.getBase().create(self,data) |
|---|
| | 137 | self.saveModerators() |
|---|
| | 138 | |
|---|
| | 139 | def demoteRank(self): |
|---|
| | 140 | # increase rank value to lower it's priority in the list |
|---|
| | 141 | ranks = self.getColumnValues("rank",{"projectid":self.projectid,"categoryid":self.categoryid}) |
|---|
| | 142 | print "\nRanks:",ranks |
|---|
| | 143 | idx = ranks.index(self.rank) |
|---|
| | 144 | |
|---|
| | 145 | if idx < len(ranks)-1: |
|---|
| | 146 | # swap with next highest rank |
|---|
| | 147 | self.updateMany({"rank":ranks[idx+1]},{"rank":self.rank}) |
|---|
| | 148 | self.rank = ranks[idx+1] |
|---|
| | 149 | |
|---|
| | 150 | def promoteRank(self): |
|---|
| | 151 | # decrease rank value to raise it's priority in the list |
|---|
| | 152 | ranks = self.getColumnValues("rank",{"projectid":self.projectid,"categoryid":self.categoryid}) |
|---|
| | 153 | print "\nRanks:",ranks |
|---|
| | 154 | idx = ranks.index(self.rank) |
|---|
| | 155 | |
|---|
| | 156 | if idx > 0: |
|---|
| | 157 | # swap with next lowest rank |
|---|
| | 158 | self.updateMany({"rank":ranks[idx-1]},{"rank":self.rank}) |
|---|
| | 159 | self.rank = ranks[idx-1] |
|---|
| | 160 | |
|---|
| | 161 | |
|---|
| | 162 | ForumModelWithRecentPost = ORMSchema( |
|---|
| | 163 | base = ForumModel, |
|---|
| | 164 | columns={ |
|---|
| | 165 | "recentpost": ORMJoin( |
|---|
| | 166 | model = ORMImportModel("tracforums.models.message","MessageModelWithProfileAndTopic"), |
|---|
| | 167 | relationship = {"recentpostid":"messageid"} |
|---|
| | 168 | ) |
|---|
| | 169 | } |
|---|
| | 170 | ) |
|---|
| | 171 | |
|---|
| | 172 | from tracforums.models.topic import TopicModel,TopicModelWithLeadMessage |
|---|
| | 173 | from tracforums.models.watch import ForumWatchModelWithProfile,ProjectWatchModel,ProjectWatchModelWithProfile |
|---|
| | 174 | |
|---|
| | 175 | class ForumController(Controller): |
|---|
| | 176 | def __init__(self,req,env,db): |
|---|
| | 177 | Controller.__init__(self,req,env,db) |
|---|
| | 178 | |
|---|
| | 179 | def doView(self): |
|---|
| | 180 | args = self.getArgs() |
|---|
| | 181 | forum = ForumModel(self.db,self).load({"id": args["viewid"]}) |
|---|
| | 182 | |
|---|
| | 183 | self.assertAction( |
|---|
| | 184 | forum != None, |
|---|
| | 185 | "Forum does not exist" |
|---|
| | 186 | ) |
|---|
| | 187 | |
|---|
| | 188 | self.assertAction( |
|---|
| | 189 | forum.canView, |
|---|
| | 190 | "You do not have permission to view this item." |
|---|
| | 191 | ) |
|---|
| | 192 | |
|---|
| | 193 | # handle watch behavior |
|---|
| | 194 | args = self.getArgs() |
|---|
| | 195 | action = args.get("action",None) |
|---|
| | 196 | try: |
|---|
| | 197 | if action == "watch": |
|---|
| | 198 | from tracforums.models.watch import ForumWatchModel |
|---|
| | 199 | ForumWatchModel(self.db,self).save({ |
|---|
| | 200 | "username": self.getAuthname(), |
|---|
| | 201 | "forumid": forum.id |
|---|
| | 202 | }) |
|---|
| | 203 | |
|---|
| | 204 | #force changes through before display |
|---|
| | 205 | self.db.commit() |
|---|
| | 206 | validateErrors = None |
|---|
| | 207 | except ModelValidateException,e: |
|---|
| | 208 | validateErrors = e.reasons |
|---|
| | 209 | |
|---|
| | 210 | return ("forum/view.cs",{ |
|---|
| | 211 | "forum": forum, |
|---|
| | 212 | "validateErrors": validateErrors, |
|---|
| | 213 | "topics": TopicModelWithLeadMessage(self.db,self).getMany({"forumid": forum.id}), |
|---|
| | 214 | "watching": ForumWatchModelWithProfile(self.db,self).getMany({"forumid": forum.id}) |
|---|
| | 215 | }) |
|---|
| | 216 | |
|---|
| | 217 | def doEdit(self): |
|---|
| | 218 | args = self.getArgs() |
|---|
| | 219 | forum = ForumModel(self.db,self) |
|---|
| | 220 | |
|---|
| | 221 | if args.get("viewid",0) != 0: |
|---|
| | 222 | forum.load({"id": args["viewid"]}) |
|---|
| | 223 | action = args.get("action","edit") |
|---|
| | 224 | validateErrors = [] |
|---|
| | 225 | |
|---|
| | 226 | self.assertAction( |
|---|
| | 227 | forum != None, |
|---|
| | 228 | "Forum does not exist" |
|---|
| | 229 | ) |
|---|
| | 230 | |
|---|
| | 231 | self.assertAction( |
|---|
| | 232 | forum.canModify, |
|---|
| | 233 | "You do not have permission to edit this item." |
|---|
| | 234 | ) |
|---|
| | 235 | |
|---|
| | 236 | try: |
|---|
| | 237 | if 'forum_description_rows' in args: |
|---|
| | 238 | self.setSessionVar('forum_description_rows',args['forum_description_rows']) |
|---|
| | 239 | forum.set(args) |
|---|
| | 240 | if action == "preview": |
|---|
| | 241 | pass |
|---|
| | 242 | elif action == "save": |
|---|
| | 243 | forum.save() |
|---|
| | 244 | self.forumRedirect(args.get("returnto","forum/view/" + str(forum.id))); |
|---|
| | 245 | except ModelValidateException,e: |
|---|
| | 246 | validateErrors = e.reasons |
|---|
| | 247 | |
|---|
| | 248 | return ("forum/edit.cs",{ |
|---|
| | 249 | "returnto": args.get("returnto",None), |
|---|
| | 250 | "forum": forum, |
|---|
| | 251 | "preview": [forum], |
|---|
| | 252 | "categories": CategoryModel(self.db,self).getMany({"projectid": self.getProjectId()}), |
|---|
| | 253 | "forum_description_rows": self.getSessionVar("forum_description_rows",8), |
|---|
| | 254 | "validateErrors": validateErrors |
|---|
| | 255 | }) |
|---|
| | 256 | |
|---|
| | 257 | def doManage(self): |
|---|
| | 258 | args = self.getArgs() |
|---|
| | 259 | forum = ForumModel(self.db,self).load({"id": args["viewid"]}) |
|---|
| | 260 | |
|---|
| | 261 | self.assertAction( |
|---|
| | 262 | forum != None, |
|---|
| | 263 | "Forum does not exist" |
|---|
| | 264 | ) |
|---|
| | 265 | |
|---|
| | 266 | self.assertAction( |
|---|
| | 267 | forum.canView, |
|---|
| | 268 | "You do not have permission to view this item." |
|---|
| | 269 | ) |
|---|
| | 270 | |
|---|
| | 271 | # handle watch behavior |
|---|
| | 272 | args = self.getArgs() |
|---|
| | 273 | action = args.get("action",None) |
|---|
| | 274 | topicid = args.get("topicid",None) |
|---|
| | 275 | |
|---|
| | 276 | try: |
|---|
| | 277 | if action == "changetype": |
|---|
| | 278 | topic = TopicModel(self.db,self).load({"id":topicid}) |
|---|
| | 279 | topic.type = args.get("type","") |
|---|
| | 280 | topic.save() |
|---|
| | 281 | |
|---|
| | 282 | elif action == "deleteSelection": |
|---|
| | 283 | selectedTopics = args.getArray("selectedtopics") |
|---|
| | 284 | if len(selectedTopics) > 0: |
|---|
| | 285 | TopicModel(self.db,self).deleteMany({ |
|---|
| | 286 | "id": selectedTopics, |
|---|
| | 287 | "forumid": forum.id |
|---|
| 349 | | else: |
|---|
| 350 | | cursor.execute( |
|---|
| 351 | | "SELECT id from forum WHERE name=%(name)s AND id <> %(id)s AND projectid=%(projectid)s", |
|---|
| 352 | | { |
|---|
| 353 | | 'projectid': context.getProjectId(), |
|---|
| 354 | | 'name': self.instance['name'], |
|---|
| 355 | | 'id': self.instance['id'], |
|---|
| 356 | | }) |
|---|
| 357 | | if cursor.fetchone(): |
|---|
| 358 | | reasons.append("The forum name, '" + self.instance['name'] + "', is already taken.") |
|---|
| 359 | | |
|---|
| 360 | | # check moderators |
|---|
| 361 | | for moderator in self.instance['moderators'].split(","): |
|---|
| 362 | | if len(moderator): |
|---|
| 363 | | moderator = moderator.strip() |
|---|
| 364 | | cursor.execute("SELECT username from profile WHERE username=%s",(moderator,)) |
|---|
| 365 | | if not cursor.fetchone(): |
|---|
| 366 | | reasons.append("The moderator '" + moderator + "' is not a valid username.") |
|---|
| 367 | | |
|---|
| 368 | | return reasons |
|---|
| 369 | | |
|---|
| 370 | | def validateCreate(self,context): |
|---|
| 371 | | reasons = self._validate(context) |
|---|
| 372 | | return reasons |
|---|
| 373 | | |
|---|
| 374 | | def validateSave(self,context): |
|---|
| 375 | | reasons = self._validate(context) |
|---|
| 376 | | cursor = self.db.cursor() |
|---|
| 377 | | |
|---|
| 378 | | cursor.execute( |
|---|
| 379 | | "SELECT id from forum WHERE id=%(id)s AND projectid=%(projectid)s", |
|---|
| 380 | | { |
|---|
| 381 | | 'id': self.instance['id'], |
|---|
| 382 | | 'projectid': context.getProjectId() |
|---|
| 383 | | } |
|---|
| 384 | | ) |
|---|
| 385 | | if not cursor.fetchone(): |
|---|
| 386 | | raise TracError('Invalid Forum Id %s' % self.instance['id']) |
|---|
| 387 | | |
|---|
| 388 | | return reasons |
|---|
| 389 | | |
|---|
| 390 | | ### Model-to-Model Helpers ### |
|---|
| 391 | | |
|---|
| 392 | | def getViewRestriction(context): |
|---|
| 393 | | if context.isForumAdmin(): |
|---|
| 394 | | return "" |
|---|
| 395 | | elif context.isForumUser(): |
|---|
| 396 | | return """AND (not forum.hidden |
|---|
| 397 | | OR %(username)s in (SELECT username FROM moderators WHERE moderators.forumid = forum.id) |
|---|
| 398 | | )""" |
|---|
| 399 | | elif context.isForumGuest: |
|---|
| 400 | | return "AND not forum.hidden" |
|---|
| 401 | | else: |
|---|
| 402 | | return None |
|---|
| 403 | | |
|---|
| 404 | | getViewRestriction = Callable(getViewRestriction) |
|---|
| 405 | | |
|---|
| 406 | | def getCanModifyRestriction(context): |
|---|
| 407 | | if context.isForumAdmin(): |
|---|
| 408 | | return "" |
|---|
| 409 | | elif context.isForumUser(): |
|---|
| 410 | | return """AND ( |
|---|
| 411 | | %(username)s in (SELECT username FROM moderators WHERE moderators.forumid = forum.id) |
|---|
| 412 | | )""" |
|---|
| 413 | | else: |
|---|
| 414 | | return None |
|---|
| 415 | | |
|---|
| 416 | | getCanModifyRestriction = Callable(getCanModifyRestriction) |
|---|
| 417 | | |
|---|
| 418 | | def getCanModifyValue(context): |
|---|
| 419 | | if context.isForumAdmin(): |
|---|
| 420 | | return "true" |
|---|
| 421 | | elif context.isForumUser(): |
|---|
| 422 | | return """( |
|---|
| 423 | | %(username)s in (SELECT username FROM moderators WHERE moderators.forumid = forum.id) |
|---|
| 424 | | )""" |
|---|
| 425 | | else: |
|---|
| 426 | | return "false" |
|---|
| 427 | | |
|---|
| 428 | | getCanModifyValue = Callable(getCanModifyValue) |
|---|
| 429 | | |
|---|
| 430 | | def getCanDeleteValue(context): |
|---|
| 431 | | if context.isForumAdmin(): |
|---|
| 432 | | return "true" |
|---|
| 433 | | else: |
|---|
| 434 | | return "false" |
|---|
| 435 | | |
|---|
| 436 | | getCanDeleteValue = Callable(getCanDeleteValue) |
|---|
| 437 | | |
|---|
| 438 | | def getById(self,forumid,context): |
|---|
| 439 | | cursor = self.db.cursor() |
|---|
| 440 | | |
|---|
| 441 | | cursor.execute(""" |
|---|
| 442 | | SELECT name, id, created, modified, description, hidden, locked, projectid, categoryid |
|---|
| 443 | | FROM forum WHERE id=%(id)s AND projectid=%(projectid)s |
|---|
| 444 | | """,{ |
|---|
| 445 | | "id":forumid, |
|---|
| 446 | | "projectid":context.getProjectId(), |
|---|
| 447 | | }) |
|---|
| 448 | | row = cursor.fetchone() |
|---|
| 449 | | if not row: |
|---|
| 450 | | raise TracError("Cannot load forum %s" % forumid) |
|---|
| 451 | | |
|---|
| 452 | | return self._getByRow(row,context) |
|---|
| 453 | | |
|---|
| 454 | | def getByName(self,name,context): |
|---|
| 455 | | cursor = self.db.cursor() |
|---|
| 456 | | |
|---|
| 457 | | cursor.execute(""" |
|---|
| 458 | | SELECT name, id, created, modified, description, hidden, locked, projectid, categoryid |
|---|
| 459 | | FROM forum WHERE name=%(name)s AND projectid=%(projectid)s |
|---|
| 460 | | """,{ |
|---|
| 461 | | "name":name, |
|---|
| 462 | | "projectid":context.getProjectId(), |
|---|
| 463 | | }) |
|---|
| 464 | | row = cursor.fetchone() |
|---|
| 465 | | if not row: |
|---|
| 466 | | raise TracError("Cannot load forum %s" % name) |
|---|
| 467 | | |
|---|
| 468 | | return self._getByRow(row,context) |
|---|
| 469 | | |
|---|
| 470 | | def _getByRow(self,row,context): |
|---|
| 471 | | columns = ('name', 'id', 'created', 'modified', 'description', 'hidden', 'locked', 'projectid', 'categoryid') |
|---|
| 472 | | |
|---|
| 473 | | forum = dict(zip(columns, row)) |
|---|
| 474 | | forum['hidden'] = toBool(forum['hidden']) |
|---|
| 475 | | forum['locked'] = toBool(forum['locked']) |
|---|
| 476 | | |
|---|
| 477 | | # get moderators |
|---|
| 478 | | forum['moderatorsArr'] = [] |
|---|
| 479 | | cursor = self.db.cursor() |
|---|
| 480 | | columns = ('username') |
|---|
| 481 | | cursor.execute("SELECT username FROM moderators WHERE forumid = %s",(forum['id'],)) |
|---|
| 482 | | for row in cursor.fetchall(): |
|---|
| 483 | | forum['moderatorsArr'].append(row[0]) |
|---|
| 484 | | |
|---|
| 485 | | if len(forum['moderatorsArr']) > 0: |
|---|
| 486 | | forum['moderators'] = ",".join(forum['moderatorsArr']) |
|---|
| 487 | | else: |
|---|
| 488 | | forum['moderators'] = '' |
|---|
| 489 | | |
|---|
| 490 | | return forum |
|---|
| 491 | | |
|---|
| 492 | | def getList(self,context): |
|---|
| 493 | | cursor = self.db.cursor() |
|---|
| 494 | | forums = [] |
|---|
| 495 | | |
|---|
| 496 | | viewRestriction = Forum.getViewRestriction(context) |
|---|
| 497 | | if viewRestriction == None: |
|---|
| 498 | | return forums |
|---|
| 499 | | |
|---|
| 500 | | canModifyValue = Forum.getCanModifyValue(context) |
|---|
| 501 | | canDeleteValue = Forum.getCanDeleteValue(context) |
|---|
| 502 | | |
|---|
| 503 | | columns = ('id', 'created', 'modified', 'name', 'description', 'locked', 'hidden', 'categoryid', |
|---|
| 504 | | 'canModify', 'canDelete', |
|---|
| 505 | | 'replies', 'topics', 'views', |
|---|
| 506 | | 'recentAuthor', 'avatarid', 'recentModified', 'recentId', 'recentTopicId' ) |
|---|
| 507 | | |
|---|
| 508 | | cursor.execute(""" |
|---|
| 509 | | SELECT |
|---|
| 510 | | distinct forum.id, forum.created, forum.modified, forum.name, forum.description, forum.locked, forum.hidden, forum.categoryid, |
|---|
| 511 | | """ + canModifyValue + """ as canModify, |
|---|
| 512 | | """ + canDeleteValue + """ as canDelete, |
|---|
| 513 | | (CASE count(message.id) WHEN 0 THEN 0 ELSE count(message.id)-1 END) as replies, |
|---|
| 514 | | COALESCE(topics.total,0) as topics, |
|---|
| 515 | | COALESCE(topicViews.total,0) as views, |
|---|
| 516 | | recentForumMessage.author, |
|---|
| 517 | | recentForumMessage.avatarid, |
|---|
| 518 | | recentForumMessage.modified, |
|---|
| 519 | | recentForumMessage.id, |
|---|
| 520 | | recentForumMessage.topicid |
|---|
| 521 | | |
|---|
| 522 | | FROM forum |
|---|
| 523 | | LEFT JOIN |
|---|
| 524 | | (SELECT message.*, recentForumMsg.forumid FROM |
|---|
| 525 | | message JOIN |
|---|
| 526 | | (SELECT recentTopicMessage.forumid,max(modified) AS modified FROM |
|---|
| 527 | | (SELECT topic.forumid,message.modified |
|---|
| 528 | | FROM topic |
|---|
| 529 | | JOIN message ON topic.id = message.topicid |
|---|
| 530 | | JOIN (SELECT topicid, MAX(modified) AS modified |
|---|
| 531 | | FROM message |
|---|
| 532 | | GROUP BY topicid |
|---|
| 533 | | ) recentMsg ON recentMsg.modified = message.modified AND recentMsg.topicid = message.topicid |
|---|
| 534 | | ) AS recentTopicMessage |
|---|
| 535 | | group by recentTopicMessage.forumid |
|---|
| 536 | | ) AS recentForumMsg ON message.modified = recentForumMsg.modified |
|---|
| 537 | | ) AS recentForumMessage ON recentForumMessage.forumid = forum.id |
|---|
| 538 | | |
|---|
| 539 | | LEFT JOIN topic ON topic.forumid = forum.id |
|---|
| 540 | | LEFT JOIN message ON message.topicid = topic.id |
|---|
| 541 | | |
|---|
| 542 | | LEFT JOIN |
|---|
| 543 | | (SELECT topic.forumid, COUNT(topic.id) AS total |
|---|
| 544 | | FROM topic |
|---|
| 545 | | GROUP BY topic.forumid |
|---|
| 546 | | ) AS topics ON topics.forumid = forum.id |
|---|
| 547 | | |
|---|
| 548 | | LEFT JOIN |
|---|
| 549 | | (SELECT topic.forumid, SUM(topic.views) AS total |
|---|
| 550 | | FROM topic |
|---|
| 551 | | GROUP BY topic.forumid |
|---|
| 552 | | ) AS topicViews ON topicViews.forumid = forum.id |
|---|
| 553 | | |
|---|
| 554 | | WHERE projectid=%(projectid)s """ + viewRestriction + """ |
|---|
| 555 | | |
|---|
| 556 | | GROUP BY |
|---|
| 557 | | forum.id, forum.created, forum.modified, forum.name, forum.description, forum.locked, forum.hidden, forum.categoryid, |
|---|
| 558 | | topics.total, topicViews.total, |
|---|
| 559 | | recentForumMessage.author, |
|---|
| 560 | | recentForumMessage.avatarid, |
|---|
| 561 | | recentForumMessage.modified, |
|---|
| 562 | | recentForumMessage.id, |
|---|
| 563 | | recentForumMessage.topicid |
|---|
| 564 | | |
|---|
| 565 | | ORDER BY forum.id |
|---|
| 566 | | """, |
|---|
| 567 | | { |
|---|
| 568 | | 'projectid': context.getProjectId(), |
|---|
| 569 | | 'usern |
|---|