files.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import logging
  2. import os
  3. from django.conf import settings
  4. from django.core.exceptions import ValidationError
  5. from django.db import models
  6. from django.urls import reverse
  7. from django.utils.translation import gettext as _
  8. from ..choices import ManagedFileRootPathChoices
  9. from netbox.models.features import SyncedDataMixin
  10. from utilities.querysets import RestrictedQuerySet
  11. __all__ = (
  12. 'ManagedFile',
  13. )
  14. logger = logging.getLogger('netbox.core.files')
  15. class ManagedFile(SyncedDataMixin, models.Model):
  16. """
  17. Database representation for a file on disk. This class is typically wrapped by a proxy class (e.g. ScriptModule)
  18. to provide additional functionality.
  19. """
  20. created = models.DateTimeField(
  21. verbose_name=_('created'),
  22. auto_now_add=True
  23. )
  24. last_updated = models.DateTimeField(
  25. verbose_name=_('last updated'),
  26. editable=False,
  27. blank=True,
  28. null=True
  29. )
  30. file_root = models.CharField(
  31. verbose_name=_('file root'),
  32. max_length=1000,
  33. choices=ManagedFileRootPathChoices
  34. )
  35. file_path = models.FilePathField(
  36. verbose_name=_('file path'),
  37. editable=False,
  38. help_text=_('File path relative to the designated root path')
  39. )
  40. objects = RestrictedQuerySet.as_manager()
  41. class Meta:
  42. ordering = ('file_root', 'file_path')
  43. constraints = (
  44. models.UniqueConstraint(
  45. fields=('file_root', 'file_path'),
  46. name='%(app_label)s_%(class)s_unique_root_path'
  47. ),
  48. )
  49. indexes = [
  50. models.Index(fields=('file_root', 'file_path'), name='core_managedfile_root_path'),
  51. ]
  52. verbose_name = _('managed file')
  53. verbose_name_plural = _('managed files')
  54. def __str__(self):
  55. return self.name
  56. def get_absolute_url(self):
  57. return reverse('core:managedfile', args=[self.pk])
  58. @property
  59. def name(self):
  60. return self.file_path
  61. @property
  62. def full_path(self):
  63. return os.path.join(self._resolve_root_path(), self.file_path)
  64. def _resolve_root_path(self):
  65. return {
  66. 'scripts': settings.SCRIPTS_ROOT,
  67. 'reports': settings.REPORTS_ROOT,
  68. }[self.file_root]
  69. def sync_data(self):
  70. if self.data_file:
  71. self.file_path = os.path.basename(self.data_path)
  72. self.data_file.write_to_disk(self.full_path, overwrite=True)
  73. def clean(self):
  74. super().clean()
  75. # Ensure that the file root and path make a unique pair
  76. if self._meta.model.objects.filter(file_root=self.file_root, file_path=self.file_path).exclude(pk=self.pk).exists():
  77. raise ValidationError(
  78. f"A {self._meta.verbose_name.lower()} with this file path already exists ({self.file_root}/{self.file_path}).")
  79. def delete(self, *args, **kwargs):
  80. # Delete file from disk
  81. try:
  82. os.remove(self.full_path)
  83. except FileNotFoundError:
  84. pass
  85. return super().delete(*args, **kwargs)