evewiki 0.0.1.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- evewiki/__init__.py +6 -0
- evewiki/admin.py +16 -0
- evewiki/app_settings.py +5 -0
- evewiki/apps.py +9 -0
- evewiki/auth_hooks.py +35 -0
- evewiki/forms.py +29 -0
- evewiki/migrations/0001_initial.py +190 -0
- evewiki/migrations/__init__.py +0 -0
- evewiki/models/__init__.py +0 -0
- evewiki/models/logs.py +15 -0
- evewiki/models/page_versions.py +48 -0
- evewiki/models/pages.py +249 -0
- evewiki/models/settings.py +35 -0
- evewiki/tasks.py +12 -0
- evewiki/templates/evewiki/base.html +11 -0
- evewiki/templates/evewiki/help.html +16 -0
- evewiki/templates/evewiki/index.html +363 -0
- evewiki/templates/evewiki/page.html +98 -0
- evewiki/templates/evewiki/page_delete.html +41 -0
- evewiki/templates/evewiki/page_tree.html +10 -0
- evewiki/templates/evewiki/sidebar.html +5 -0
- evewiki/tests/__init__.py +0 -0
- evewiki/tests/test_tasks.py +11 -0
- evewiki/urls.py +15 -0
- evewiki/views.py +149 -0
- evewiki-0.0.1.dev1.dist-info/METADATA +121 -0
- evewiki-0.0.1.dev1.dist-info/RECORD +29 -0
- evewiki-0.0.1.dev1.dist-info/WHEEL +4 -0
- evewiki-0.0.1.dev1.dist-info/licenses/LICENSE +21 -0
evewiki/__init__.py
ADDED
evewiki/admin.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Admin site."""
|
|
2
|
+
|
|
3
|
+
from django.contrib import admin
|
|
4
|
+
|
|
5
|
+
from .models.logs import Log
|
|
6
|
+
from .models.page_versions import PageVersion
|
|
7
|
+
from .models.pages import Page
|
|
8
|
+
from .models.settings import Setting
|
|
9
|
+
|
|
10
|
+
# from django.contrib import admin
|
|
11
|
+
|
|
12
|
+
# Register your models for the admin site here.
|
|
13
|
+
admin.site.register(Page)
|
|
14
|
+
admin.site.register(Setting)
|
|
15
|
+
admin.site.register(PageVersion)
|
|
16
|
+
admin.site.register(Log)
|
evewiki/app_settings.py
ADDED
evewiki/apps.py
ADDED
evewiki/auth_hooks.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
|
|
3
|
+
from allianceauth import hooks
|
|
4
|
+
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
|
5
|
+
|
|
6
|
+
from . import urls
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EveWikiMenuItem(MenuItemHook):
|
|
10
|
+
"""This class ensures only authorized users will see the menu entry"""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
# setup menu entry for sidebar
|
|
14
|
+
MenuItemHook.__init__(
|
|
15
|
+
self,
|
|
16
|
+
_("wiki"),
|
|
17
|
+
"fas fa-tree fa-fw",
|
|
18
|
+
"evewiki:index",
|
|
19
|
+
navactive=["evewiki:"],
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
def render(self, request):
|
|
23
|
+
if request.user.has_perm("evewiki.basic_access"):
|
|
24
|
+
return MenuItemHook.render(self, request)
|
|
25
|
+
return ""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@hooks.register("menu_item_hook")
|
|
29
|
+
def register_menu():
|
|
30
|
+
return EveWikiMenuItem()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@hooks.register("url_hook")
|
|
34
|
+
def register_urls():
|
|
35
|
+
return UrlHook(urls, "evewiki", r"^evewiki/")
|
evewiki/forms.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from django import forms
|
|
2
|
+
|
|
3
|
+
from .models.pages import Page
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PageForm(forms.ModelForm):
|
|
7
|
+
|
|
8
|
+
# Customised ddl inferring additional context via text-indentation
|
|
9
|
+
parent = forms.ChoiceField(
|
|
10
|
+
choices=Page.list(),
|
|
11
|
+
required=False,
|
|
12
|
+
label="Path",
|
|
13
|
+
help_text=Page._meta.get_field("parent").help_text,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
model = Page
|
|
18
|
+
fields = ["title", "parent", "slug", "priority", "states", "groups"]
|
|
19
|
+
|
|
20
|
+
def clean(self):
|
|
21
|
+
"""
|
|
22
|
+
Django needs a little help to turn the custom `parent` field back into a model.
|
|
23
|
+
"""
|
|
24
|
+
cleaned_data = super().clean()
|
|
25
|
+
parent_id = cleaned_data["parent"]
|
|
26
|
+
cleaned_data["parent"] = (
|
|
27
|
+
Page.objects.get(pk=int(parent_id)) if parent_id else None
|
|
28
|
+
)
|
|
29
|
+
return cleaned_data
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Generated by Django 4.2.21 on 2025-05-27 00:35
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
initial = True
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
("auth", "0012_alter_user_first_name_max_length"),
|
|
13
|
+
("authentication", "0024_alter_userprofile_language"),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.CreateModel(
|
|
18
|
+
name="General",
|
|
19
|
+
fields=[
|
|
20
|
+
(
|
|
21
|
+
"id",
|
|
22
|
+
models.AutoField(
|
|
23
|
+
auto_created=True,
|
|
24
|
+
primary_key=True,
|
|
25
|
+
serialize=False,
|
|
26
|
+
verbose_name="ID",
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
],
|
|
30
|
+
options={
|
|
31
|
+
"permissions": (
|
|
32
|
+
("basic_access", "Can access this app"),
|
|
33
|
+
("editor_access", "Can edit this app"),
|
|
34
|
+
),
|
|
35
|
+
"managed": False,
|
|
36
|
+
"default_permissions": (),
|
|
37
|
+
},
|
|
38
|
+
),
|
|
39
|
+
migrations.CreateModel(
|
|
40
|
+
name="Log",
|
|
41
|
+
fields=[
|
|
42
|
+
(
|
|
43
|
+
"id",
|
|
44
|
+
models.AutoField(
|
|
45
|
+
auto_created=True,
|
|
46
|
+
primary_key=True,
|
|
47
|
+
serialize=False,
|
|
48
|
+
verbose_name="ID",
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
("user", models.TextField(blank=True, default="", null=True)),
|
|
52
|
+
("action", models.TextField(blank=True, default="", null=True)),
|
|
53
|
+
("details", models.TextField(blank=True, default="", null=True)),
|
|
54
|
+
("created", models.DateTimeField(auto_now_add=True)),
|
|
55
|
+
],
|
|
56
|
+
),
|
|
57
|
+
migrations.CreateModel(
|
|
58
|
+
name="Page",
|
|
59
|
+
fields=[
|
|
60
|
+
(
|
|
61
|
+
"id",
|
|
62
|
+
models.AutoField(
|
|
63
|
+
auto_created=True,
|
|
64
|
+
primary_key=True,
|
|
65
|
+
serialize=False,
|
|
66
|
+
verbose_name="ID",
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
(
|
|
70
|
+
"title",
|
|
71
|
+
models.CharField(
|
|
72
|
+
help_text="Required: Displayed above content and in menu",
|
|
73
|
+
max_length=255,
|
|
74
|
+
),
|
|
75
|
+
),
|
|
76
|
+
(
|
|
77
|
+
"slug",
|
|
78
|
+
models.CharField(
|
|
79
|
+
help_text="Required: Indentifier for path and links",
|
|
80
|
+
max_length=255,
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
(
|
|
84
|
+
"priority",
|
|
85
|
+
models.IntegerField(
|
|
86
|
+
default=10,
|
|
87
|
+
help_text="Required: Sort order for pages on the same path",
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
("content", models.TextField(blank=True, default="", null=True)),
|
|
91
|
+
(
|
|
92
|
+
"groups",
|
|
93
|
+
models.ManyToManyField(
|
|
94
|
+
blank=True,
|
|
95
|
+
default=None,
|
|
96
|
+
help_text="Optional: Restrict Page to specific Groups",
|
|
97
|
+
related_name="+",
|
|
98
|
+
to="auth.group",
|
|
99
|
+
verbose_name="groups",
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
(
|
|
103
|
+
"parent",
|
|
104
|
+
models.ForeignKey(
|
|
105
|
+
blank=True,
|
|
106
|
+
help_text="Required: Oragnise page hierarchy",
|
|
107
|
+
null=True,
|
|
108
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
109
|
+
related_name="children",
|
|
110
|
+
to="evewiki.page",
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
(
|
|
114
|
+
"states",
|
|
115
|
+
models.ManyToManyField(
|
|
116
|
+
blank=True,
|
|
117
|
+
default=None,
|
|
118
|
+
help_text="Optional: Restrict Page to specific States",
|
|
119
|
+
related_name="+",
|
|
120
|
+
to="authentication.state",
|
|
121
|
+
verbose_name="states",
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
],
|
|
125
|
+
),
|
|
126
|
+
migrations.CreateModel(
|
|
127
|
+
name="Setting",
|
|
128
|
+
fields=[
|
|
129
|
+
(
|
|
130
|
+
"id",
|
|
131
|
+
models.AutoField(
|
|
132
|
+
auto_created=True,
|
|
133
|
+
primary_key=True,
|
|
134
|
+
serialize=False,
|
|
135
|
+
verbose_name="ID",
|
|
136
|
+
),
|
|
137
|
+
),
|
|
138
|
+
(
|
|
139
|
+
"hierarchy_max_display_depth",
|
|
140
|
+
models.IntegerField(
|
|
141
|
+
default=10,
|
|
142
|
+
help_text="Limit the depth of the tree for the hierarchy on the main display",
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
(
|
|
146
|
+
"max_versions",
|
|
147
|
+
models.IntegerField(
|
|
148
|
+
default=1000,
|
|
149
|
+
help_text="No one has infinite disk space, a sensible limit which can be modified to clear down the history",
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
],
|
|
153
|
+
),
|
|
154
|
+
migrations.CreateModel(
|
|
155
|
+
name="PageVersion",
|
|
156
|
+
fields=[
|
|
157
|
+
(
|
|
158
|
+
"id",
|
|
159
|
+
models.AutoField(
|
|
160
|
+
auto_created=True,
|
|
161
|
+
primary_key=True,
|
|
162
|
+
serialize=False,
|
|
163
|
+
verbose_name="ID",
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
("content", models.TextField(blank=True, default="", null=True)),
|
|
167
|
+
("created", models.DateTimeField(auto_now_add=True)),
|
|
168
|
+
(
|
|
169
|
+
"page",
|
|
170
|
+
models.ForeignKey(
|
|
171
|
+
blank=True,
|
|
172
|
+
help_text="Page being edited",
|
|
173
|
+
null=True,
|
|
174
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
175
|
+
to="evewiki.page",
|
|
176
|
+
),
|
|
177
|
+
),
|
|
178
|
+
(
|
|
179
|
+
"user",
|
|
180
|
+
models.ForeignKey(
|
|
181
|
+
blank=True,
|
|
182
|
+
help_text="User editing the page",
|
|
183
|
+
null=True,
|
|
184
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
185
|
+
to="authentication.userprofile",
|
|
186
|
+
),
|
|
187
|
+
),
|
|
188
|
+
],
|
|
189
|
+
),
|
|
190
|
+
]
|
|
File without changes
|
|
File without changes
|
evewiki/models/logs.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Log(models.Model):
|
|
5
|
+
"""
|
|
6
|
+
No relationships just a log.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
user = models.TextField(default="", blank=True, null=True)
|
|
10
|
+
|
|
11
|
+
action = models.TextField(default="", blank=True, null=True)
|
|
12
|
+
|
|
13
|
+
details = models.TextField(default="", blank=True, null=True)
|
|
14
|
+
|
|
15
|
+
created = models.DateTimeField(auto_now_add=True)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
from allianceauth.authentication.models import UserProfile
|
|
4
|
+
|
|
5
|
+
from .pages import Page
|
|
6
|
+
from .settings import Setting
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PageVersion(models.Model):
|
|
10
|
+
"""
|
|
11
|
+
Previous edits have value
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
page = models.ForeignKey(
|
|
15
|
+
Page,
|
|
16
|
+
on_delete=models.SET_NULL,
|
|
17
|
+
null=True,
|
|
18
|
+
blank=True,
|
|
19
|
+
help_text="Page being edited",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
user = models.ForeignKey(
|
|
23
|
+
UserProfile,
|
|
24
|
+
on_delete=models.SET_NULL,
|
|
25
|
+
null=True,
|
|
26
|
+
blank=True,
|
|
27
|
+
help_text="User editing the page",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
content = models.TextField(default="", blank=True, null=True)
|
|
31
|
+
|
|
32
|
+
created = models.DateTimeField(auto_now_add=True)
|
|
33
|
+
|
|
34
|
+
def save(self, *args, **kwargs):
|
|
35
|
+
"""
|
|
36
|
+
Clean up old versions when saving
|
|
37
|
+
"""
|
|
38
|
+
super().save(*args, **kwargs)
|
|
39
|
+
if self.page_id:
|
|
40
|
+
# Get the IDs of old versions to delete
|
|
41
|
+
settings = Setting.get_settings()
|
|
42
|
+
old_version_ids = list(
|
|
43
|
+
PageVersion.objects.filter(page=self.page)
|
|
44
|
+
.order_by("-created")
|
|
45
|
+
.values_list("id", flat=True)[settings.max_versions :]
|
|
46
|
+
)
|
|
47
|
+
if old_version_ids:
|
|
48
|
+
PageVersion.objects.filter(id__in=old_version_ids).delete()
|
evewiki/models/pages.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from html import escape
|
|
3
|
+
|
|
4
|
+
from django.contrib.auth.models import Group
|
|
5
|
+
from django.core.exceptions import ValidationError
|
|
6
|
+
from django.db import models
|
|
7
|
+
|
|
8
|
+
from allianceauth.authentication.models import State
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class General(models.Model):
|
|
12
|
+
"""A meta model for app permissions."""
|
|
13
|
+
|
|
14
|
+
class Meta:
|
|
15
|
+
managed = False
|
|
16
|
+
default_permissions = ()
|
|
17
|
+
permissions = (
|
|
18
|
+
("basic_access", "Can access this app"),
|
|
19
|
+
("editor_access", "Can edit this app"),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Page(models.Model):
|
|
24
|
+
"""
|
|
25
|
+
Pages in a hierarchy, identifiable by slugs with a light touch of folder sorting.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
title = models.CharField(
|
|
29
|
+
max_length=255,
|
|
30
|
+
null=False,
|
|
31
|
+
help_text="Required: Displayed above content and in menu",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
parent = models.ForeignKey(
|
|
35
|
+
"self",
|
|
36
|
+
on_delete=models.PROTECT,
|
|
37
|
+
related_name="children",
|
|
38
|
+
null=True,
|
|
39
|
+
blank=True,
|
|
40
|
+
help_text="Required: Oragnise page hierarchy",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
slug = models.CharField(
|
|
44
|
+
max_length=255, help_text="Required: Indentifier for path and links"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
priority = models.IntegerField(
|
|
48
|
+
default=10, help_text="Required: Sort order for pages on the same path"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
states = models.ManyToManyField(
|
|
52
|
+
State,
|
|
53
|
+
default=None,
|
|
54
|
+
blank=True,
|
|
55
|
+
related_name="+",
|
|
56
|
+
verbose_name=("states"),
|
|
57
|
+
help_text="Optional: Restrict Page to specific States",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
groups = models.ManyToManyField(
|
|
61
|
+
Group,
|
|
62
|
+
default=None,
|
|
63
|
+
blank=True,
|
|
64
|
+
related_name="+",
|
|
65
|
+
verbose_name=("groups"),
|
|
66
|
+
help_text="Optional: Restrict Page to specific Groups",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
content = models.TextField(default="", blank=True, null=True)
|
|
70
|
+
|
|
71
|
+
def __str__(self):
|
|
72
|
+
return self.title
|
|
73
|
+
|
|
74
|
+
def clean(self):
|
|
75
|
+
|
|
76
|
+
# Force slugs to be alphanumber lowercase with dashes
|
|
77
|
+
self.slug = self.slug.lower().replace(" ", "-")
|
|
78
|
+
self.slug = re.sub(r"[^a-z0-9\-]", "", self.slug)
|
|
79
|
+
|
|
80
|
+
# Check slug is unique for this path
|
|
81
|
+
# Prepare the queryset for siblings (excluding self if updating)
|
|
82
|
+
siblings_qs = Page.objects.filter(parent=self.parent)
|
|
83
|
+
if self.pk is not None:
|
|
84
|
+
siblings_qs = siblings_qs.exclude(pk=self.pk)
|
|
85
|
+
|
|
86
|
+
sibling_slugs = list(siblings_qs.values_list("slug", flat=True))
|
|
87
|
+
if self.slug in sibling_slugs:
|
|
88
|
+
raise ValidationError(
|
|
89
|
+
f'A slug with "{self.slug}" already exists ar this path'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Check circular reference to self
|
|
93
|
+
if self.parent and self.pk and self.parent.pk == self.pk:
|
|
94
|
+
raise ValidationError("A page cannot be its own parent.")
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def path(self):
|
|
98
|
+
segments = []
|
|
99
|
+
node = self
|
|
100
|
+
while node is not None:
|
|
101
|
+
segments.append(node.slug)
|
|
102
|
+
node = node.parent
|
|
103
|
+
return "/".join(reversed(segments))
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def summary(self):
|
|
107
|
+
"""
|
|
108
|
+
Generates a summary of headers from Markdown text as a nested HTML list of links.
|
|
109
|
+
"""
|
|
110
|
+
header_pattern = re.compile(r"^(#{1,6})\s+(.*)", re.MULTILINE)
|
|
111
|
+
headers = []
|
|
112
|
+
|
|
113
|
+
for match in header_pattern.finditer(self.content):
|
|
114
|
+
hashes, title = match.groups()
|
|
115
|
+
level = len(hashes)
|
|
116
|
+
anchor = re.sub(r"[^\w\s-]", "", title).strip().lower()
|
|
117
|
+
anchor = re.sub(r"\s+", "-", anchor)
|
|
118
|
+
headers.append((level, title, anchor))
|
|
119
|
+
|
|
120
|
+
# Build nested HTML list
|
|
121
|
+
html = []
|
|
122
|
+
prev_level = 0
|
|
123
|
+
for level, title, anchor in headers:
|
|
124
|
+
while prev_level < level:
|
|
125
|
+
html.append("<ul>")
|
|
126
|
+
prev_level += 1
|
|
127
|
+
while prev_level > level:
|
|
128
|
+
html.append("</ul>")
|
|
129
|
+
prev_level -= 1
|
|
130
|
+
html.append(f'<li><a href="#{escape(anchor)}">{escape(title)}</a></li>')
|
|
131
|
+
while prev_level > 0:
|
|
132
|
+
html.append("</ul>")
|
|
133
|
+
prev_level -= 1
|
|
134
|
+
|
|
135
|
+
return "\n".join(html)
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def list(cls, pages=None, parent=None, level=0):
|
|
139
|
+
"""
|
|
140
|
+
Returns a flat list of pages with titles indented by hierarchy level.
|
|
141
|
+
Returns: list of tuple
|
|
142
|
+
"""
|
|
143
|
+
if pages is None:
|
|
144
|
+
pages = list(cls.objects.all().select_related("parent"))
|
|
145
|
+
|
|
146
|
+
flat = []
|
|
147
|
+
children = [p for p in pages if p.parent_id == (parent.id if parent else None)]
|
|
148
|
+
children.sort(key=lambda p: p.priority)
|
|
149
|
+
|
|
150
|
+
for child in children:
|
|
151
|
+
indent = "\u2003" * level
|
|
152
|
+
display = f"{indent}{child.title} [/{child.path}]"
|
|
153
|
+
flat.append((child.id, display))
|
|
154
|
+
flat.extend(cls.list(pages, child, level + 1))
|
|
155
|
+
|
|
156
|
+
if parent is None and level == 0:
|
|
157
|
+
flat.insert(0, ("", "/"))
|
|
158
|
+
|
|
159
|
+
return flat
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def tree(cls, user=None, pages=None, parent=None, depth=0):
|
|
163
|
+
"""
|
|
164
|
+
Recursively builds a tree of pages as nested dicts,
|
|
165
|
+
ordered by priority, with 'path' property.
|
|
166
|
+
Returns: List[Dict]
|
|
167
|
+
"""
|
|
168
|
+
if pages is None:
|
|
169
|
+
pages = list(cls.objects.all().select_related("parent"))
|
|
170
|
+
|
|
171
|
+
tree = []
|
|
172
|
+
children = [p for p in pages if p.parent_id == (parent.id if parent else None)]
|
|
173
|
+
children.sort(key=lambda p: p.priority)
|
|
174
|
+
|
|
175
|
+
for child in children:
|
|
176
|
+
node = {
|
|
177
|
+
"id": child.id,
|
|
178
|
+
"title": child.title,
|
|
179
|
+
"slug": child.slug,
|
|
180
|
+
"priority": child.priority,
|
|
181
|
+
"path": getattr(child, "path", None),
|
|
182
|
+
"depth": depth,
|
|
183
|
+
"children": cls.tree(user, pages, child, depth=depth + 1),
|
|
184
|
+
}
|
|
185
|
+
if child.user_access(user=user):
|
|
186
|
+
tree.append(node)
|
|
187
|
+
return tree
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def get_by_path(cls, path: str, user):
|
|
191
|
+
"""
|
|
192
|
+
Find an id for a given path
|
|
193
|
+
Returns: Page id or None
|
|
194
|
+
"""
|
|
195
|
+
path = path.split("/")
|
|
196
|
+
parent = None
|
|
197
|
+
page = None
|
|
198
|
+
for slug in path:
|
|
199
|
+
try:
|
|
200
|
+
page = cls.objects.filter(slug=slug, parent=parent).first()
|
|
201
|
+
except cls.DoesNotExist:
|
|
202
|
+
return None
|
|
203
|
+
parent = page
|
|
204
|
+
|
|
205
|
+
if page and not page.user_access(user=user):
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
return page if page else None
|
|
209
|
+
|
|
210
|
+
def user_access(self, user):
|
|
211
|
+
|
|
212
|
+
# Admin has full access
|
|
213
|
+
if user.profile.state.name == "Admin":
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
# Editor has full access
|
|
217
|
+
if user.has_perm("evewiki.editor_access"):
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
access = False
|
|
221
|
+
|
|
222
|
+
# If no groups or states assigned then yes
|
|
223
|
+
groups_empty = not self.groups.exists()
|
|
224
|
+
states_empty = not self.states.exists()
|
|
225
|
+
if groups_empty and states_empty:
|
|
226
|
+
access = True
|
|
227
|
+
|
|
228
|
+
# If any of the user's groups are allowed access
|
|
229
|
+
user_has_group = False
|
|
230
|
+
if any(
|
|
231
|
+
group in self.groups.all()
|
|
232
|
+
for group in user.groups.values_list("name", flat=True)
|
|
233
|
+
):
|
|
234
|
+
access = True
|
|
235
|
+
user_has_group = True
|
|
236
|
+
|
|
237
|
+
# If the user's states is in the states list
|
|
238
|
+
user_has_state = False
|
|
239
|
+
if self.states.filter(name=user.profile.state.name).exists():
|
|
240
|
+
access = True
|
|
241
|
+
user_has_state = True
|
|
242
|
+
|
|
243
|
+
# If Page has both User needs both
|
|
244
|
+
if not groups_empty and not states_empty:
|
|
245
|
+
access = False
|
|
246
|
+
if user_has_group and user_has_state:
|
|
247
|
+
access = True
|
|
248
|
+
|
|
249
|
+
return access
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Setting(models.Model):
|
|
5
|
+
"""
|
|
6
|
+
Settings and feature flags.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
hierarchy_max_display_depth = models.IntegerField(
|
|
10
|
+
default=10,
|
|
11
|
+
null=False,
|
|
12
|
+
help_text="Limit the depth of the tree for the hierarchy on the main display",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
max_versions = models.IntegerField(
|
|
16
|
+
default=1000,
|
|
17
|
+
null=False,
|
|
18
|
+
help_text="No one has infinite disk space, a sensible limit which can be modified to clear down the history",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
def get_settings():
|
|
22
|
+
"""
|
|
23
|
+
Returns the Setting instance with the lowest id,
|
|
24
|
+
or a default Setting instance (not saved) if none exist.
|
|
25
|
+
"""
|
|
26
|
+
setting = Setting.objects.order_by("id").first()
|
|
27
|
+
if setting is not None:
|
|
28
|
+
return setting
|
|
29
|
+
|
|
30
|
+
return Setting(
|
|
31
|
+
hierarchy_max_display_depth=Setting._meta.get_field(
|
|
32
|
+
"hierarchy_max_display_depth"
|
|
33
|
+
).get_default(),
|
|
34
|
+
max_versions=Setting._meta.get_field("max_versions").get_default(),
|
|
35
|
+
)
|
evewiki/tasks.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{% block details %}
|
|
2
|
+
<h1>Wiki</h1>
|
|
3
|
+
Use the `+` icon to add a new Page.<br />
|
|
4
|
+
On the left; the menu/hierarchy will appear as pages are added.<br />
|
|
5
|
+
On the right; details/options for a page will become available as you navigate the hierarchy.
|
|
6
|
+
|
|
7
|
+
{% endblock %}
|
|
8
|
+
|
|
9
|
+
{% block extra_javascript %}
|
|
10
|
+
{% endblock %}
|
|
11
|
+
|
|
12
|
+
{% block extra_css %}
|
|
13
|
+
{% endblock %}
|
|
14
|
+
|
|
15
|
+
{% block extra_script %}
|
|
16
|
+
{% endblock %}
|