added import and export of wiki content

This commit is contained in:
2025-12-27 05:45:31 +01:00
parent f9b46d5fe9
commit 9a9a7065c8
8 changed files with 645 additions and 248 deletions

View File

@@ -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')),
],
),
] ]

View File

@@ -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'),
@@ -31,21 +33,21 @@ class Migration(migrations.Migration):
] ]
GROUPS = [ GROUPS = [
('tinywiki-moderator',('tinywiki-read-all', ('tinywiki-moderator', ('tinywiki-read-all',
'tinywiki-delete-all', 'tinywiki-delete-all',
'tinywiki-edit-all', 'tinywiki-edit-all',
'tinywiki-create')), 'tinywiki-create')),
('tinywiki-author',('tinywiki-create', ('tinywiki-author', ('tinywiki-create',
'tinywiki-edit', 'tinywiki-edit',
'tinywiki-delete')), 'tinywiki-delete')),
('tinywiki-reader',('tinywiki-read-all',)), ('tinywiki-reader', ('tinywiki-read-all',)),
('tinywiki-admin',('tinywiki-read-all', ('tinywiki-admin', ('tinywiki-read-all',
'tinywiki-create', 'tinywiki-create',
'tinywiki-create-system', 'tinywiki-create-system',
'tinywiki-delete-all', 'tinywiki-delete-all',
'tinywiki-delete-system', 'tinywiki-delete-system',
'tinywiki-edit-all', 'tinywiki-edit-all',
'tinywiki-edit-system')) 'tinywiki-edit-system'))
] ]
perm_mapping = {} perm_mapping = {}
@@ -54,30 +56,32 @@ 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()
Page.objects.create(slug=slug, Page.objects.create(slug=slug,
@@ -86,23 +90,29 @@ 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)
def init_builtin_images(apps,schema_edit): @staticmethod
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
image_dir = Path(__file__).resolve().parent / "images" image_dir = Path(__file__).resolve().parent / "images"
images_json_file = image_dir/ "images.json" images_json_file = image_dir / "images.json"
if not images_json_file.is_file(): if not images_json_file.is_file():
return return
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),

View File

@@ -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 = [

View File

@@ -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')),
],
),
]

View File

@@ -1,7 +1,12 @@
{ {
"tw-image-01": { "version": 0,
"alt": "Foggy Mountain-tops in the dawn", "app": "tinywiki",
"image": "fabian-bachli-jQAe44MEIXU-unsplash.jpg", "prefix": "tw-",
"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]" "images": {
"tw-image-01": {
"alt": "Foggy Mountain-tops in the dawn",
"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]"
}
} }
} }

View File

@@ -1,26 +1,31 @@
{ {
"tw-license": { "version": 0,
"title": "TinyWiki License", "app": "tinywiki",
"content_type":"bbcode", "prefix": "tw-",
"status":"published", "pages": {
"file":"license.bbcode" "tw-license": {
}, "title": "TinyWiki License",
"tw-bootstrap-license": { "content_type": "bbcode",
"title": "Bootstrap License", "status": "published",
"content_type": "bbcode", "file": "license.bbcode"
"status":"published", },
"file":"bs-license.bbcode" "tw-bootstrap-license": {
}, "title": "Bootstrap License",
"tw-bbcode": { "content_type": "bbcode",
"title": "BBCode used by TinyWiki", "status": "published",
"content_type": "bbcode", "file": "bs-license.bbcode"
"status": "published", },
"file": "bbcode.bbcode" "tw-bbcode": {
}, "title": "BBCode used by TinyWiki",
"tw-markdown": { "content_type": "bbcode",
"title": "Markdown used by TinyWiki", "status": "published",
"content_type": "markdown", "file": "bbcode.bbcode"
"status": "draft", },
"file": "markdown.md" "tw-markdown": {
"title": "Markdown used by TinyWiki",
"content_type": "markdown",
"status": "draft",
"file": "markdown.md"
}
} }
} }

View File

@@ -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
View 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-')