Package amplee :: Package atompub :: Package member
[hide private]
[frames] | no frames]

Source Code for Package amplee.atompub.member

  1  # -*- coding: utf-8 -*- 
  2  __docformat__ = 'epytext en' 
  3   
  4  __doc__ = """ 
  5  @see: Collection from RFC 5023 at U{http://tools.ietf.org/html/rfc5023#section-9} 
  6  @undocumented: __doc__  
  7  """ 
  8   
  9  __all__ = ['MemberResource'] 
 10   
 11  import copy 
 12  import os.path 
 13  from xml.sax import SAXParseException 
 14  from urlparse import urljoin, urlparse 
 15  from urllib import quote 
 16   
 17  import amara 
 18  from amplee.utils import generate_uuid_uri, get_isodate, safe_quote, \ 
 19       safe_unquote, safe_url_join, qname 
 20  from amplee.comparer import app_edited_comparer 
 21  from amplee.error import ResourceOperationException 
 22  from amplee.atompub.member.helper import MemberHelper 
 23   
 24  from amplee.utils import ATOM10_PREFIX, ATOMPUB_PREFIX, XML_PREFIX, XML_NS, \ 
 25       ATOM10_NS, ATOMPUB_NS, XHTML1_NS, XHTML1_PREFIX 
 26   
27 -class MemberResource(object):
28 - def __init__(self, collection, id=None, atom=None, media_type=None):
29 """ 30 Represents in amplee an AtomPub member resource and provides 31 a fairly extensive API to manipulate it. 32 33 @type collection: L{AtomPubCollection} 34 @param collection: collection holding the reference to this member 35 36 @type id: string 37 @param id: identifier for this member 38 39 @type atom: L{amara.bindery.root_base} 40 @param atom: amara instance representing the Atom entry 41 42 @type media_type: string 43 @param media_type: media type of the associated resource 44 """ 45 self.collection = collection 46 self.entry = atom 47 self.member_id = id 48 self.media_id = None 49 self.media_type = media_type or u'application/atom+xml;type=entry' 50 self.draft = False 51 self.comparer = app_edited_comparer 52 self.xslt_path = None
53
54 - def __cmp__(self, other):
55 """ 56 By default members are compared following their app:edited element, 57 but you can change that behavior by setting the C{self.comparer} attribute 58 to a callable that will perform the comparison. 59 60 @rtype: int 61 @return: 62 """ 63 return self.comparer(self, other)
64
65 - def _getentry(self):
66 if self.entry is None: 67 # Lazy loading of the atom entry 68 info = self.collection.get_meta_data_info(self.member_id) 69 source = self.collection.get_meta_data(info) 70 self.entry = amara.parse(source, prefixes={ATOM10_PREFIX: ATOM10_NS, 71 ATOMPUB_PREFIX: ATOMPUB_NS}) 72 return self.entry
73
74 - def _setentry(self, entry):
75 raise AttributeError("Cannot overwrite the underlying Atom entry")
76
77 - def _delentry(self):
78 raise AttributeError("Cannot delete the underlying Atom entry")
79 80 atom = property(_getentry, _setentry, _delentry) 81
82 - def _getmediacontent(self):
83 return self.collection.get_content(self.collection.get_content_info(self.media_id))
84
85 - def _setmediacontent(self, entry):
86 raise AttributeError("Cannot overwrite the underlying content")
87
88 - def _delmediacontent(self):
89 raise AttributeError("Cannot delete the media content")
90 91 content = property(_getmediacontent, _setmediacontent, _delmediacontent) 92 93 # For the pickling of this class
94 - def __getstate__(self):
95 return {'member_id': self.member_id, 96 'media_id': self.media_id, 97 'media_type': self.media_type}
98 #'entry': self.atom.xml(indent=False)} 99
100 - def __setstate__(self, state):
101 self.entry = None 102 self.member_id = state['member_id'] 103 self.media_id = state['media_id'] 104 self.media_type = state['media_type']
105 #self.entry = E.load(state['entry'], as_attribute=atom_as_attr, as_list=atom_as_list, 106 # as_attribute_of_element=atom_attribute_of_element).xml_root 107
108 - def from_entry(self, entry, info=None):
109 """ 110 Allows the filling of the current instance from an Atom entry. 111 112 This will lookup for two links: 113 - rel='edit' 114 - rel='edit-media' 115 116 @type entry: L{amara.bindery.root_base} 117 @param entry: atom entry attached to the L{MemberResource} instance. 118 Sets the instance attribute to the values extracted from the entry. 119 """ 120 self.entry = copy.deepcopy(entry).ownerDocument 121 122 entry.ownerDocument.xmlns_prefixes.update({ATOM10_PREFIX: ATOM10_NS, 123 ATOMPUB_PREFIX: ATOMPUB_NS}) 124 self.member_id = self.generate_resource_id(entry=entry, info=info) 125 self.media_type = u'application/atom+xml;type=entry' 126 self.media_id = self.collection.convert_id(self.member_id)[-1] 127 128 edit_media_links = entry.xml_xpath('./atom:link[@rel="edit-media"]') 129 if edit_media_links: 130 if 'type' in edit_media_links[0].attributes: 131 self.media_type = unicode(edit_media_links[0].type) 132 133 self.draft = self.is_draft()
134
135 - def set_xslt(self, path):
136 """ 137 Sets the XSLT to associate to the Atom entry. A processing 138 instruction will be inserted when the C{self.xml()} method is 139 called. 140 141 @type path: string 142 @param path: URI to the the XSLT resource 143 """ 144 self.xslt_path = path
145
146 - def xml(self, indent=False):
147 """ 148 Serializes the entry as a string. 149 150 @type indent: bool 151 @param indent: indicates if the serialization should be indented 152 153 @rtype: string 154 @return: serialized representation of the atom entry 155 """ 156 if self.xslt_path: 157 pi = amara.bindery.pi_base(u"xml-stylesheet", u'href="%s" type="text/xsl"' % self.xslt_path) 158 159 if isinstance(self.entry, amara.bindery.root_base): 160 d = self.entry.ownerDocument 161 d.xml_insert_before(d.childNodes[0], pi) 162 else: 163 d = amara.create_document() 164 d.xmlns_prefixes.update({ATOM10_PREFIX: ATOM10_NS, 165 ATOMPUB_PREFIX: ATOMPUB_NS}) 166 d.xml_append(pi) 167 d.xml_append(self.entry) 168 169 return d.xml(indent=indent, force_nsdecls=d.xmlns_prefixes) 170 171 return self.entry.xml(indent=indent, force_nsdecls=self.entry.xmlns_prefixes)
172
173 - def load_document(self, source):
174 """ 175 Loads a source document into an amara instance or throws a 176 L{ResourceOperationException} if the operation failed. 177 178 @type source: string or file-like object 179 @param source: L{amara.bindery.root_base} instance of the document 180 """ 181 try: 182 doc = amara.parse(source, prefixes={ATOM10_PREFIX: ATOM10_NS, 183 ATOMPUB_PREFIX: ATOMPUB_NS}) 184 except SAXParseException, er: 185 raise ResourceOperationException("Could not parse posted Atom entry", 400, body=str(er)) 186 187 doc.xmlns_prefixes[u'app'] = u"http://www.w3.org/2007/app" 188 doc.xmlns_prefixes[u'atom'] = u"http://www.w3.org/2005/Atom" 189 190 return doc
191
193 """ 194 Inserts the collection categories into the Atom entry. 195 """ 196 categories = self.collection.categories or None 197 if categories: 198 entry = self.entry.entry 199 doc = entry.ownerDocument 200 for category in categories: 201 entry.xml_append(doc.xml_create_element(qname(u"category", ATOM10_PREFIX), 202 ns=ATOM10_NS, attributes=category))
203
204 - def use_published_date_from(self, other_member):
205 """ 206 Sets the current instance atom entry published element value to the 207 one from the provided member. 208 209 @type other_member: L{MemberResource} or subclass 210 @param other_member: member from which the published date value must be taken from 211 """ 212 entry = self.entry.entry 213 other_entry = other_member.atom.entry 214 215 pub = entry.xml_xpath('atom:published') 216 other_pub = other_entry.xml_xpath('atom:published') 217 if other_pub and pub: 218 entry.published = unicode(other_pub[0]) 219 elif other_pub: 220 d = entry.ownerDocument 221 entry.xml_append(d.xml_create_element(qname(u"published", ATOM10_PREFIX), 222 ns=ATOM10_NS, content=unicode(other_pub[0])))
223
224 - def update_dates(self):
225 """ 226 Sets the updated and edited date values to the current UTC time. 227 """ 228 entry = self.entry.entry 229 isodate = get_isodate() 230 d = entry.ownerDocument 231 232 updated = entry.xml_xpath('atom:updated') 233 if not updated: 234 entry.xml_append(d.xml_create_element(qname(u"updated", ATOM10_PREFIX), 235 ns=ATOM10_NS)) 236 entry.updated = isodate 237 238 edited = entry.xml_child_elements.get('edited') 239 if not edited: 240 entry.xml_append(d.xml_create_element(qname(u"edited", ATOMPUB_PREFIX), 241 ns=ATOMPUB_NS)) 242 entry.edited = isodate
243
244 - def merge_dates(self, new_member):
245 """ 246 Sets the updated and edited date values to the values from the member 247 provided. 248 249 @type new_member: L{MemberResource} or subclass 250 @param new_member: member from which the date values must be taken from 251 """ 252 entry = self.entry.entry 253 isodate = get_isodate() 254 d = entry.ownerDocument 255 256 updated = entry.xml_child_elements.get('updated') 257 if updated: 258 updated = isodate 259 else: 260 entry.xml_append(d.xml_create_element(qname(u"updated", ATOM10_PREFIX), 261 ns=ATOM10_NS, content=isodate)) 262 263 new_entry = new_member.atom 264 new_edited = new_entry.xml_child_elements.get('edited') 265 if new_edited: 266 isodate = unicode(new_edited) 267 edited = entry.xml_child_elements.get('edited') 268 if edited: 269 edited = isodate 270 else: 271 entry.xml_append(d.xml_create_element(qname(u"edited", ATOMPUB_PREFIX), 272 ns=ATOMPUB_NS, content=isodate))
273 274
275 - def prepare_for_public(self, content=None, external_content=False, 276 rel=u'alternate', media_type=u'text/html', 277 xslt_path=None):
278 """ 279 Generates and returns an atom entry that is appropriate to be used in 280 a public atom feed while retaining information from the member atom entry. 281 282 @type content: string (not unicode) 283 @param content: the XHTML content, as a string, to be set as the atom:content value 284 285 @type external_content: bool 286 @param external_content: if C{True}, the atom:content element will have 287 a C{src} attribute and no body. The URI is generated automatically. 288 289 @type rel: string 290 @param rel: defines the C{rel} attribute of the atom:link element 291 292 @type media_type: string 293 @param media_type: defines the C{type} attribute value of the atom:link element 294 295 @type xslt_path: string 296 @param xslt_path: URI of the XSLT associated with the entry as a processing-instruction 297 298 @rtype: L{amara.bindery.root_base} 299 @return: the amara instance representing the atom entry 300 """ 301 clone = copy.deepcopy(self.entry) 302 if isinstance(clone, amara.bindery.root_base): 303 clone = clone.entry 304 d = clone.ownerDocument 305 306 xml_base = self.collection.get_xml_base() 307 if xml_base: 308 clone.xml_set_attribute((qname(u'base', XML_PREFIX), XML_NS), xml_base) 309 310 edited = clone.xml_child_elements.get('edited') 311 if edited: clone.xml_remove_child(edited) 312 313 control = clone.xml_child_elements.get('control') 314 if control: clone.xml_remove_child(control) 315 316 links = clone.xml_xpath('./atom:link[@rel="edit"]') 317 for link in links: 318 clone.xml_remove_child(link) 319 320 links = clone.xml_xpath('./atom:link[@rel="edit-media"]') 321 for link in links: 322 clone.xml_remove_child(link) 323 324 # link rel="self" 325 if xml_base: 326 href = safe_url_join([self.collection.base_uri, self.member_id]) 327 else: 328 href = safe_url_join([self.collection.get_base_uri(), self.member_id]) 329 links = clone.xml_xpath('./atom:link[@rel="self"]') 330 if links: 331 links[0].href = href 332 links[0].type = u'application/atom+xml;type=entry' 333 else: 334 clone.xml_append(d.xml_create_element(qname(u"link", ATOM10_PREFIX), 335 ns=ATOM10_NS, 336 attributes={u'rel': u'self', 337 u'type': u'application/atom+xml;type=entry', 338 u'href': href})) 339 340 # link rel="%s" % rel 341 if self.media_id: 342 if xml_base: 343 href = safe_url_join([self.collection.base_uri, self.media_id]) 344 else: 345 href = self.public_uri 346 347 links = clone.xml_xpath('./atom:link[@rel="%s"]' % rel) 348 if links: 349 links[0].href = href 350 links[0].type = media_type 351 else: 352 clone.xml_append(d.xml_create_element(qname(u"link", ATOM10_PREFIX), 353 ns=ATOM10_NS, 354 attributes={u'rel': rel, 355 u'type': media_type, 356 u'href': href})) 357 358 ct = clone.xml_child_elements.get('content') 359 if ct: clone.xml_remove_child(ct) 360 361 i