Piki is a minimal wiki
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

page.py 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from django.conf import settings
  2. import fstools
  3. import json
  4. import logging
  5. from pages import messages, url_page
  6. import mycreole
  7. import os
  8. import time
  9. logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__)
  10. class meta_data(dict):
  11. KEY_CREATION_TIME = "creation_time"
  12. KEY_MODIFIED_TIME = "modified_time"
  13. KEY_MODIFIED_USER = "modified_user"
  14. def __init__(self, meta_filename, page_exists):
  15. self._meta_filename = meta_filename
  16. # Load data from disk
  17. try:
  18. with open(meta_filename, 'r') as fh:
  19. super().__init__(json.load(fh))
  20. except (FileNotFoundError, json.decoder.JSONDecodeError) as e:
  21. super().__init__()
  22. # Add missing information to meta_data
  23. missing_keys = False
  24. if self.KEY_CREATION_TIME not in self:
  25. missing_keys = True
  26. self[self.KEY_CREATION_TIME] = int(time.time())
  27. if self.KEY_MODIFIED_TIME not in self:
  28. self[self.KEY_MODIFIED_TIME] = self[self.KEY_CREATION_TIME]
  29. if missing_keys and page_exists:
  30. self.save()
  31. def update(self, username):
  32. self[self.KEY_MODIFIED_TIME] = int(time.time())
  33. self[self.KEY_MODIFIED_USER] = username
  34. #
  35. self.save()
  36. def save(self):
  37. with open(self._meta_filename, 'w') as fh:
  38. json.dump(self, fh, indent=4)
  39. class base_page(object):
  40. PAGE_FILE_NAME = 'page'
  41. META_FILE_NAME = 'meta.json'
  42. SPLITCHAR = ":"
  43. def __init__(self, path):
  44. if path.startswith(settings.PAGES_ROOT):
  45. self._path = path
  46. else:
  47. self._path = os.path.join(settings.PAGES_ROOT, path.replace("/", 2*self.SPLITCHAR))
  48. self._raw_page_src = None
  49. #
  50. self._meta_data = meta_data(self._meta_filename, self.is_available())
  51. def _load_page_src(self):
  52. if self._raw_page_src is None:
  53. try:
  54. with open(self.filename, 'r') as fh:
  55. self._raw_page_src = fh.read()
  56. except FileNotFoundError:
  57. self._raw_page_src = ""
  58. def update_page(self, page_txt):
  59. from .search import update_item
  60. #
  61. folder = os.path.dirname(self.filename)
  62. if not os.path.exists(folder):
  63. fstools.mkdir(folder)
  64. with open(self.filename, 'w') as fh:
  65. fh.write(page_txt)
  66. update_item(self)
  67. #
  68. self._update_metadata()
  69. @property
  70. def filename(self):
  71. return os.path.join(self._path, self.PAGE_FILE_NAME)
  72. @property
  73. def _meta_filename(self):
  74. return os.path.join(self._path, self.META_FILE_NAME)
  75. @property
  76. def rel_path(self):
  77. return os.path.basename(self._path).replace(2*self.SPLITCHAR, "/")
  78. def rel_path_is_valid(self):
  79. return not self.SPLITCHAR in self.rel_path
  80. def is_available(self):
  81. is_a = os.path.isfile(self.filename)
  82. if not is_a:
  83. logger.info("page.is_available: Not available - %s", self.filename)
  84. return is_a
  85. @property
  86. def title(self):
  87. return os.path.basename(self._path).split("::")[-1]
  88. @property
  89. def raw_page_src(self):
  90. self._load_page_src()
  91. return self._raw_page_src
  92. def _update_metadata(self):
  93. username = None
  94. try:
  95. if self._request.user.is_authenticated:
  96. username = self._request.user.username
  97. else:
  98. logger.warning("Page edit without having a logged in user. This is not recommended. Check your access definitions!")
  99. except AttributeError:
  100. logger.exception("Page edit without having a request object. Check programming!")
  101. self._meta_data.update(username)
  102. class creole_page(base_page):
  103. FOLDER_ATTACHMENTS = "attachments"
  104. def __init__(self, request, path) -> None:
  105. self._request = request
  106. super().__init__(path)
  107. @property
  108. def attachment_path(self):
  109. return os.path.join(os.path.basename(self._path), self.FOLDER_ATTACHMENTS)
  110. def render_to_html(self):
  111. if self.is_available():
  112. return self.render_text(self._request, self.raw_page_src)
  113. else:
  114. messages.unavailable_msg_page(self._request, self.rel_path)
  115. return ""
  116. def render_text(self, request, txt):
  117. macros = {
  118. "subpages": self.macro_subpages,
  119. "allpages": self.macro_allpages,
  120. }
  121. return mycreole.render(request, txt, self.attachment_path, macros=macros)
  122. def macro_allpages(self, *args, **kwargs):
  123. kwargs["allpages"] = True
  124. return self.macro_subpages(*args, **kwargs)
  125. def macro_subpages(self, *args, **kwargs):
  126. allpages = kwargs.pop("allpages", False)
  127. #
  128. def parse_depth(s: str):
  129. try:
  130. return int(s)
  131. except ValueError:
  132. pass
  133. params = kwargs.get('', '')
  134. startname = ''
  135. depth = parse_depth(params)
  136. if depth is None:
  137. params = params.split(",")
  138. depth = parse_depth(params[0])
  139. if len(params) == 2:
  140. startname = params[1]
  141. elif depth is None:
  142. startname = params[0]
  143. if depth is None:
  144. depth = 9999
  145. #
  146. rv = ""
  147. # create a rel_path list
  148. pathlist = [base_page(path).rel_path for path in fstools.dirlist(settings.PAGES_ROOT, rekursive=False)]
  149. # sort basename
  150. pathlist.sort(key=os.path.basename)
  151. last_char = None
  152. for contentname in pathlist:
  153. #
  154. if (contentname.startswith(self.rel_path) or allpages) and contentname != self.rel_path:
  155. if allpages:
  156. name = contentname
  157. else:
  158. name = contentname[len(self.rel_path)+1:]
  159. if name.count('/') < depth and name.startswith(startname):
  160. if last_char != os.path.basename(name)[0].upper():
  161. last_char = os.path.basename(name)[0].upper()
  162. if last_char is not None:
  163. rv += "</ul>\n"
  164. rv += f'<h3>{last_char}</h3>\n<ul>\n'
  165. rv += f' <li><a href="{url_page(self._request, contentname)}">{name}</a></li>\n'
  166. if len(rv) > 0:
  167. rv += "</ul>\n"
  168. return rv