Updates
This commit is contained in:
parent
d305ec2dd3
commit
1260de6bce
|
|
@ -937,23 +937,25 @@ def remove_non_printable_characters(value: str, remove_newline=True) -> str:
|
|||
return cleaned
|
||||
|
||||
|
||||
def clean_markdown(value: str) -> str:
|
||||
"""Clean a markdown string.
|
||||
def get_markdownify_settings() -> dict:
|
||||
"""Return the settings for markdownify, or an empty dict if not defined."""
|
||||
try:
|
||||
return settings.MARKDOWNIFY['default']
|
||||
except (AttributeError, KeyError):
|
||||
return {}
|
||||
|
||||
|
||||
def markdown_to_html(value: str) -> str:
|
||||
"""Convert a markdown string to HTML.
|
||||
|
||||
This function will remove javascript and other potentially harmful content from the markdown string.
|
||||
"""
|
||||
import markdown
|
||||
|
||||
try:
|
||||
markdownify_settings = settings.MARKDOWNIFY['default']
|
||||
except (AttributeError, KeyError):
|
||||
markdownify_settings = {}
|
||||
|
||||
markdownify_settings = get_markdownify_settings()
|
||||
extensions = markdownify_settings.get('MARKDOWN_EXTENSIONS', [])
|
||||
extension_configs = markdownify_settings.get('MARKDOWN_EXTENSION_CONFIGS', {})
|
||||
|
||||
# Generate raw HTML from provided markdown (without sanitizing)
|
||||
# Note: The 'html' output_format is required to generate self closing tags, e.g. <tag> instead of <tag />
|
||||
html = markdown.markdown(
|
||||
value or '',
|
||||
extensions=extensions,
|
||||
|
|
@ -961,7 +963,18 @@ def clean_markdown(value: str) -> str:
|
|||
output_format='html',
|
||||
)
|
||||
|
||||
# nh3 sanitizer settings
|
||||
return html
|
||||
|
||||
|
||||
def clean_markdown(value: str) -> str:
|
||||
"""Clean a markdown string.
|
||||
|
||||
This function will remove javascript and other potentially harmful content from the markdown string.
|
||||
"""
|
||||
html = markdown_to_html(value)
|
||||
|
||||
markdownify_settings = get_markdownify_settings()
|
||||
|
||||
whitelist_tags = markdownify_settings.get('WHITELIST_TAGS', DEFAULT_TAGS)
|
||||
whitelist_attrs = markdownify_settings.get('WHITELIST_ATTRS', DEFAULT_ATTRS)
|
||||
whitelist_styles = markdownify_settings.get('WHITELIST_STYLES', DEFAULT_CSS)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,16 @@ class SelectionListAdmin(admin.ModelAdmin):
|
|||
inlines = [SelectionListEntryInlineAdmin]
|
||||
|
||||
|
||||
@admin.register(common.models.Note)
|
||||
class NoteAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for Note objects."""
|
||||
|
||||
list_display = ('title', 'model_type', 'model_id')
|
||||
|
||||
list_filter = ('model_type',)
|
||||
search_fields = ('title', 'description')
|
||||
|
||||
|
||||
@admin.register(common.models.Attachment)
|
||||
class AttachmentAdmin(admin.ModelAdmin):
|
||||
"""Admin interface for Attachment objects."""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Generated by Django 5.2.14 on 2026-05-18 14:16
|
||||
|
||||
import common.validators
|
||||
import InvenTree.models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
|
@ -73,6 +74,7 @@ class Migration(migrations.Migration):
|
|||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="contenttypes.contenttype",
|
||||
validators=[common.validators.validate_notes_model_type]
|
||||
),
|
||||
),
|
||||
(
|
||||
|
|
|
|||
|
|
@ -4,40 +4,72 @@ from tqdm import tqdm
|
|||
|
||||
from django.db import migrations
|
||||
|
||||
from InvenTree.helpers import markdown_to_html
|
||||
|
||||
|
||||
def migrate_notes(apps, schema_editor):
|
||||
"""Migrate existing notes to the new Note model."""
|
||||
|
||||
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||
|
||||
# New target models
|
||||
Note = apps.get_model('common', 'Note')
|
||||
NotesImage = apps.get_model('common', 'NotesImage')
|
||||
|
||||
for app, model in [
|
||||
('build', 'Build'),
|
||||
('company', 'Company'),
|
||||
('company', 'ManufacturerPart'),
|
||||
('company', 'SupplierPart'),
|
||||
('order', 'PurchaseOrder'),
|
||||
('order', 'ReturnOrder'),
|
||||
('order', 'SalesOrder'),
|
||||
('order', 'TransferOrder'),
|
||||
('part', 'Part'),
|
||||
('stock', 'StockItem'),
|
||||
('build', 'build'),
|
||||
('company', 'company'),
|
||||
('company', 'manufacturerpart'),
|
||||
('company', 'supplierpart'),
|
||||
('order', 'purchaseorder'),
|
||||
('order', 'returnorder'),
|
||||
('order', 'salesorder'),
|
||||
('order', 'transferorder'),
|
||||
('part', 'part'),
|
||||
('stock', 'stockitem'),
|
||||
]:
|
||||
# Find old model which contains the 'notes' field
|
||||
OldModel = apps.get_model(app, model)
|
||||
with_notes = OldModel.objects.exclude(notes__isnull=True).exclude(notes='')
|
||||
content_type, _created = ContentType.objects.get_or_create(app_label=app, model=model)
|
||||
|
||||
if not with_notes.exists():
|
||||
continue
|
||||
|
||||
progress = tqdm(total=with_notes.count(), desc=f'Migrating notes for {app}.{model}')
|
||||
|
||||
for obj in with_notes:
|
||||
# TODO ...
|
||||
initial_note_count = Note.objects.count()
|
||||
expected_note_count = initial_note_count + with_notes.count()
|
||||
|
||||
for instance in with_notes:
|
||||
# Tasks:
|
||||
# [x] - Convert markdown notes to HTML format
|
||||
# [x] - Create a new Note instance (mark it as 'primary')
|
||||
# [x] - Copy the content of the 'notes' field to the Note instance
|
||||
# [x] - Link the Note instance to the original object (using GenericForeignKey)
|
||||
# [ ] - Handle any associated images (if applicable)
|
||||
|
||||
content = markdown_to_html(instance.notes)
|
||||
|
||||
note = Note.objects.create(
|
||||
title="Note", # We don't have a title field in the old model, so we'll just use a default value
|
||||
content=content,
|
||||
model_type=content_type,
|
||||
model_id=instance.pk,
|
||||
primary=True
|
||||
)
|
||||
|
||||
progress.update(1)
|
||||
|
||||
# Find any existing NotesImage objects associated with this instance
|
||||
images = NotesImage.objects.filter(model_type__iexact=model, model_id=instance.pk)
|
||||
|
||||
# Point any associated images to the new Note instance
|
||||
for image in images:
|
||||
image.note = note
|
||||
image.save()
|
||||
|
||||
assert Note.objects.count() == expected_note_count, f"Expected {expected_note_count} notes, but found {Note.objects.count()} after migration."
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
|
|
|
|||
|
|
@ -3055,7 +3055,11 @@ class Note(
|
|||
if instance and isinstance(instance, InvenTreeNoteMixin):
|
||||
instance.check_note_delete(self)
|
||||
|
||||
model_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
model_type = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
validators=[common.validators.validate_notes_model_type],
|
||||
)
|
||||
|
||||
model_id = models.PositiveIntegerField()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue