Initial piki commit with first rough functionality
This commit is contained in:
parent
96a348330a
commit
eec925eef1
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
|
# piki
|
||||||
|
data/pages
|
||||||
|
data/static
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
12
.gitmodules
vendored
Normal file
12
.gitmodules
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[submodule "fstools"]
|
||||||
|
path = fstools
|
||||||
|
url = https://git.mount-mockery.de/pylib/fstools.git
|
||||||
|
[submodule "mycreole"]
|
||||||
|
path = mycreole
|
||||||
|
url = https://git.mount-mockery.de/django_lib/mycreole.git
|
||||||
|
[submodule "themes"]
|
||||||
|
path = themes
|
||||||
|
url = https://git.mount-mockery.de/django_lib/themes.git
|
||||||
|
[submodule "users"]
|
||||||
|
path = users
|
||||||
|
url = https://git.mount-mockery.de/django_lib/users.git
|
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
|
||||||
|
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
|
||||||
|
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: Main File execution",
|
||||||
|
"type": "debugpy",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/manage.py",
|
||||||
|
"args": ["runserver"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
14
.vscode/settings.json
vendored
Normal file
14
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"python.defaultInterpreterPath": "./venv/bin/python",
|
||||||
|
"autopep8.args": ["--max-line-length=150"],
|
||||||
|
"[python]": {
|
||||||
|
"python.formatting.provider": "none",
|
||||||
|
"editor.defaultFormatter": "ms-python.autopep8",
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
},
|
||||||
|
"editor.fontSize": 14,
|
||||||
|
"emmet.includeLanguages": { "django-html": "html" },
|
||||||
|
"python.testing.pytestArgs": ["-v", "--cov", "--cov-report=xml", "__test__"],
|
||||||
|
"python.testing.unittestEnabled": false,
|
||||||
|
"python.testing.pytestEnabled": true
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
# piki
|
# piki
|
||||||
|
|
||||||
Piki is a minimal wiki
|
Piki is a minimal wiki
|
||||||
|
|
||||||
|
# TODO: Add an installation instruction
|
||||||
|
12
config.py
Normal file
12
config.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import os
|
||||||
|
#
|
||||||
|
#
|
||||||
|
APP_NAME = "piki"
|
||||||
|
STARTPAGE = "startpage"
|
||||||
|
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
#
|
||||||
|
SECRET_KEY = "*+=wy%2lo2^@fxxtmx0)14x507%6v73ke-%24%_fb6f+3h^c)-"
|
||||||
|
#
|
BIN
data/media/theme/logo.png
Normal file
BIN
data/media/theme/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
1
fstools
Submodule
1
fstools
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit c10e8792abb05671dab6de51cdadda3bf8ead50f
|
22
manage.py
Executable file
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'piki.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
1
mycreole
Submodule
1
mycreole
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 057388e3b44b5fe42b133fe030c41aa6a254de58
|
9
pages/__init__.py
Normal file
9
pages/__init__.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.urls.base import reverse
|
||||||
|
|
||||||
|
|
||||||
|
def url_page(request, rel_path):
|
||||||
|
return reverse('pages-pages', kwargs={'rel_path': rel_path})
|
||||||
|
|
||||||
|
|
||||||
|
def url_helpview(request, page):
|
||||||
|
return reverse('pages-helpview', kwargs={'page': page})
|
8
pages/access.py
Normal file
8
pages/access.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# TODO: Implement access control for pages
|
||||||
|
|
||||||
|
def read_attachment(request, rel_path):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def modify_attachment(request, rel_path):
|
||||||
|
return True
|
3
pages/admin.py
Normal file
3
pages/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
pages/apps.py
Normal file
6
pages/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PagesConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'pages'
|
289
pages/context.py
Normal file
289
pages/context.py
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
|
||||||
|
from .help import actionbar as actionbar_add_help
|
||||||
|
import mycreole
|
||||||
|
import pages
|
||||||
|
from themes import empty_entry_parameters, gray_icon_url, color_icon_url
|
||||||
|
from users.context import menubar as menubar_users
|
||||||
|
|
||||||
|
try:
|
||||||
|
from config import APP_NAME as ROOT_LOGGER_NAME
|
||||||
|
except ImportError:
|
||||||
|
ROOT_LOGGER_NAME = 'root'
|
||||||
|
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
|
||||||
|
|
||||||
|
ATTACHMENT_UID = 'attachment'
|
||||||
|
BACK_UID = 'back'
|
||||||
|
HELP_UID = 'help'
|
||||||
|
|
||||||
|
|
||||||
|
def context_adaption(context, request, **kwargs):
|
||||||
|
caller_name = inspect.currentframe().f_back.f_code.co_name
|
||||||
|
try:
|
||||||
|
context.set_additional_title(kwargs.pop('title'))
|
||||||
|
except KeyError:
|
||||||
|
pass # no title in kwargs
|
||||||
|
menubar_users(context[context.MENUBAR], request)
|
||||||
|
menubar(context, request, caller_name, **kwargs)
|
||||||
|
actionbar(context, request, caller_name, **kwargs)
|
||||||
|
navigationbar(context, request, caller_name, **kwargs)
|
||||||
|
for key in kwargs:
|
||||||
|
context[key] = kwargs[key]
|
||||||
|
logger.debug("context adapted: %s", repr(context))
|
||||||
|
|
||||||
|
|
||||||
|
def navigationbar(context, request, caller_name, **kwargs):
|
||||||
|
bar = context[context.NAVIGATIONBAR]
|
||||||
|
add_back_menu(request, bar)
|
||||||
|
# TODO: Add the pages navigation, if source is pages
|
||||||
|
finalise_bar(request, bar)
|
||||||
|
|
||||||
|
|
||||||
|
def add_back_menu(request, bar):
|
||||||
|
bar.append_entry(
|
||||||
|
BACK_UID, # uid
|
||||||
|
_('Back'), # name
|
||||||
|
gray_icon_url(request, 'back.png'), # icon
|
||||||
|
'javascript:history.back()', # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def menubar(context, request, caller_name, **kwargs):
|
||||||
|
bar = context[context.MENUBAR]
|
||||||
|
# replace_profile(request, bar)
|
||||||
|
add_help_menu(request, bar)
|
||||||
|
# add_tasklist_menu(request, bar)
|
||||||
|
# add_filter_submenu(request, bar, VIEW_TASKLIST_UID)
|
||||||
|
# add_projectlist_menu(request, bar)
|
||||||
|
# add_printview_menu(request, bar)
|
||||||
|
finalise_bar(request, bar)
|
||||||
|
|
||||||
|
|
||||||
|
def add_help_menu(request, bar):
|
||||||
|
bar.append_entry(
|
||||||
|
HELP_UID, # uid
|
||||||
|
_('Help'), # name
|
||||||
|
color_icon_url(request, 'help.png'), # icon
|
||||||
|
pages.url_helpview(request, 'main'), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def actionbar(context, request, caller_name, **kwargs):
|
||||||
|
bar = context[context.ACTIONBAR]
|
||||||
|
if caller_name == 'pages':
|
||||||
|
# acc = acc_task(kwargs['task'], request.user)
|
||||||
|
# if acc.modify or acc.modify_limited:
|
||||||
|
# add_edittask_menu(request, bar, kwargs['task'].id)
|
||||||
|
# if acc.add_comments:
|
||||||
|
# add_newcomment_menu(request, bar, kwargs['task'].id)
|
||||||
|
add_manageupload_menu(request, bar, kwargs['upload_path'])
|
||||||
|
elif caller_name == 'helpview':
|
||||||
|
actionbar_add_help(context, request, **kwargs)
|
||||||
|
finalise_bar(request, bar)
|
||||||
|
|
||||||
|
|
||||||
|
def add_manageupload_menu(request, bar, upload_path):
|
||||||
|
bar.append_entry(
|
||||||
|
ATTACHMENT_UID, # uid
|
||||||
|
_("Attachments"), # name
|
||||||
|
color_icon_url(request, 'upload.png'), # icon
|
||||||
|
mycreole.url_manage_uploads(request, upload_path), # url
|
||||||
|
True, # left
|
||||||
|
False, # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def finalise_bar(request, bar):
|
||||||
|
if len(bar) == 0:
|
||||||
|
bar.append_entry(*empty_entry_parameters(request))
|
||||||
|
|
||||||
|
|
||||||
|
"""from .access import create_project_possible, create_task_possible, acc_task
|
||||||
|
from django.db.models.functions import Lower
|
||||||
|
import patt
|
||||||
|
from .search import common_searches
|
||||||
|
from themes import empty_entry_parameters, color_icon_url, gray_icon_url
|
||||||
|
from users.context import PROFILE_ENTRY_UID
|
||||||
|
|
||||||
|
|
||||||
|
COMMENTNEW_UID = 'commentnew'
|
||||||
|
CREATE_PROJECT_UID = 'create-project'
|
||||||
|
CREATE_TASK_UID = 'create-task'
|
||||||
|
PRINTVIEW_UID = 'printview'
|
||||||
|
TASKEDIT_UID = 'taskedit'
|
||||||
|
VIEW_PROJECTLIST_UID = 'view-projectlist'
|
||||||
|
VIEW_TASKLIST_UID = 'view-tasklist'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def replace_profile(request, bar):
|
||||||
|
try:
|
||||||
|
bar.replace_entry(
|
||||||
|
PROFILE_ENTRY_UID,
|
||||||
|
PROFILE_ENTRY_UID, # uid
|
||||||
|
request.user.username, # name
|
||||||
|
color_icon_url(request, 'user.png'), # icon
|
||||||
|
patt.url_profile(request), # url
|
||||||
|
False, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
pass # Profile entry does not exist, so exchange is not needed (e.g. no user is logged in)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_tasklist_menu(request, bar):
|
||||||
|
bar.append_entry(
|
||||||
|
VIEW_TASKLIST_UID, # uid
|
||||||
|
_('Tasklist'), # name
|
||||||
|
color_icon_url(request, 'task.png'), # icon
|
||||||
|
patt.url_tasklist(request), # url
|
||||||
|
True, # left
|
||||||
|
patt.is_tasklistview(request) # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_projectlist_menu(request, bar):
|
||||||
|
bar.append_entry(
|
||||||
|
VIEW_PROJECTLIST_UID, # uid
|
||||||
|
_('Projectlist'), # name
|
||||||
|
color_icon_url(request, 'folder.png'), # icon
|
||||||
|
patt.url_projectlist(request), # url
|
||||||
|
True, # left
|
||||||
|
patt.is_projectlistview(request) # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_printview_menu(request, bar):
|
||||||
|
bar.append_entry(
|
||||||
|
PRINTVIEW_UID, # uid
|
||||||
|
_('Printview'), # name
|
||||||
|
color_icon_url(request, 'print.png'), # icon
|
||||||
|
patt.url_printview(request), # url
|
||||||
|
True, # left
|
||||||
|
patt.is_printview(request) # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_newtask_menu(request, bar, project_id):
|
||||||
|
bar.append_entry(
|
||||||
|
CREATE_TASK_UID, # uid
|
||||||
|
_('New Task'), # name
|
||||||
|
color_icon_url(request, 'plus.png'), # icon
|
||||||
|
patt.url_tasknew(request, project_id), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_edittask_menu(request, bar, task_id):
|
||||||
|
bar.append_entry(
|
||||||
|
TASKEDIT_UID, # uid
|
||||||
|
_('Edit'), # name
|
||||||
|
color_icon_url(request, 'edit.png'), # icon
|
||||||
|
patt.url_taskedit(request, task_id), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_newcomment_menu(request, bar, task_id):
|
||||||
|
bar.append_entry(
|
||||||
|
COMMENTNEW_UID, # uid
|
||||||
|
_('Add Comment'), # name
|
||||||
|
color_icon_url(request, 'edit2.png'), # icon
|
||||||
|
patt.url_commentnew(request, task_id), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_newproject_menu(request, bar):
|
||||||
|
bar.append_entry(
|
||||||
|
CREATE_PROJECT_UID, # uid
|
||||||
|
_('New Project'), # name
|
||||||
|
color_icon_url(request, 'plus.png'), # icon
|
||||||
|
patt.url_projectnew(request), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_filter_submenu(request, bar, menu_uid):
|
||||||
|
bar.append_entry_to_entry(
|
||||||
|
menu_uid,
|
||||||
|
menu_uid + '-easysearch', # uid
|
||||||
|
_('Easysearch'), # name
|
||||||
|
gray_icon_url(request, 'search.png'), # icon
|
||||||
|
patt.url_easysearch(request), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
if patt.get_search_query(request) is not None:
|
||||||
|
bar.append_entry_to_entry(
|
||||||
|
menu_uid,
|
||||||
|
menu_uid + '-save', # uid
|
||||||
|
_('Save Search as Filter'), # name
|
||||||
|
gray_icon_url(request, 'save.png'), # icon
|
||||||
|
patt.url_filteredit(request), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
bar.append_entry_to_entry(
|
||||||
|
menu_uid,
|
||||||
|
menu_uid + '-all', # uid
|
||||||
|
_('All Tasks'), # name
|
||||||
|
gray_icon_url(request, 'task.png'), # icon
|
||||||
|
patt.url_tasklist(request), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
cs = common_searches(request)
|
||||||
|
for common_filter_id in cs:
|
||||||
|
bar.append_entry_to_entry(
|
||||||
|
menu_uid,
|
||||||
|
menu_uid + '-common', # uid
|
||||||
|
_(cs[common_filter_id][0]), # name
|
||||||
|
gray_icon_url(request, 'filter.png'), # icon
|
||||||
|
patt.url_tasklist(request, common_filter_id=common_filter_id), # url
|
||||||
|
True, # left
|
||||||
|
False # active
|
||||||
|
)
|
||||||
|
for s in request.user.search_set.order_by(Lower('name')):
|
||||||
|
active = patt.is_tasklistview(request, s.id)
|
||||||
|
if active is True:
|
||||||
|
url = patt.url_filteredit(request, s.id)
|
||||||
|
else:
|
||||||
|
url = patt.url_tasklist(request, user_filter_id=s.id)
|
||||||
|
if active:
|
||||||
|
icon = 'settings.png'
|
||||||
|
else:
|
||||||
|
icon = 'favourite.png'
|
||||||
|
bar.append_entry_to_entry(
|
||||||
|
menu_uid,
|
||||||
|
menu_uid + '-sub', # uid
|
||||||
|
s.name, # name
|
||||||
|
gray_icon_url(request, icon), # icon
|
||||||
|
url, # url
|
||||||
|
True, # left
|
||||||
|
active # active
|
||||||
|
)
|
||||||
|
"""
|
27
pages/creole.py
Normal file
27
pages/creole.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django.urls.base import reverse
|
||||||
|
|
||||||
|
# TODO: Add a filter to show all subpages <<piki-subpages>> and add it to settings and help
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
def page_link_filter(text):
|
||||||
|
render_txt = ''
|
||||||
|
while len(text) > 0:
|
||||||
|
try:
|
||||||
|
pos = text.index('[[page:')
|
||||||
|
except ValueError:
|
||||||
|
pos = len(text)
|
||||||
|
print(pos)
|
||||||
|
render_txt += text[:pos]
|
||||||
|
text = text[pos + 7:]
|
||||||
|
if len(text):
|
||||||
|
pos = text.index(']]')
|
||||||
|
try:
|
||||||
|
rel_path = int(text[:pos])
|
||||||
|
except ValueError:
|
||||||
|
render_txt += "[[page:" + text[:pos + 2]
|
||||||
|
else:
|
||||||
|
render_txt += '[[%s|%s]]' % (reverse('pages-pages', kwargs={'rel_path': rel_path}), rel_path)
|
||||||
|
text = text[pos + 2:]
|
||||||
|
return render_txt
|
||||||
|
"""
|
58
pages/help.py
Normal file
58
pages/help.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from django.utils.translation import gettext as _
|
||||||
|
import mycreole
|
||||||
|
import pages
|
||||||
|
from themes import color_icon_url
|
||||||
|
|
||||||
|
|
||||||
|
HELP_UID = 'help'
|
||||||
|
|
||||||
|
MAIN = mycreole.render_simple(_(
|
||||||
|
"""
|
||||||
|
= Piki
|
||||||
|
|
||||||
|
**piki** is a minimal wiki implemented with python and django.
|
||||||
|
|
||||||
|
== Help
|
||||||
|
* [[creole|Creole Markup Language]]
|
||||||
|
* [[access|Access Control for the site content]]
|
||||||
|
* [[search|Help on Search]]
|
||||||
|
"""))
|
||||||
|
|
||||||
|
CREOLE = mycreole.mycreole_help_pagecontent()
|
||||||
|
CREOLE += mycreole.render_simple("""
|
||||||
|
= Piki Markup
|
||||||
|
{{{[[rel_path_to_page|Name]]}}} will result in a Link to the given wiki page.
|
||||||
|
""")
|
||||||
|
|
||||||
|
ACCESS = mycreole.render_simple(_("""
|
||||||
|
= TBD
|
||||||
|
"""))
|
||||||
|
|
||||||
|
SEARCH = mycreole.render_simple(_("""
|
||||||
|
= TBD
|
||||||
|
"""))
|
||||||
|
|
||||||
|
help_pages = {
|
||||||
|
'main': MAIN,
|
||||||
|
'creole': CREOLE,
|
||||||
|
'access': ACCESS,
|
||||||
|
'search': SEARCH,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def actionbar(context, request, current_help_page=None, **kwargs):
|
||||||
|
actionbar_entries = (
|
||||||
|
('1', 'Main'),
|
||||||
|
('2', 'Creole'),
|
||||||
|
('3', 'Access'),
|
||||||
|
('4', 'Search'),
|
||||||
|
)
|
||||||
|
for num, name in actionbar_entries:
|
||||||
|
context[context.ACTIONBAR].append_entry(
|
||||||
|
HELP_UID + '-%s' % name.lower(), # uid
|
||||||
|
_(name), # name
|
||||||
|
color_icon_url(request, num + '.png'), # icon
|
||||||
|
pages.url_helpview(request, name.lower()), # url
|
||||||
|
True, # left
|
||||||
|
name.lower() == current_help_page, # active
|
||||||
|
)
|
0
pages/migrations/__init__.py
Normal file
0
pages/migrations/__init__.py
Normal file
3
pages/models.py
Normal file
3
pages/models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
47
pages/page.py
Normal file
47
pages/page.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
import mycreole
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class page(object):
|
||||||
|
SPLITCHAR = ":"
|
||||||
|
FOLDER_ATTACHMENTS = "attachments"
|
||||||
|
FOLDER_CONTENT = 'content'
|
||||||
|
FILE_NAME = 'page'
|
||||||
|
|
||||||
|
def __init__(self, rel_path) -> None:
|
||||||
|
self._rel_path = rel_path
|
||||||
|
|
||||||
|
def rel_path_is_valid(self):
|
||||||
|
return not self.SPLITCHAR in self._rel_path
|
||||||
|
|
||||||
|
def is_available(self):
|
||||||
|
return os.path.isfile(self.content_file_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def title(self):
|
||||||
|
return os.path.basename(self._rel_path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attachment_path(self):
|
||||||
|
return os.path.join(self.content_folder_name, self.FOLDER_ATTACHMENTS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content_folder_name(self):
|
||||||
|
return self._rel_path.replace('/', '::')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content_file_name(self):
|
||||||
|
return os.path.join(settings.PAGES_ROOT, self.content_folder_name, self.FOLDER_CONTENT, self.FILE_NAME)
|
||||||
|
|
||||||
|
def __read_content__(self):
|
||||||
|
if self.is_available():
|
||||||
|
with open(self.content_file_name, 'r') as fh:
|
||||||
|
return fh.read()
|
||||||
|
else:
|
||||||
|
# TODO: Create message for creation or no content dependent of user has write access
|
||||||
|
return "Page not available. Create it."
|
||||||
|
|
||||||
|
def render_to_html(self, request):
|
||||||
|
return mycreole.render(request, self.__read_content__(), self.attachment_path, "next_anchor")
|
5
pages/templates/pages/page.html
Normal file
5
pages/templates/pages/page.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% extends "themes/"|add:settings.page_theme|add:"/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ page_content|safe }}
|
||||||
|
{% endblock content %}
|
3
pages/tests.py
Normal file
3
pages/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
56
pages/views.py
Normal file
56
pages/views.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import config
|
||||||
|
from . import url_page
|
||||||
|
from .context import context_adaption
|
||||||
|
from .help import help_pages
|
||||||
|
from .page import page
|
||||||
|
from themes import Context
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def root(request):
|
||||||
|
return HttpResponseRedirect(url_page(request, config.STARTPAGE))
|
||||||
|
|
||||||
|
|
||||||
|
def pages(request, rel_path=''):
|
||||||
|
context = Context(request) # needs to be executed first because of time mesurement
|
||||||
|
#
|
||||||
|
p = page(rel_path)
|
||||||
|
#
|
||||||
|
context_adaption(
|
||||||
|
context,
|
||||||
|
request,
|
||||||
|
title=p.title,
|
||||||
|
upload_path=p.attachment_path,
|
||||||
|
page_content=p.render_to_html(request)
|
||||||
|
)
|
||||||
|
return render(request, 'pages/page.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
|
def search(request):
|
||||||
|
context = Context(request) # needs to be executed first because of time mesurement
|
||||||
|
context_adaption(
|
||||||
|
context,
|
||||||
|
request,
|
||||||
|
page_content="Search is not yet implemented..."
|
||||||
|
)
|
||||||
|
return render(request, 'pages/page.html', context=context)
|
||||||
|
|
||||||
|
|
||||||
|
def helpview(request, page='main'):
|
||||||
|
context = Context(request) # needs to be executed first because of time mesurement
|
||||||
|
page_content = help_pages[page]
|
||||||
|
context_adaption(
|
||||||
|
context, # the base context
|
||||||
|
request, # the request object to be used in context_adaption
|
||||||
|
current_help_page=page, # the current help_page to identify which taskbar entry has to be highlighted
|
||||||
|
page_content=page_content, # the help content itself (template)
|
||||||
|
title=_('Help') # the title for the page (template)
|
||||||
|
)
|
||||||
|
return render(request, 'pages/page.html', context=context)
|
0
piki/__init__.py
Normal file
0
piki/__init__.py
Normal file
16
piki/asgi.py
Normal file
16
piki/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for piki project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'piki.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
178
piki/settings.py
Normal file
178
piki/settings.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
"""
|
||||||
|
Django settings for piki project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.1.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import config
|
||||||
|
from config import APP_NAME as ROOT_LOGGER_NAME
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import random
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# Check permission of config.py
|
||||||
|
#
|
||||||
|
if sys.platform == 'linux' or sys.platform == 'linux2':
|
||||||
|
st = os.stat(os.path.join(BASE_DIR, 'config.py'))
|
||||||
|
if st.st_mode & stat.S_IRGRP or st.st_mode & stat.S_IROTH:
|
||||||
|
raise PermissionError("conig.py is readable by group or others.")
|
||||||
|
|
||||||
|
# Default values, if not defined in config.py
|
||||||
|
#
|
||||||
|
USER_CONFIG_DEFAULTS = {
|
||||||
|
'DEBUG': False,
|
||||||
|
'SECRET_KEY': None,
|
||||||
|
'DEFAULT_THEME': 'clear-blue',
|
||||||
|
'ALLOWED_HOSTS': ['127.0.0.1', 'localhost', ],
|
||||||
|
'CSRF_TRUSTED_ORIGINS': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set configuration parameters
|
||||||
|
#
|
||||||
|
thismodule = sys.modules[__name__]
|
||||||
|
for property_name in USER_CONFIG_DEFAULTS:
|
||||||
|
try:
|
||||||
|
value = getattr(config, property_name)
|
||||||
|
except AttributeError:
|
||||||
|
value = USER_CONFIG_DEFAULTS[property_name]
|
||||||
|
setattr(thismodule, property_name, value)
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
#
|
||||||
|
if SECRET_KEY is None:
|
||||||
|
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
||||||
|
s_key = ''.join([random.choice(chars) for n in range(50)])
|
||||||
|
secret_key_warning = "You need to create a config.py file including at least a SECRET_KEY definition (e.g.: --> %s <--). " % repr(s_key)
|
||||||
|
raise KeyError(secret_key_warning)
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'pages.apps.PagesConfig',
|
||||||
|
'themes.apps.ThemesConfig',
|
||||||
|
'mycreole.apps.MycreoleConfig',
|
||||||
|
'users.apps.UsersConfig',
|
||||||
|
#
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'piki.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'piki.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR, 'data', 'static')
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'data', 'media')
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
|
PAGES_ROOT = os.path.join(BASE_DIR, 'data', 'pages')
|
||||||
|
|
||||||
|
MYCREOLE_ROOT = os.path.join(BASE_DIR, 'data', 'pages')
|
||||||
|
MYCREOLE_ATTACHMENT_ACCESS = {
|
||||||
|
'read': 'pages.access.read_attachment',
|
||||||
|
'modify': 'pages.access.modify_attachment',
|
||||||
|
}
|
||||||
|
MYCREOLE_BAR = {
|
||||||
|
'navibar': 'pages.context.navigationbar',
|
||||||
|
'menubar': 'pages.context.menubar',
|
||||||
|
}
|
||||||
|
MYCREOLE_EXT_FILTERS = [
|
||||||
|
# 'pages.creole.page_link_filter',
|
||||||
|
]
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
38
piki/urls.py
Normal file
38
piki/urls.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for piki project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.1/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
import pages.views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
#
|
||||||
|
path('', pages.views.root, name='pages-root'),
|
||||||
|
path('pages/', pages.views.root, name='pages-root'),
|
||||||
|
path('pages/<path:rel_path>', pages.views.pages, name='pages-pages'),
|
||||||
|
path('helpview/', pages.views.helpview, name='pages-helpview'),
|
||||||
|
path('helpview/<str:page>', pages.views.helpview, name='pages-helpview'),
|
||||||
|
path('search/', pages.views.search, name='search'),
|
||||||
|
path('mycreole/', include('mycreole.urls')),
|
||||||
|
path('users/', include('users.urls')),
|
||||||
|
]
|
||||||
|
|
||||||
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
16
piki/wsgi.py
Normal file
16
piki/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for piki project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'piki.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Django
|
||||||
|
Pillow
|
||||||
|
python-creole
|
||||||
|
pytz
|
||||||
|
|
1
themes
Submodule
1
themes
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 13a8d8ebc4c82d2000af4464b863cf3b38a7a1fe
|
1
users
Submodule
1
users
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 0827a5311fdbbef689365c6db5762881e048ba9c
|
Loading…
x
Reference in New Issue
Block a user