| 1 |
from datetime import datetime |
|---|
| 2 |
|
|---|
| 3 |
from zope.interface import implements, Interface |
|---|
| 4 |
|
|---|
| 5 |
from twisted.application import service, strports |
|---|
| 6 |
from twisted.cred import portal, checkers, credentials |
|---|
| 7 |
from twisted.cred.credentials import UsernamePassword |
|---|
| 8 |
from twisted.python.components import registerAdapter |
|---|
| 9 |
|
|---|
| 10 |
from nevow import appserver, guard, inevow, loaders, rend, stan, tags as T, url |
|---|
| 11 |
from formless import annotate, webform |
|---|
| 12 |
|
|---|
| 13 |
from axiom import attributes, item, store, substore, userbase |
|---|
| 14 |
|
|---|
| 15 |
LOGIN_URL_SEGMENT = "login" |
|---|
| 16 |
|
|---|
| 17 |
############################## |
|---|
| 18 |
class NewsItem(item.Item): |
|---|
| 19 |
title = attributes.text(allowNone=False) |
|---|
| 20 |
story = attributes.text(allowNone=False) |
|---|
| 21 |
authorName = attributes.text(allowNone=False) |
|---|
| 22 |
dateLastUpdated = attributes.bytes() |
|---|
| 23 |
|
|---|
| 24 |
class INewsManager(Interface): |
|---|
| 25 |
def list(offset=None, limit=None): |
|---|
| 26 |
pass |
|---|
| 27 |
|
|---|
| 28 |
def save(title, story, storeId): |
|---|
| 29 |
pass |
|---|
| 30 |
|
|---|
| 31 |
def delete(storeId): |
|---|
| 32 |
pass |
|---|
| 33 |
|
|---|
| 34 |
class NewsManager(object): |
|---|
| 35 |
implements(INewsManager) |
|---|
| 36 |
def __init__(self, avatar, store): |
|---|
| 37 |
self.avatar = avatar |
|---|
| 38 |
self.store = store |
|---|
| 39 |
|
|---|
| 40 |
def list(self, offset=None, limit=None): |
|---|
| 41 |
return self.store.query(NewsItem) |
|---|
| 42 |
|
|---|
| 43 |
def save(self, title, story, storeId=None): |
|---|
| 44 |
if storeId: |
|---|
| 45 |
item = self.store.getItemById(storeId) |
|---|
| 46 |
item.title = title |
|---|
| 47 |
item.story = story |
|---|
| 48 |
else: |
|---|
| 49 |
item = NewsItem( |
|---|
| 50 |
store=self.store, |
|---|
| 51 |
title=title, |
|---|
| 52 |
story=story, |
|---|
| 53 |
authorName=IPerson(self.avatar).fullName()) |
|---|
| 54 |
|
|---|
| 55 |
item.dateLastUpdated = datetime.now().strftime("%Y-%m-%d") |
|---|
| 56 |
|
|---|
| 57 |
def avatarToNewsManagerAdapter(avatar): |
|---|
| 58 |
if avatar.parent is None: |
|---|
| 59 |
return NewsManager(avatar, avatar) |
|---|
| 60 |
else: |
|---|
| 61 |
return NewsManager(avatar, avatar.parent) |
|---|
| 62 |
|
|---|
| 63 |
registerAdapter(avatarToNewsManagerAdapter, store.Store, INewsManager) |
|---|
| 64 |
|
|---|
| 65 |
############################# |
|---|
| 66 |
class NewsSaveFragment(rend.Fragment): |
|---|
| 67 |
def __init__(self, *args, **kwargs): |
|---|
| 68 |
self.avatar = kwargs.pop('avatar') |
|---|
| 69 |
super(NewsSaveFragment, self).__init__(*args, **kwargs) |
|---|
| 70 |
|
|---|
| 71 |
def bind_save(self, ctx): |
|---|
| 72 |
return [ |
|---|
| 73 |
('title', annotate.String(required=True, unicode=True)), |
|---|
| 74 |
('story', annotate.Text(required=True, unicode=True)), |
|---|
| 75 |
] |
|---|
| 76 |
|
|---|
| 77 |
def save(self, title, story): |
|---|
| 78 |
INewsManager(self.avatar).save(title, story) |
|---|
| 79 |
|
|---|
| 80 |
def render_inputform(self, ctx, data): |
|---|
| 81 |
return webform.renderForms("NewsSaveFragment") |
|---|
| 82 |
|
|---|
| 83 |
class NewsListFragment(rend.Fragment): |
|---|
| 84 |
def __init__(self, *args, **kwargs): |
|---|
| 85 |
self.avatar = kwargs.pop('avatar', None) |
|---|
| 86 |
super(NewsListFragment, self).__init__(*args, **kwargs) |
|---|
| 87 |
|
|---|
| 88 |
def data_items(self, ctx, data): |
|---|
| 89 |
return INewsManager(self.avatar).list() |
|---|
| 90 |
|
|---|
| 91 |
def render_axiomitem(self, ctx, data): |
|---|
| 92 |
for k, v in data.getSchema(): |
|---|
| 93 |
ctx.tag.fillSlots(k, getattr(data, k)) |
|---|
| 94 |
return ctx.tag |
|---|
| 95 |
|
|---|
| 96 |
class LoginStatusFragment(rend.Fragment): |
|---|
| 97 |
def __init__(self, *args, **kwargs): |
|---|
| 98 |
self.avatar = kwargs.pop('avatar', None) |
|---|
| 99 |
super(LoginStatusFragment, self).__init__(*args, **kwargs) |
|---|
| 100 |
|
|---|
| 101 |
def rend(self, ctx, data): |
|---|
| 102 |
res = rend.Fragment.rend(self, ctx, data) |
|---|
| 103 |
person = IPerson(self.avatar, None) |
|---|
| 104 |
if person: |
|---|
| 105 |
pattern = res.tag.onePattern("loggedin") |
|---|
| 106 |
pattern.fillSlots('username', person.firstName) |
|---|
| 107 |
else: |
|---|
| 108 |
pattern = res.tag.onePattern("anonymous") |
|---|
| 109 |
|
|---|
| 110 |
res.tag[pattern] |
|---|
| 111 |
return res |
|---|
| 112 |
|
|---|
| 113 |
############################## |
|---|
| 114 |
class GuardedResourceWrapper: |
|---|
| 115 |
implements(inevow.IResource) |
|---|
| 116 |
|
|---|
| 117 |
def __init__(self, wrapped, avatar): |
|---|
| 118 |
self.wrapped = wrapped |
|---|
| 119 |
self.avatar = avatar |
|---|
| 120 |
|
|---|
| 121 |
def renderHTTP(self, ctx): |
|---|
| 122 |
req = inevow.IRequest(ctx) |
|---|
| 123 |
if self.avatar.avatarId is checkers.ANONYMOUS: |
|---|
| 124 |
return url.root.child(LOGIN_URL_SEGMENT).add('nextUri', req.uri) |
|---|
| 125 |
else: |
|---|
| 126 |
return self.wrapped.renderHTTP(ctx) |
|---|
| 127 |
|
|---|
| 128 |
def locateChild(self, ctx, segments): |
|---|
| 129 |
req = inevow.IRequest(ctx) |
|---|
| 130 |
if self.avatar.avatarId is checkers.ANONYMOUS: |
|---|
| 131 |
return url.root.child(LOGIN_URL_SEGMENT).add('nextUri', req.uri), [] |
|---|
| 132 |
else: |
|---|
| 133 |
return self.wrapped, segments |
|---|
| 134 |
|
|---|
| 135 |
class UnauthorisedFragment: |
|---|
| 136 |
implements(inevow.IRenderer) |
|---|
| 137 |
def __init__(self, avatar, requiredRole): |
|---|
| 138 |
self.avatar = avatar |
|---|
| 139 |
self.requiredRole = requiredRole |
|---|
| 140 |
|
|---|
| 141 |
def rend(self, ctx, data): |
|---|
| 142 |
req = inevow.IRequest(ctx) |
|---|
| 143 |
if self.requiredRole in IRole(self.avatar).allRoles(): |
|---|
| 144 |
return self.wrappedFactory(avatar=self.avatar, **self.kwargs).rend(ctx, data) |
|---|
| 145 |
else: |
|---|
| 146 |
return T.fieldset[ |
|---|
| 147 |
T.legend["You must be logged in with role %s to use this fragment." % self.requiredRole], |
|---|
| 148 |
T.p[ |
|---|
| 149 |
"[", |
|---|
| 150 |
T.a(href=url.root.child(LOGIN_URL_SEGMENT).add('nextUri', req.uri))["Login"], |
|---|
| 151 |
"]" |
|---|
| 152 |
] |
|---|
| 153 |
] |
|---|
| 154 |
|
|---|
| 155 |
def guardFrag(wrappedFactory, requiredRole): |
|---|
| 156 |
def fragFactory(**kwargs): |
|---|
| 157 |
avatar = kwargs.get('avatar') |
|---|
| 158 |
if requiredRole in IRole(avatar).allRoles(): |
|---|
| 159 |
return wrappedFactory(**kwargs) |
|---|
| 160 |
else: |
|---|
| 161 |
return UnauthorisedFragment(avatar, requiredRole) |
|---|
| 162 |
return fragFactory |
|---|
| 163 |
|
|---|
| 164 |
############################## |
|---|
| 165 |
class PageWithAvatar(rend.Page): |
|---|
| 166 |
def __init__(self, *args, **kwargs): |
|---|
| 167 |
self.avatar = kwargs.pop('avatar') |
|---|
| 168 |
super(PageWithAvatar, self).__init__(*args, **kwargs) |
|---|
| 169 |
|
|---|
| 170 |
class IPatternFactory(Interface): |
|---|
| 171 |
pass |
|---|
| 172 |
|
|---|
| 173 |
class BasePage(PageWithAvatar): |
|---|
| 174 |
addSlash = False |
|---|
| 175 |
docFactory = loaders.xmlstr(""" |
|---|
| 176 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" |
|---|
| 177 |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
|---|
| 178 |
<html xmlns:n="http://nevow.com/ns/nevow/0.1" n:render="pattern page"></html> |
|---|
| 179 |
""") |
|---|
| 180 |
patternFactory = None |
|---|
| 181 |
|
|---|
| 182 |
def __init__(self, *args, **kwargs): |
|---|
| 183 |
super(BasePage, self).__init__(*args, **kwargs) |
|---|
| 184 |
store = IRole(self.avatar).store |
|---|
| 185 |
|
|---|
| 186 |
EVERYONE = getEveryoneRole(store) |
|---|
| 187 |
AUTHENTICATED = getAuthenticatedRole(store) |
|---|
| 188 |
|
|---|
| 189 |
self.allowedFragments = dict( |
|---|
| 190 |
LoginStatusFragment = guardFrag(LoginStatusFragment, EVERYONE), |
|---|
| 191 |
NewsListFragment = guardFrag(NewsListFragment, EVERYONE), |
|---|
| 192 |
NewsSaveFragment = guardFrag(NewsSaveFragment, AUTHENTICATED), |
|---|
| 193 |
) |
|---|
| 194 |
|
|---|
| 195 |
def beforeRender(self, ctx): |
|---|
| 196 |
if self.patternFactory: |
|---|
| 197 |
ctx.remember(self.patternFactory, IPatternFactory) |
|---|
| 198 |
|
|---|
| 199 |
def locateChild(self, ctx, segments): |
|---|
| 200 |
name = segments[0] |
|---|
| 201 |
# XXX: Hack to allow formless to find the configurables in fragments |
|---|
| 202 |
if name.startswith('freeform_post!'): |
|---|
| 203 |
configurableName, bindingName = name.split('!')[1:3] |
|---|
| 204 |
fragFactory = self.allowedFragments.get(configurableName, None) |
|---|
| 205 |
if fragFactory is not None: |
|---|
| 206 |
frag = fragFactory(avatar=self.avatar) |
|---|
| 207 |
#print "LOCATECHILD SETTING: configurable_", frag.__class__.__name__, " TO ", frag |
|---|
| 208 |
setattr(self, "configurable_%s" % frag.__class__.__name__, lambda ctx: frag) |
|---|
| 209 |
|
|---|
| 210 |
if self.patternFactory: |
|---|
| 211 |
ctx.remember(self.patternFactory, IPatternFactory) |
|---|
| 212 |
return super(BasePage, self).locateChild(ctx, segments) |
|---|
| 213 |
|
|---|
| 214 |
def locateFragment(self, fragmentName, **kwargs): |
|---|
| 215 |
fragFactory = self.allowedFragments[fragmentName] |
|---|
| 216 |
frag = fragFactory(**kwargs) |
|---|
| 217 |
# So that fragments work with formless |
|---|
| 218 |
#print "LOCATEFRAG SETTING: configurable_", frag.__class__.__name__, " TO ", frag |
|---|
| 219 |
setattr(self, "configurable_%s" % frag.__class__.__name__, lambda ctx: frag) |
|---|
| 220 |
return frag |
|---|
| 221 |
|
|---|
| 222 |
def render_fragment(self, name, *args): |
|---|
| 223 |
def f(ctx, data): |
|---|
| 224 |
kwargs = dict( |
|---|
| 225 |
avatar=self.avatar, |
|---|
| 226 |
docFactory = loaders.stan(ctx.tag.children) |
|---|
| 227 |
) |
|---|
| 228 |
return ctx.tag.clear()[self.locateFragment(name, **kwargs)] |
|---|
| 229 |
return f |
|---|
| 230 |
|
|---|
| 231 |
def render_pattern(self, name): |
|---|
| 232 |
def f(ctx, data): |
|---|
| 233 |
pattern = self.findPattern(ctx, name) |
|---|
| 234 |
if pattern is not None: |
|---|
| 235 |
return ctx.tag.clear()[pattern.children] |
|---|
| 236 |
return ctx.tag |
|---|
| 237 |
return f |
|---|
| 238 |
|
|---|
| 239 |
def findPattern(self, ctx, name, depth=1): |
|---|
| 240 |
try: |
|---|
| 241 |
doc = ctx.locate(IPatternFactory, depth) |
|---|
| 242 |
except KeyError: |
|---|
| 243 |
return None |
|---|
| 244 |
try: |
|---|
| 245 |
return inevow.IQ(doc).onePattern(name) |
|---|
| 246 |
except stan.NodeNotFound: |
|---|
| 247 |
return self.findPattern(ctx, name, depth+1) |
|---|
| 248 |
|
|---|
| 249 |
class LoginPage(BasePage): |
|---|
| 250 |
|
|---|
| 251 |
patternFactory = loaders.xmlstr(""" |
|---|
| 252 |
<html xmlns:n="http://nevow.com/ns/nevow/0.1"> |
|---|
| 253 |
<head> |
|---|
| 254 |
<title n:pattern="page_title">Login</title> |
|---|
| 255 |
</head> |
|---|
| 256 |
<body> |
|---|
| 257 |
<div n:pattern="page_body_content"> |
|---|
| 258 |
<h1>Login</h1> |
|---|
| 259 |
<n:invisible n:render="loginform" /> |
|---|
| 260 |
</div> |
|---|
| 261 |
</body> |
|---|
| 262 |
</html> |
|---|
| 263 |
""") |
|---|
| 264 |
|
|---|
| 265 |
def bind_login(self, ctx): |
|---|
| 266 |
return [ |
|---|
| 267 |
('ctx', annotate.Context()), |
|---|
| 268 |
('username', annotate.String(required=True)), |
|---|
| 269 |
('password', annotate.PasswordEntry(required=True)), |
|---|
| 270 |
] |
|---|
| 271 |
|
|---|
| 272 |
def login(self, ctx, username, password): |
|---|
| 273 |
sess = inevow.ISession(ctx) |
|---|
| 274 |
req = inevow.IRequest(ctx) |
|---|
| 275 |
credentials = UsernamePassword(username, password) |
|---|
| 276 |
|
|---|
| 277 |
def errorHandler(error): |
|---|
| 278 |
raise annotate.ValidateError( |
|---|
| 279 |
{}, |
|---|
| 280 |
formErrorMessage="Your username and / or password were not recognised.", |
|---|
| 281 |
partialForm={'username':username}) |
|---|
| 282 |
|
|---|
| 283 |
def successHandler(pageAndRemainingSegments): |
|---|
| 284 |
return url.root |
|---|
| 285 |
|
|---|
| 286 |
return sess.guard.login(req, sess, credentials, []).addCallback(successHandler).addErrback(errorHandler) |
|---|
| 287 |
|
|---|
| 288 |
def render_loginform(self, ctx, data): |
|---|
| 289 |
return ctx.tag.clear()[webform.renderForms()] |
|---|
| 290 |
|
|---|
| 291 |
################################### |
|---|
| 292 |
class RootPage(BasePage): |
|---|
| 293 |
addSlash = True |
|---|
| 294 |
patternFactory = loaders.xmlstr(""" |
|---|
| 295 |
<html xmlns:n="http://nevow.com/ns/nevow/0.1"> |
|---|
| 296 |
<head> |
|---|
| 297 |
<title n:pattern="page_title">Homepage</title> |
|---|
| 298 |
</head> |
|---|
| 299 |
<body> |
|---|
| 300 |
<div n:pattern="page_body_menu"> |
|---|
| 301 |
<ul> |
|---|
| 302 |
<li><a href="/">Home</a></li> |
|---|
| 303 |
<li><a href="/admin">Administration</a></li> |
|---|
| 304 |
<li><a href="/news">News</a></li> |
|---|
| 305 |
</ul> |
|---|
| 306 |
</div> |
|---|
| 307 |
<div n:pattern="page_body_content"> |
|---|
| 308 |
<h1>Homepage</h1> |
|---|
| 309 |
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam vestibulum pretium augue. Maecenas a risus. Morbi quis ante sit amet orci ultrices iaculis. Nullam ut ante nec lacus elementum sagittis. Duis non magna vitae erat gravida pellentesque. In sit amet purus sed quam hendrerit luctus. Nullam ac turpis ac orci dictum sollicitudin. Fusce sem ligula, vehicula in, hendrerit a, tempus vitae, sem. Phasellus odio. In vitae tellus nec nunc lobortis venenatis.</p> |
|---|
| 310 |
</div> |
|---|
| 311 |
</body> |
|---|
| 312 |
</html> |
|---|
| 313 |
""") |
|---|
| 314 |
|
|---|
| 315 |
def child_form_css(self, ctx): |
|---|
| 316 |
return webform.defaultCSS |
|---|
| 317 |
|
|---|
| 318 |
def child_login(self, ctx): |
|---|
| 319 |
return LoginPage(avatar=self.avatar) |
|---|
| 320 |
|
|---|
| 321 |
def child_news(self, ctx): |
|---|
| 322 |
return NewsPage(avatar=self.avatar) |
|---|
| 323 |
|
|---|
| 324 |
def child_admin(self, ctx): |
|---|
| 325 |
return AdminPage(avatar=self.avatar) |
|---|
| 326 |
|
|---|
| 327 |
class AdminPage(BasePage): |
|---|
| 328 |
patternFactory = loaders.xmlstr(""" |
|---|
| 329 |
<html xmlns:n="http://nevow.com/ns/nevow/0.1"> |
|---|
| 330 |
<head> |
|---|
| 331 |
<title n:pattern="page_title">Administration</title> |
|---|
| 332 |
</head> |
|---|
| 333 |
<body> |
|---|
| 334 |
<div n:pattern="page_body_menu"> |
|---|
| 335 |
<ul> |
|---|
| 336 |
<li><a href="/admin/news">News Admin</a></li> |
|---|
| 337 |
</ul> |
|---|
| 338 |
</div> |
|---|
| 339 |
<div n:pattern="page_body_content"> |
|---|
| 340 |
<h1>Administration</h1> |
|---|
| 341 |
<p>Welcome to the administration page.</p> |
|---|
| 342 |
</div> |
|---|
| 343 |
</body> |
|---|
| 344 |
</html> |
|---|
| 345 |
""") |
|---|
| 346 |
|
|---|
| 347 |
def child_news(self, ctx): |
|---|
| 348 |
return NewsAdminPage(avatar=self.avatar) |
|---|
| 349 |
|
|---|
| 350 |
class NewsAdminPage(BasePage): |
|---|
| 351 |
patternFactory = loaders.xmlstr(""" |
|---|
| 352 |
<html xmlns:n="http://nevow.com/ns/nevow/0.1"> |
|---|
| 353 |
<head> |
|---|
| 354 |
<title n:pattern="page_title">News Administration</title> |
|---|
| 355 |
</head> |
|---|
| 356 |
<body> |
|---|
| 357 |
<div n:pattern="page_body_menu"> |
|---|
| 358 |
<ul> |
|---|
| 359 |
<li><a href="/admin/news">News Admin</a></li> |
|---|
| 360 |
</ul> |
|---|
| 361 |
</div> |
|---|
| 362 |
<div n:pattern="page_body_content"> |
|---|
| 363 |
<h1>News Administration</h1> |
|---|
| 364 |
<n:invisible n:render="fragment NewsSaveFragment"> |
|---|
| 365 |
<div n:render="inputform" /> |
|---|
| 366 |
</n:invisible> |
|---|
| 367 |
|
|---|
| 368 |
<div n:render="fragment NewsListFragment"> |
|---|
| 369 |
<h2 class="tab">Existing News</h2> |
|---|
| 370 |
<table n:render="sequence" n:data="items" class="" style="width:100%;" cellpadding="0" cellspacing="0" border="1" id="feedback-grid"> |
|---|
| 371 |
<thead n:pattern="header"> |
|---|
| 372 |
<tr> |
|---|
| 373 |
<td>Title</td> |
|---|
| 374 |
<td>Author</td> |
|---|
| 375 |
<td>Date</td> |
|---|
| 376 |
</tr> |
|---|
| 377 |
</thead> |
|---|
| 378 |
<tbody> |
|---|
| 379 |
<tr n:pattern="item"> |
|---|
| 380 |
<n:invisible n:render="axiomitem"> |
|---|
| 381 |
<td><n:slot name="title" /></td> |
|---|
| 382 |
<td><n:slot name="authorName" /></td> |
|---|
| 383 |
<td><n:slot name="dateLastUpdated" /></td> |
|---|
| 384 |
</n:invisible> |
|---|
| 385 |
</tr> |
|---|
| 386 |
</tbody> |
|---|
| 387 |
<tr n:pattern="empty"> |
|---|
| 388 |
<td colspan="3">No items found</td> |
|---|
| 389 |
</tr> |
|---|
| 390 |
</table> |
|---|
| 391 |
</div> |
|---|
| 392 |
</div> |
|---|
| 393 |
</body> |
|---|
| 394 |
</html> |
|---|
| 395 |
""") |
|---|
| 396 |
|
|---|
| 397 |
class NewsPage(BasePage): |
|---|
| 398 |
patternFactory = loaders.xmlstr(""" |
|---|
| 399 |
<html xmlns:n="http://nevow.com/ns/nevow/0.1"> |
|---|
| 400 |
<head> |
|---|
| 401 |
<title n:pattern="page_title">News List</title> |
|---|
| 402 |
</head> |
|---|
| 403 |
<body> |
|---|
| 404 |
<div n:pattern="page_body_content"> |
|---|
| 405 |
<h1>Latest News</h1> |
|---|
| 406 |
<p>This is a list of the latest news items in our database</p> |
|---|
| 407 |
<n:invisible n:render="fragment NewsListFragment"> |
|---|
| 408 |
<ul n:render="sequence" n:data="items"> |
|---|
| 409 |
<li n:pattern="item"> |
|---|
| 410 |
<n:invisible n:render="axiomitem"> |
|---|
| 411 |
<h3><n:slot name="title" /></h3> |
|---|
| 412 |
<p><n:slot name="story" /></p> |
|---|
| 413 |
<p> |
|---|
| 414 |
By: <strong><n:slot name="authorName" /></strong>, |
|---|
| 415 |
Last Updated: <strong><n:slot name="dateLastUpdated" /></strong> |
|---|
| 416 |
</p> |
|---|
| 417 |
</n:invisible> |
|---|
| 418 |
</li> |
|---|
| 419 |
<li n:pattern="empty"> |
|---|
| 420 |
No items found |
|---|
| 421 |
</li> |
|---|
| 422 |
</ul> |
|---|
| 423 |
</n:invisible> |
|---|
| 424 |
</div> |
|---|
| 425 |
</body> |
|---|
| 426 |
</html> |
|---|
| 427 |
""") |
|---|
| 428 |
|
|---|
| 429 |
def siteRootFactory(avatar): |
|---|
| 430 |
root = RootPage(avatar=avatar) |
|---|
| 431 |
return root |
|---|
| 432 |
registerAdapter(siteRootFactory, store.Store, inevow.IResource) |
|---|
| 433 |
|
|---|
| 434 |
################################ |
|---|
| 435 |
class IPerson(Interface): |
|---|
| 436 |
pass |
|---|
| 437 |
|
|---|
| 438 |
class Person(item.Item): |
|---|
| 439 |
implements(IPerson) |
|---|
| 440 |
|
|---|
| 441 |
typeName = "Person" |
|---|
| 442 |
|
|---|
| 443 |
firstName = attributes.text(allowNone=False) |
|---|
| 444 |
lastName = attributes.text(allowNone=False) |
|---|
| 445 |
|
|---|
| 446 |
installedOn = attributes.reference() |
|---|
| 447 |
|
|---|
| 448 |
def installOn(self, other): |
|---|
| 449 |
assert self.installedOn is None, 'cannot install Person on more than one User' |
|---|
| 450 |
self.installedOn = other |
|---|
| 451 |
other.powerUp(self, IPerson) |
|---|
| 452 |
|
|---|
| 453 |
def fullName(self): |
|---|
| 454 |
return u"%s %s" % (self.firstName, self.lastName) |
|---|
| 455 |
|
|---|
| 456 |
############################## |
|---|
| 457 |
class IAxiomStore(Interface): |
|---|
| 458 |
pass |
|---|
| 459 |
registerAdapter(lambda substoreItem: substoreItem.open(), substore.SubStore, IAxiomStore) |
|---|
| 460 |
|
|---|
| 461 |
class IRole(Interface): |
|---|
| 462 |
pass |
|---|
| 463 |
|
|---|
| 464 |
def storeToRoleAdapter(store): |
|---|
| 465 |
if store.parent is None: #anonymous avatar is the main store |
|---|
| 466 |
return getEveryoneRole(store) |
|---|
| 467 |
else: # logged in avatar is a user substore. |
|---|
| 468 |
return getSelfRole(store) |
|---|
| 469 |
|
|---|
| 470 |
registerAdapter(storeToRoleAdapter, store.Store, IRole) |
|---|
| 471 |
|
|---|
| 472 |
############################## |
|---|
| 473 |
def createAccount(store, username, domain, password, firstName, lastName): |
|---|
| 474 |
def transaction(): |
|---|
| 475 |
loginSystem = store.findFirst(userbase.LoginSystem) |
|---|
| 476 |
loginSystem.installOn(store) |
|---|
| 477 |
|
|---|
| 478 |
account = loginSystem.addAccount(username, domain, password) |
|---|
| 479 |
substore = IAxiomStore(account.avatars) |
|---|
| 480 |
|
|---|
| 481 |
person = Person(store=substore, firstName=firstName, lastName=lastName) |
|---|
| 482 |
person.installOn(substore) |
|---|
| 483 |
return account |
|---|
| 484 |
return store.transact(transaction) |
|---|
| 485 |
|
|---|
| 486 |
################################## |
|---|
| 487 |
import os |
|---|
| 488 |
|
|---|
| 489 |
DB_PATH = os.tmpnam() |
|---|
| 490 |
s = store.Store(DB_PATH) |
|---|
| 491 |
loginSystem = s.findOrCreate(userbase.LoginSystem) |
|---|
| 492 |
createAccount(s, u"joebloggs", u"localhost", u"password", u"Joe", u"Bloggs") |
|---|
| 493 |
createAccount(s, u"Janedoe", u"localhost", u"password", u"Jane", u"Doe") |
|---|
| 494 |
|
|---|
| 495 |
################################### |
|---|
| 496 |
porta = portal.Portal(realm = loginSystem) |
|---|
| 497 |
porta.registerChecker(checkers.AllowAnonymousAccess(), credentials.IAnonymous) |
|---|
| 498 |
porta.registerChecker(loginSystem) |
|---|
| 499 |
root = guard.SessionWrapper(porta) |
|---|
| 500 |
|
|---|
| 501 |
patternFactory = loaders.xmlstr(""" |
|---|
| 502 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" |
|---|
| 503 |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
|---|
| 504 |
<html xmlns:n="http://nevow.com/ns/nevow/0.1" n:pattern="page"> |
|---|
| 505 |
<head> |
|---|
| 506 |
<title n:render="pattern page_title">Page Title</title> |
|---|
| 507 |
<style> |
|---|
| 508 |
#login-status{ |
|---|
| 509 |
border-bottom : 1px solid Black; |
|---|
| 510 |
} |
|---|
| 511 |
</style> |
|---|
| 512 |
<link rel="stylesheet" href="/form_css" type="text/css" /> |
|---|
| 513 |
</head> |
|---|
| 514 |
<body> |
|---|
| 515 |
<div n:render="pattern page_header"> |
|---|
| 516 |
<div n:render="fragment LoginStatusFragment" id="login-status"> |
|---|
| 517 |
<p n:pattern="loggedin"> |
|---|
| 518 |
Welcome <strong><n:slot name="username" /></strong>, [<a href="/__logout__">Logout</a>] |
|---|
| 519 |
</p> |
|---|
| 520 |
<p n:pattern="anonymous"> |
|---|
| 521 |
You are not logged in, [<a href="/login">Login</a>] |
|---|
| 522 |
</p> |
|---|
| 523 |
</div> |
|---|
| 524 |
</div> |
|---|
| 525 |
<div n:render="pattern page_body"> |
|---|
| 526 |
<table width="100%" border="1"> |
|---|
| 527 |
<tr> |
|---|
| 528 |
<td n:render="pattern page_body_menu" width="20%" valign="top"> |
|---|
| 529 |
<ul> |
|---|
| 530 |
<li><a href="/">Home</a></li> |
|---|
| 531 |
</ul> |
|---|
| 532 |
</td> |
|---|
| 533 |
<td n:render="pattern page_body_content" width="80%" valign="top"> |
|---|
| 534 |
<h1>Site Title</h1> |
|---|
| 535 |
<p>Placeholder</p> |
|---|
| 536 |
</td> |
|---|
| 537 |
</tr> |
|---|
| 538 |
</table> |
|---|
| 539 |
</div> |
|---|
| 540 |
<div n:render="pattern page_footer"> |
|---|
| 541 |
<p>©Default Site Footer 2006</p> |
|---|
| 542 |
</div> |
|---|
| 543 |
</body> |
|---|
| 544 |
</html> |
|---|
| 545 |
""") |
|---|
| 546 |
site = appserver.NevowSite(root) |
|---|
| 547 |
site.remember(patternFactory, IPatternFactory) |
|---|
| 548 |
application = service.Application("examples") |
|---|
| 549 |
strports.service("8080", site).setServiceParent(application) |
|---|
| 550 |
|
|---|
| 551 |
#################################### |
|---|
| 552 |
from twisted.internet import reactor |
|---|
| 553 |
|
|---|
| 554 |
def cleanUp(): |
|---|
| 555 |
print "DELETING DATABASE: ", DB_PATH |
|---|
| 556 |
for root, dirs, files in os.walk(DB_PATH, topdown=False): |
|---|
| 557 |
for name in files: |
|---|
| 558 |
os.remove(os.path.join(root, name)) |
|---|
| 559 |
for name in dirs: |
|---|
| 560 |
os.rmdir(os.path.join(root, name)) |
|---|
| 561 |
os.removedirs(DB_PATH) |
|---|
| 562 |
reactor.addSystemEventTrigger("after", "shutdown", cleanUp) |
|---|
| 563 |
|
|---|
| 564 |
############################# |
|---|
| 565 |
# STOLEN from http://divmod.org/trac/browser/branches/sharing-382-5/Mantissa/xmantissa/sharing.py |
|---|
| 566 |
class RoleRelationship(item.Item): |
|---|
| 567 |
schemaVersion = 1 |
|---|
| 568 |
typeName = 'sharing_relationship' |
|---|
| 569 |
|
|---|
| 570 |
member = attributes.reference() |
|---|
| 571 |
group = attributes.reference() |
|---|
| 572 |
|
|---|
| 573 |
class Role(item.Item): |
|---|
| 574 |
schemaVersion = 1 |
|---|
| 575 |
typeName = 'sharing_role' |
|---|
| 576 |
externalID = attributes.text(allowNone=False) |
|---|
| 577 |
description = attributes.text() |
|---|
| 578 |
|
|---|
| 579 |
def becomeMemberOf(self, groupRole): |
|---|
| 580 |
self.store.findOrCreate(RoleRelationship, |
|---|
| 581 |
group=groupRole, |
|---|
| 582 |
member=self) |
|---|
| 583 |
|
|---|
| 584 |
def allRoles(self, memo=None): |
|---|
| 585 |
if memo is None: |
|---|
| 586 |
memo = set() |
|---|
| 587 |
elif self in memo: |
|---|
| 588 |
# this is bad, but we have successfully detected and prevented the |
|---|
| 589 |
# only really bad symptom, an infinite loop. |
|---|
| 590 |
return |
|---|
| 591 |
memo.add(self) |
|---|
| 592 |
yield self |
|---|
| 593 |
for groupRole in self.store.query(Role, |
|---|
| 594 |
attributes.AND(RoleRelationship.member == self, |
|---|
| 595 |
RoleRelationship.group == Role.storeID)): |
|---|
| 596 |
for roleRole in groupRole.allRoles(memo): |
|---|
| 597 |
yield roleRole |
|---|
| 598 |
|
|---|
| 599 |
def __repr__(self): |
|---|
| 600 |
return self.externalID.upper() |
|---|
| 601 |
|
|---|
| 602 |
############################# |
|---|
| 603 |
def getEveryoneRole(store): |
|---|
| 604 |
""" |
|---|
| 605 |
Get a base 'Everyone' role for this store, which is the role that every user has no matter what. |
|---|
| 606 |
""" |
|---|
| 607 |
return store.findOrCreate(Role, externalID=u'Everyone') |
|---|
| 608 |
|
|---|
| 609 |
def getAuthenticatedRole(store): |
|---|
| 610 |
""" |
|---|
| 611 |
Get the base 'Authenticated' role for this store, which is the role that |
|---|
| 612 |
every user with a proper username has, no matter what. |
|---|
| 613 |
""" |
|---|
| 614 |
def tx(): |
|---|
| 615 |
def addToEveryone(newAuthenticatedRole): |
|---|
| 616 |
newAuthenticatedRole.becomeMemberOf(getEveryoneRole(store)) |
|---|
| 617 |
return newAuthenticatedRole |
|---|
| 618 |
return store.findOrCreate(Role, addToEveryone, externalID=u'Authenticated') |
|---|
| 619 |
return store.transact(tx) |
|---|
| 620 |
|
|---|
| 621 |
def getPrimaryRole(store, primaryRoleName, createIfNotFound=False): |
|---|
| 622 |
""" |
|---|
| 623 |
Get Role object corresponding to an identifier name. If the role name |
|---|
| 624 |
passed is the empty string, it is assumed that the user is not |
|---|
| 625 |
authenticated, and the 'Everybody' role is primary. If the role name |
|---|
| 626 |
passed is non-empty, but has no corresponding role, the 'Authenticated' |
|---|
| 627 |
role - which is a member of 'Everybody' - is primary. Finally, a specific |
|---|
| 628 |
role can be primary if one exists for the user's given credentials, that |
|---|
| 629 |
will automatically always be a member of 'Authenticated', and by extension, |
|---|
| 630 |
of 'Everybody'. |
|---|
| 631 |
""" |
|---|
| 632 |
if not primaryRoleName: |
|---|
| 633 |
return getEveryoneRole(store) |
|---|
| 634 |
ff = store.findUnique(Role, Role.externalID == primaryRoleName, default=None) |
|---|
| 635 |
if ff is not None: |
|---|
| 636 |
return ff |
|---|
| 637 |
authRole = getAuthenticatedRole(store) |
|---|
| 638 |
if createIfNotFound: |
|---|
| 639 |
role = Role(store=store, |
|---|
| 640 |
externalID=primaryRoleName) |
|---|
| 641 |
role.becomeMemberOf(authRole) |
|---|
| 642 |
return role |
|---|
| 643 |
return authRole |
|---|
| 644 |
|
|---|
| 645 |
def getSelfRole(store): |
|---|
| 646 |
""" |
|---|
| 647 |
Retrieve the Role which corresponds to the user to whom the given store |
|---|
| 648 |
belongs. |
|---|
| 649 |
""" |
|---|
| 650 |
for (localpart, domain) in userbase.getAccountNames(store): |
|---|
| 651 |
return getPrimaryRole(store, u'%s@%s' % (localpart, domain), createIfNotFound=True) |
|---|
| 652 |
raise ValueError("Cannot get self-role for unnamed account.") |
|---|