from peak.api import * |
from peak.api import * |
from datetime import datetime |
from datetime import datetime |
from peak.util.imports import importString |
from peak.util.imports import importString |
import re |
import re, os, shutil, json, mimetypes |
|
from pkg_resources import ensure_directory |
|
dt = lambda d: datetime.fromordinal(d.toordinal()) |
|
dt.str = lambda d: d.strftime("%Y-%m-%d %H:%M:%S") |
|
|
FOG = re.compile('PTMFOG(\d+)(\.[A-Za-z]*)?') |
FOG = re.compile('PTMFOG(\d+)(\.[A-Za-z]*)?') |
|
BRACKET = re.compile(r'\[\[([^]]+)]]') |
|
|
def sql_equals(data, clauses=(), sep=' AND ', pre=' WHERE '): |
def sql_equals(data, clauses=(), sep=' AND ', pre=' WHERE '): |
clauses = [c for c in clauses if c] |
clauses = [c for c in clauses if c] |
def sqlWHERE(data, clauses=()): |
def sqlWHERE(data, clauses=()): |
return sql_equals(data, clauses) |
return sql_equals(data, clauses) |
|
|
|
try: |
|
sorted = sorted |
|
except NameError: |
|
def sorted(seq,key=None): |
|
if key: |
|
d = [(key(v),v) for v in seq] |
|
d.sort() |
|
if key: |
|
d = [v[1] for v in d] |
|
return d |
|
|
|
|
|
|
|
|
|
|
|
|
class SQL_DM(storage.EntityDM): |
class SQL_DM(storage.EntityDM): |
self.db('UPDATE '+tbl+sSet+sWhere, values+what) |
self.db('UPDATE '+tbl+sSet+sWhere, values+what) |
|
|
|
|
|
class Base(Persistent): |
|
class __metaclass__(binding.Activator, Persistent.__class__): pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Base(Persistent): |
|
class __metaclass__(binding.Activator, Persistent.__class__): pass |
|
|
|
class Item(Base): |
class Item(Base): |
"""An item that's editable in CityDesk""" |
"""An item that's editable in CityDesk""" |
|
|
+ self.ext |
+ self.ext |
) |
) |
|
|
def linkTo(self,tgt): |
def linkTo(self,tgt,root=None): |
|
if root: |
|
return root[:len(root)-root.endswith('/')]+'/'+tgt.path |
src, tgt = self.path.split('/'), tgt.path.split('/') |
src, tgt = self.path.split('/'), tgt.path.split('/') |
if src==tgt: |
if src==tgt: |
return tgt[-1] |
return tgt[-1] |
del src[0], tgt[0] |
del src[0], tgt[0] |
dots = len(src) - bool(tgt) |
dots = len(src) - bool(tgt) |
return '/'.join( ['..']*dots + tgt) |
return '/'.join( ['..']*dots + tgt) |
|
|
# (1) : (2/3/4) -> ../../1 src - 1 |
# (1) : (2/3/4) -> ../../1 src - 1 |
# 1/2 : 1/2/(3) -> .. src |
# 1/2 : 1/2/(3) -> .. src |
# 1/(2) : 1/(3) -> 2 src - 1 |
# 1/(2) : 1/(3) -> 2 src - 1 |
|
|
parent = None |
parent = None |
ext = '' # XXX |
ext = '' # XXX |
|
active = 1 |
|
|
|
def save(self, dir): |
|
dir = os.path.dirname(self.filename(dir)) |
|
if not os.path.isdir(dir): |
|
os.makedirs(dir) |
|
|
|
def filename(self, dir): |
|
return os.path.join(dir, *self.path.split('/')) |
|
|
|
def _write_file(self, dir, data, suffix=''): |
|
Item.save(self, dir) |
|
filename = self.filename(dir)+suffix |
|
if os.path.isfile(filename): |
|
old = open(filename,'rb').read() |
|
if old==str(data): |
|
return |
|
open(filename,'wb').write(data) |
|
|
|
def _dump(self, dir, metadata, data): |
|
metadata = json.dumps(metadata, sort_keys=True, indent=0) |
|
if is_text(self.path.split('/')[-1]): |
|
self._write_file(dir, |
|
"---\n%s\n---\n%s" % (metadata, data) |
|
) |
|
else: |
|
self._write_file(dir, data) |
|
self._write_file(dir, metadata, '.json') |
|
|
|
|
|
def is_text(filename): |
|
if filename=='.htaccess': |
|
return True |
|
mt = mimetypes.guess_type(filename, False)[0] |
|
if mt: |
|
return mt.startswith('text/') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Folder(Item): |
|
"""A Folder""" |
|
|
|
class File(Item): |
|
"""A File""" |
|
|
|
|
|
class Article(Item): |
class Article(Item): |
) |
) |
|
|
body = binding.Make(lambda self: str(self._article.sArticle).decode('utf16')) |
body = binding.Make(lambda self: str(self._article.sArticle).decode('utf16')) |
|
author = binding.Obtain("_article/sAuthor") |
teaser = binding.Obtain("_article/sTeaser") |
teaser = binding.Obtain("_article/sTeaser") |
sidebar = binding.Obtain("_article/sSidebar") |
sidebar = binding.Obtain("_article/sSidebar") |
about = binding.Obtain("_article/sAboutTheAuthor") |
about = binding.Obtain("_article/sAboutTheAuthor") |
extra1 = binding.Obtain("_article/sExtra1") |
extra1 = binding.Obtain("_article/sExtra1") |
extra2 = binding.Obtain("_article/sExtra2") |
extra2 = binding.Obtain("_article/sExtra2") |
|
|
# get the extension and 'kid' objects from the template |
# get the extension and 'kid' objects from the template |
kid = ext = binding.Delegate("template") |
kid = ext = binding.Delegate("template") |
|
|
def __call__(self, **kw): |
def __call__(self, **kw): |
text = str(self.template(my=self, **kw)) |
text = self.template(my=self, **kw).serialize(output=self.format) |
return FOG.sub(self.defog, text) |
return FOG.sub(self.defog, text) |
|
|
def defog(self, match): |
def defog(self, match, root=None): |
item = self._p_jar[int(match.group(1))] |
item = self._p_jar[int(match.group(1))] |
return self.linkTo(item) |
return self.linkTo(item, root) |
|
|
def active(self): |
def active(self): |
now = datetime.now() |
now = datetime.now() |
return ( |
return ( |
(self.effective is None or self.effective<=now) and |
(self.effective is None or dt(self.effective)<=now) and |
(self.retire is None or self.retire >=now) |
(self.retire is None or dt(self.retire) >=now) |
) |
) |
active = binding.Make(active) |
active = binding.Make(active) |
|
|
|
text = binding.Make(lambda self: FOG.sub(self.defog, self.body)) |
|
|
|
|
|
def teaser_for(self, tgt, root=None): |
|
return BRACKET.sub( |
|
lambda m: '<a href="%s#begin-post">%s</a>' % |
|
(self.linkTo(tgt,root), m.group(1)), |
|
tgt.teaser |
|
) |
|
|
|
def save(self, dir): |
|
self._write_file(dir, self()) |
|
|
|
format = binding.Make(lambda self: self.ext=='xml' and 'xml' or 'xhtml') |
|
|
|
def abstext(self, root=None): |
|
return FOG.sub(lambda t: self.defog(t,root), self.body) |
|
|
|
_dump_map = dict([(k,k) for k in [ |
|
"author", "teaser", "sidebar", "about", "extra1", "extra2", "headline", |
|
"audience", "keywords" |
|
]], **{"Fog-ID":"_p_oid"}) |
|
del k |
|
|
|
def dump(self, dir): |
|
Item.save(self, dir) |
|
#self._write_file(dir, self.body.encode('utf8')) |
|
data = dict([ |
|
(k, getattr(self,v)) for k,v in self._dump_map.items() |
|
if getattr(self,v) |
|
]) |
|
for k in ["effective", "retire", "filed", "modified"]: |
|
v = getattr(self, k, None) |
|
if v: |
|
data[k] = dt.str(v) |
|
data["template"] = self.template.name |
|
#data = json.dumps(data, sort_keys=True, indent=0) |
|
#data = '---\n%s\n---\n%s' % (data, self.body.encode('utf8')) |
|
self._dump(dir, data, self.body.encode('utf8')) #, '.json') |
|
|
|
|
|
|
|
|
|
|
|
class Folder(Item): |
|
"""A Folder""" |
|
|
|
def delete_orphans(self, dir, items=None, dump=False): |
|
"""Remove files or dirs that aren't in `items`""" |
|
if items is None: |
|
items = self._p_jar.where(ixParent=self._p_oid) |
|
|
|
# Build a mapping of known item file/dir names |
|
d = dict.fromkeys( |
|
[item.path.split('/')[-1] for item in items if item.parent is self] |
|
) |
|
|
|
if dump: |
|
for key in d.keys(): |
|
if not is_text(key): |
|
d[key+'.json'] = None |
|
|
|
filename = self.filename(dir) |
|
|
|
for f in os.listdir(filename): |
|
if f not in d: |
|
f = os.path.join(filename, f) |
|
if os.path.isdir(f) and not os.path.islink(f): |
|
shutil.rmtree(f) |
|
else: |
|
os.unlink(f) |
|
|
|
def save(self, dir): |
|
dir = self.filename(dir) |
|
if not os.path.isdir(dir): |
|
os.makedirs(dir) |
|
|
|
dump = save |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class File(Item): |
|
"""A File""" |
|
|
|
raw_data = binding.Make( |
|
lambda self: |
|
(~self._p_jar.db( |
|
"select oleFile from tblFiles WHERE ixStructure=?", (self._p_oid,) |
|
))[0] |
|
) |
|
|
|
def save(self, dir): |
|
Item.save(self, dir) |
|
data = self.raw_data |
|
if self.path.endswith('.html'): |
|
def defog(match, root=None): |
|
item = self._p_jar[int(match.group(1))] |
|
return self.linkTo(item, root) |
|
data = FOG.sub(defog, data) |
|
self._write_file(dir, data) |
|
|
|
def dump(self, dir): |
|
Item.save(self, dir) |
|
self._write_file(dir, self.raw_data) |
|
self._dump(dir, {"Fog-ID": self._p_oid}, self.raw_data) |
|
|
|
|
item_types = { |
item_types = { |
1.0: Folder, |
1.0: Folder, |
2.0: Article, |
2.0: Article, |
3.0: File, |
3.0: File, |
} |
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Template(Base): |
class Template(Base): |
"""A CityDesk template""" |
"""A CityDesk template""" |
|
|
kid = binding.Make( |
kid = binding.Make( |
lambda self: importString('kid.Template')( |
lambda self: self.load_template(str(self.body).replace('\000','')) |
source = str(self.body).replace('\000',''), |
|
).module.Template |
|
) |
) |
|
|
def __call__(self, **kw): |
name = binding.Make( |
return self.kid( |
lambda self:(~self._p_jar.db( |
templates = self._p_jar.templates, site = self._p_jar.vars, **kw |
"select * from tblTemplateStructure where ixTemplateStructure=?", (self._p_oid,) |
|
)).sName |
) |
) |
|
|
|
def __call__(self, **kw): |
|
return self.call_template(self.kid, **kw) |
|
|
|
call_template = load_template = binding.Delegate('_p_jar') |
|
|
|
|
class TemplateDM(SQL_DM): |
class TemplateDM(SQL_DM): |
resetStatesAfterTxn = False |
resetStatesAfterTxn = False |
defaultClass = Template |
defaultClass = Template |
def stateForRow(self, row): |
def stateForRow(self, row): |
return dict(ext = str(row.sExt), body = row.utf8Template) |
return dict(ext = str(row.sExt), body = row.utf8Template) |
|
|
vars = binding.Obtain('vars') |
cmd = binding.Obtain('..') |
templates = binding.Obtain('templates') |
call_template = load_template = binding.Delegate('cmd') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_select = """ |
_select = """ |
SELECT iType, ixItem, sName, sPublishAs, sKeywords, ixTemplateStructure, |
SELECT iType, ixItem, sName, sPublishAs, sKeywords, ixTemplateStructure, |
dtModified, dtFiled, dtEffective, dtRetire, ixParent, sHeadline, |
dtModified, dtFiled, dtEffective, dtRetire, ixParent, sHeadline, |
ixArticle, i.ixStructure |
ixArticle, ixAudience, i.ixStructure |
FROM (tblStructure i LEFT OUTER JOIN tblArticleSet s ON s.ixSet=i.ixItem) |
FROM (tblStructure i LEFT OUTER JOIN tblArticleSet s ON s.ixSet=i.ixItem) |
LEFT OUTER JOIN tblArticle a ON a.ixSet=s.ixSet |
LEFT OUTER JOIN tblArticle a ON a.ixSet=s.ixSet |
""" |
""" |
|
|
def stateForRow(self, row): |
def stateForRow(self, row): |
state = dict( |
state = dict( |
_itemType = row.iType, |
_itemType = row.iType, _itemNo = row.ixItem, |
_itemNo = row.ixItem, |
name = row.sName, publishAs = str(row.sPublishAs), |
name = row.sName, |
|
publishAs = str(row.sPublishAs), |
|
keywords = row.sKeywords, |
keywords = row.sKeywords, |
filed = row.dtFiled, |
filed = row.dtFiled, |
effective = row.dtEffective, |
effective = row.dtEffective, |
modified = row.dtModified, |
modified = row.dtModified, |
headline = row.sHeadline, |
headline = row.sHeadline, |
_articleNo = row.ixArticle, |
_articleNo = row.ixArticle, |
|
audience = self.audiences[row.ixAudience] |
) |
) |
if row.ixTemplateStructure: |
if row.ixTemplateStructure: |
state['template'] = self.templateDM[row.ixTemplateStructure] |
state['template'] = self.templateDM[row.ixTemplateStructure] |
state['parent'] = self[row.ixParent] |
state['parent'] = self[row.ixParent] |
return state |
return state |
|
|
|
|
def rowsForOb(self, ob): |
def rowsForOb(self, ob): |
yield ( |
yield ( |
'tblStructure', |
'tblStructure', |
dict(ixArticle=ob._articleNo) |
dict(ixArticle=ob._articleNo) |
) |
) |
|
|
|
audiences = binding.Make( |
|
lambda self: dict( |
|
list(self.db( |
|
"SELECT ixAudience,sAudience FROM tblAudience WHERE fDeleted=0" |
|
)) + [(0, "(Everyone)"), (None, None)] |
|
) |
|
) |
|
|
|
|
|
|