mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-02 10:18:42 +00:00
Add new generic "notes" model
This commit is contained in:
@@ -632,14 +632,14 @@ class InvenTreeParameterMixin(InvenTreePermissionCheckMixin, models.Model):
|
|||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def check_parameter_delete(self, parameter):
|
def check_parameter_delete(self, parameter) -> bool:
|
||||||
"""Run a check to determine if the provided parameter can be deleted.
|
"""Run a check to determine if the provided parameter can be deleted.
|
||||||
|
|
||||||
The default implementation always returns True, but this can be overridden in the implementing class.
|
The default implementation always returns True, but this can be overridden in the implementing class.
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_parameter_save(self, parameter):
|
def check_parameter_save(self, parameter) -> bool:
|
||||||
"""Run a check to determine if the provided parameter can be saved.
|
"""Run a check to determine if the provided parameter can be saved.
|
||||||
|
|
||||||
The default implementation always returns True, but this can be overridden in the implementing class.
|
The default implementation always returns True, but this can be overridden in the implementing class.
|
||||||
@@ -647,6 +647,84 @@ class InvenTreeParameterMixin(InvenTreePermissionCheckMixin, models.Model):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeNoteMixin(InvenTreePermissionCheckMixin):
|
||||||
|
"""Provides an abstracted class for managing notes.
|
||||||
|
|
||||||
|
Links the implementing model to the common.models.Note table,
|
||||||
|
and provides multiple accessor / helper methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options for InvenTreeNoteMixin."""
|
||||||
|
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
# Define a reverse relation to the Note model
|
||||||
|
notes_list = GenericRelation(
|
||||||
|
'common.Note', content_type_field='model_type', object_id_field='model_id'
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def notes(self) -> QuerySet:
|
||||||
|
"""Return a queryset containing all notes for this model."""
|
||||||
|
# Check the query cache for pre-fetched parameters
|
||||||
|
if cache := getattr(self, '_prefetched_objects_cache', None):
|
||||||
|
if 'notes_list' in cache:
|
||||||
|
return cache['notes_list']
|
||||||
|
|
||||||
|
return self.notes_list.all()
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
"""Handle the deletion of a model instance.
|
||||||
|
|
||||||
|
Before deleting the model instance, delete any associated notes.
|
||||||
|
"""
|
||||||
|
self.notes_list.all().delete()
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def copy_notes_from(self, other, **kwargs):
|
||||||
|
"""Copy all notes from another model instance.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
other: The other model instance to copy notes from
|
||||||
|
**kwargs: Additional keyword arguments to pass to the Note constructor
|
||||||
|
"""
|
||||||
|
import common.models
|
||||||
|
|
||||||
|
notes = []
|
||||||
|
|
||||||
|
content_type = ContentType.objects.get_for_model(self.__class__)
|
||||||
|
|
||||||
|
for note in other.notes.all():
|
||||||
|
note.pk = None
|
||||||
|
note.model_id = self.pk
|
||||||
|
note.model_type = content_type
|
||||||
|
|
||||||
|
notes.append(note)
|
||||||
|
|
||||||
|
if len(notes) > 0:
|
||||||
|
common.models.Note.objects.bulk_create(notes, batch_size=250)
|
||||||
|
|
||||||
|
def get_note(self, title: str):
|
||||||
|
"""Return a Note instance for the given note title."""
|
||||||
|
return self.notes_list.filter(title=title).first()
|
||||||
|
|
||||||
|
def check_note_delete(self, note) -> bool:
|
||||||
|
"""Run a check to determine if the provided note can be deleted.
|
||||||
|
|
||||||
|
The default implementation always returns True, but this can be overridden in the implementing class.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_note_save(self, note) -> bool:
|
||||||
|
"""Run a check to determine if the provided note can be saved.
|
||||||
|
|
||||||
|
The default implementation always returns True, but this can be overridden in the implementing class.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class InvenTreeAttachmentMixin(InvenTreePermissionCheckMixin):
|
class InvenTreeAttachmentMixin(InvenTreePermissionCheckMixin):
|
||||||
"""Provides an abstracted class for managing file attachments.
|
"""Provides an abstracted class for managing file attachments.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# Generated by Django 5.2.14 on 2026-05-18 13:55
|
||||||
|
|
||||||
|
import InvenTree.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("common", "0041_auto_20251203_1244"),
|
||||||
|
("contenttypes", "0002_remove_content_type_name"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Note",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"metadata",
|
||||||
|
models.JSONField(
|
||||||
|
blank=True,
|
||||||
|
help_text="JSON metadata field, for use by external plugins",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Plugin Metadata",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"updated",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Timestamp of last update",
|
||||||
|
null=True,
|
||||||
|
verbose_name="Updated",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("model_id", models.PositiveIntegerField()),
|
||||||
|
(
|
||||||
|
"title",
|
||||||
|
models.CharField(
|
||||||
|
help_text="Note title", max_length=100, verbose_name="Title"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional description field",
|
||||||
|
max_length=250,
|
||||||
|
verbose_name="Description",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"model_type",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="contenttypes.contenttype",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"updated_by",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
help_text="User who last updated this object",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="%(class)s_updated",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Update By",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Note",
|
||||||
|
"verbose_name_plural": "Notes",
|
||||||
|
},
|
||||||
|
bases=(
|
||||||
|
InvenTree.models.ContentTypeMixin,
|
||||||
|
InvenTree.models.PluginValidationMixin,
|
||||||
|
models.Model,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -2906,6 +2906,88 @@ class Parameter(
|
|||||||
return self.template.description
|
return self.template.description
|
||||||
|
|
||||||
|
|
||||||
|
class Note(
|
||||||
|
UpdatedUserMixin, InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel
|
||||||
|
):
|
||||||
|
"""Class which represents a note assigned to a particular model instance.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
model_type: The type of model to which this note is linked
|
||||||
|
model_id: The ID of the model to which this note is linked
|
||||||
|
user: The user who created the note
|
||||||
|
title: The title of the note
|
||||||
|
description: A description of the note (optional)
|
||||||
|
content: The content of the note
|
||||||
|
created: Date/time that the note was created
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Meta options for Note model."""
|
||||||
|
|
||||||
|
verbose_name = _('Note')
|
||||||
|
verbose_name_plural = _('Notes')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_api_url() -> str:
|
||||||
|
"""Return the API URL associated with the Parameter model."""
|
||||||
|
return reverse('api-note-list')
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""Perform custom save checks before saving a Note instance."""
|
||||||
|
self.check_save()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"""Perform custom delete checks before deleting a Parameter instance."""
|
||||||
|
self.check_delete()
|
||||||
|
super().delete()
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""Clean / validate the note before saving to the database."""
|
||||||
|
# TODO: Implement this
|
||||||
|
|
||||||
|
def check_save(self):
|
||||||
|
"""Check if this note can be saved."""
|
||||||
|
from InvenTree.models import InvenTreeNoteMixin
|
||||||
|
|
||||||
|
try:
|
||||||
|
instance = self.content_object
|
||||||
|
except InvenTree.models.InvenTreeModel.DoesNotExist:
|
||||||
|
return
|
||||||
|
|
||||||
|
if instance and isinstance(instance, InvenTreeNoteMixin):
|
||||||
|
instance.check_note_save(self)
|
||||||
|
|
||||||
|
def check_delete(self):
|
||||||
|
"""Check if this note can be deleted."""
|
||||||
|
from InvenTree.models import InvenTreeNoteMixin
|
||||||
|
|
||||||
|
try:
|
||||||
|
instance = self.content_object
|
||||||
|
except InvenTree.models.InvenTreeModel.DoesNotExist:
|
||||||
|
return
|
||||||
|
|
||||||
|
if instance and isinstance(instance, InvenTreeNoteMixin):
|
||||||
|
instance.check_note_delete(self)
|
||||||
|
|
||||||
|
model_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
model_id = models.PositiveIntegerField()
|
||||||
|
|
||||||
|
content_object = GenericForeignKey('model_type', 'model_id')
|
||||||
|
|
||||||
|
title = models.CharField(
|
||||||
|
max_length=100, verbose_name=_('Title'), help_text=_('Note title')
|
||||||
|
)
|
||||||
|
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=250,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_('Description'),
|
||||||
|
help_text=_('Optional description field'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BarcodeScanResult(InvenTree.models.InvenTreeModel):
|
class BarcodeScanResult(InvenTree.models.InvenTreeModel):
|
||||||
"""Model for storing barcode scans results."""
|
"""Model for storing barcode scans results."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user