Browse Source

Page data moved to db. Prep for access control

master
Dirk Alders 2 months ago
parent
commit
a528ac19cb

+ 1
- 1
.gitignore View File

1
 # piki
1
 # piki
2
 data/media
2
 data/media
3
-data/pages
3
+data/mycreole
4
 data/static
4
 data/static
5
 data/whoosh
5
 data/whoosh
6
 db.sqlite3
6
 db.sqlite3

+ 0
- 5
data/system-pages/index/meta.json View File

1
-{
2
-    "creation_time": 1728465254,
3
-    "modified_time": 1728465254,
4
-    "modified_user": "system-page"
5
-}

+ 0
- 2
data/system-pages/index/page View File

1
-= Index
2
-<<allpages>>

+ 0
- 5
data/system-pages/startpage/meta.json View File

1
-{
2
-    "creation_time": 1728465495,
3
-    "modified_time": 1728649989,
4
-    "modified_user": "system-page"
5
-}

+ 0
- 5
data/system-pages/startpage/page View File

1
-= Default startpage
2
-Edit this page to get your own first startpage.
3
-
4
-If you need need assistance to edit a page, visit the [[/helpview/main|help pages]].
5
-

+ 0
- 5
data/system-pages/tree/meta.json View File

1
-{
2
-    "modified_time": 1729022206,
3
-    "modified_user": "system-page",
4
-    "creation_time": 1729022206
5
-}

+ 0
- 2
data/system-pages/tree/page View File

1
-= Tree
2
-<<allpagestree>>

+ 19
- 8
pages/access.py View File

1
-def read_page(request, rel_path):
2
-    return "private" not in rel_path or write_page(request, rel_path)
1
+class access_control(object):
2
+    def __init__(self, request, rel_path):
3
+        self._request = request
4
+        self._rel_path = rel_path
3
 
5
 
6
+    def may_read(self):
7
+        return "private" not in self._rel_path or self.may_write()
4
 
8
 
5
-def write_page(request, rel_path):
6
-    return request.user.is_authenticated and request.user.username in ['root', 'dirk']
9
+    def may_write(self):
10
+        # /!\ rel_path is the filsystem rel_path - caused by the flat folder structure /!\
11
+        return self._request.user.is_authenticated and self._request.user.username in ['root', 'dirk']
12
+
13
+    def may_read_attachment(self):
14
+        return self.may_read()
15
+
16
+    def may_modify_attachment(self):
17
+        return self.may_write()
7
 
18
 
8
 
19
 
9
 def read_attachment(request, rel_path):
20
 def read_attachment(request, rel_path):
10
-    # /!\ rel_path is the filsystem rel_path - caused by the flat folder structure /!\
11
-    return True
21
+    # Interface for external module mycreole
22
+    return access_control(request, rel_path).may_read_attachment()
12
 
23
 
13
 
24
 
14
 def modify_attachment(request, rel_path):
25
 def modify_attachment(request, rel_path):
15
-    # /!\ rel_path is the filsystem rel_path - caused by the flat folder structure /!\
16
-    return request.user.is_authenticated and request.user.username in ['root', 'dirk']
26
+    # Interface for external module mycreole
27
+    return access_control(request, rel_path).may_modify_attachment()

+ 15
- 1
pages/admin.py View File

1
 from django.contrib import admin
1
 from django.contrib import admin
2
+from simple_history.admin import SimpleHistoryAdmin
2
 
3
 
3
-# Register your models here.
4
+from .models import PikiPage
5
+
6
+
7
+class PikiPageAdmin(SimpleHistoryAdmin):
8
+    list_display = ('rel_path', 'tags', 'deleted')
9
+    history_list_display = ('rel_path', 'tags', 'deleted')
10
+    search_fields = ('rel_path', 'tags', )
11
+    list_filter = (
12
+        ('deleted', admin.BooleanFieldListFilter),
13
+    )
14
+    ordering = ["rel_path"]
15
+
16
+
17
+admin.site.register(PikiPage, PikiPageAdmin)

+ 5
- 4
pages/context.py View File

5
 from django.conf import settings
5
 from django.conf import settings
6
 from django.utils.translation import gettext as _
6
 from django.utils.translation import gettext as _
7
 
7
 
8
-from pages import access
8
+from pages.access import access_control
9
 import pages.parameter
9
 import pages.parameter
10
 from .help import actionbar as actionbar_add_help
10
 from .help import actionbar as actionbar_add_help
11
 import mycreole
11
 import mycreole
74
     bar = context[context.ACTIONBAR]
74
     bar = context[context.ACTIONBAR]
75
     if not cms_mode_active(request):
75
     if not cms_mode_active(request):
76
         if caller_name in ['page', 'edit', 'delete', 'rename']:
76
         if caller_name in ['page', 'edit', 'delete', 'rename']:
77
-            if access.write_page(request, kwargs["rel_path"]):
77
+            acc = access_control(request, kwargs["rel_path"])
78
+            if acc.may_write():
78
                 add_page_menu(request, bar, kwargs["rel_path"], kwargs.get('is_available', False))
79
                 add_page_menu(request, bar, kwargs["rel_path"], kwargs.get('is_available', False))
79
-            if access.modify_attachment(request, kwargs["rel_path"]):
80
+            if acc.may_modify_attachment():
80
                 add_manageupload_menu(request, bar, kwargs['upload_path'], kwargs.get('is_available', False))
81
                 add_manageupload_menu(request, bar, kwargs['upload_path'], kwargs.get('is_available', False))
81
-            if access.read_page(request, kwargs["rel_path"]):
82
+            if acc.may_read():
82
                 add_meta_menu(request, bar, kwargs["rel_path"], kwargs.get('is_available', False))
83
                 add_meta_menu(request, bar, kwargs["rel_path"], kwargs.get('is_available', False))
83
         elif caller_name == 'helpview':
84
         elif caller_name == 'helpview':
84
             actionbar_add_help(context, request, **kwargs)
85
             actionbar_add_help(context, request, **kwargs)

+ 5
- 9
pages/forms.py View File

3
 from django.forms.renderers import BaseRenderer
3
 from django.forms.renderers import BaseRenderer
4
 from django.forms.utils import ErrorList
4
 from django.forms.utils import ErrorList
5
 
5
 
6
+from .models import PikiPage
6
 
7
 
7
-class EditForm(forms.Form):  # Note that it is not inheriting from forms.ModelForm
8
-    page_txt = forms.CharField(max_length=20000, label="Page source text", widget=forms.Textarea(attrs={"rows": "20"}))
9
-    page_tags = forms.CharField(max_length=500, label="Tags (words separated by spaces)", required=False)
10
 
8
 
11
-    def __init__(self, *args, **kwargs) -> None:
12
-        page_data = kwargs.pop("page_data")
13
-        page_tags = kwargs.pop("page_tags")
14
-        super().__init__(*args, **kwargs)
15
-        self.fields['page_txt'].initial = page_data
16
-        self.fields['page_tags'].initial = page_tags
9
+class EditForm(forms.ModelForm):
10
+    class Meta:
11
+        model = PikiPage
12
+        fields = ["page_txt", "tags", "owner", "group"]
17
 
13
 
18
 
14
 
19
 class RenameForm(forms.Form):  # Note that it is not inheriting from forms.ModelForm
15
 class RenameForm(forms.Form):  # Note that it is not inheriting from forms.ModelForm

+ 57
- 0
pages/management/commands/import_system_pages.py View File

1
+from django.conf import settings
2
+from django.contrib.auth.models import User
3
+from django.core.management.base import BaseCommand
4
+
5
+from pages.models import PikiPage
6
+
7
+from datetime import datetime
8
+import fstools
9
+from zoneinfo import ZoneInfo
10
+
11
+
12
+SYSTEM_PAGES = {
13
+    "tree": """= Tree
14
+<<allpagestree>>""",
15
+    "index": """= Index
16
+<<allpages>>""",
17
+}
18
+
19
+
20
+def add_page_data(rel_path, tags, page_txt, creation_time, creation_user, modified_time, modified_user):
21
+    try:
22
+        page = PikiPage.objects.get(rel_path=rel_path)
23
+    except PikiPage.DoesNotExist:
24
+        page = PikiPage(rel_path=rel_path)
25
+    #
26
+    page.tags = tags
27
+    page.page_txt = page_txt
28
+    #
29
+    page.creation_time = creation_time
30
+    try:
31
+        page.creation_user = User.objects.get(username=creation_user)
32
+    except User.DoesNotExist:
33
+        page.creation_user = None
34
+    page.modified_time = modified_time
35
+    try:
36
+        page.modified_user = User.objects.get(username=modified_user)
37
+    except User.DoesNotExist:
38
+        page.modified_user = None
39
+    #
40
+    page.save()
41
+
42
+
43
+class Command(BaseCommand):
44
+    def handle(self, *args, **options):
45
+        for rel_path in SYSTEM_PAGES:
46
+            self.stdout.write(self.style.MIGRATE_HEADING("Migration of page '%s'" % rel_path))
47
+            #
48
+            dtm = datetime.now(ZoneInfo("UTC"))
49
+            add_page_data(
50
+                rel_path,
51
+                "",
52
+                SYSTEM_PAGES[rel_path],
53
+                dtm,
54
+                None,
55
+                dtm,
56
+                None
57
+            )

+ 73
- 0
pages/management/commands/migrate_to_db.py View File

1
+from django.conf import settings
2
+from django.contrib.auth.models import User
3
+from django.core.management.base import BaseCommand
4
+from pages.page import full_path_all_pages, page_wrapped
5
+
6
+from pages.models import PikiPage
7
+
8
+from datetime import datetime
9
+import fstools
10
+import os
11
+import shutil
12
+from zoneinfo import ZoneInfo
13
+
14
+
15
+def add_page_data(rel_path, tags, page_txt, creation_time, creation_user, modified_time, modified_user):
16
+    try:
17
+        page = PikiPage.objects.get(rel_path=rel_path)
18
+    except PikiPage.DoesNotExist:
19
+        page = PikiPage(rel_path=rel_path)
20
+    #
21
+    page.tags = tags
22
+    page.page_txt = page_txt
23
+    #
24
+    page.creation_time = datetime.fromtimestamp(creation_time, ZoneInfo("UTC"))
25
+    creation_user = creation_user or "dirk"
26
+    page.creation_user = User.objects.get(username=creation_user)
27
+    modified_user = modified_user or "dirk"
28
+    page.modified_time = datetime.fromtimestamp(modified_time, ZoneInfo("UTC"))
29
+    page.modified_user = User.objects.get(username=modified_user)
30
+    page.owner = page.owner or page.creation_user
31
+    #
32
+    page.save()
33
+
34
+
35
+class Command(BaseCommand):
36
+    def handle(self, *args, **options):
37
+        for path in full_path_all_pages():
38
+            fs_page = page_wrapped(None, path)
39
+            if fs_page._page.is_available():
40
+                self.stdout.write(self.style.MIGRATE_HEADING("Migration of page '%s'" % fs_page.rel_path))
41
+                for history_number in fs_page._page.history_numbers_list():
42
+                    self.stdout.write(self.style.MIGRATE_HEADING("  * Adding history version %d" % history_number))
43
+                    h_page = page_wrapped(None, path, history_version=history_number)
44
+                    add_page_data(
45
+                        rel_path=h_page.rel_path,
46
+                        tags=h_page.tags,
47
+                        page_txt=h_page._page.raw_page_src,
48
+                        #
49
+                        creation_time=h_page.creation_time,
50
+                        creation_user=h_page.creation_user,
51
+                        modified_time=h_page.modified_time,
52
+                        modified_user=h_page.modified_user
53
+                    )
54
+                #
55
+                self.stdout.write(self.style.MIGRATE_HEADING("  * Adding current version"))
56
+                add_page_data(
57
+                    rel_path=fs_page.rel_path,
58
+                    tags=fs_page.tags,
59
+                    page_txt=fs_page._page.raw_page_src,
60
+                    #
61
+                    creation_time=fs_page.creation_time,
62
+                    creation_user=fs_page.creation_user,
63
+                    modified_time=fs_page.modified_time,
64
+                    modified_user=fs_page.modified_user
65
+                )
66
+                #
67
+                src = os.path.join(path, "attachments")
68
+                if os.path.isdir(src):
69
+                    dst = os.path.join(settings.MYCREOLE_ROOT, fs_page.rel_path)
70
+                    for attachment in fstools.filelist(src):
71
+                        self.stdout.write(self.style.MIGRATE_HEADING("  * Copy attachment ''%s to new location" % os.path.basename(attachment)))
72
+                        fstools.mkdir(dst)
73
+                        shutil.copy(attachment, dst)

+ 10
- 0
pages/messages.py View File

8
     messages.error(request, _("Permission denied: You don't have sufficient acces to the Page '%s'. Please contact the adminstrator.") % rel_path)
8
     messages.error(request, _("Permission denied: You don't have sufficient acces to the Page '%s'. Please contact the adminstrator.") % rel_path)
9
 
9
 
10
 
10
 
11
+def deleted_page(request):
12
+    # TODO: Add translation for this message
13
+    messages.info(request, _("Page was deleted. Recover not yet implemented. Use the 'Administration' area for recovery. Rebuild the search index afterwards."))
14
+
15
+
11
 def unavailable_msg_page(request, rel_path):
16
 def unavailable_msg_page(request, rel_path):
12
     # TODO: Add translation for this message
17
     # TODO: Add translation for this message
13
     messages.info(request, _("Unavailable: The Page '%s' is not available. Create it or follow a valid link, please.") % rel_path)
18
     messages.info(request, _("Unavailable: The Page '%s' is not available. Create it or follow a valid link, please.") % rel_path)
38
     messages.info(request, _('The page has been renamed.'))
43
     messages.info(request, _('The page has been renamed.'))
39
 
44
 
40
 
45
 
46
+def internal_error(request):
47
+    # TODO: Add translation for this message
48
+    messages.error(request, _('internal ERROR: Action not performed..'))
49
+
50
+
41
 def history_version_display(request, rel_path, history_version):
51
 def history_version_display(request, rel_path, history_version):
42
     # TODO: Add translation for this message
52
     # TODO: Add translation for this message
43
     messages.warning(request, _("You see an old version of the page (Version = %d). Click <a href='%s'>here</a> to recover this Version.") % (
53
     messages.warning(request, _("You see an old version of the page (Version = %d). Click <a href='%s'>here</a> to recover this Version.") % (

+ 63
- 0
pages/migrations/0001_initial.py View File

1
+# Generated by Django 5.1.2 on 2024-10-21 04:20
2
+
3
+import django.db.models.deletion
4
+import simple_history.models
5
+from django.conf import settings
6
+from django.db import migrations, models
7
+
8
+
9
+class Migration(migrations.Migration):
10
+
11
+    initial = True
12
+
13
+    dependencies = [
14
+        ('auth', '0012_alter_user_first_name_max_length'),
15
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16
+    ]
17
+
18
+    operations = [
19
+        migrations.CreateModel(
20
+            name='HistoricalPikiPage',
21
+            fields=[
22
+                ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
23
+                ('rel_path', models.CharField(db_index=True, max_length=1000)),
24
+                ('page_txt', models.TextField(max_length=50000)),
25
+                ('tags', models.CharField(blank=True, max_length=1000, null=True)),
26
+                ('deleted', models.BooleanField(default=False)),
27
+                ('creation_time', models.DateTimeField(blank=True, null=True)),
28
+                ('modified_time', models.DateTimeField(blank=True, null=True)),
29
+                ('history_id', models.AutoField(primary_key=True, serialize=False)),
30
+                ('history_date', models.DateTimeField(db_index=True)),
31
+                ('history_change_reason', models.CharField(max_length=100, null=True)),
32
+                ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
33
+                ('creation_user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
34
+                ('group', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='auth.group')),
35
+                ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
36
+                ('modified_user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
37
+                ('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
38
+            ],
39
+            options={
40
+                'verbose_name': 'historical piki page',
41
+                'verbose_name_plural': 'historical piki pages',
42
+                'ordering': ('-history_date', '-history_id'),
43
+                'get_latest_by': ('history_date', 'history_id'),
44
+            },
45
+            bases=(simple_history.models.HistoricalChanges, models.Model),
46
+        ),
47
+        migrations.CreateModel(
48
+            name='PikiPage',
49
+            fields=[
50
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
51
+                ('rel_path', models.CharField(max_length=1000, unique=True)),
52
+                ('page_txt', models.TextField(max_length=50000)),
53
+                ('tags', models.CharField(blank=True, max_length=1000, null=True)),
54
+                ('deleted', models.BooleanField(default=False)),
55
+                ('creation_time', models.DateTimeField(blank=True, null=True)),
56
+                ('modified_time', models.DateTimeField(blank=True, null=True)),
57
+                ('creation_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='creation_user', to=settings.AUTH_USER_MODEL)),
58
+                ('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='group', to='auth.group')),
59
+                ('modified_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modified_user', to=settings.AUTH_USER_MODEL)),
60
+                ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owner', to=settings.AUTH_USER_MODEL)),
61
+            ],
62
+        ),
63
+    ]

+ 282
- 1
pages/models.py View File

1
+from django.conf import settings
2
+from django.contrib.auth.models import User, Group
1
 from django.db import models
3
 from django.db import models
4
+from django.utils.translation import gettext as _
5
+from simple_history.models import HistoricalRecords
2
 
6
 
3
-# Create your models here.
7
+from datetime import datetime
8
+import difflib
9
+import logging
10
+import os
11
+from zoneinfo import ZoneInfo
12
+
13
+from users.models import get_userprofile
14
+from pages import url_page
15
+
16
+import mycreole
17
+
18
+logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__)
19
+
20
+
21
+class PikiPage(models.Model):
22
+    SAVE_ON_CHANGE_FIELDS = ["rel_path", "page_txt", "tags", "deleted", "owner", "group"]
23
+    #
24
+    rel_path = models.CharField(unique=True, max_length=1000)
25
+    page_txt = models.TextField(max_length=50000)
26
+    tags = models.CharField(max_length=1000, null=True, blank=True)
27
+    deleted = models.BooleanField(default=False)
28
+    #
29
+    creation_time = models.DateTimeField(null=True, blank=True)
30
+    creation_user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name="creation_user")
31
+    modified_time = models.DateTimeField(null=True, blank=True)
32
+    modified_user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name="modified_user")
33
+    #
34
+    owner = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name="owner")
35
+    group = models.ForeignKey(Group, null=True, blank=True, on_delete=models.SET_NULL, related_name="group")
36
+    # owner_perms
37
+    # group_perms
38
+    # other_perms
39
+    #
40
+    history = HistoricalRecords()
41
+
42
+    def __init__(self, *args, **kwargs):
43
+        super().__init__(*args, **kwargs)
44
+
45
+    def prepare_save(self, request):
46
+        # Set date
47
+        tmd = datetime.now(tz=ZoneInfo("UTC")).replace(microsecond=0)
48
+        self.creation_time = self.creation_time or tmd
49
+        self.modified_time = tmd
50
+        # Set user
51
+        self.creation_user = self.creation_user or request.user
52
+        self.owner = self.owner or request.user
53
+        self.modified_user = request.user
54
+
55
+    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
56
+        if self.id and not force_update:
57
+            orig = PikiPage.objects.get(id=self.id)
58
+            for key in self.SAVE_ON_CHANGE_FIELDS:
59
+                if getattr(self, key) != getattr(orig, key):
60
+                    break
61
+            else:
62
+                self.save_needed = False
63
+                return False
64
+        self.save_needed = True
65
+        return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
66
+
67
+    #
68
+    # Set history datetime to modified datetime
69
+    #
70
+    @property
71
+    def _history_date(self):
72
+        return self.modified_time
73
+
74
+    @_history_date.setter
75
+    def _history_date(self, value):
76
+        self.modified_time = value
77
+
78
+    #
79
+    # My information
80
+    #
81
+    @property
82
+    def title(self):
83
+        return self.rel_path.split("/")[-1]
84
+
85
+    #
86
+    # My methods
87
+    #
88
+    def render_to_html(self, request, history=None):
89
+        if history:
90
+            h = self.history.get(history_id=history)
91
+            return self.render_text(request, h.page_txt)
92
+        else:
93
+            return self.render_text(request, self.page_txt)
94
+
95
+    def user_datetime(self, request, dtm):
96
+        try:
97
+            up = get_userprofile(request.user)
98
+        except AttributeError:
99
+            tz = ZoneInfo("UTC")
100
+        else:
101
+            tz = ZoneInfo(up.timezone)
102
+        #
103
+        return datetime.astimezone(dtm, tz)
104
+
105
+    def render_meta(self, request, history):
106
+        # Page information
107
+        meta = f'= {_("Meta data")}\n'
108
+        meta += f'|{_("Created by")}:|{self.creation_user}|\n'
109
+        meta += f'|{_("Created at")}:|{self.user_datetime(request, self.creation_time)}|\n'
110
+        meta += f'|{_("Modified by")}:|{self.modified_user}|\n'
111
+        meta += f'|{_("Modified at")}:|{self.user_datetime(request, self.modified_time)}|\n'
112
+        meta += f'|{_("Owner")}:|{self.owner or "---"}|\n'
113
+        meta += f'|{_("Group")}:|{self.group or "---"}|\n'
114
+        meta += f'|{_("Tags")}|{self.tags or "---"}|\n'
115
+        #
116
+        # List of history page versions
117
+        #
118
+        hl = self.history.all()[1:]
119
+        if len(hl) > 0:
120
+            meta += f'= {_("History")}\n'
121
+            meta += f'| ={_("Version")} | ={_("Date")} | ={_("Page")} | ={_("Meta data")} | ={_("Page changed")} | ={_("Tags changed")} | \n'
122
+            # Current
123
+            name = _("Current")
124
+            meta += f"| {name} \
125
+                      | {self.user_datetime(request, self.modified_time)} \
126
+                      | [[{url_page(self.rel_path)} | Page]] \
127
+                      | [[{url_page(self.rel_path, meta=None)} | Meta]] |"
128
+            page_content = self.page_txt.replace("\r\n", "\n").strip("\n")
129
+            tags = self.tags
130
+            for h_page in hl:
131
+                page_changed = page_content != h_page.page_txt.replace("\r\n", "\n").strip("\n")
132
+                tags_changed = tags != h_page.tags
133
+                if page_changed or tags_changed:
134
+                    meta += " %s |" % ("Yes" if page_changed else "No")
135
+                    meta += " %s |" % ("Yes" if tags_changed else "No")
136
+                    meta += "\n"
137
+                    meta += f"| {h_page.history_id} \
138
+                                | {self.user_datetime(request, h_page.modified_time)} \
139
+                                | [[{url_page(self.rel_path, history=h_page.history_id)} | Page]] \
140
+                                | [[{url_page(self.rel_path, meta=None, history=h_page.history_id)} | Meta]] (with diff to current) |"
141
+                    page_content = h_page.page_txt[:].replace("\r\n", "\n").strip("\n")
142
+                    tags = h_page.tags
143
+            meta += " --- | --- |\n"
144
+        # Diff
145
+        html_diff = ""
146
+        if history:
147
+            h_page = self.history.get(history_id=history)
148
+            #
149
+            meta += f'= {_("Page differences")}\n'
150
+            #
151
+            left_lines = self.page_txt.splitlines()
152
+            right_lines = h_page.page_txt.splitlines()
153
+            html_diff = difflib.HtmlDiff(wrapcolumn=80).make_table(left_lines, right_lines, "Current page", "Page Version %d" % history)
154
+        #
155
+        return mycreole.render_simple(meta) + html_diff
156
+
157
+    #
158
+    # Creole stuff
159
+    #
160
+    def render_text(self, request, txt):
161
+        macros = {
162
+            "subpages": self.macro_subpages,
163
+            "allpages": self.macro_allpages,
164
+            "subpagetree": self.macro_subpagetree,
165
+            "allpagestree": self.macro_allpagestree,
166
+        }
167
+        return mycreole.render(request, txt, self.rel_path, macros=macros)
168
+
169
+    def macro_subpages(self, *args, **kwargs):
170
+        return self.macro_pages(*args, **kwargs)
171
+
172
+    def macro_allpages(self, *args, **kwargs):
173
+        kwargs["allpages"] = True
174
+        return self.macro_pages(*args, **kwargs)
175
+
176
+    def macro_allpagestree(self, *args, **kwargs):
177
+        kwargs["allpages"] = True
178
+        kwargs["tree"] = True
179
+        return self.macro_pages(*args, **kwargs)
180
+
181
+    def macro_subpagetree(self, * args, **kwargs):
182
+        kwargs["tree"] = True
183
+        return self.macro_pages(*args, **kwargs)
184
+
185
+    def macro_pages(self, *args, **kwargs):
186
+        allpages = kwargs.pop("allpages", False)
187
+        tree = kwargs.pop("tree", False)
188
+        #
189
+
190
+        def parse_depth(s: str):
191
+            try:
192
+                return int(s)
193
+            except ValueError:
194
+                pass
195
+
196
+        params = kwargs.get('', '')
197
+        filter_str = ''
198
+        depth = parse_depth(params)
199
+        if depth is None:
200
+            params = params.split(",")
201
+            depth = parse_depth(params[0])
202
+            if len(params) == 2:
203
+                filter_str = params[1]
204
+            elif depth is None:
205
+                filter_str = params[0]
206
+        #
207
+        if not allpages:
208
+            filter_str = os.path.join(self.rel_path, filter_str)
209
+        #
210
+        pages = PikiPage.objects.filter(rel_path__contains=filter_str)
211
+        pl = page_list([p for p in pages if not p.deleted])
212
+        #
213
+        if tree:
214
+            return "<pre>\n" + page_tree(pl).html() + "</pre>\n"
215
+        else:
216
+            return pl.html_list(depth=depth, filter_str=filter_str, parent_rel_path='' if allpages else self.rel_path)
217
+
218
+
219
+class page_list(list):
220
+    def __init__(self, *args, **kwargs):
221
+        return super().__init__(*args, **kwargs)
222
+
223
+    def sort_basename(self):
224
+        return list.sort(self, key=lambda x: os.path.basename(x.rel_path))
225
+
226
+    def creole_list(self, depth=None, filter_str='', parent_rel_path=''):
227
+        self.sort_basename()
228
+        depth = depth or 9999   # set a random high value if None
229
+        #
230
+        rv = ""
231
+        last_char = None
232
+        for page in self:
233
+            if page.rel_path.startswith(filter_str) and page.rel_path != filter_str:
234
+                name = page.rel_path[len(parent_rel_path):].lstrip("/")
235
+                if name.count('/') < depth:
236
+                    first_char = os.path.basename(name)[0].upper()
237
+                    if last_char != first_char:
238
+                        last_char = first_char
239
+                        rv += f"=== {first_char}\n"
240
+                    rv += f"* [[{url_page(page.rel_path)} | {name} ]]\n"
241
+        return rv
242
+
243
+    def html_list(self, depth=9999, filter_str='', parent_rel_path=''):
244
+        return mycreole.render_simple(self.creole_list(depth, filter_str, parent_rel_path))
245
+
246
+
247
+class page_tree(dict):
248
+    T_PATTERN = "├── "
249
+    L_PATTERN = "└── "
250
+    I_PATTERN = "│   "
251
+    D_PATTERN = "    "
252
+
253
+    def __init__(self, pl: page_list):
254
+        super().__init__()
255
+        for page in pl:
256
+            store_item = self
257
+            for entry in page.rel_path.split("/"):
258
+                if not entry in store_item:
259
+                    store_item[entry] = {}
260
+                store_item = store_item[entry]
261
+
262
+    def html(self, rel_path=None, fill=""):
263
+        base = self
264
+        try:
265
+            for key in rel_path.split("/"):
266
+                base = base[key]
267
+        except AttributeError:
268
+            rel_path = ''
269
+        #
270
+        rv = ""
271
+        #
272
+        l = len(base)
273
+        for entry in sorted(list(base.keys())):
274
+            l -= 1
275
+            page_path = os.path.join(rel_path, entry)
276
+            try:
277
+                PikiPage.objects.get(rel_path=page_path)
278
+            except PikiPage.DoesNotExist:
279
+                pass
280
+            else:
281
+                entry = f'<a href="{url_page(page_path)}">{entry}</a>'
282
+            rv += fill + (self.L_PATTERN if l == 0 else self.T_PATTERN) + entry + "\n"
283
+            rv += self.html(page_path, fill=fill+(self.D_PATTERN if l == 0 else self.I_PATTERN))
284
+        return rv

+ 31
- 236
pages/page.py View File

30
     return rv
30
     return rv
31
 
31
 
32
 
32
 
33
-def full_path_deleted_pages(expression="*"):
34
-    system_pages = fstools.dirlist(settings.SYSTEM_PAGES_ROOT, expression=expression, rekursive=False)
35
-    system_pages = [os.path.join(settings.PAGES_ROOT, os.path.basename(path)) for path in system_pages]
36
-    pages = fstools.dirlist(settings.PAGES_ROOT, expression=expression, rekursive=False)
37
-    rv = []
38
-    for path in set(system_pages + pages):
39
-        p = page_wrapped(None, path)
40
-        if not p.is_available():
41
-            rv.append(path)
42
-    return rv
33
+class base(dict):
34
+    @property
35
+    def rel_path(self):
36
+        return os.path.basename(self._path).replace(2*SPLITCHAR, "/")
43
 
37
 
38
+    def is_available(self):
39
+        is_a = os.path.isfile(self.filename)
40
+        if not is_a:
41
+            logger.debug("Not available - %s", self.filename)
42
+        return is_a
44
 
43
 
45
-class meta_data(dict):
44
+    def history_numbers_list(self):
45
+        history_folder = os.path.join(self._path, HISTORY_FOLDER_NAME)
46
+        return list(set([int(os.path.basename(filename)[:5]) for filename in fstools.filelist(history_folder)]))
47
+
48
+
49
+class meta_data(base):
46
     META_FILE_NAME = 'meta.json'
50
     META_FILE_NAME = 'meta.json'
47
     #
51
     #
48
     KEY_CREATION_TIME = "creation_time"
52
     KEY_CREATION_TIME = "creation_time"
53
+    KEY_CREATION_USER = "creation_user"
49
     KEY_MODIFIED_TIME = "modified_time"
54
     KEY_MODIFIED_TIME = "modified_time"
50
     KEY_MODIFIED_USER = "modified_user"
55
     KEY_MODIFIED_USER = "modified_user"
51
     KEY_TAGS = "tags"
56
     KEY_TAGS = "tags"
85
             if username:
90
             if username:
86
                 self[self.KEY_MODIFIED_TIME] = int(time.time())
91
                 self[self.KEY_MODIFIED_TIME] = int(time.time())
87
                 self[self.KEY_MODIFIED_USER] = username
92
                 self[self.KEY_MODIFIED_USER] = username
93
+                #
94
+                if self.KEY_CREATION_USER not in self:
95
+                    self[self.KEY_CREATION_USER] = self[self.KEY_MODIFIED_USER]
88
                 if self.KEY_CREATION_TIME not in self:
96
                 if self.KEY_CREATION_TIME not in self:
89
                     self[self.KEY_CREATION_TIME] = self[self.KEY_MODIFIED_TIME]
97
                     self[self.KEY_CREATION_TIME] = self[self.KEY_MODIFIED_TIME]
90
             if tags:
98
             if tags:
109
         shutil.copy(self.filename, history_filename)
117
         shutil.copy(self.filename, history_filename)
110
 
118
 
111
 
119
 
112
-class page_data(object):
120
+class page_data(base):
113
     PAGE_FILE_NAME = 'page'
121
     PAGE_FILE_NAME = 'page'
114
 
122
 
115
     def __init__(self, path, history_version=None):
123
     def __init__(self, path, history_version=None):
168
     def rel_path(self):
176
     def rel_path(self):
169
         return os.path.basename(self._path).replace(2*SPLITCHAR, "/")
177
         return os.path.basename(self._path).replace(2*SPLITCHAR, "/")
170
 
178
 
171
-    def is_available(self):
172
-        is_a = os.path.isfile(self.filename)
173
-        if not is_a:
174
-            logger.debug("page.is_available: Not available - %s", self.filename)
175
-        return is_a
176
-
177
     @property
179
     @property
178
     def title(self):
180
     def title(self):
179
         return os.path.basename(self._path).split(2*SPLITCHAR)[-1]
181
         return os.path.basename(self._path).split(2*SPLITCHAR)[-1]
189
         shutil.copy(self.filename, history_filename)
191
         shutil.copy(self.filename, history_filename)
190
 
192
 
191
 
193
 
192
-class page_django(page_data):
193
-    FOLDER_ATTACHMENTS = "attachments"
194
-
195
-    def __init__(self, request, path, history_version=None) -> None:
196
-        self._request = request
197
-        super().__init__(path, history_version=history_version)
198
-
199
-    @property
200
-    def attachment_path(self):
201
-        return os.path.join(os.path.basename(self._path), self.FOLDER_ATTACHMENTS)
202
-
203
-    def render_to_html(self):
204
-        if self.is_available():
205
-            return self.render_text(self._request, self.raw_page_src)
206
-        else:
207
-            messages.unavailable_msg_page(self._request, self.rel_path)
208
-            return ""
209
-
210
-    def history_numbers_list(self):
211
-        history_folder = os.path.join(self._path, HISTORY_FOLDER_NAME)
212
-        return list(set([int(os.path.basename(filename)[:5]) for filename in fstools.filelist(history_folder)]))
213
-
214
-    def render_meta(self, ctime, mtime, user, tags):
215
-        #
216
-        # Page meta data
217
-        #
218
-        meta = f'=== {_("Meta data")}\n'
219
-        meta += f'|{_("Created")}:|{timestamp_to_datetime(self._request, ctime)}|\n'
220
-        meta += f'|{_("Modified")}:|{timestamp_to_datetime(self._request, mtime)}|\n'
221
-        meta += f'|{_("Editor")}|{user}|\n'
222
-        meta += f'|{_("Tags")}|{tags}|\n'
223
-        #
224
-        # List of hostory page versions
225
-        #
226
-        hnl = self.history_numbers_list()
227
-        if hnl:
228
-            meta += f'=== {_("History")}\n'
229
-            meta += f'| ={_("Version")} | ={_("Date")} | ={_("Page")} | ={_("Meta data")} | \n'
230
-            # Current
231
-            name = _("Current")
232
-            meta += f"| {name} \
233
-                      | {timestamp_to_datetime(self._request, mtime)} \
234
-                      | [[{url_page(self.rel_path)} | Page]] \
235
-                      | [[{url_page(self.rel_path, meta=None)} | Meta]]\n"
236
-            # History
237
-            for num in reversed(hnl):
238
-                p = page_wrapped(self._request, self._path, history_version=num)
239
-                meta += f"| {num} \
240
-                          | {timestamp_to_datetime(self._request, p.modified_time)} \
241
-                          | [[{url_page(p.rel_path, history=num)} | Page]] \
242
-                          | [[{url_page(p.rel_path, meta=None, history=num)} | Meta]] (with page changes)\n"
243
-        # Diff
244
-        html_diff = ""
245
-        if self._history_version:
246
-            meta += f'=== {_("Page differences")}\n'
247
-            #
248
-            c = page_django(self._request, self._path)
249
-            left_lines = c.raw_page_src.splitlines()
250
-            right_lines = self.raw_page_src.splitlines()
251
-            html_diff = difflib.HtmlDiff(wrapcolumn=80).make_table(left_lines, right_lines)
252
-        #
253
-        return mycreole.render_simple(meta) + html_diff
254
-
255
-    def render_text(self, request, txt):
256
-        macros = {
257
-            "subpages": self.macro_subpages,
258
-            "allpages": self.macro_allpages,
259
-            "subpagetree": self.macro_subpagetree,
260
-            "allpagestree": self.macro_allpagestree,
261
-        }
262
-        return mycreole.render(request, txt, self.attachment_path, macros=macros)
263
-
264
-    def macro_allpages(self, *args, **kwargs):
265
-        kwargs["allpages"] = True
266
-        return self.macro_subpages(*args, **kwargs)
267
-
268
-    def macro_subpages(self, *args, **kwargs):
269
-        allpages = kwargs.pop("allpages", False)
270
-        tree = kwargs.pop("tree", False)
271
-        #
272
-
273
-        def parse_depth(s: str):
274
-            try:
275
-                return int(s)
276
-            except ValueError:
277
-                pass
278
-
279
-        params = kwargs.get('', '')
280
-        filter_str = ''
281
-        depth = parse_depth(params)
282
-        if depth is None:
283
-            params = params.split(",")
284
-            depth = parse_depth(params[0])
285
-            if len(params) == 2:
286
-                filter_str = params[1]
287
-            elif depth is None:
288
-                filter_str = params[0]
289
-        #
290
-        rv = ""
291
-        # create a page_list
292
-        if allpages:
293
-            expression = "*"
294
-            parent_rel_path = ""
295
-        else:
296
-            expression = os.path.basename(self._path) + 2 * SPLITCHAR + "*"
297
-            parent_rel_path = self.rel_path
298
-        #
299
-        pl = page_list(
300
-            self._request,
301
-            [page_django(self._request, path) for path in full_path_all_pages(expression)]
302
-        )
303
-        if tree:
304
-            return "<pre>\n" + page_tree(pl).html() + "</pre>\n"
305
-        else:
306
-            return pl.html_list(depth=depth, filter_str=filter_str, parent_rel_path=parent_rel_path)
307
-
308
-    def macro_allpagestree(self, *args, **kwargs):
309
-        kwargs["allpages"] = True
310
-        kwargs["tree"] = True
311
-        return self.macro_subpages(*args, **kwargs)
312
-
313
-    def macro_subpagetree(self, * args, **kwargs):
314
-        kwargs["tree"] = True
315
-        return self.macro_subpages(*args, **kwargs)
316
-
317
-
318
-class page_list(list):
319
-    def __init__(self, request, *args, **kwargs):
320
-        self._request = request
321
-        return super().__init__(*args, **kwargs)
322
-
323
-    def sort_basename(self):
324
-        return list.sort(self, key=lambda x: os.path.basename(x.rel_path))
325
-
326
-    def creole_list(self, depth=None, filter_str='', parent_rel_path=''):
327
-        self.sort_basename()
328
-        depth = depth or 9999   # set a random high value if None
329
-        #
330
-        parent_rel_path = parent_rel_path + "/" if len(parent_rel_path) > 0 else ""
331
-        #
332
-        rv = ""
333
-        last_char = None
334
-        for page in self:
335
-            name = page.rel_path[len(parent_rel_path):]
336
-            if name.startswith(filter_str) and name != filter_str:
337
-                if name.count('/') < depth:
338
-                    first_char = os.path.basename(name)[0].upper()
339
-                    if last_char != first_char:
340
-                        last_char = first_char
341
-                        rv += f"=== {first_char}\n"
342
-                    rv += f"* [[{url_page(page.rel_path)} | {name} ]]\n"
343
-        return rv
344
-
345
-    def html_list(self, depth=9999, filter_str='', parent_rel_path=''):
346
-        return mycreole.render_simple(self.creole_list(depth, filter_str, parent_rel_path))
347
-
348
-
349
-class page_tree(dict):
350
-    T_PATTERN = "├── "
351
-    L_PATTERN = "└── "
352
-    I_PATTERN = "│   "
353
-    D_PATTERN = "    "
354
-
355
-    def __init__(self, pl: page_list):
356
-        super().__init__()
357
-        for page in pl:
358
-            store_item = self
359
-            for entry in page.rel_path.split("/"):
360
-                if not entry in store_item:
361
-                    store_item[entry] = {}
362
-                store_item = store_item[entry]
363
-
364
-    def html(self, rel_path=None, fill=""):
365
-        base = self
366
-        try:
367
-            for key in rel_path.split("/"):
368
-                base = base[key]
369
-        except AttributeError:
370
-            rel_path = ''
371
-        #
372
-        rv = ""
373
-        #
374
-        l = len(base)
375
-        for entry in sorted(list(base.keys())):
376
-            l -= 1
377
-            page_path = os.path.join(rel_path, entry)
378
-            page = page_wrapped(None, page_path)
379
-            if page.is_available():
380
-                entry = f'<a href="{url_page(page_path)}">{entry}</a>'
381
-            rv += fill + (self.L_PATTERN if l == 0 else self.T_PATTERN) + entry + "\n"
382
-            rv += self.html(page_path, fill=fill+(self.D_PATTERN if l == 0 else self.I_PATTERN))
383
-        return rv
384
-
385
-
386
 class page_wrapped(object):
194
 class page_wrapped(object):
387
     """
195
     """
388
     This class holds different page and meta instances and decides which will be used in which case.
196
     This class holds different page and meta instances and decides which will be used in which case.
399
         self._request = request
207
         self._request = request
400
         #
208
         #
401
         page_path = self.__page_path__(path)
209
         page_path = self.__page_path__(path)
402
-        system_page_path = self.__system_page_path__(path)
403
         # Page
210
         # Page
404
-        if request:
405
-            self._page = page_django(request, page_path, history_version=history_version)
406
-        else:
407
-            self._page = page_data(page_path, history_version=history_version)
211
+        self._page = page_data(page_path, history_version=history_version)
408
         self._page_meta = meta_data(page_path, history_version=history_version)
212
         self._page_meta = meta_data(page_path, history_version=history_version)
409
-        # System page
410
-        if request:
411
-            self._system_page = page_django(request, system_page_path)
412
-        else:
413
-            self._system_page = page_data(system_page_path)
414
-        self._system_meta_data = meta_data(system_page_path)
415
 
213
 
416
     def __page_path__(self, path):
214
     def __page_path__(self, path):
417
         if path.startswith(settings.PAGES_ROOT):
215
         if path.startswith(settings.PAGES_ROOT):
421
             # must be a relative url
219
             # must be a relative url
422
             return os.path.join(settings.PAGES_ROOT, path.replace("/", 2*SPLITCHAR))
220
             return os.path.join(settings.PAGES_ROOT, path.replace("/", 2*SPLITCHAR))
423
 
221
 
424
-    def __system_page_path__(self, path):
425
-        return os.path.join(settings.SYSTEM_PAGES_ROOT, os.path.basename(path))
426
-
427
     def __page_choose__(self):
222
     def __page_choose__(self):
428
-        if not self._page.is_available():
429
-            return self._system_page
430
-        else:
431
-            return self._page
223
+        return self._page
432
 
224
 
433
     def __meta_choose__(self):
225
     def __meta_choose__(self):
434
-        if not self._page.is_available():
435
-            return self._system_meta_data
436
-        else:
437
-            return self._page_meta
226
+        return self._page_meta
438
 
227
 
439
     def __store_history__(self):
228
     def __store_history__(self):
440
         if self._page.is_available():
229
         if self._page.is_available():
454
         rv = meta.get(meta.KEY_CREATION_TIME)
243
         rv = meta.get(meta.KEY_CREATION_TIME)
455
         return rv
244
         return rv
456
 
245
 
246
+    @property
247
+    def creation_user(self):
248
+        meta = self.__meta_choose__()
249
+        rv = meta.get(meta.KEY_CREATION_USER)
250
+        return rv
251
+
457
     def delete(self):
252
     def delete(self):
458
         self.__store_history__()
253
         self.__store_history__()
459
         self._page.delete()
254
         self._page.delete()
490
         return rv
285
         return rv
491
 
286
 
492
     def is_available(self):
287
     def is_available(self):
493
-        return self._page.is_available() or self._system_page.is_available()
288
+        return self._page.is_available()
494
 
289
 
495
     def userpage_is_available(self):
290
     def userpage_is_available(self):
496
         return self._page.is_available()
291
         return self._page.is_available()
509
 
304
 
510
     def render_meta(self):
305
     def render_meta(self):
511
         page = self.__page_choose__()
306
         page = self.__page_choose__()
512
-        rv = page.render_meta(self.creation_time, self.modified_time, self.modified_user, self.tags)
307
+        rv = page.render_meta(self.creation_time, self.modified_time, self.creation_user, self.modified_user, self.tags)
513
         return rv
308
         return rv
514
 
309
 
515
     def render_to_html(self):
310
     def render_to_html(self):

+ 20
- 20
pages/search.py View File

8
 from whoosh.qparser.dateparse import DateParserPlugin
8
 from whoosh.qparser.dateparse import DateParserPlugin
9
 from whoosh import index, qparser
9
 from whoosh import index, qparser
10
 
10
 
11
-from pages.page import page_wrapped, full_path_all_pages
11
+from .models import PikiPage
12
 
12
 
13
 logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__)
13
 logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__)
14
 
14
 
38
 
38
 
39
 
39
 
40
 def rebuild_index(ix):
40
 def rebuild_index(ix):
41
-    page_path = full_path_all_pages()
42
-    for path in page_path:
43
-        pw = page_wrapped(None, path)
44
-        add_item(ix, pw)
45
-    return len(page_path)
41
+    pages = PikiPage.objects.all()
42
+    for pp in pages:
43
+        if not pp.deleted:
44
+            add_item(ix, pp)
45
+    return len(pages)
46
 
46
 
47
 
47
 
48
 def load_index():
48
 def load_index():
56
     return ix
56
     return ix
57
 
57
 
58
 
58
 
59
-def add_item(ix, pw: page_wrapped):
59
+def add_item(ix, pp: PikiPage):
60
     # Define Standard data
60
     # Define Standard data
61
     #
61
     #
62
     data = dict(
62
     data = dict(
63
-        id=pw.rel_path,
63
+        id=pp.rel_path,
64
         #
64
         #
65
-        title=pw.title,
66
-        page_src=pw.raw_page_src,
67
-        tag=pw.tags,
65
+        title=pp.title,
66
+        page_src=pp.page_txt,
67
+        tag=pp.tags,
68
         #
68
         #
69
-        creation_time=datetime.fromtimestamp(pw.creation_time),
70
-        modified_time=datetime.fromtimestamp(pw.modified_time),
71
-        modified_user=pw.modified_user
69
+        creation_time=pp.creation_time,
70
+        modified_time=pp.modified_time,
71
+        modified_user=None if pp.modified_user is None else pp.modified_user.username
72
     )
72
     )
73
     with ix.writer() as w:
73
     with ix.writer() as w:
74
         logger.info('Adding document with id=%s to the search index.', data.get('id'))
74
         logger.info('Adding document with id=%s to the search index.', data.get('id'))
95
         return rpl
95
         return rpl
96
 
96
 
97
 
97
 
98
-def delete_item(ix, pw: page_wrapped):
98
+def delete_item(ix, pp: PikiPage):
99
     with ix.writer() as w:
99
     with ix.writer() as w:
100
-        logger.info('Removing document with id=%s from the search index.', pw.rel_path)
101
-        w.delete_by_term("id", pw.rel_path)
100
+        logger.info('Removing document with id=%s from the search index.', pp.rel_path)
101
+        w.delete_by_term("id", pp.rel_path)
102
 
102
 
103
 
103
 
104
-def update_item(pw: page_wrapped):
104
+def update_item(pp: PikiPage):
105
     ix = load_index()
105
     ix = load_index()
106
-    delete_item(ix, pw)
107
-    add_item(ix, pw)
106
+    delete_item(ix, pp)
107
+    add_item(ix, pp)

+ 99
- 51
pages/views.py View File

6
 
6
 
7
 import logging
7
 import logging
8
 
8
 
9
-from . import access
9
+
10
+from .access import access_control
10
 from . import messages
11
 from . import messages
11
 from . import url_page
12
 from . import url_page
12
 from . import get_search_query
13
 from . import get_search_query
14
 from .context import context_adaption
15
 from .context import context_adaption
15
 from .forms import EditForm, RenameForm
16
 from .forms import EditForm, RenameForm
16
 from .help import help_pages
17
 from .help import help_pages
18
+from .models import PikiPage, page_list
17
 import mycreole
19
 import mycreole
18
-from .page import page_wrapped, page_list
19
-from .search import whoosh_search, load_index, delete_item, add_item
20
+from .search import whoosh_search, load_index, delete_item, add_item, update_item
20
 from themes import Context
21
 from themes import Context
21
 
22
 
22
 logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__)
23
 logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__)
23
 
24
 
24
 
25
 
26
+SUCCESS_PAGE = _("""= Default startpage
27
+**Congratulations!!!**
28
+
29
+Seeing this page means, that you installed Piki successfull.
30
+
31
+Edit this page to get your own first startpage.
32
+
33
+If you need need assistance to edit a page, visit the [[/helpview/main|help pages]].
34
+""")
35
+
36
+
25
 def root(request):
37
 def root(request):
26
     return HttpResponseRedirect(url_page(config.STARTPAGE))
38
     return HttpResponseRedirect(url_page(config.STARTPAGE))
27
 
39
 
29
 def page(request, rel_path):
41
 def page(request, rel_path):
30
     context = Context(request)      # needs to be executed first because of time mesurement
42
     context = Context(request)      # needs to be executed first because of time mesurement
31
     #
43
     #
44
+    try:
45
+        p = PikiPage.objects.get(rel_path=rel_path)
46
+    except PikiPage.DoesNotExist:
47
+        p = None
32
     meta = "meta" in request.GET
48
     meta = "meta" in request.GET
33
     history = request.GET.get("history")
49
     history = request.GET.get("history")
34
     if history:
50
     if history:
35
         history = int(history)
51
         history = int(history)
36
     #
52
     #
37
-    p = page_wrapped(request, rel_path, history_version=history)
38
-    if access.read_page(request, rel_path):
39
-        if meta:
40
-            page_content = p.render_meta()
53
+    title = rel_path.split("/")[-1]
54
+    #
55
+    acc = access_control(request, rel_path)
56
+    if acc.may_read():
57
+        if p is None or p.deleted:
58
+            if rel_path == config.STARTPAGE:
59
+                page_content = mycreole.render_simple(SUCCESS_PAGE)
60
+            else:
61
+                page_content = ""
62
+            if p is not None and p.deleted:
63
+                messages.deleted_page(request)
64
+            else:
65
+                messages.unavailable_msg_page(request, rel_path)
41
         else:
66
         else:
42
-            page_content = p.render_to_html()
43
-        if history:
44
-            messages.history_version_display(request, rel_path, history)
67
+            title = p.title
68
+            if meta:
69
+                page_content = p.render_meta(request, history)
70
+            else:
71
+                page_content = p.render_to_html(request, history)
72
+            if history:
73
+                messages.history_version_display(request, rel_path, history)
45
     else:
74
     else:
46
         messages.permission_denied_msg_page(request, rel_path)
75
         messages.permission_denied_msg_page(request, rel_path)
47
         page_content = ""
76
         page_content = ""
50
         context,
79
         context,
51
         request,
80
         request,
52
         rel_path=rel_path,
81
         rel_path=rel_path,
53
-        title=p.title,
54
-        upload_path=p.attachment_path,
82
+        title=title,
83
+        upload_path=rel_path,
55
         page_content=page_content,
84
         page_content=page_content,
56
-        is_available=p.userpage_is_available()
85
+        is_available=p is not None and not p.deleted
57
     )
86
     )
58
     return render(request, 'pages/page.html', context=context)
87
     return render(request, 'pages/page.html', context=context)
59
 
88
 
60
 
89
 
61
 def edit(request, rel_path):
90
 def edit(request, rel_path):
62
-    if access.write_page(request, rel_path):
91
+    acc = access_control(request, rel_path)
92
+    if acc.may_write():
63
         context = Context(request)      # needs to be executed first because of time mesurement
93
         context = Context(request)      # needs to be executed first because of time mesurement
64
         #
94
         #
95
+        try:
96
+            p = PikiPage.objects.get(rel_path=rel_path)
97
+            is_available = True
98
+        except PikiPage.DoesNotExist:
99
+            p = PikiPage(rel_path=rel_path)
100
+            is_available = False
101
+        #
65
         if not request.POST:
102
         if not request.POST:
66
             history = request.GET.get("history")
103
             history = request.GET.get("history")
67
             if history:
104
             if history:
68
                 history = int(history)
105
                 history = int(history)
69
-            #
70
-            p = page_wrapped(request, rel_path, history_version=history)
71
-            #
72
-            form = EditForm(page_data=p.raw_page_src, page_tags=p.tags)
106
+                form = EditForm(instance=p.history.get(history_id=history))
107
+            else:
108
+                form = EditForm(instance=p)
73
             #
109
             #
74
             context_adaption(
110
             context_adaption(
75
                 context,
111
                 context,
76
                 request,
112
                 request,
77
                 rel_path=rel_path,
113
                 rel_path=rel_path,
78
-                is_available=p.userpage_is_available(),
114
+                is_available=is_available,
79
                 form=form,
115
                 form=form,
80
                 # TODO: Add translation
116
                 # TODO: Add translation
81
                 title=_("Edit page %s") % repr(p.title),
117
                 title=_("Edit page %s") % repr(p.title),
82
-                upload_path=p.attachment_path,
118
+                upload_path=rel_path,
83
             )
119
             )
84
             return render(request, 'pages/page_edit.html', context=context)
120
             return render(request, 'pages/page_edit.html', context=context)
85
         else:
121
         else:
86
-            p = page_wrapped(request, rel_path)
122
+            form = EditForm(request.POST, instance=p)
87
             #
123
             #
88
             save = request.POST.get("save")
124
             save = request.POST.get("save")
89
-            page_txt = request.POST.get("page_txt")
90
-            tags = request.POST.get("page_tags")
91
             preview = request.POST.get("preview")
125
             preview = request.POST.get("preview")
92
             #
126
             #
93
             if save is not None:
127
             if save is not None:
94
-                if p.update_page(page_txt, tags):
95
-                    messages.edit_success(request)
128
+                if form.is_valid():
129
+                    form.instance.prepare_save(request)
130
+                    page = form.save()
131
+                    if page.save_needed:
132
+                        messages.edit_success(request)
133
+                        # update search index
134
+                        update_item(page)
135
+                    else:
136
+                        messages.no_change(request)
96
                 else:
137
                 else:
97
-                    messages.no_change(request)
138
+                    messages.internal_error(request)
98
                 return HttpResponseRedirect(url_page(rel_path))
139
                 return HttpResponseRedirect(url_page(rel_path))
99
             elif preview is not None:
140
             elif preview is not None:
100
-                form = EditForm(page_data=page_txt, page_tags=tags)
101
-                #
102
                 context_adaption(
141
                 context_adaption(
103
                     context,
142
                     context,
104
                     request,
143
                     request,
105
                     rel_path=rel_path,
144
                     rel_path=rel_path,
106
-                    is_available=p.userpage_is_available(),
145
+                    is_available=is_available,
107
                     form=form,
146
                     form=form,
108
                     # TODO: Add translation
147
                     # TODO: Add translation
109
                     title=_("Edit page %s") % repr(p.title),
148
                     title=_("Edit page %s") % repr(p.title),
110
-                    upload_path=p.attachment_path,
111
-                    page_content=p.render_text(request, page_txt)
149
+                    upload_path=rel_path,
150
+                    page_content=p.render_text(request, form.data.get("page_txt"))
112
                 )
151
                 )
113
                 return render(request, 'pages/page_edit.html', context=context)
152
                 return render(request, 'pages/page_edit.html', context=context)
114
             else:
153
             else:
119
 
158
 
120
 
159
 
121
 def delete(request, rel_path):
160
 def delete(request, rel_path):
122
-    if access.write_page(request, rel_path):
161
+    acc = access_control(request, rel_path)
162
+    if acc.may_write():
123
         context = Context(request)      # needs to be executed first because of time mesurement
163
         context = Context(request)      # needs to be executed first because of time mesurement
124
         #
164
         #
165
+        try:
166
+            p = PikiPage.objects.get(rel_path=rel_path)
167
+            is_available = True
168
+        except PikiPage.DoesNotExist:
169
+            p = PikiPage(rel_path=rel_path)
170
+            is_available = False
171
+        #
125
         if not request.POST:
172
         if not request.POST:
126
-            p = page_wrapped(request, rel_path)
127
             #
173
             #
128
             # form = DeleteForm(page_data=p.raw_page_src, page_tags=p.tags)
174
             # form = DeleteForm(page_data=p.raw_page_src, page_tags=p.tags)
129
             #
175
             #
131
                 context,
177
                 context,
132
                 request,
178
                 request,
133
                 rel_path=rel_path,
179
                 rel_path=rel_path,
134
-                is_available=p.userpage_is_available(),
135
-                # form=form,
180
+                is_available=is_available,
136
                 # TODO: Add translation
181
                 # TODO: Add translation
137
                 title=_("Delete page %s") % repr(p.title),
182
                 title=_("Delete page %s") % repr(p.title),
138
-                upload_path=p.attachment_path,
139
-                page_content=p.render_to_html(),
183
+                upload_path=rel_path,
184
+                page_content=p.render_to_html(request),
140
             )
185
             )
141
         else:
186
         else:
142
-            p = page_wrapped(request, rel_path)
143
-            #
144
             delete = request.POST.get("delete")
187
             delete = request.POST.get("delete")
145
             #
188
             #
146
             if delete:
189
             if delete:
190
+                p.deleted = True
191
+                p.save()
147
                 # delete page from search index
192
                 # delete page from search index
148
                 ix = load_index()
193
                 ix = load_index()
149
                 delete_item(ix, p)
194
                 delete_item(ix, p)
150
-                # delete move files to history
151
-                p.delete()
152
                 # add delete message
195
                 # add delete message
153
                 messages.page_deleted(request, p.title)
196
                 messages.page_deleted(request, p.title)
154
                 return HttpResponseRedirect("/")
197
                 return HttpResponseRedirect("/")
162
 
205
 
163
 
206
 
164
 def rename(request, rel_path):
207
 def rename(request, rel_path):
165
-    if access.write_page(request, rel_path):
208
+    acc = access_control(request, rel_path)
209
+    if acc.may_write():
166
         context = Context(request)      # needs to be executed first because of time mesurement
210
         context = Context(request)      # needs to be executed first because of time mesurement
167
         #
211
         #
212
+        try:
213
+            p = PikiPage.objects.get(rel_path=rel_path)
214
+            is_available = True
215
+        except PikiPage.DoesNotExist:
216
+            p = PikiPage(rel_path=rel_path)
217
+            is_available = False
218
+        #
168
         if not request.POST:
219
         if not request.POST:
169
-            p = page_wrapped(request, rel_path)
170
-            #
171
             form = RenameForm(page_name=p.rel_path)
220
             form = RenameForm(page_name=p.rel_path)
172
             #
221
             #
173
             context_adaption(
222
             context_adaption(
174
                 context,
223
                 context,
175
                 request,
224
                 request,
176
                 rel_path=rel_path,
225
                 rel_path=rel_path,
177
-                is_available=p.userpage_is_available(),
226
+                is_available=is_available,
178
                 form=form,
227
                 form=form,
179
                 # TODO: Add translation
228
                 # TODO: Add translation
180
                 title=_("Delete page %s") % repr(p.title),
229
                 title=_("Delete page %s") % repr(p.title),
181
-                upload_path=p.attachment_path,
182
-                page_content=p.render_to_html(),
230
+                upload_path=rel_path,
231
+                page_content=p.render_to_html(request),
183
             )
232
             )
184
         else:
233
         else:
185
-            p = page_wrapped(request, rel_path)
186
-            #
187
             rename = request.POST.get("rename")
234
             rename = request.POST.get("rename")
188
             page_name = request.POST.get("page_name")
235
             page_name = request.POST.get("page_name")
189
             if rename:
236
             if rename:
194
                     ix = load_index()
241
                     ix = load_index()
195
                     delete_item(ix, p)
242
                     delete_item(ix, p)
196
                     # rename the storage folder
243
                     # rename the storage folder
197
-                    p.rename(page_name)
244
+                    p.rel_path = page_name
245
+                    p.save()
198
                     # add the renamed page to the search index
246
                     # add the renamed page to the search index
199
                     add_item(ix, p)
247
                     add_item(ix, p)
200
                     # add rename message
248
                     # add rename message
217
     if sr is None:
265
     if sr is None:
218
         django_messages.error(request, _('Invalid search pattern: %s') % repr(search_txt))
266
         django_messages.error(request, _('Invalid search pattern: %s') % repr(search_txt))
219
         sr = []
267
         sr = []
220
-    pl = page_list(request, [page_wrapped(request, rel_path) for rel_path in set(sr)])
268
+    pl = page_list([PikiPage.objects.get(rel_path=rel_path) for rel_path in set(sr)])
221
     #
269
     #
222
     context_adaption(
270
     context_adaption(
223
         context,
271
         context,

+ 3
- 1
piki/settings.py View File

41
     'django.contrib.sessions',
41
     'django.contrib.sessions',
42
     'django.contrib.messages',
42
     'django.contrib.messages',
43
     'django.contrib.staticfiles',
43
     'django.contrib.staticfiles',
44
+    #
45
+    'simple_history',
44
 ]
46
 ]
45
 
47
 
46
 MIDDLEWARE = [
48
 MIDDLEWARE = [
124
 MEDIA_ROOT = os.path.join(BASE_DIR, 'data', 'media')
126
 MEDIA_ROOT = os.path.join(BASE_DIR, 'data', 'media')
125
 MEDIA_URL = '/media/'
127
 MEDIA_URL = '/media/'
126
 
128
 
127
-MYCREOLE_ROOT = os.path.join(BASE_DIR, 'data', 'pages')
129
+MYCREOLE_ROOT = os.path.join(BASE_DIR, 'data', 'mycreole')
128
 MYCREOLE_ATTACHMENT_ACCESS = {
130
 MYCREOLE_ATTACHMENT_ACCESS = {
129
     'read': 'pages.access.read_attachment',
131
     'read': 'pages.access.read_attachment',
130
     'modify': 'pages.access.modify_attachment',
132
     'modify': 'pages.access.modify_attachment',

Loading…
Cancel
Save