Переглянути джерело

Page data moved to db. Prep for access control

master
Dirk Alders 2 місяці тому
джерело
коміт
a528ac19cb

+ 1
- 1
.gitignore Переглянути файл

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

+ 0
- 5
data/system-pages/index/meta.json Переглянути файл

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

+ 0
- 2
data/system-pages/index/page Переглянути файл

@@ -1,2 +0,0 @@
1
-= Index
2
-<<allpages>>

+ 0
- 5
data/system-pages/startpage/meta.json Переглянути файл

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

+ 0
- 5
data/system-pages/startpage/page Переглянути файл

@@ -1,5 +0,0 @@
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 Переглянути файл

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

+ 0
- 2
data/system-pages/tree/page Переглянути файл

@@ -1,2 +0,0 @@
1
-= Tree
2
-<<allpagestree>>

+ 19
- 8
pages/access.py Переглянути файл

@@ -1,16 +1,27 @@
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 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 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 Переглянути файл

@@ -1,3 +1,17 @@
1 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 Переглянути файл

@@ -5,7 +5,7 @@ import os
5 5
 from django.conf import settings
6 6
 from django.utils.translation import gettext as _
7 7
 
8
-from pages import access
8
+from pages.access import access_control
9 9
 import pages.parameter
10 10
 from .help import actionbar as actionbar_add_help
11 11
 import mycreole
@@ -74,11 +74,12 @@ def actionbar(context, request, caller_name, **kwargs):
74 74
     bar = context[context.ACTIONBAR]
75 75
     if not cms_mode_active(request):
76 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 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 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 83
                 add_meta_menu(request, bar, kwargs["rel_path"], kwargs.get('is_available', False))
83 84
         elif caller_name == 'helpview':
84 85
             actionbar_add_help(context, request, **kwargs)

+ 5
- 9
pages/forms.py Переглянути файл

@@ -3,17 +3,13 @@ from django import forms
3 3
 from django.forms.renderers import BaseRenderer
4 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 15
 class RenameForm(forms.Form):  # Note that it is not inheriting from forms.ModelForm

+ 57
- 0
pages/management/commands/import_system_pages.py Переглянути файл

@@ -0,0 +1,57 @@
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 Переглянути файл

@@ -0,0 +1,73 @@
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 Переглянути файл

@@ -8,6 +8,11 @@ def permission_denied_msg_page(request, rel_path):
8 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 16
 def unavailable_msg_page(request, rel_path):
12 17
     # TODO: Add translation for this message
13 18
     messages.info(request, _("Unavailable: The Page '%s' is not available. Create it or follow a valid link, please.") % rel_path)
@@ -38,6 +43,11 @@ def page_renamed(request):
38 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 51
 def history_version_display(request, rel_path, history_version):
42 52
     # TODO: Add translation for this message
43 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 Переглянути файл

@@ -0,0 +1,63 @@
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 Переглянути файл

@@ -1,3 +1,284 @@
1
+from django.conf import settings
2
+from django.contrib.auth.models import User, Group
1 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 Переглянути файл

@@ -30,22 +30,27 @@ def full_path_all_pages(expression="*"):
30 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 50
     META_FILE_NAME = 'meta.json'
47 51
     #
48 52
     KEY_CREATION_TIME = "creation_time"
53
+    KEY_CREATION_USER = "creation_user"
49 54
     KEY_MODIFIED_TIME = "modified_time"
50 55
     KEY_MODIFIED_USER = "modified_user"
51 56
     KEY_TAGS = "tags"
@@ -85,6 +90,9 @@ class meta_data(dict):
85 90
             if username:
86 91
                 self[self.KEY_MODIFIED_TIME] = int(time.time())
87 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 96
                 if self.KEY_CREATION_TIME not in self:
89 97
                     self[self.KEY_CREATION_TIME] = self[self.KEY_MODIFIED_TIME]
90 98
             if tags:
@@ -109,7 +117,7 @@ class meta_data(dict):
109 117
         shutil.copy(self.filename, history_filename)
110 118
 
111 119
 
112
-class page_data(object):
120
+class page_data(base):
113 121
     PAGE_FILE_NAME = 'page'
114 122
 
115 123
     def __init__(self, path, history_version=None):
@@ -168,12 +176,6 @@ class page_data(object):
168 176
     def rel_path(self):
169 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 179
     @property
178 180
     def title(self):
179 181
         return os.path.basename(self._path).split(2*SPLITCHAR)[-1]
@@ -189,200 +191,6 @@ class page_data(object):
189 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 194
 class page_wrapped(object):
387 195
     """
388 196
     This class holds different page and meta instances and decides which will be used in which case.
@@ -399,19 +207,9 @@ class page_wrapped(object):
399 207
         self._request = request
400 208
         #
401 209
         page_path = self.__page_path__(path)
402
-        system_page_path = self.__system_page_path__(path)
403 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 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 214
     def __page_path__(self, path):
417 215
         if path.startswith(settings.PAGES_ROOT):
@@ -421,20 +219,11 @@ class page_wrapped(object):
421 219
             # must be a relative url
422 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 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 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 228
     def __store_history__(self):
440 229
         if self._page.is_available():
@@ -454,6 +243,12 @@ class page_wrapped(object):
454 243
         rv = meta.get(meta.KEY_CREATION_TIME)
455 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 252
     def delete(self):
458 253
         self.__store_history__()
459 254
         self._page.delete()
@@ -490,7 +285,7 @@ class page_wrapped(object):
490 285
         return rv
491 286
 
492 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 290
     def userpage_is_available(self):
496 291
         return self._page.is_available()
@@ -509,7 +304,7 @@ class page_wrapped(object):
509 304
 
510 305
     def render_meta(self):
511 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 308
         return rv
514 309
 
515 310
     def render_to_html(self):

+ 20
- 20
pages/search.py Переглянути файл

@@ -8,7 +8,7 @@ from whoosh.fields import Schema, ID, TEXT, DATETIME
8 8
 from whoosh.qparser.dateparse import DateParserPlugin
9 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 13
 logger = logging.getLogger(settings.ROOT_LOGGER_NAME).getChild(__name__)
14 14
 
@@ -38,11 +38,11 @@ def create_index():
38 38
 
39 39
 
40 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 48
 def load_index():
@@ -56,19 +56,19 @@ def load_index():
56 56
     return ix
57 57
 
58 58
 
59
-def add_item(ix, pw: page_wrapped):
59
+def add_item(ix, pp: PikiPage):
60 60
     # Define Standard data
61 61
     #
62 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 73
     with ix.writer() as w:
74 74
         logger.info('Adding document with id=%s to the search index.', data.get('id'))
@@ -95,13 +95,13 @@ def whoosh_search(search_txt):
95 95
         return rpl
96 96
 
97 97
 
98
-def delete_item(ix, pw: page_wrapped):
98
+def delete_item(ix, pp: PikiPage):
99 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 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 Переглянути файл

@@ -6,7 +6,8 @@ from django.utils.translation import gettext as _
6 6
 
7 7
 import logging
8 8
 
9
-from . import access
9
+
10
+from .access import access_control
10 11
 from . import messages
11 12
 from . import url_page
12 13
 from . import get_search_query
@@ -14,14 +15,25 @@ import config
14 15
 from .context import context_adaption
15 16
 from .forms import EditForm, RenameForm
16 17
 from .help import help_pages
18
+from .models import PikiPage, page_list
17 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 21
 from themes import Context
21 22
 
22 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 37
 def root(request):
26 38
     return HttpResponseRedirect(url_page(config.STARTPAGE))
27 39
 
@@ -29,19 +41,36 @@ def root(request):
29 41
 def page(request, rel_path):
30 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 48
     meta = "meta" in request.GET
33 49
     history = request.GET.get("history")
34 50
     if history:
35 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 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 74
     else:
46 75
         messages.permission_denied_msg_page(request, rel_path)
47 76
         page_content = ""
@@ -50,65 +79,75 @@ def page(request, rel_path):
50 79
         context,
51 80
         request,
52 81
         rel_path=rel_path,
53
-        title=p.title,
54
-        upload_path=p.attachment_path,
82
+        title=title,
83
+        upload_path=rel_path,
55 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 87
     return render(request, 'pages/page.html', context=context)
59 88
 
60 89
 
61 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 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 102
         if not request.POST:
66 103
             history = request.GET.get("history")
67 104
             if history:
68 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 110
             context_adaption(
75 111
                 context,
76 112
                 request,
77 113
                 rel_path=rel_path,
78
-                is_available=p.userpage_is_available(),
114
+                is_available=is_available,
79 115
                 form=form,
80 116
                 # TODO: Add translation
81 117
                 title=_("Edit page %s") % repr(p.title),
82
-                upload_path=p.attachment_path,
118
+                upload_path=rel_path,
83 119
             )
84 120
             return render(request, 'pages/page_edit.html', context=context)
85 121
         else:
86
-            p = page_wrapped(request, rel_path)
122
+            form = EditForm(request.POST, instance=p)
87 123
             #
88 124
             save = request.POST.get("save")
89
-            page_txt = request.POST.get("page_txt")
90
-            tags = request.POST.get("page_tags")
91 125
             preview = request.POST.get("preview")
92 126
             #
93 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 137
                 else:
97
-                    messages.no_change(request)
138
+                    messages.internal_error(request)
98 139
                 return HttpResponseRedirect(url_page(rel_path))
99 140
             elif preview is not None:
100
-                form = EditForm(page_data=page_txt, page_tags=tags)
101
-                #
102 141
                 context_adaption(
103 142
                     context,
104 143
                     request,
105 144
                     rel_path=rel_path,
106
-                    is_available=p.userpage_is_available(),
145
+                    is_available=is_available,
107 146
                     form=form,
108 147
                     # TODO: Add translation
109 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 152
                 return render(request, 'pages/page_edit.html', context=context)
114 153
             else:
@@ -119,11 +158,18 @@ def edit(request, rel_path):
119 158
 
120 159
 
121 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 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 172
         if not request.POST:
126
-            p = page_wrapped(request, rel_path)
127 173
             #
128 174
             # form = DeleteForm(page_data=p.raw_page_src, page_tags=p.tags)
129 175
             #
@@ -131,24 +177,21 @@ def delete(request, rel_path):
131 177
                 context,
132 178
                 request,
133 179
                 rel_path=rel_path,
134
-                is_available=p.userpage_is_available(),
135
-                # form=form,
180
+                is_available=is_available,
136 181
                 # TODO: Add translation
137 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 186
         else:
142
-            p = page_wrapped(request, rel_path)
143
-            #
144 187
             delete = request.POST.get("delete")
145 188
             #
146 189
             if delete:
190
+                p.deleted = True
191
+                p.save()
147 192
                 # delete page from search index
148 193
                 ix = load_index()
149 194
                 delete_item(ix, p)
150
-                # delete move files to history
151
-                p.delete()
152 195
                 # add delete message
153 196
                 messages.page_deleted(request, p.title)
154 197
                 return HttpResponseRedirect("/")
@@ -162,28 +205,32 @@ def delete(request, rel_path):
162 205
 
163 206
 
164 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 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 219
         if not request.POST:
169
-            p = page_wrapped(request, rel_path)
170
-            #
171 220
             form = RenameForm(page_name=p.rel_path)
172 221
             #
173 222
             context_adaption(
174 223
                 context,
175 224
                 request,
176 225
                 rel_path=rel_path,
177
-                is_available=p.userpage_is_available(),
226
+                is_available=is_available,
178 227
                 form=form,
179 228
                 # TODO: Add translation
180 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 233
         else:
185
-            p = page_wrapped(request, rel_path)
186
-            #
187 234
             rename = request.POST.get("rename")
188 235
             page_name = request.POST.get("page_name")
189 236
             if rename:
@@ -194,7 +241,8 @@ def rename(request, rel_path):
194 241
                     ix = load_index()
195 242
                     delete_item(ix, p)
196 243
                     # rename the storage folder
197
-                    p.rename(page_name)
244
+                    p.rel_path = page_name
245
+                    p.save()
198 246
                     # add the renamed page to the search index
199 247
                     add_item(ix, p)
200 248
                     # add rename message
@@ -217,7 +265,7 @@ def search(request):
217 265
     if sr is None:
218 266
         django_messages.error(request, _('Invalid search pattern: %s') % repr(search_txt))
219 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 270
     context_adaption(
223 271
         context,

+ 3
- 1
piki/settings.py Переглянути файл

@@ -41,6 +41,8 @@ INSTALLED_APPS = [
41 41
     'django.contrib.sessions',
42 42
     'django.contrib.messages',
43 43
     'django.contrib.staticfiles',
44
+    #
45
+    'simple_history',
44 46
 ]
45 47
 
46 48
 MIDDLEWARE = [
@@ -124,7 +126,7 @@ STATIC_URL = 'static/'
124 126
 MEDIA_ROOT = os.path.join(BASE_DIR, 'data', 'media')
125 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 130
 MYCREOLE_ATTACHMENT_ACCESS = {
129 131
     'read': 'pages.access.read_attachment',
130 132
     'modify': 'pages.access.modify_attachment',

Завантаження…
Відмінити
Зберегти