1 #!/usr/bin/env python 2 3 """ 4 Django classes. 5 6 Copyright (C) 2006 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 21 """ 22 23 import WebStack.Generic 24 from WebStack.Helpers import Environment 25 from WebStack.Helpers.Request import decode_value, FileContent, Cookie 26 from WebStack.Helpers.Response import ConvertingStream 27 from django.http import HttpResponse 28 from StringIO import StringIO 29 30 class Transaction(WebStack.Generic.Transaction): 31 32 """ 33 Django transaction interface. 34 """ 35 36 def __init__(self, request): 37 38 """ 39 Initialise the transaction with the Django 'request' object. 40 """ 41 42 self.request = request 43 44 # Attributes which may be changed later. 45 46 self.content_type = None 47 48 # The response is created here but must be modified later. 49 # NOTE: It is unfortunate that Django wants to initialise the response 50 # NOTE: with the content type immediately. 51 52 self.response = HttpResponse() 53 self.content = StringIO() 54 55 def commit(self): 56 57 "Commit the transaction by finishing some things off." 58 59 self.content.seek(0) 60 self.response.content = self.content.read() 61 62 # Server-related methods. 63 64 def get_server_name(self): 65 66 "Returns the server name." 67 68 return self.request.META.get("SERVER_NAME") 69 70 def get_server_port(self): 71 72 "Returns the server port as a string." 73 74 return self.request.META.get("SERVER_PORT") 75 76 # Request-related methods. 77 78 def get_request_stream(self): 79 80 """ 81 Returns the request stream for the transaction. 82 """ 83 84 # Unfortunately, we get given a string from Django. Thus, we need to 85 # create a stream around that string. 86 87 return StringIO(self.request.raw_post_data) 88 89 def get_request_method(self): 90 91 """ 92 Returns the request method. 93 """ 94 95 return self.request.META.get("REQUEST_METHOD") 96 97 def get_headers(self): 98 99 """ 100 Returns all request headers as a dictionary-like object mapping header 101 names to values. 102 """ 103 104 return Environment.get_headers(self.request.META) 105 106 def get_header_values(self, key): 107 108 """ 109 Returns a list of all request header values associated with the given 110 'key'. Note that according to RFC 2616, 'key' is treated as a 111 case-insensitive string. 112 """ 113 114 return self.convert_to_list(self.get_headers().get(key)) 115 116 def get_content_type(self): 117 118 """ 119 Returns the content type specified on the request, along with the 120 charset employed. 121 """ 122 123 return self.parse_content_type(self.request.META.get("CONTENT_TYPE")) 124 125 def get_content_charsets(self): 126 127 """ 128 Returns the character set preferences. 129 130 NOTE: Not decently supported. 131 """ 132 133 return self.parse_content_preferences(None) 134 135 def get_content_languages(self): 136 137 """ 138 Returns extracted language information from the transaction. 139 140 NOTE: Not decently supported. 141 """ 142 143 return self.parse_content_preferences(None) 144 145 def get_path(self, encoding=None): 146 147 """ 148 Returns the entire path from the request as a Unicode object. Any "URL 149 encoded" character values in the part of the path before the query 150 string will be decoded and presented as genuine characters; the query 151 string will remain "URL encoded", however. 152 153 If the optional 'encoding' is set, use that in preference to the default 154 encoding to convert the path into a form not containing "URL encoded" 155 character values. 156 """ 157 158 return decode_value(self.request.get_full_path(), encoding) 159 160 def get_path_without_query(self, encoding=None): 161 162 """ 163 Returns the entire path from the request minus the query string as a 164 Unicode object containing genuine characters (as opposed to "URL 165 encoded" character values). 166 167 If the optional 'encoding' is set, use that in preference to the default 168 encoding to convert the path into a form not containing "URL encoded" 169 character values. 170 """ 171 172 path = self.get_path(encoding) 173 return path.split("?")[0] 174 175 def get_path_info(self, encoding=None): 176 177 """ 178 Returns the "path info" (the part of the URL after the resource name 179 handling the current request) from the request as a Unicode object 180 containing genuine characters (as opposed to "URL encoded" character 181 values). 182 183 If the optional 'encoding' is set, use that in preference to the default 184 encoding to convert the path into a form not containing "URL encoded" 185 character values. 186 """ 187 188 path_info = self.request.META.get("PATH_INFO") or "" 189 return decode_value(path_info, encoding) 190 191 def get_query_string(self): 192 193 """ 194 Returns the query string from the path in the request. 195 """ 196 197 return self.request.META.get("QUERY_STRING") or "" 198 199 # Higher level request-related methods. 200 201 def get_fields_from_path(self, encoding=None): 202 203 """ 204 Extracts fields (or request parameters) from the path specified in the 205 transaction. The underlying framework may refuse to supply fields from 206 the path if handling a POST transaction. The optional 'encoding' 207 parameter specifies the character encoding of the query string for cases 208 where the default encoding is to be overridden. 209 210 Returns a dictionary mapping field names to lists of values (even if a 211 single value is associated with any given field name). 212 """ 213 214 return self._get_fields(self.request.GET, encoding) 215 216 def get_fields_from_body(self, encoding=None): 217 218 """ 219 Extracts fields (or request parameters) from the message body in the 220 transaction. The optional 'encoding' parameter specifies the character 221 encoding of the message body for cases where no such information is 222 available, but where the default encoding is to be overridden. 223 224 Returns a dictionary mapping field names to lists of values (even if a 225 single value is associated with any given field name). Each value is 226 either a Unicode object (representing a simple form field, for example) 227 or a WebStack.Helpers.Request.FileContent object (representing a file 228 upload form field). 229 """ 230 231 fields = {} 232 self._update_fields(fields, self._get_fields(self.request.POST, encoding)) 233 self._update_fields(fields, self._get_files()) 234 return fields 235 236 def _get_fields(self, source, encoding=None): 237 fields = {} 238 for name in source.keys(): 239 name = decode_value(name, encoding) 240 fields[name] = [] 241 for value in source.getlist(name): 242 value = decode_value(value, encoding) 243 fields[name].append(value) 244 return fields 245 246 def _get_files(self): 247 files = {} 248 for name, file in self.request.FILES.items(): 249 files[name] = [FileContent(file.get("content", ""), { 250 "Content-Type" : file.get("content-type", ""), 251 "Content-Disposition" : "%s; filename=%s" % (name, file.get("filename", "")) 252 })] 253 return files 254 255 def get_fields(self, encoding=None): 256 257 """ 258 Extracts fields (or request parameters) from both the path specified in 259 the transaction as well as the message body. The optional 'encoding' 260 parameter specifies the character encoding of the message body for cases 261 where no such information is available, but where the default encoding 262 is to be overridden. 263 264 Returns a dictionary mapping field names to lists of values (even if a 265 single value is associated with any given field name). Each value is 266 either a Unicode object (representing a simple form field, for example) 267 or a WebStack.Helpers.Request.FileContent object (representing a file 268 upload form field). 269 270 Where a given field name is used in both the path and message body to 271 specify values, the values from both sources will be combined into a 272 single list associated with that field name. 273 """ 274 275 fields = {} 276 fields.update(self.get_fields_from_path(encoding)) 277 self._update_fields(fields, self.get_fields_from_body(encoding)) 278 return fields 279 280 def _update_fields(self, fields, new_fields): 281 for name, values in new_fields.items(): 282 if not fields.has_key(name): 283 fields[name] = values 284 else: 285 fields[name] += values 286 287 def get_user(self): 288 289 """ 290 Extracts user information from the transaction. 291 292 Returns a username as a string or None if no user is defined. 293 """ 294 295 if self.user is not None: 296 return self.user 297 298 if hasattr(self.request.user, "username"): 299 return self.request.user.username 300 else: 301 return None 302 303 def get_cookies(self): 304 305 """ 306 Obtains cookie information from the request. 307 308 Returns a dictionary mapping cookie names to cookie objects. 309 """ 310 311 return self.process_cookies(self.request.COOKIES, using_strings=1) 312 313 def get_cookie(self, cookie_name): 314 315 """ 316 Obtains cookie information from the request. 317 318 Returns a cookie object for the given 'cookie_name' or None if no such 319 cookie exists. 320 """ 321 322 value = self.request.COOKIES.get(self.encode_cookie_value(cookie_name)) 323 if value is not None: 324 return Cookie(cookie_name, self.decode_cookie_value(value)) 325 else: 326 return None 327 328 # Response-related methods. 329 330 def get_response_stream(self): 331 332 """ 333 Returns the response stream for the transaction. 334 """ 335 336 # Unicode can upset this operation. Using either the specified charset 337 # or a default encoding. 338 339 encoding = self.get_response_stream_encoding() 340 return ConvertingStream(self.content, encoding) 341 342 def get_response_stream_encoding(self): 343 344 """ 345 Returns the response stream encoding. 346 """ 347 348 if self.content_type: 349 encoding = self.content_type.charset 350 else: 351 encoding = None 352 return encoding or self.default_charset 353 354 def get_response_code(self): 355 356 """ 357 Get the response code associated with the transaction. If no response 358 code is defined, None is returned. 359 """ 360 361 return self.response.status_code 362 363 def set_response_code(self, response_code): 364 365 """ 366 Set the 'response_code' using a numeric constant defined in the HTTP 367 specification. 368 """ 369 370 self.response.status_code = response_code 371 372 def set_header_value(self, header, value): 373 374 """ 375 Set the HTTP 'header' with the given 'value'. 376 """ 377 378 self.response.headers[header] = value 379 380 def set_content_type(self, content_type): 381 382 """ 383 Sets the 'content_type' for the response. 384 """ 385 386 self.content_type = content_type 387 self.response.headers["Content-Type"] = str(content_type) 388 389 # Higher level response-related methods. 390 391 def set_cookie(self, cookie): 392 393 """ 394 Stores the given 'cookie' object in the response. 395 """ 396 397 self.set_cookie_value(cookie.name, cookie.value) 398 399 def set_cookie_value(self, name, value, path=None, expires=None): 400 401 """ 402 Stores a cookie with the given 'name' and 'value' in the response. 403 404 The optional 'path' is a string which specifies the scope of the cookie, 405 and the optional 'expires' parameter is a value compatible with the 406 time.time function, and indicates the expiry date/time of the cookie. 407 """ 408 409 self.response.set_cookie(self.encode_cookie_value(name), self.encode_cookie_value(value), path=path, expires=expires) 410 411 def delete_cookie(self, cookie_name): 412 413 """ 414 Adds to the response a request that the cookie with the given 415 'cookie_name' be deleted/discarded by the client. 416 """ 417 418 #self.response.delete_cookie(self.encode_cookie_value(cookie_name)) 419 420 # Create a special cookie, given that we do not know whether the browser 421 # has been sent the cookie or not. 422 # NOTE: Magic discovered in Webware. 423 424 name = self.encode_cookie_value(cookie_name) 425 self.response.set_cookie(name, "", path="/", expires=0, max_age=0) 426 427 # Session-related methods. 428 429 def get_session(self, create=1): 430 431 """ 432 Gets a session corresponding to an identifier supplied in the 433 transaction. 434 435 If no session has yet been established according to information 436 provided in the transaction then the optional 'create' parameter 437 determines whether a new session will be established. 438 439 Where no session has been established and where 'create' is set to 0 440 then None is returned. In all other cases, a session object is created 441 (where appropriate) and returned. 442 """ 443 444 # NOTE: Dubious access to a more dictionary-like object. 445 446 if create: 447 self.request.session["_hack"] = "created" 448 return Session(self.request.session) 449 450 def expire_session(self): 451 452 """ 453 Expires any session established according to information provided in the 454 transaction. 455 """ 456 457 # NOTE: Not trivially supported! 458 459 class Session: 460 def __init__(self, session): 461 self.session = session 462 def __getattr__(self, name): 463 return getattr(self.session, name) 464 def keys(self): 465 return self.session._session.keys() 466 def values(self): 467 return self.session._session.values() 468 def items(self): 469 return self.session._session.items() 470 471 # vim: tabstop=4 expandtab shiftwidth=4