added import and export of wiki content
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.4 on 2025-09-15 00:26
|
# Generated by Django 5.2.9 on 2025-12-27 04:35
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import tinywiki.models
|
import tinywiki.models
|
||||||
@@ -15,6 +15,33 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BuiltinImages',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('app', models.CharField(max_length=255, unique=True, verbose_name='app')),
|
||||||
|
('version', models.PositiveIntegerField(verbose_name='version')),
|
||||||
|
('prefix', models.CharField(verbose_name='prefix')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BuiltinPages',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('app', models.CharField(max_length=255, unique=True, verbose_name='app')),
|
||||||
|
('version', models.PositiveIntegerField(verbose_name='version')),
|
||||||
|
('prefix', models.CharField(verbose_name='prefix')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SidebarSection',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||||
|
('priority', models.PositiveIntegerField(verbose_name='priority')),
|
||||||
|
('is_visible', models.BooleanField(default=True, verbose_name='is visible')),
|
||||||
|
],
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Image',
|
name='Image',
|
||||||
fields=[
|
fields=[
|
||||||
@@ -33,7 +60,7 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('slug', models.SlugField(max_length=255, unique=True, verbose_name='slug')),
|
('slug', models.SlugField(max_length=255, unique=True, verbose_name='slug')),
|
||||||
('title', models.CharField(max_length=255, verbose_name='title')),
|
('title', models.CharField(max_length=255, verbose_name='title')),
|
||||||
('status_data', models.CharField(default='', max_length=15, verbose_name='status')),
|
('status_data', models.CharField(choices=[('in_progress', 'in progress'), ('draft', 'draft'), ('published', 'published')], default='in_progress', max_length=15, verbose_name='status')),
|
||||||
('content_type_data', models.CharField(choices=[('markdown', 'Markdown'), ('bbcode', 'BBCode')], default='bbcode', verbose_name='content type')),
|
('content_type_data', models.CharField(choices=[('markdown', 'Markdown'), ('bbcode', 'BBCode')], default='bbcode', verbose_name='content type')),
|
||||||
('content', models.TextField(verbose_name='Page content')),
|
('content', models.TextField(verbose_name='Page content')),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')),
|
||||||
@@ -43,4 +70,17 @@ class Migration(migrations.Migration):
|
|||||||
('last_edited_by', models.ForeignKey(default=tinywiki.models.get_tinywiki_default_user, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='tinywiki_last_edited', to=settings.AUTH_USER_MODEL, verbose_name='last edited by')),
|
('last_edited_by', models.ForeignKey(default=tinywiki.models.get_tinywiki_default_user, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='tinywiki_last_edited', to=settings.AUTH_USER_MODEL, verbose_name='last edited by')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SidebarEntry',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=255, verbose_name='Title')),
|
||||||
|
('is_visible', models.BooleanField(default=True, verbose_name='is visible')),
|
||||||
|
('priority', models.PositiveIntegerField(verbose_name='Priority')),
|
||||||
|
('wiki_slug', models.CharField(blank=True, max_length=255, null=True, verbose_name='Wiki slug')),
|
||||||
|
('url', models.CharField(blank=True, max_length=512, null=True, verbose_name='Link URL')),
|
||||||
|
('widget', models.TextField(blank=True, null=True, verbose_name='Widget')),
|
||||||
|
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tinywiki.sidebarsection', verbose_name='Section')),
|
||||||
|
],
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
from .. import settings
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('tinywiki', '0001_initial'),
|
('tinywiki', '0001_initial'),
|
||||||
@@ -54,28 +56,30 @@ class Migration(migrations.Migration):
|
|||||||
permission = Permission.objects.create(codename=perm,content_type=content_type)
|
permission = Permission.objects.create(codename=perm,content_type=content_type)
|
||||||
perm_mapping[perm] = permission
|
perm_mapping[perm] = permission
|
||||||
|
|
||||||
|
|
||||||
for grp, perms in GROUPS:
|
for grp, perms in GROUPS:
|
||||||
group = Group.objects.create(name=grp)
|
group = Group.objects.create(name=grp)
|
||||||
for perm in perms:
|
for perm in perms:
|
||||||
group.permissions.add(perm_mapping[perm])
|
group.permissions.add(perm_mapping[perm])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def init_builtin_pages(apps,schema_editor)->None:
|
def init_builtin_pages(apps, schema_editor):
|
||||||
from ..models import Page,Image
|
from ..models import Page, BuiltinPages
|
||||||
from ..enums import WikiContentType, WikiPageStatus
|
from ..enums import WikiContentType, WikiPageStatus
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import json
|
import json
|
||||||
|
|
||||||
page_path = Path(__file__).resolve().parent / "pages"
|
page_path = Path(__file__).resolve().parent / "pages"
|
||||||
json_file = page_path / "pages.json"
|
json_file = page_path / "pages.json"
|
||||||
|
|
||||||
if json_file.is_file():
|
if json_file.is_file():
|
||||||
with open(json_file, "rt", encoding="utf-8") as ifile:
|
with open(json_file, "rt", encoding="utf-8") as ifile:
|
||||||
data = json.loads(ifile.read())
|
data = json.loads(ifile.read())
|
||||||
|
|
||||||
|
version = data["version"]
|
||||||
|
app = data["app"]
|
||||||
|
prefix = data['prefix']
|
||||||
|
|
||||||
|
for slug, spec in data['pages'].items():
|
||||||
for slug,spec in data.items():
|
|
||||||
filename = page_path / spec['file']
|
filename = page_path / spec['file']
|
||||||
with open(filename, "rt", encoding="utf-8") as ifile:
|
with open(filename, "rt", encoding="utf-8") as ifile:
|
||||||
content = ifile.read()
|
content = ifile.read()
|
||||||
@@ -86,10 +90,12 @@ class Migration(migrations.Migration):
|
|||||||
content_type_data=WikiContentType.from_string(spec['content_type']).value,
|
content_type_data=WikiContentType.from_string(spec['content_type']).value,
|
||||||
content=content)
|
content=content)
|
||||||
|
|
||||||
|
BuiltinPages.objects.create(app=app, prefix=prefix, version=version)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def init_builtin_images(apps, schema_edit):
|
def init_builtin_images(apps, schema_edit):
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ..models import Image
|
from ..models import Image, BuiltinImages
|
||||||
from django.core.files.images import ImageFile
|
from django.core.files.images import ImageFile
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -102,7 +108,11 @@ class Migration(migrations.Migration):
|
|||||||
with open(images_json_file, "rt", encoding="utf-8") as ifile:
|
with open(images_json_file, "rt", encoding="utf-8") as ifile:
|
||||||
images_data = json.loads(ifile.read())
|
images_data = json.loads(ifile.read())
|
||||||
|
|
||||||
for slug,_spec in images_data.items():
|
version = images_data['version']
|
||||||
|
app = images_data['app']
|
||||||
|
prefix = images_data['prefix']
|
||||||
|
|
||||||
|
for slug, _spec in images_data['images'].items():
|
||||||
spec = dict(_spec)
|
spec = dict(_spec)
|
||||||
image_basename = spec['image']
|
image_basename = spec['image']
|
||||||
image_filename = image_dir / spec["image"]
|
image_filename = image_dir / spec["image"]
|
||||||
@@ -115,14 +125,8 @@ class Migration(migrations.Migration):
|
|||||||
img.image.save(image_basename,img_file)
|
img.image.save(image_basename,img_file)
|
||||||
img.save()
|
img.save()
|
||||||
|
|
||||||
|
BuiltinImages.objects.create(app=app, prefix=prefix, version=version)
|
||||||
|
|
||||||
def init_user_imges(apps,schema_edit)->None:
|
|
||||||
#TODO
|
|
||||||
pass
|
|
||||||
|
|
||||||
def init_user_pages(apps,schema_edit)->None:
|
|
||||||
#TODO
|
|
||||||
pass
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(init_tinywiki_groups_and_permissions),
|
migrations.RunPython(init_tinywiki_groups_and_permissions),
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class Migration(migrations.Migration):
|
|||||||
SidebarEntry.objects.create(section=section, **item_spec)
|
SidebarEntry.objects.create(section=section, **item_spec)
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('tinywiki', '0003_sidebarsection_alter_page_status_data_sidebarentry'),
|
('tinywiki', '0002_initial_data'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Generated by Django 5.2.9 on 2025-12-23 15:57
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('tinywiki', '0002_initial_data'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SidebarSection',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='title')),
|
|
||||||
('priority', models.PositiveIntegerField(verbose_name='priority')),
|
|
||||||
('is_visible', models.BooleanField(default=True, verbose_name='is visible')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='page',
|
|
||||||
name='status_data',
|
|
||||||
field=models.CharField(choices=[('in_progress', 'in progress'), ('draft', 'draft'), ('published', 'published')], default='in_progress', max_length=15, verbose_name='status'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='SidebarEntry',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='Title')),
|
|
||||||
('is_visible', models.BooleanField(default=True, verbose_name='is visible')),
|
|
||||||
('priority', models.PositiveIntegerField(verbose_name='Priority')),
|
|
||||||
('wiki_slug', models.CharField(blank=True, max_length=255, null=True, verbose_name='Wiki slug')),
|
|
||||||
('url', models.CharField(blank=True, max_length=512, null=True, verbose_name='Link URL')),
|
|
||||||
('widget', models.TextField(blank=True, null=True, verbose_name='Widget')),
|
|
||||||
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tinywiki.sidebarsection', verbose_name='Section')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"version": 0,
|
||||||
|
"app": "tinywiki",
|
||||||
|
"prefix": "tw-",
|
||||||
|
"images": {
|
||||||
"tw-image-01": {
|
"tw-image-01": {
|
||||||
"alt": "Foggy Mountain-tops in the dawn",
|
"alt": "Foggy Mountain-tops in the dawn",
|
||||||
"image": "fabian-bachli-jQAe44MEIXU-unsplash.jpg",
|
"image": "fabian-bachli-jQAe44MEIXU-unsplash.jpg",
|
||||||
"description": "Foto from [url=\"https://unsplash.com/de/@fabianbaechli?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash\"]Fabian Bächli[/url] on [url=\"https://unsplash.com/de/fotos/nebelige-berggipfel-in-der-morgendammerung-mit-sanften-wolken-jQAe44MEIXU?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash\"]Unsplash[/url]"
|
"description": "Foto from [url=\"https://unsplash.com/de/@fabianbaechli?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash\"]Fabian Bächli[/url] on [url=\"https://unsplash.com/de/fotos/nebelige-berggipfel-in-der-morgendammerung-mit-sanften-wolken-jQAe44MEIXU?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash\"]Unsplash[/url]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"version": 0,
|
||||||
|
"app": "tinywiki",
|
||||||
|
"prefix": "tw-",
|
||||||
|
"pages": {
|
||||||
"tw-license": {
|
"tw-license": {
|
||||||
"title": "TinyWiki License",
|
"title": "TinyWiki License",
|
||||||
"content_type": "bbcode",
|
"content_type": "bbcode",
|
||||||
@@ -24,3 +28,4 @@
|
|||||||
"file": "markdown.md"
|
"file": "markdown.md"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -60,7 +60,6 @@ class Page(models.Model):
|
|||||||
null=False,
|
null=False,
|
||||||
blank=False)
|
blank=False)
|
||||||
|
|
||||||
|
|
||||||
created_at = models.DateTimeField(_("created at"),
|
created_at = models.DateTimeField(_("created at"),
|
||||||
auto_now_add=True)
|
auto_now_add=True)
|
||||||
created_by = models.ForeignKey(get_user_model(),
|
created_by = models.ForeignKey(get_user_model(),
|
||||||
@@ -80,6 +79,7 @@ class Page(models.Model):
|
|||||||
@property
|
@property
|
||||||
def content_type(self)->WikiContentType:
|
def content_type(self)->WikiContentType:
|
||||||
return WikiContentType.from_string(self.content_type_data)
|
return WikiContentType.from_string(self.content_type_data)
|
||||||
|
|
||||||
@content_type.setter
|
@content_type.setter
|
||||||
def content_type(self, content_type: str | WikiContentType):
|
def content_type(self, content_type: str | WikiContentType):
|
||||||
if isinstance(content_type, str):
|
if isinstance(content_type, str):
|
||||||
@@ -92,6 +92,7 @@ class Page(models.Model):
|
|||||||
@property
|
@property
|
||||||
def status(self)->WikiPageStatus:
|
def status(self)->WikiPageStatus:
|
||||||
return WikiPageStatus.from_string(self.status_data)
|
return WikiPageStatus.from_string(self.status_data)
|
||||||
|
|
||||||
@status.setter
|
@status.setter
|
||||||
def status(self, status: str | WikiPageStatus):
|
def status(self, status: str | WikiPageStatus):
|
||||||
if isinstance(status, str):
|
if isinstance(status, str):
|
||||||
@@ -215,3 +216,19 @@ class SidebarEntry(models.Model):
|
|||||||
return mark_safe(f"<li class=\"sidebar-item\">{self.widget}</li>")
|
return mark_safe(f"<li class=\"sidebar-item\">{self.widget}</li>")
|
||||||
else:
|
else:
|
||||||
return mark_safe(f"<li class=\"sidebar-item\">{self.link}</li>")
|
return mark_safe(f"<li class=\"sidebar-item\">{self.link}</li>")
|
||||||
|
|
||||||
|
|
||||||
|
class BuiltinPages(models.Model):
|
||||||
|
app = models.CharField(_("app"),
|
||||||
|
max_length=255,
|
||||||
|
unique=True)
|
||||||
|
version = models.PositiveIntegerField(_("version"))
|
||||||
|
prefix = models.CharField(_("prefix"))
|
||||||
|
|
||||||
|
|
||||||
|
class BuiltinImages(models.Model):
|
||||||
|
app = models.CharField(_("app"),
|
||||||
|
max_length=255,
|
||||||
|
unique=True)
|
||||||
|
version = models.PositiveIntegerField(_("version"))
|
||||||
|
prefix = models.CharField(_("prefix"))
|
||||||
|
|||||||
367
tinywiki/utils.py
Normal file
367
tinywiki/utils.py
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import zipfile
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .models import Image, Page, BuiltinImages, BuiltinPages, get_tinywiki_default_user
|
||||||
|
from .enums import WikiContentType, WikiPageStatus
|
||||||
|
from django.core.files.images import ImageFile
|
||||||
|
from django.conf import settings as django_settings
|
||||||
|
|
||||||
|
|
||||||
|
def import_builtin_pages(json_file: str | Path, user=None):
|
||||||
|
"""
|
||||||
|
Import builtin pages
|
||||||
|
|
||||||
|
:param json_file: The json file which holds the data.
|
||||||
|
:type json_file: str | Path
|
||||||
|
:raises ValueError: If file does not exist or json_file is not a file.
|
||||||
|
:return: `True` if pages were successfully imported, `False` otherwise
|
||||||
|
:rvalue: bool
|
||||||
|
"""
|
||||||
|
if isinstance(json_file, str):
|
||||||
|
json_file = Path(json_file).resolve()
|
||||||
|
|
||||||
|
if not json_file.exists():
|
||||||
|
raise ValueError("File \"{json_file}\" does not exist!")
|
||||||
|
if not json_file.isfile():
|
||||||
|
raise ValueError("json_file needs to be a valid file!")
|
||||||
|
|
||||||
|
with open(json_file, "rt", encoding="utf-8") as ifile:
|
||||||
|
data = json.loads(ifile.read())
|
||||||
|
|
||||||
|
app = data['app']
|
||||||
|
prefix = data['prefix']
|
||||||
|
version = data['version']
|
||||||
|
|
||||||
|
page_path = json_file.parent
|
||||||
|
|
||||||
|
try:
|
||||||
|
bp = BuiltinPages.objects.get(app=app)
|
||||||
|
if version <= bp.version:
|
||||||
|
print("Page already imported, skipping!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for slug, spec in data['pages'].items():
|
||||||
|
filename = page_path / spec['file']
|
||||||
|
with open(filename, "rt", encoding="utf-8") as ifile:
|
||||||
|
content = ifile.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
p = Page.objects.get()
|
||||||
|
p.title = spec['title']
|
||||||
|
p.status_data = WikiPageStatus.from_string(spec['status']).value
|
||||||
|
p.content_type_data = WikiContentType.from_string(spec['content_type']).value,
|
||||||
|
p.content = content
|
||||||
|
p.author = user if user else get_tinywiki_default_user()
|
||||||
|
p.save()
|
||||||
|
except Page.DoesNotExist:
|
||||||
|
Page.objects.create(slug=slug,
|
||||||
|
title=spec['title'],
|
||||||
|
status_data=WikiPageStatus.from_string(spec['status']).value,
|
||||||
|
content_type_data=WikiContentType.from_string(spec['content_type']).value,
|
||||||
|
content=content,
|
||||||
|
author=user if user else get_tinywiki_default_user())
|
||||||
|
bp.prefix = prefix
|
||||||
|
bp.version = version
|
||||||
|
bp.save()
|
||||||
|
return True
|
||||||
|
except BuiltinPages.DoesNotExist:
|
||||||
|
for slug, spec in data['pages'].items():
|
||||||
|
filename = page_path / spec['file']
|
||||||
|
with open(filename, "rt", encoding="utf-8") as ifile:
|
||||||
|
content = ifile.read()
|
||||||
|
|
||||||
|
Page.objects.create(slug=slug,
|
||||||
|
title=spec['title'],
|
||||||
|
status_data=WikiPageStatus.from_string(spec['status']).value,
|
||||||
|
content_type_data=WikiContentType.from_string(spec['content_type']).value,
|
||||||
|
content=content,
|
||||||
|
author=user if user else get_tinywiki_default_user())
|
||||||
|
BuiltinPages.objects.create(app=app, prefix=prefix, version=version)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def import_builtin_pages_from_zip(zip: str | Path, user=None):
|
||||||
|
"""
|
||||||
|
Import builtin pages
|
||||||
|
|
||||||
|
:param zip: The zip file which holds the data.
|
||||||
|
:type zip: str | Path
|
||||||
|
:raises ValueError: If file does not exist or json_file is not a file.
|
||||||
|
:return: `True` if pages were successfully imported, `False` otherwise
|
||||||
|
:rvalue: bool
|
||||||
|
"""
|
||||||
|
if isinstance(zip, str):
|
||||||
|
zip = Path(zip).resolve()
|
||||||
|
|
||||||
|
if not zip.exists():
|
||||||
|
raise ValueError(f"File \"{zip}\" does not exist!")
|
||||||
|
if not zip.isfile():
|
||||||
|
raise ValueError("zip needs to be a valid file!")
|
||||||
|
if not zipfile.is_zipfile(zip):
|
||||||
|
raise ValueError("zip needs to be a zip file!")
|
||||||
|
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip, "r") as zf:
|
||||||
|
with zf.open('pages.json') as json_file:
|
||||||
|
data = json.loads(json_file.read().decode('utf-8'))
|
||||||
|
|
||||||
|
app = data['app']
|
||||||
|
prefix = data['prefix']
|
||||||
|
version = data['version']
|
||||||
|
|
||||||
|
try:
|
||||||
|
bp = BuiltinPages.objects.get(app=app)
|
||||||
|
if version <= bp.version:
|
||||||
|
print("Page already imported, skipping!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for slug, spec in data['pages'].items():
|
||||||
|
with zf.open(spec['file']) as ifile:
|
||||||
|
content = ifile.read().decode('utf-8')
|
||||||
|
|
||||||
|
try:
|
||||||
|
p = Page.objects.get()
|
||||||
|
p.title = spec['title']
|
||||||
|
p.status_data = WikiPageStatus.from_string(spec['status']).value
|
||||||
|
p.content_type_data = WikiContentType.from_string(spec['content_type']).value,
|
||||||
|
p.content = content
|
||||||
|
p.author = user if user else get_tinywiki_default_user()
|
||||||
|
p.save()
|
||||||
|
except Page.DoesNotExist:
|
||||||
|
Page.objects.create(slug=slug,
|
||||||
|
title=spec['title'],
|
||||||
|
status_data=WikiPageStatus.from_string(spec['status']).value,
|
||||||
|
content_type_data=WikiContentType.from_string(spec['content_type']).value,
|
||||||
|
content=content,
|
||||||
|
author=user if user else get_tinywiki_default_user())
|
||||||
|
bp.prefix = prefix
|
||||||
|
bp.version = version
|
||||||
|
bp.save()
|
||||||
|
return True
|
||||||
|
except BuiltinPages.DoesNotExist:
|
||||||
|
for slug, spec in data['pages'].items():
|
||||||
|
with zf.open(spec['file'], "rt", encoding="utf-8") as ifile:
|
||||||
|
content = ifile.read()
|
||||||
|
|
||||||
|
Page.objects.create(slug=slug,
|
||||||
|
title=spec['title'],
|
||||||
|
status_data=WikiPageStatus.from_string(spec['status']).value,
|
||||||
|
content_type_data=WikiContentType.from_string(spec['content_type']).value,
|
||||||
|
content=content,
|
||||||
|
author=user if user else get_tinywiki_default_user())
|
||||||
|
BuiltinPages.objects.create(app=app, prefix=prefix, version=version)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def import_builtin_images(json_file: str | Path, user=None):
|
||||||
|
if isinstance(json_file, str):
|
||||||
|
json_file = Path(json_file).resolve()
|
||||||
|
if not json_file.exists():
|
||||||
|
raise ValueError(f"File \"{json_file}\" does not exist!")
|
||||||
|
if not json_file.isfile():
|
||||||
|
raise ValueError("json_file needs to be a valid file!")
|
||||||
|
|
||||||
|
image_dir = json_file.parent
|
||||||
|
|
||||||
|
with open(json_file, "rt", encoding="utf-8") as ifile:
|
||||||
|
data = json.loads(ifile.read())
|
||||||
|
|
||||||
|
version = data['version']
|
||||||
|
app = data['app']
|
||||||
|
prefix = data['prefix']
|
||||||
|
|
||||||
|
try:
|
||||||
|
bi = BuiltinImages.objects.get(app=app)
|
||||||
|
if bi.version <= data['version']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for slug, spec in data['images'].items():
|
||||||
|
try:
|
||||||
|
img = Image.objects.get(slug=spec['slug'])
|
||||||
|
continue
|
||||||
|
except Image.DoesNotExist:
|
||||||
|
image_basename = spec['image']
|
||||||
|
image_filename = image_dir / spec.pop("image")
|
||||||
|
spec['slug'] = slug
|
||||||
|
spec['user'] = user if user else get_tinywiki_default_user()
|
||||||
|
|
||||||
|
img = Image(**spec)
|
||||||
|
with open(image_filename, "rb") as ifile:
|
||||||
|
img_file = ImageFile(ifile, image_basename)
|
||||||
|
img.image.save(image_basename, img_file)
|
||||||
|
img.save()
|
||||||
|
bi.version = version
|
||||||
|
bi.save()
|
||||||
|
return True
|
||||||
|
except BuiltinImages.DoesNotExist:
|
||||||
|
for slug, spec in data['images'].items():
|
||||||
|
spec = spec
|
||||||
|
image_basename = spec['image']
|
||||||
|
image_filename = image_dir / spec.pop("image")
|
||||||
|
spec['slug'] = slug
|
||||||
|
spec['user'] = user if user else get_tinywiki_default_user()
|
||||||
|
|
||||||
|
img = Image(**spec)
|
||||||
|
with open(image_filename, "rb") as ifile:
|
||||||
|
img_file = ImageFile(ifile, image_basename)
|
||||||
|
img.image.save(image_basename, img_file)
|
||||||
|
img.save()
|
||||||
|
|
||||||
|
BuiltinImages.objects.create(app=app, prefix=prefix, version=version)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def import_builtin_images_from_zip(zip: str | Path, user=None):
|
||||||
|
if isinstance(zip, str):
|
||||||
|
zip = Path(zip).resolve()
|
||||||
|
if not zip.exists():
|
||||||
|
raise ValueError(f"File \"{zip}\" does not exist!")
|
||||||
|
if not zip.isfile():
|
||||||
|
raise ValueError("zip needs to be a valid file!")
|
||||||
|
if not zipfile.is_zipfile(zip):
|
||||||
|
raise ValueError("zip needs to be a zip file!")
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip, "r", encoding="utf-8") as zf:
|
||||||
|
data = json.loads(zf.open('images.json').read().decode('utf-8'))
|
||||||
|
|
||||||
|
version = data['version']
|
||||||
|
app = data['app']
|
||||||
|
prefix = data['prefix']
|
||||||
|
|
||||||
|
try:
|
||||||
|
bi = BuiltinImages.objects.get(app=app)
|
||||||
|
if bi.version <= data['version']:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for slug, spec in data['images'].items():
|
||||||
|
try:
|
||||||
|
img = Image.objects.get(slug=spec['slug'])
|
||||||
|
continue
|
||||||
|
except Image.DoesNotExist:
|
||||||
|
image_basename = os.path.basename(spec['image'])
|
||||||
|
image_filename = spec.pop("image")
|
||||||
|
spec['slug'] = slug
|
||||||
|
spec['user'] = user if user else get_tinywiki_default_user()
|
||||||
|
|
||||||
|
img = Image(**spec)
|
||||||
|
with zf.open(image_filename) as ifile:
|
||||||
|
img_file = ImageFile(ifile, image_basename)
|
||||||
|
img.image.save(image_basename, img_file)
|
||||||
|
img.save()
|
||||||
|
bi.version = version
|
||||||
|
bi.save()
|
||||||
|
return True
|
||||||
|
except BuiltinImages.DoesNotExist:
|
||||||
|
for slug, spec in data['images'].items():
|
||||||
|
spec = spec
|
||||||
|
image_basename = os.path.basename(spec['image'])
|
||||||
|
image_filename = spec.pop("image")
|
||||||
|
spec['slug'] = slug
|
||||||
|
spec['user'] = user if user else get_tinywiki_default_user()
|
||||||
|
|
||||||
|
img = Imagwith zie(**spec)
|
||||||
|
with zf.open(image_filename) as ifile:
|
||||||
|
img_file = ImageFile(ifile, image_basename)
|
||||||
|
img.image.save(image_basename, img_file)
|
||||||
|
img.save()
|
||||||
|
|
||||||
|
BuiltinImages.objects.create(app=app, prefix=prefix, version=version)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def export_wiki_content(app: str,
|
||||||
|
filename,
|
||||||
|
prefix: str|None = None,
|
||||||
|
page_version: int = 0,
|
||||||
|
image_version: int = 0) -> bool:
|
||||||
|
prefix = prefix
|
||||||
|
try:
|
||||||
|
bp = BuiltinPages.objects.get(app=app)
|
||||||
|
if page_version < bp.version:
|
||||||
|
page_version = bp.version + 1
|
||||||
|
if prefix is None:
|
||||||
|
prefix = bp.prefix
|
||||||
|
except BuiltinPages.DoesNotExist:
|
||||||
|
bp = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
bi = BuiltinImages.objects.get(app=app)
|
||||||
|
if image_version < bi.version:
|
||||||
|
image_version = bi.version + 1
|
||||||
|
if prefix is None:
|
||||||
|
prefix = bp.prefix
|
||||||
|
except BuiltinImages.DoesNotExist:
|
||||||
|
bi = None
|
||||||
|
|
||||||
|
if not prefix:
|
||||||
|
raise RuntimeError("No slug prefix! Can not export!", file=sys.stderr)
|
||||||
|
|
||||||
|
pages = Page.objects.filter(slug__startswith=prefix)
|
||||||
|
images = Image.objects.filter(slug__statrswith=prefix)
|
||||||
|
|
||||||
|
if not pages and not images:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with zipfile.ZipFile(filename, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
|
||||||
|
if pages:
|
||||||
|
|
||||||
|
pages_data = {
|
||||||
|
'app': app,
|
||||||
|
'prefix': prefix,
|
||||||
|
'version': page_version,
|
||||||
|
'pages': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for p in pages:
|
||||||
|
if WikiContentType.BBCODE == p.content_type:
|
||||||
|
extension = 'bbcode'
|
||||||
|
else:
|
||||||
|
extension = 'md'
|
||||||
|
page_file = f"pages/{p.slug}.{extension}"
|
||||||
|
pages_data['pages'][p.slug] = {
|
||||||
|
'title': p.title,
|
||||||
|
'content_type': p.content_type.value,
|
||||||
|
'status': p.status.value,
|
||||||
|
'file': page_file,
|
||||||
|
}
|
||||||
|
zf.writestr(page_file, p.content)
|
||||||
|
zf.writestr('pages.json', json.dumps(pages_data, ensure_ascii=False, indent=4))
|
||||||
|
if bp:
|
||||||
|
bp.version = page_version
|
||||||
|
bp.save()
|
||||||
|
else:
|
||||||
|
BuiltinPages.objects.create(app=app, prefix=prefix, version=page_version)
|
||||||
|
|
||||||
|
if images:
|
||||||
|
images_data = {
|
||||||
|
'app': app,
|
||||||
|
'prefix': prefix,
|
||||||
|
'version': image_version,
|
||||||
|
'images': {}
|
||||||
|
}
|
||||||
|
for i in images:
|
||||||
|
if not os.path.isfile(i.image.path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
arcname = f"images/{os.path.basename(i.image.path)}"
|
||||||
|
images_data['images'][i.slug] = {
|
||||||
|
'slug': i.slug,
|
||||||
|
'alt': i.alt,
|
||||||
|
'file': arcname,
|
||||||
|
'description': i.description,
|
||||||
|
}
|
||||||
|
zf.write(i.image.path, arcname)
|
||||||
|
zf.writestr(images.json, json.dumps(images_data, ensure_ascii=False, indent=4))
|
||||||
|
if bi:
|
||||||
|
bi.version = image_version
|
||||||
|
bi.save()
|
||||||
|
else:
|
||||||
|
BuiltinImages.objects.create(app=app, prefix=prefix, version=image_version)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def export_tinywiki_wiki_content(filename=None):
|
||||||
|
if filename is None:
|
||||||
|
filename = Path(django_settings.MEDIA_ROOT) / "tinywiki-tinywiki.zip"
|
||||||
|
export_wiki_content('tinywiki', filename, 'tw-')
|
||||||
Reference in New Issue
Block a user