Changeset 81
- Timestamp:
- 09/18/07 00:10:03 (1 year ago)
- Files:
-
- trunk/sql/postgres.sql (modified) (1 diff)
- trunk/todo.txt (modified) (2 diffs)
- trunk/tracforums/model.py (modified) (2 diffs)
- trunk/tracforums/models/avatar.py (modified) (5 diffs)
- trunk/tracforums/models/forum.py (modified) (1 diff)
- trunk/tracforums/models/profile.py (modified) (8 diffs)
- trunk/tracforums/models/watch.py (modified) (2 diffs)
- trunk/tracforums/orm.py (modified) (6 diffs)
- trunk/tracforums/templates/tracforums/forum/edit.cs (modified) (1 diff)
- trunk/tracforums/templates/tracforums/forum/view.cs (modified) (1 diff)
- trunk/tracforums/templates/tracforums/main/index.cs (modified) (2 diffs)
- trunk/tracforums/templates/tracforums/main/manage.cs (modified) (1 diff)
- trunk/tracforums/templates/tracforums/profile/avatars.cs (modified) (2 diffs)
- trunk/tracforums/templates/tracforums/profile/edit.cs (modified) (3 diffs)
- trunk/tracforums/templates/tracforums/profile/forums.cs (added)
- trunk/tracforums/templates/tracforums/profile/projects.cs (added)
- trunk/tracforums/templates/tracforums/profile/topics.cs (added)
- trunk/tracforums/templates/tracforums/profile/view.cs (modified) (1 diff)
- trunk/tracforums/templates/tracforums/profile/watches.cs (modified) (2 diffs)
- trunk/tracforums/templates/tracforums/topic/edit.cs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/sql/postgres.sql
r71 r81 1 -- Sequence: avatar_id_seq 2 3 DROP SEQUENCE avatar_id_seq; 1 -- 2 -- PostgreSQL database dump 3 -- 4 5 -- Started on 2007-09-16 23:41:07 Eastern Daylight Time 6 7 SET client_encoding = 'UTF8'; 8 SET check_function_bodies = false; 9 SET client_min_messages = warning; 10 11 -- 12 -- TOC entry 1612 (class 0 OID 0) 13 -- Dependencies: 4 14 -- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres 15 -- 16 17 COMMENT ON SCHEMA public IS 'Standard public schema'; 18 19 20 SET search_path = public, pg_catalog; 21 22 SET default_tablespace = ''; 23 24 SET default_with_oids = false; 25 26 -- 27 -- TOC entry 1233 (class 1259 OID 20723) 28 -- Dependencies: 1592 4 29 -- Name: avatar; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 30 -- 31 32 CREATE TABLE avatar ( 33 username character varying(32), 34 mimetype character varying(256), 35 name text, 36 id integer DEFAULT nextval(('avatar_id_seq'::text)::regclass) 37 ); 38 39 40 ALTER TABLE public.avatar OWNER TO postgres; 41 42 -- 43 -- TOC entry 1234 (class 1259 OID 20729) 44 -- Dependencies: 4 45 -- Name: avatar_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres 46 -- 4 47 5 48 CREATE SEQUENCE avatar_id_seq 6 INCREMENT 1 7 MINVALUE 1 8 MAXVALUE 9223372036854775807 9 START 1 10 CACHE 1; 11 12 -- Sequence: forum_id_seq 13 14 DROP SEQUENCE forum_id_seq; 49 INCREMENT BY 1 50 NO MAXVALUE 51 NO MINVALUE 52 CACHE 1; 53 54 55 ALTER TABLE public.avatar_id_seq OWNER TO postgres; 56 57 -- 58 -- TOC entry 1228 (class 1259 OID 20679) 59 -- Dependencies: 1569 1570 1571 1572 4 60 -- Name: forum; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 61 -- 62 63 CREATE TABLE forum ( 64 id integer DEFAULT nextval(('forum_id_seq'::text)::regclass) NOT NULL, 65 name text, 66 created integer, 67 modified integer, 68 description text, 69 projectid character varying(32), 70 locked boolean DEFAULT false, 71 hidden boolean DEFAULT false, 72 categoryid integer DEFAULT 0, 73 rank serial NOT NULL 74 ); 75 76 77 ALTER TABLE public.forum OWNER TO postgres; 78 79 -- 80 -- TOC entry 1243 (class 1259 OID 1115442) 81 -- Dependencies: 4 82 -- Name: forum_category; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 83 -- 84 85 CREATE TABLE forum_category ( 86 id serial NOT NULL, 87 projectid character varying(32), 88 description text, 89 rank serial NOT NULL 90 ); 91 92 93 ALTER TABLE public.forum_category OWNER TO postgres; 94 95 -- 96 -- TOC entry 1226 (class 1259 OID 20675) 97 -- Dependencies: 4 98 -- Name: forum_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres 99 -- 15 100 16 101 CREATE SEQUENCE forum_id_seq 17 INCREMENT 1 18 MINVALUE 1 19 MAXVALUE 9223372036854775807 20 START 1 21 CACHE 1; 22 23 -- Sequence: message_id_seq 24 25 DROP SEQUENCE message_id_seq; 102 INCREMENT BY 1 103 NO MAXVALUE 104 NO MINVALUE 105 CACHE 1; 106 107 108 ALTER TABLE public.forum_id_seq OWNER TO postgres; 109 110 -- 111 -- TOC entry 1237 (class 1259 OID 1115372) 112 -- Dependencies: 4 113 -- Name: forum_watch; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 114 -- 115 116 CREATE TABLE forum_watch ( 117 id serial NOT NULL, 118 forumid integer, 119 username text 120 ); 121 122 123 ALTER TABLE public.forum_watch OWNER TO postgres; 124 125 -- 126 -- TOC entry 1229 (class 1259 OID 20689) 127 -- Dependencies: 1574 1575 1576 1577 4 128 -- Name: message; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 129 -- 130 131 CREATE TABLE message ( 132 id integer DEFAULT nextval(('message_id_seq'::text)::regclass) NOT NULL, 133 topicid integer, 134 created integer, 135 modified integer DEFAULT 0, 136 author text, 137 body text, 138 avatarid integer DEFAULT 0, 139 modifiedby character varying(32), 140 modcount integer DEFAULT 0 141 ); 142 143 144 ALTER TABLE public.message OWNER TO postgres; 145 146 -- 147 -- TOC entry 1225 (class 1259 OID 20673) 148 -- Dependencies: 4 149 -- Name: message_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres 150 -- 26 151 27 152 CREATE SEQUENCE message_id_seq 28 INCREMENT 1 29 MINVALUE 1 30 MAXVALUE 9223372036854775807 31 START 1 32 CACHE 1; 33 34 -- Sequence: profile_id_seq 35 36 37 DROP SEQUENCE profile_id_seq; 153 INCREMENT BY 1 154 NO MAXVALUE 155 NO MINVALUE 156 CACHE 1; 157 158 159 ALTER TABLE public.message_id_seq OWNER TO postgres; 160 161 -- 162 -- TOC entry 1232 (class 1259 OID 20719) 163 -- Dependencies: 4 164 -- Name: moderators; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 165 -- 166 167 CREATE TABLE moderators ( 168 forumid integer NOT NULL, 169 username character varying(25) NOT NULL 170 ); 171 172 173 ALTER TABLE public.moderators OWNER TO postgres; 174 175 -- 176 -- TOC entry 1231 (class 1259 OID 20705) 177 -- Dependencies: 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 4 178 -- Name: profile; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 179 -- 180 181 CREATE TABLE profile ( 182 id integer DEFAULT nextval(('profile_id_seq'::text)::regclass) NOT NULL, 183 sig text DEFAULT ''::text, 184 username character varying(25) NOT NULL, 185 email character varying(255), 186 regdate integer DEFAULT 0 NOT NULL, 187 lastvisit integer DEFAULT 0 NOT NULL, 188 defaultavatarid integer DEFAULT 0, 189 bio text, 190 logindate integer DEFAULT 0, 191 timezone character varying(8), 192 viewemail boolean DEFAULT true, 193 isactive boolean DEFAULT false, 194 isexpert boolean DEFAULT false, 195 posts integer DEFAULT 0 196 ); 197 198 199 ALTER TABLE public.profile OWNER TO postgres; 200 201 -- 202 -- TOC entry 1227 (class 1259 OID 20677) 203 -- Dependencies: 4 204 -- Name: profile_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres 205 -- 38 206 39 207 CREATE SEQUENCE profile_id_seq 40 INCREMENT 1 41 MINVALUE 1 42 MAXVALUE 9223372036854775807 43 START 1 44 CACHE 1; 45 46 47 -- Sequence: topic_id_seq 48 49 DROP SEQUENCE topic_id_seq; 208 INCREMENT BY 1 209 NO MAXVALUE 210 NO MINVALUE 211 CACHE 1; 212 213 214 ALTER TABLE public.profile_id_seq OWNER TO postgres; 215 216 -- 217 -- TOC entry 1235 (class 1259 OID 1106501) 218 -- Dependencies: 1593 4 219 -- Name: project; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 220 -- 221 222 CREATE TABLE project ( 223 id character varying(32) NOT NULL, 224 name text NOT NULL, 225 active boolean DEFAULT false NOT NULL 226 ); 227 228 229 ALTER TABLE public.project OWNER TO postgres; 230 231 -- 232 -- TOC entry 1241 (class 1259 OID 1115394) 233 -- Dependencies: 4 234 -- Name: project_watch; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 235 -- 236 237 CREATE TABLE project_watch ( 238 id serial NOT NULL, 239 username text, 240 projectid character varying(32) 241 ); 242 243 244 ALTER TABLE public.project_watch OWNER TO postgres; 245 246 -- 247 -- TOC entry 1230 (class 1259 OID 20697) 248 -- Dependencies: 1578 1579 1580 1581 4 249 -- Name: topic; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 250 -- 251 252 CREATE TABLE topic ( 253 id integer DEFAULT nextval(('topic_id_seq'::text)::regclass) NOT NULL, 254 forumid integer, 255 subject text, 256 leadmessageid integer DEFAULT 0, 257 views integer DEFAULT 0, 258 "type" character varying(16) DEFAULT ''::character varying 259 ); 260 261 262 ALTER TABLE public.topic OWNER TO postgres; 263 264 -- 265 -- TOC entry 1224 (class 1259 OID 20671) 266 -- Dependencies: 4 267 -- Name: topic_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres 268 -- 50 269 51 270 CREATE SEQUENCE topic_id_seq 52 INCREMENT 1 53 MINVALUE 1 54 MAXVALUE 9223372036854775807 55 START 1 56 CACHE 1; 57 58 -- Table: avatar 59 60 DROP TABLE avatar; 61 62 CREATE TABLE avatar 63 ( 64 username varchar(32), 65 mimetype varchar(256), 66 name text, 67 id int4 DEFAULT nextval(('avatar_id_seq'::text)::regclass) 68 ) 69 WITHOUT OIDS; 70 71 -- Table: forum 72 73 DROP TABLE forum; 74 75 CREATE TABLE forum 76 ( 77 id int4 NOT NULL DEFAULT nextval(('forum_id_seq'::text)::regclass), 78 name text, 79 created int4, 80 modified int4, 81 description text, 82 projectid varchar(32), 83 locked bool DEFAULT false, 84 hidden bool DEFAULT false, 85 categoryid int4 DEFAULT 0, 86 CONSTRAINT forum_pkey PRIMARY KEY (id), 87 CONSTRAINT forum_id_key UNIQUE (id, name) 88 ) 89 WITHOUT OIDS; 90 91 -- Table: message 92 93 DROP TABLE message; 94 95 CREATE TABLE message 96 ( 97 id int4 NOT NULL DEFAULT nextval(('message_id_seq'::text)::regclass), 98 topicid int4, 99 created int4, 100 modified int4 DEFAULT 0, 101 author text, 102 body text, 103 avatarid int4 DEFAULT 0, 104 modifiedby varchar(32), 105 modcount int4 DEFAULT 0, 106 CONSTRAINT message_pkey PRIMARY KEY (id) 107 ) 108 WITHOUT OIDS; 109 110 -- Table: moderators 111 112 DROP TABLE moderators; 113 114 CREATE TABLE moderators 115 ( 116 forumid int4 NOT NULL, 117 username varchar(25) NOT NULL 118 ) 119 WITHOUT OIDS; 120 121 -- Table: profile 122 123 124 DROP TABLE profile; 125 126 CREATE TABLE profile 127 ( 128 id int4 NOT NULL DEFAULT nextval(('profile_id_seq'::text)::regclass), 129 sig text DEFAULT ''::text, 130 username varchar(25) NOT NULL, 131 email varchar(255), 132 regdate int4 NOT NULL DEFAULT 0, 133 lastvisit int4 NOT NULL DEFAULT 0, 134 defaultavatarid int4 DEFAULT 0, 135 bio text, 136 logindate int4 DEFAULT 0, 137 timezone varchar(8), 138 viewemail bool DEFAULT true, 139 isactive bool DEFAULT false, 140 isexpert bool DEFAULT false, 141 posts int4 DEFAULT 0 142 ) 143 WITHOUT OIDS; 144 145 146 -- Table: topic 147 148 DROP TABLE topic; 149 150 CREATE TABLE topic 151 ( 152 id int4 NOT NULL DEFAULT nextval(('topic_id_seq'::text)::regclass), 153 forumid int4, 154 subject text, 155 leadmessageid int4 DEFAULT 0, 156 views int4 DEFAULT 0, 157 "type" varchar(16) DEFAULT ''::character varying, 158 CONSTRAINT topic_pkey PRIMARY KEY (id) 159 ) 160 WITHOUT OIDS; 161 162 -- Table: project 163 164 DROP TABLE project; 165 166 CREATE TABLE project 167 ( 168 id varchar(32) NOT NULL, 169 name text NOT NULL, 170 active bool NOT NULL DEFAULT false 171 ) 172 WITHOUT OIDS; 173 174 -- Table: forum_watch 175 176 -- DROP TABLE forum_watch; 177 178 CREATE TABLE forum_watch 179 ( 180 id serial NOT NULL, 181 forum_id int4, 182 username text 183 ) 184 WITHOUT OIDS; 185 ALTER TABLE forum_watch OWNER TO postgres; 186 187 -- Table: topic_watch 188 189 -- DROP TABLE topic_watch; 190 191 CREATE TABLE topic_watch 192 ( 193 id serial NOT NULL, 194 topicid int4, 195 username text 196 ) 197 WITHOUT OIDS; 198 ALTER TABLE topic_watch OWNER TO postgres; 199 200 -- Table: project_watch 201 202 -- DROP TABLE project_watch; 203 204 CREATE TABLE project_watch 205 ( 206 id serial NOT NULL, 207 username text, 208 projectid varchar(32) 209 ) 210 WITHOUT OIDS; 211 ALTER TABLE project_watch OWNER TO postgres; 212 213 -- Table: forum_category 214 215 -- DROP TABLE forum_category; 216 217 CREATE TABLE forum_category 218 ( 219 id serial NOT NULL, 220 projectid varchar(32), 221 description text, 222 sortorder int4 DEFAULT 0 223 ) 224 WITHOUT OIDS; 225 ALTER TABLE forum_category OWNER TO postgres; 271 INCREMENT BY 1 272 NO MAXVALUE 273 NO MINVALUE 274 CACHE 1; 275 276 277 ALTER TABLE public.topic_id_seq OWNER TO postgres; 278 279 -- 280 -- TOC entry 1239 (class 1259 OID 1115386) 281 -- Dependencies: 4 282 -- Name: topic_watch; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 283 -- 284 285 CREATE TABLE topic_watch ( 286 id serial NOT NULL, 287 topicid integer, 288 username text 289 ); 290 291 292 ALTER TABLE public.topic_watch OWNER TO postgres; 293 294 -- 295 -- TOC entry 1247 (class 1259 OID 1115554) 296 -- Dependencies: 4 297 -- Name: touched_topic; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 298 -- 299 300 CREATE TABLE touched_topic ( 301 id serial NOT NULL, 302 username character varying(32), 303 topicid integer 304 ); 305 306 307 ALTER TABLE public.touched_topic OWNER TO postgres; 308 309 -- 310 -- TOC entry 1222 (class 1259 OID 20364) 311 -- Dependencies: 4 312 -- Name: trac_cookies; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 313 -- 314 315 CREATE TABLE trac_cookies ( 316 envname text NOT NULL, 317 cookie text NOT NULL, 318 username text NOT NULL, 319 ipnr text NOT NULL, 320 unixtime integer NOT NULL 321 ); 322 323 324 ALTER TABLE public.trac_cookies OWNER TO postgres; 325 326 -- 327 -- TOC entry 1223 (class 1259 OID 20369) 328 -- Dependencies: 4 329 -- Name: trac_permissions; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 330 -- 331 332 CREATE TABLE trac_permissions ( 333 envname text NOT NULL, 334 username text NOT NULL, 335 groupname text NOT NULL 336 ); 337 338 339 ALTER TABLE public.trac_permissions OWNER TO postgres; 340 341 -- 342 -- TOC entry 1221 (class 1259 OID 20357) 343 -- Dependencies: 4 344 -- Name: trac_users; Type: TABLE; Schema: public; Owner: postgres; Tablespace: 345 -- 346 347 CREATE TABLE trac_users ( 348 envname text NOT NULL, 349 username text NOT NULL, 350 "password" text NOT NULL, 351 email text 352 ); 353 354 355 ALTER TABLE public.trac_users OWNER TO postgres; 356 357 -- 358 -- TOC entry 1603 (class 2606 OID 20688) 359 -- Dependencies: 1228 1228 1228 360 -- Name: forum_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: 361 -- 362 363 ALTER TABLE ONLY forum 364 ADD CONSTRAINT forum_id_key UNIQUE (id, name); 365 366 367 -- 368 -- TOC entry 1605 (class 2606 OID 20686) 369 -- Dependencies: 1228 1228 370 -- Name: forum_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: 371 -- 372 373 ALTER TABLE ONLY forum 374 ADD CONSTRAINT forum_pkey PRIMARY KEY (id); 375 376 377 -- 378 -- TOC entry 1608 (class 2606 OID 20696) 379 -- Dependencies: 1229 1229 380 -- Name: message_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: 381 -- 382 383 ALTER TABLE ONLY message 384 ADD CONSTRAINT message_pkey PRIMARY KEY (id); 385 386 387 -- 388 -- TOC entry 1610 (class 2606 OID 20704) 389 -- Dependencies: 1230 1230 390 -- Name: topic_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: 391 -- 392 393 ALTER TABLE ONLY topic 394 ADD CONSTRAINT topic_pkey PRIMARY KEY (id); 395 396 397 -- 398 -- TOC entry 1601 (class 2606 OID 20363) 399 -- Dependencies: 1221 1221 1221 400 -- Name: trac_users_envname_key; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace: 401 -- 402 403 ALTER TABLE ONLY trac_users 404 ADD CONSTRAINT trac_users_envname_key UNIQUE (envname, username); 405 406 407 -- 408 -- TOC entry 1606 (class 1259 OID 1097439) 409 -- Dependencies: 1229 410 -- Name: message_modified_idx; Type: INDEX; Schema: public; Owner: postgres; Tablespace: 411 -- 412 413 CREATE INDEX message_modified_idx ON message USING btree (modified); 414 415 416 -- 417 -- TOC entry 1613 (class 0 OID 0) 418 -- Dependencies: 4 419 -- Name: public; Type: ACL; Schema: -; Owner: postgres 420 -- 421 422 REVOKE ALL ON SCHEMA public FROM PUBLIC; 423 REVOKE ALL ON SCHEMA public FROM postgres; 424 GRANT ALL ON SCHEMA public TO postgres; 425 GRANT ALL ON SCHEMA public TO PUBLIC; 426 427 428 -- Completed on 2007-09-16 23:41:08 Eastern Daylight Time 429 430 -- 431 -- PostgreSQL database dump complete 432 -- 433 trunk/todo.txt
r80 r81 9 9 * Document code 10 10 * Complete console interface (?) 11 * Publish updated database schema 11 12 Macros: 12 13 * Blog-style reports and displays … … 16 17 * Potential security flaws in selection/move/delete interface 17 18 * clamp "forum get" security to current project for non trac-admins, system wide 18 Refactor:19 * reduce SQL redundancy in models20 * reduce repeated code blocks in models21 19 Wishlist: 22 20 * RSS trunk/tracforums/model.py
r80 r81 139 139 def get(self,name,defaultValue=None): 140 140 val = dict.get(self,name,defaultValue) 141 if not val: return defaultValue141 if val == None: return defaultValue 142 142 return val 143 143 … … 219 219 def getProfilePath(self): 220 220 return self.env.config.get("tracforums","profilepath",'/') 221 221 222 def getAvatarMetrics(self): 223 return { 224 "number": self.env.config.get("tracforums","max_avatars",10), 225 "size": self.env.config.get("tracforums","max_avatar_bytes",15000), 226 "width": self.env.config.get("tracforums","max_avatar_width",100), 227 "height": self.env.config.get("tracforums","max_avatar_height",100) 228 } 229 222 230 def forumRedirect(self,path,suffix=""): 223 231 self.db.commit() trunk/tracforums/models/avatar.py
r73 r81 10 10 Avatar 11 11 """ 12 AvatarModel =ORMSchema(13 tablename = "avatar",14 columns = {15 "id": ORMKey(type='int', auto_increment = True),12 class AvatarModel(ORMSchema( 13 tablename = "avatar", 14 columns = { 15 "id": ORMKey(type='int', auto_increment = True), 16 16 "username": ORMColumn(), 17 17 "mimetype": ORMColumn(), … … 19 19 "avatarid": ORMAlias(sql="id"), 20 20 } 21 ) 22 23 class AvatarController(Controller): 21 )): 24 22 mimeMap = { 25 'bmp': 'image/bmp',23 'bmp': 'image/bmp', 26 24 'cod': 'image/cis-cod', 27 'gif': 'image/gif', 25 'gif': 'image/gif', 28 26 'ief': 'image/ief', 29 'jpe': 'image/jpeg', 27 'jpe': 'image/jpeg', 30 28 'jpeg': 'image/jpeg', 31 29 'jpg': 'image/jpeg', 32 'jfif': 'image/pipeg',33 'svg': 'image/svg+xml', 30 'jfif': 'image/pipeg', 31 'svg': 'image/svg+xml', 34 32 'tif': 'image/tiff', 35 33 'tiff': 'image/tiff', … … 38 36 'ico': 'image/x-icon', 39 37 'png': 'iimage/png', 40 'pnm': 'image/x-portable-anymap',41 'pbm': 'image/x-portable-bitmap',42 'pgm': 'image/x-portable-graymap',43 'ppm': 'image/x-portable-pixmap', 38 'pnm': 'image/x-portable-anymap', 39 'pbm': 'image/x-portable-bitmap', 40 'pgm': 'image/x-portable-graymap', 41 'ppm': 'image/x-portable-pixmap', 44 42 'rgb': 'image/x-rgb', 45 43 'xbm': 'image/x-xbitmap', 46 44 'xpm': 'image/x-xpixmap', 47 'xwd': 'image/x-xwindowdump'45 'xwd': 'image/x-xwindowdump' 48 46 } 49 47 48 def save(self,data={}): 49 self.getBase().save(self,data) 50 51 if "filedata" in self.__dict__: 52 #get the mime-type by file extension 53 suffix = self.filedata.filename.split('.')[-1] 54 self.mimetype = self.mimeMap[suffix] 55 56 # store the file 57 flags = os.O_CREAT + os.O_WRONLY + os.O_EXCL 58 if hasattr(os, 'O_BINARY'): 59 flags += os.O_BINARY 60 61 targetPath = self.getTargetPath() 62 try: # Q&D way to clear out the file if it's already there 63 os.remove(targetPath) 64 except Exception,e: pass 65 66 targetFile = os.fdopen(os.open(targetPath, flags), 'w') 67 68 shutil.copyfileobj(self.filedata.file,targetFile) 69 targetFile.close() 70 71 # save again - forces the ORM to update this record with the mimetype 72 self.getBase().save(self) 73 74 def delete(self,data={}): 75 self.getBase().delete(self,data) 76 77 # reset default any associated profile avatar ids if set 78 from tracforums.models.profile import ProfileModel 79 ProfileModel(self.db,self.formatContext).updateMany({"defaultavatarid":self.id},{"defaultavatarid":0}) 80 81 from tracforums.models.message import MessageModel 82 ProfileModel(self.db,self.formatContext).updateMany({"avatarid":self.id},{"avatarid":0}) 83 84 # remove avatar image 85 os.remove(self.getTargetPath()) 86 87 def validate(self): 88 reasons = [] 89 90 if self.username == None or self.username == "": 91 reasons.append("The avatar username is required.") 92 93 # check if the file has been uploaded 94 if "filedata" in self.__dict__: 95 print "FILE:",self.filedata 96 metrics = self.formatContext.getAvatarMetrics() 97 98 if not hasattr(self.filedata, 'filename') or not self.filedata.filename: 99 reasons.append("No file uploaded.") 100 return reasons 101 # check the file size 102 103 if hasattr(self.filedata.file, 'fileno'): 104 size = os.fstat(self.filedata.file.fileno())[6] 105 else: 106 size = self.filedata.file.len 107 108 if size == 0: 109 reasons.append("Can't upload empty file") 110 return reasons 111 elif size >= metrics["size"]: 112 reasons.append("File size cannot exceed %s bytes" % metrics["size"]) 113 return reasons 114 115 # check the mime type 116 suffix = self.filedata.filename.split('.')[-1] 117 if not (suffix in self.mimeMap): 118 reasons.append("Unrecognized file type: '" + suffix + "'") 119 return reasons 120 121 # check the file dimensions (via PIL) 122 import Image 123 124 img = Image.open(self.filedata.file) 125 (width,height) = img.size 126 if height > metrics["height"] or width > metrics["width"]: 127 reasons.append("File exceeds allowed width and or height (%spx high by %spx wide )" % (metrics["height"],metrics["width"])) 128 129 self.filedata.file.seek(0) 130 return reasons 131 132 def validateCreate(self): 133 reasons = self.validate() 134 135 totalAvatars = len(self.getColumnValues("id")) 136 metrics = self.formatContext.getAvatarMetrics() 137 if totalAvatars > metrics["number"]: 138 reasons.append("This user already has the maximum number of allowed avatars (%s)." % metrics.number) 139 140 return reasons 141 142 def validateSave(self): 143 reasons = self.validate() 144 return reasons 145 146 def getTargetPath(self): 147 # get the target path 148 targetPath = os.path.join(self.formatContext.getProfilePath(),'avatar') 149 150 # make the directory if it's not there 151 if not os.access(targetPath, os.F_OK): 152 os.makedirs(targetPath) 153 154 # extend the path to point to the correct file 155 targetPath = os.path.join(targetPath,str(self.id) + ".data") 156 157 return targetPath 158 159 AvatarModelWithDetails = ORMSchema( 160 base = AvatarModel, 161 columns = { 162 "usedcount" : ORMAlias(sql=""" 163 select count(m.id) 164 from message as m 165 where m.avatarid = avatar.id 166 """), 167 "recentpostid" : ORMAlias(sql=""" 168 select max(m.id) 169 from message as m 170 where m.avatarid = avatar.id 171 """), 172 "recentpost": ORMJoin( 173 model = ORMImportModel("tracforums.models.message","MessageModel"), 174 relationship = {"recentpostid":"messageid"} 175 ) 176 } 177 ) 178 179 class AvatarController(Controller): 50 180 def __init__(self,req,env,db): 51 181 Controller.__init__(self,req,env,db) … … 54 184 args = self.getArgs() 55 185 avatar = AvatarModel(self.db,self).load({"id":args["viewid"]}) 56 57 # get the target path 58 targetPath = os.path.join(args["profilepath"],'avatar') 59 60 # make the directory if it's not there 61 if not os.access(targetPath, os.F_OK): 62 os.makedirs(targetPath) 63 64 # extend the path to point to the correct file 65 targetPath = os.path.join(targetPath,str(avatar.id) + ".data") 66 67 # render 68 self.renderAsFile(targetPath,avatar.mimetype) 186 187 self.renderAsFile(avatar.getTargetPath(),avatar.mimetype) 69 188 return "", None 70 189 … … 73 192 "default": doView 74 193 } 75 76 ####################################################77 78 79 class Avatar(ISecurityContext,IModel,IUIModel):80 def __init__(self,db):81 self.db = db82 self.instance = {}83 84 def _getTargetPath(self):85 # get the target path86 targetPath = os.path.join(self.instance["profilepath"],'avatar')87 88 # make the directory if it's not there89 if not os.access(targetPath, os.F_OK):90 os.makedirs(targetPath)91 92 # extend the path to point to the correct file93 targetPath = os.path.join(targetPath,str(self.instance["id"]) + ".data")94 return targetPath95 96 mimeMap = {97 'bmp': 'image/bmp',98 'cod': 'image/cis-cod',99 'gif': 'image/gif',100 'ief': 'image/ief',101 'jpe': 'image/jpeg',102 'jpeg': 'image/jpeg',103 'jpg': 'image/jpeg',104 'jfif': 'image/pipeg',105 'svg': 'image/svg+xml',106 'tif': 'image/tiff',107 'tiff': 'image/tiff',108 'ras': 'image/x-cmu-raster',109 'cmx': 'image/x-cmx',110 'ico': 'image/x-icon',111 'png': 'iimage/png',112 'pnm': 'image/x-portable-anymap',113 'pbm': 'image/x-portable-bitmap',114 'pgm': 'image/x-portable-graymap',115 'ppm': 'image/x-portable-pixmap',116 'rgb': 'image/x-rgb',117 'xbm': 'image/x-xbitmap',118 'xpm': 'image/x-xpixmap',119 'xwd': 'image/x-xwindowdump'120 }121 122 ### IUIModel Methods ###123 124 def _getAssets(self,context):125 assets = {}126 self.instance['filedata'] = '' # strike filedata from display set127 assets['avatar'] = self.instance128 return assets129 130 def getViewTemplate(self,context):131 context.renderAsFile(self._getTargetPath(),self.instance["mimetype"])132 return "", None133 134 def getEditTemplate(self,context):135 return "avatar/edit.cs",self._getAssets(context)136 137 def getSaveTemplate(self,context):138 from tracforums.models.profile import Profile139 profile = Profile(self.db).getById(context.getAuthname(),context)140 141 if profile['isexpert']:142 context.forumRedirect("/profile/" + str(self.instance['username']));143 else:144 return "avatar/save.cs",self._getAssets(context)145 146 def getNewTemplate(self,context):147 return self.getEditTemplate(context)148 149 def getCreateTemplate(self,context):150 return self.getSaveTemplate(context)151 152 def getDeletedTemplate(self,context):153 return self.getSaveTemplate(context)154 155 ### ISecurityContext Methods ###156 157 def canView(self,context):158 return context.isForumGuest()159 160 def canCreate(self,context):161 return context.isForumUser()162 163 def canModify(self,context):164 if context.isForumAdmin():165 return True166 else:167 return context.getAuthname() == self.instance['username']168 169 def canDelete(self,context):170 if context.isForumAdmin():171 return True172 else:173 return context.getAuthname() == self.instance['username']174 175 ### IModel Methods ###176 177 def load(self,context):178 args = context.getArgs()179 if "id" in args:180 id = args["id"]181 else:182 raise TracError("cannot load profile avatar")183 184 # load instance (if possible)185 cursor = self.db.cursor()186 columns = ('id','username','mimetype','name')187 cursor.execute(188 "SELECT id, username, mimetype, name "189 "FROM avatar WHERE id = %s",190 (id,))191 row = cursor.fetchone()192 self.instance = dict(zip(columns, row))193 self.instance["profilepath"] = args["profilepath"]194 195 def set(self,context):196 args = context.getArgs()197 # populate instance with changes and fresh values198 self.instance = {199 'id': self.getDefault((args,self.instance),'id',''),200 'username': self.getDefault((args,self.instance),'username',''),201 'mimetype': self.getDefault((args,self.instance),'mimetype',''),202 'name': self.getDefault((args,self.instance),'name',''),203 'profilepath': args["profilepath"],204 'filedata': self.getDefault((args,self.instance),'filedata',{})205 }206 207 def _saveAvatarImage(self,context):208 # store the file209 flags = os.O_CREAT + os.O_WRONLY + os.O_EXCL210 if hasattr(os, 'O_BINARY'):211 flags += os.O_BINARY212 213 targetPath = self._getTargetPath()214 targetFile = os.fdopen(os.open(targetPath, flags), 'w')215 216 shutil.copyfileobj(self.instance['filedata'].file,targetFile)217 targetFile.close()218 219 def _setMimeType(self,context):220 #get the mime-type by file extension221 filedata = self.instance['filedata']222 suffix = filedata.filename.split('.')[-1]223 self.instance['mimetype'] = self.mimeMap[suffix]224 225 def create(self,context):226 self._setMimeType(context)227 228 cursor = self.db.cursor()229 cursor.execute(230 "INSERT INTO avatar (username,mimetype,name) "231 "VALUES (%(username)s, %(mimetype)s, %(name)s)",232 self.instance)233 self.instance["id"] = self.db.get_last_id(cursor, 'avatar')234 235 self._saveAvatarImage(context)236 237 def save(self,context):238 # do file stuff if the file was changed239 filedata = self.instance["filedata"]240 if not hasattr(filedata, 'filename') or not filedata.filename:241 self._saveAvatarImage(context)242 self._setMimeType(context)243 244 # update database245 cursor = self.db.cursor()246 cursor.execute(247 "UPDATE avatar "248 "SET name=%(name)s, mimetype=%(mimetype)s "249 "WHERE id=%(id)s ",250 self.instance)251 252 def delete(self,context):253 cursor = self.db.cursor()254 cursor.execute(255 "DELETE FROM avatar WHERE id=%(id)s",256 self.instance)257 258 # reset default profile avatar id if set259 cursor.execute(260 "UPDATE profile SET defaultavatarid = 0 WHERE profile.defaultavatarid = %(id)s",261 self.instance)262 263 # remove avatar image264 os.remove(self._getTargetPath())265 266 def _validate(self,context):267 reasons = []268 269 if self.instance['username'] == "":270 reasons.append("The avatar username is required.")271 272 if self.instance['name'] == "":273 reasons.append("The avatar name is required.")274 275 return reasons276 277 def validateCreate(self,context):278 reasons = self._validate(context)279 cursor = self.db.cursor()280 281 cursor.execute("SELECT count(id) FROM avatar WHERE username=%(username)s",self.instance)282 row = cursor.fetchone()283 if row and row[0] > 10: #TODO: use a configurable value284 reasons.append("This user already has the maximum number of allowed avatars.")285 return reasons286 287 # check if the file has been uploaded288 filedata = self.instance["filedata"]289 290 if not hasattr(filedata, 'filename') or not filedata.filename:291 reasons.append("No file uploaded")292 return reasons293
