def inner(parent_id, latest, kind, tag, values, kw): if kind is not None: kind = kind.lower().split(',') if values is None: values = 'name', 'title', 'description', 'path' else: values = values.split(',') for i in values: if i not in ('name', 'title', 'description', 'path', 'kind', 'rendered', 'tags', 'children', 'descendants'): return 'json:', dict(status="error", message="Forbidden.") children = [] def criteria(child): if kind is not None and child.__class__.__name__.lower() not in kind: return False if tag is not None: if tag[0] != '!' and tag not in child.tags: return False elif tag[0] == '!' and tag in child.tags: return False if not child.controller.allowed: return False return True try: for child in parent.children: if not criteria(child): continue data = dict() for value in values: if value == 'kind': data[value] = child.__class__.__name__.lower() continue if value == 'children': # We won't return the actual child objects… just if we have children or not. data[value] = bool(len(Asset.objects(parent=child).only('id'))) continue if value == 'descendants': # Match child objects that also meet this search criteria. data[value] = bool([i for i in Asset.objects(parent=child).only('acl', 'parent', 'parents', 'tags') if criteria(i)]) continue data[value] = getattr(child, value, '') children.append(data) except: log.exception("Error iterating children.") return 'json:', dict(status="error", message="Error determining asset contents.") return 'json:', dict(status="success", children=children)
def __lookup__(self, *remainder, **kw): from web.extras.contentment.components.asset.model import Asset asset = self.asset if not remainder: return self, [asset.default] remainder = list(remainder) if asset.path == '/' and remainder == ['sitemap.xml']: return self, ['sitemap_xml'] if web.core.request.script_name == '': # Path-based lookup, used only when starting from the site root. paths = [] for i in xrange(1, len(remainder) + (0 if ':' in remainder[-1] else 1)): paths.append('/' + '/'.join(remainder[:i])) nearest = Asset.objects(path__in=paths).order_by('-path').first() if nearest: consumed = paths.index(nearest.path) + 1 remainder = remainder[consumed:] for i in range(consumed): web.core.request.path_info_pop() return nearest.controller, remainder if remainder else [nearest.default] # Attribute-based lookup. node = remainder.pop(0).replace('%3A', ':') log.debug("Looking in %r for %r *%r...", self, node, remainder) if ":" in node: return self, [node.replace(":", "_")] + list(remainder) if isinstance(node, basestring): record = Asset.objects(name=node, parent=asset).first() if not record: raise http.HTTPNotFound("Unable to find resource at this location.") web.core.request.path_info_pop() return record.controller, remainder if remainder else [record.default] raise http.HTTPNotFound("Unable to find resource at this location.")
def iter_templates(): from web.extras.contentment.components.asset.model import Asset container = Asset.objects(path='/settings/templates/custom').first() assert container yield ':none', "No Template" yield '', "Default Template" custom = Asset.objects(parent=container).order_by('title') if len(custom): yield "Custom Templates", [(template.path, template.title) for template in custom]
def native(self, value): from web.extras.contentment.components.asset.model import Asset value = super(AssetPathTransform, self).native(value) if value is None: return None return Asset.objects(path=value).first()
def inner(self, *args, **kw): if not authorized(self.asset): raise web.core.http.HTTPUnauthorized template = "json:" data = f(self, *args, **kw) if not isinstance(data, tuple): return data template, data = data if data is None: data = dict() root = data['root'] = Asset.objects(path='/').first() asset = data['asset'] = self.asset if not asset.template: data['template'] = root.properties.get('org-contentment-default-template', root.properties['org-contentment-theme'] + '.templates.master') elif asset.template == ':none': data['template'] = root.properties['org-contentment-theme'] + '.templates.master' if data.get('template', asset.template)[0] == '/': template_ = Asset.objects(path=data.get('template', asset.template)).first() if template_: fh = tempfile.NamedTemporaryFile(mode='wb', prefix='contentment-template-', suffix='.html', delete=False) fh.write('<%inherit file="' + root.properties['org-contentment-theme'] + '.templates.master"/>\n' + template_.content) fh.close() data['template'] = fh.name else: data['template'] = root.properties['org-contentment-theme'] + '.templates.master' base = '.'.join(f.__module__.split('.')[:-1]) + '.templates.' # TODO: Allow overriding of templates by the theme. return 'mako:' + base + template, data
def asset(self): from web.extras.contentment.components.asset.model import Asset identifier = self._identifier try: if isinstance(identifier, Asset): return identifier elif identifier is not None: return Asset.objects.with_id(identifier) return Asset.objects(path='/').first() except: log.exception("Error loading model instance for %r instance using %r.", self.__class__.__name__, identifier) raise http.HTTPNotFound("Unable to find resource at this location.")
def bulk(self, upload): f = File() f.title, _, _ = upload.filename.rpartition('.') f.content = upload.file f.content.content_type = f.mimetype = upload.type f.content.filename = f.filename = upload.filename f.size = upload.length siblings = [i.name for i in Asset.objects(parent=self.controller.asset).only('name')] f.name = normalize(f.title.lower(), siblings) f.save() f.attach(self.controller.asset) return 'json:', dict()
def cache(name, date): from web.extras.contentment.components.page import engines engine = engines[self.engine] content = engine(self.content) # Determine substitutions and associated asset dates. replacements = [] if engine.replacements: find = self.content.find index = find('${') while index != -1: newline = find('\n', index) close = find('}', index) if close == -1 or (newline != -1 and newline < close): return u"""<div class="error">Error parsing includes.</div>""" replacements.append(self.content[index:close+1]) index = find('${', close) if replacements: from web.extras.contentment.components.asset.model import Asset for replacement in replacements: path, _, args = replacement[2:-1].partition(' ') embedded = Asset.objects(path=path).first() if not embedded: content = content.replace(replacement, u"""<span class="error">Error looking up embedded asset with path <tt>%s</tt>.</span>""" % (path, )) # TODO: There has to be a better way to do this that accounts for quoted strings. args = dict([(i.split('=')[0], i.split('=')[1]) for i in args.split()]) try: # embedded = u'''<div class="embed" id="%s-embed">%s</div>''' % (embedded.name, unicode(embedded.embed(**args))) content = content.replace(replacement, unicode(embedded.embed(**args))) except: log.exception("Error while embedding.") content = content.replace(replacement, u"""<span class="error">Error embedding asset with path <tt>%s</tt>.</span>""" % (path, )) return content
def native(self, value): from web.extras.contentment.components.asset.model import Asset value = super(AssetListTransform, self).native(value) if value is None: return [] result = [] try: for i in value.split(','): i = i.strip() result.append(Asset.objects(path=i).first()) except: log.exception("Error.") raise TransformException() return result
def do(asset): try: asset = Asset.objects(id=asset).first() if not asset: return '' except: return num if hasattr(asset, 'content') and asset.content is None: print("missing content... ", end="") asset.content = '' try: asset.reindex() except: return asset.path asset.save() return asset.path
def gen(): for record in Asset.objects(**aquery).only('title', 'description', 'path', 'acl').order_by('created'): yield 1.0, record
def iterresults(): for score, id_ in results: yield score, Asset.objects(id=id_, **aquery).only('title', 'description', 'path', 'acl').first()
def __call__(self, value): from web.extras.contentment.components.asset.model import Asset if not value: return u'' return u", ".join([Asset.objects(id=i.id).first().path for i in value])
def post(self): print("Bootstrapping default site content...") from web.extras.contentment.components.asset.model import db, Asset, AdvancedACLRule, OwnerACLRule, UserACLRule, AllUsersACLRule, InheritACLRules from web.extras.contentment.components.page.model import Page from web.extras.contentment.components.folder.model import Folder from web.extras.contentment.components.identity.model import PasswordCredential, Identity from web.extras.contentment.components.authenticator.model import Authenticator from web.extras.contentment.components.search.model import Search from web.extras.contentment.components.settings.model import Settings from web.extras.contentment.themes.default.model import DefaultTheme settings = self.options scheme, parts = settings.db.split('://', 1) parts, db = parts.split('/', 1) auth, host = parts.split('@', 1) if '@' in parts else (None, parts) connection = dict() connection[b'host'], connection[b'port'] = host.split(':') if ':' in host else (host, '27017') connection[b'port'] = int(connection[b'port']) if auth: connection[b'username'], _, connection[b'password'] = auth.partition(':') mongoengine.connect(db, **connection) if Asset.objects().count() > 0: print("Existing content found, bootstrap cancelled.") # TODO: Interactively ask to overwrite data. return root = Asset(name="", path="/", title=settings['title'], default="default", immutable=True, properties={ 'org-contentment-formats-date': '%B %e, %G at %H:%M:%S', 'org-contentment-theme': 'web.extras.contentment.themes.default', 'org-contentment-option-attribution': True, 'org-contentment-option-showdates': True, 'org-contentment-lang': 'en', 'org-contentment-cache': True }) admin = Identity(name=settings['admin.name'], title=settings['admin.title'], email=settings['admin.email']) ; admin.save() root.acl.append(AdvancedACLRule(allow=False, permission="action:delete", attributes={'immutable': True})) root.acl.append(OwnerACLRule(allow=True, permission="*")) root.acl.append(UserACLRule(allow=True, permission="*", reference=admin)) root.acl.append(AllUsersACLRule(allow=False, permission="view:acl")) root.acl.append(InheritACLRules()) root.acl.append(AllUsersACLRule(allow=True, permission="view:*")) root.acl.append(AllUsersACLRule(allow=False, permission="*")) root.save() settings_ = Settings(name="settings", title="Site Settings", immutable=True, default="view:contents") settings_.acl.append(UserACLRule(allow=False, permission="*", inverse=True, reference=admin)) settings_.save() ; settings_.attach(root) extensions = Folder(name="extensions", title="Site Extensions", immutable=True) ; extensions.save() ; extensions.attach(settings_) templates = Folder(name="templates", title="Site Templates", immutable=True) ; templates.save() ; templates.attach(settings_) custom = Folder(name="custom", title="Custom Page Templates", immutable=True) ; custom.save() ; custom.attach(templates) theme = DefaultTheme(name="theme", title="Default Theme", immutable=True); theme.save() ; theme.attach(root) users = Authenticator(name="users", title="Users", immutable=True) users.acl.append(AllUsersACLRule(allow=True, permission="action:authenticate")) users.acl.append(AllUsersACLRule(allow=True, permission="action:expire")) users.save() ; users.attach(root) password = PasswordCredential(identity=settings['admin.name']) password.password = settings['admin.password'] admin.credentials.append(password) ; admin.save() ; admin.attach(users) header = Page(name="header", title="Global Site Header", content="""h1. "%s":/""" % (settings['title'], )) ; header.save() ; header.attach(templates) menu = Page(name="menu", title="Main Menu", engine="raw", content=""" <menu class="container"> <li class="nav-default"><a href="/">Home<br><label> </label></a></li ><li class="nav-start"><a href="/start">Get Started<br><label>Getting Started with Contentment</label></a></li> </menu>""") ; menu.save() ; menu.attach(templates) footer = Page(name="footer", title="Global Site Footer", engine="raw", content="""<p class="fr">© %s %s</p> <menu> <li><a href="/about">About the Site</a></li ><li><a href="/about/privacy">Privacy Policy</a></li ><li><a href="/about/colophon">Colophon</a></li> </menu>""" % (datetime.datetime.now().year, settings['admin.title'])) ; footer.save() ; footer.attach(templates) search = Search(name="search", title="Site Search") ; search.save() ; search.attach(root) default = Page(name="default", title="Welcome", owner=admin, content="""h1. Welcome to Contentment Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.""" ) ; default.save() ; default.attach(root) print("Finished bootstrapping the default site.")
def getChildren(self, kind=None, tag=None, values=None, **kw): request = web.core.request response = web.core.response parent = self.controller.asset _latest = Asset.objects(parent=parent).only('created', 'modified') latest = None for i in _latest: if latest is None or i.created > latest: latest = i.created if i.modified and i.modified > latest: latest = i.modified response.last_modified = latest response.cache_control = 'public' response.etag = "%d" % ( int(time.mktime(latest.timetuple())) if latest else 0, ) if latest and request.if_modified_since and request.if_modified_since >= response.last_modified: raise web.core.http.HTTPNotModified() if response.etag in request.if_none_match: raise web.core.http.HTTPNotModified() # The scanning (esp. security) is difficult to compute. # So we cache the result based on the above arguments and modification times. # TODO: Cached for one day; this should be configurable. # Initial timings (with debug logging enabled!) on the following complex query: # kind=gallery & values=title,path & tag=!nav:hidden # W/o cache: ~50/s, w/ cache: ~72/s. @web.core.cache.cache('page.content', expires=86400) def inner(parent_id, latest, kind, tag, values, kw): if kind is not None: kind = kind.lower().split(',') if values is None: values = 'name', 'title', 'description', 'path' else: values = values.split(',') for i in values: if i not in ('name', 'title', 'description', 'path', 'kind', 'rendered', 'tags', 'children', 'descendants'): return 'json:', dict(status="error", message="Forbidden.") children = [] def criteria(child): if kind is not None and child.__class__.__name__.lower() not in kind: return False if tag is not None: if tag[0] != '!' and tag not in child.tags: return False elif tag[0] == '!' and tag in child.tags: return False if not child.controller.allowed: return False return True try: for child in parent.children: if not criteria(child): continue data = dict() for value in values: if value == 'kind': data[value] = child.__class__.__name__.lower() continue if value == 'children': # We won't return the actual child objects… just if we have children or not. data[value] = bool(len(Asset.objects(parent=child).only('id'))) continue if value == 'descendants': # Match child objects that also meet this search criteria. data[value] = bool([i for i in Asset.objects(parent=child).only('acl', 'parent', 'parents', 'tags') if criteria(i)]) continue data[value] = getattr(child, value, '') children.append(data) except: log.exception("Error iterating children.") return 'json:', dict(status="error", message="Error determining asset contents.") return 'json:', dict(status="success", children=children) return inner(str(parent.id), latest, kind, tag, values, kw)
def _save(self, action, asset, data): from web.extras.contentment.components.asset.model import Asset form = self._form(asset, web.core.request.referrer if action == 'create' else None, action.title()) if not data: return action, dict(kind=asset.__class__.__name__, form=form, data=asset.prepare()) data.pop('submit', None) try: result, remaining = form.native(data) except: if web.core.config.get('debug', False): raise log.exception("Error processing form.") web.core.session['flash'] = dict(cls="error", title="Server Error", message="Unable to create asset; see system log for details." % (asset.__class__.__name__, asset.title, asset.path)) return action, dict(kind=asset.__class__.__name__, form=form, data=asset.prepare()) # Root node must not be renamed. if asset.path == '/': del result['name'] dirty = [] siblings = [i.name for i in Asset.objects(parent=self.asset if action=="create" else self.asset.parent).only('name')] if asset.path != '/' and ( 'name' not in result or not result['name'] ): result['name'] = normalize(result['title'].lower(), siblings) elif asset.path != '/' and action != "create": result['name'] = normalize(result['name'].lower(), [i for i in siblings if i != asset.name]) elif asset.path != '/' and action == "create": result['name'] = normalize(result['name'].lower(), siblings) del siblings if result.get('tags', None) is None: result['tags'] = [] # Handle explicit setting of the modification time. if action == 'modify' and ( not result['modified'] or result['modified'] == asset.modified.replace(microsecond=0) ): result['modified'] = datetime.now(UTC) result = asset.process(result) for name, value in result.iteritems(): if action == 'modify' and getattr(asset, name) == value: continue dirty.append(name) setattr(asset, name, value) try: asset.save(dirty=dirty) except: if web.core.config.get('debug', False): raise log.exception('Error saving record.') web.core.session['flash'] = dict(cls="error", title="Server Error", message="Unable to save asset; see system log for details." % (asset.__class__.__name__, asset.title, asset.path)) return action, dict(kind=asset.__class__.__name__, form=form, data=asset.prepare()) if action == 'create': asset.attach(self.asset)