files.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  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. _netbox_private = True
  42. class Meta:
  43. ordering = ('file_root', 'file_path')
  44. constraints = (
  45. models.UniqueConstraint(
  46. fields=('file_root', 'file_path'),
  47. name='%(app_label)s_%(class)s_unique_root_path'
  48. ),
  49. )
  50. indexes = [
  51. models.Index(fields=('file_root', 'file_path'), name='core_managedfile_root_path'),
  52. ]
  53. verbose_name = _('managed file')
  54. verbose_name_plural = _('managed files')
  55. def __str__(self):
  56. return self.name
  57. def get_absolute_url(self):
  58. return reverse('core:managedfile', args=[self.pk])
  59. @property
  60. def name(self):
  61. return self.file_path
  62. @property
  63. def full_path(self):
  64. return os.path.join(self._resolve_root_path(), self.file_path)
  65. def _resolve_root_path(self):
  66. return {
  67. 'scripts': settings.SCRIPTS_ROOT,
  68. 'reports': settings.REPORTS_ROOT,
  69. }[self.file_root]
  70. def sync_data(self):
  71. if self.data_file:
  72. self.file_path = os.path.basename(self.data_path)
  73. self.data_file.write_to_disk(self.full_path, overwrite=True)
  74. def clean(self):
  75. super().clean()
  76. # Ensure that the file root and path make a unique pair
  77. if self._meta.model.objects.filter(file_root=self.file_root, file_path=self.file_path).exclude(pk=self.pk).exists():
  78. raise ValidationError(
  79. f"A {self._meta.verbose_name.lower()} with this file path already exists ({self.file_root}/{self.file_path}).")
  80. def delete(self, *args, **kwargs):
  81. # Delete file from disk
  82. try:
  83. os.remove(self.full_path)
  84. except FileNotFoundError:
  85. pass
  86. return super().delete(*args, **kwargs)