import os import shutil from django.conf import settings from django.core.files.storage import default_storage from django.db import models from django.utils._os import safe_join from django.utils.translation import gettext_lazy as _ from rest_framework.serializers import ValidationError from common.db.models import JMSBaseModel from common.utils import lazyproperty from common.utils.yml import yaml_load_with_i18n __all__ = ['VirtualApp', 'VirtualAppPublication'] class VirtualApp(JMSBaseModel): name = models.SlugField(max_length=128, verbose_name=_('Name'), unique=True) display_name = models.CharField(max_length=128, verbose_name=_('Display name')) version = models.CharField(max_length=16, verbose_name=_('Version')) author = models.CharField(max_length=128, verbose_name=_('Author')) is_active = models.BooleanField(default=True, verbose_name=_('Is active')) protocols = models.JSONField(default=list, verbose_name=_('Protocol')) image_name = models.CharField(max_length=128, verbose_name=_('Image name')) image_protocol = models.CharField(max_length=16, default='vnc', verbose_name=_('Image protocol')) image_port = models.IntegerField(default=5900, verbose_name=_('Image port')) comment = models.TextField(default='', blank=True, verbose_name=_('Comment')) tags = models.JSONField(default=list, verbose_name=_('Tags')) providers = models.ManyToManyField( through_fields=('app', 'provider',), through='VirtualAppPublication', to='AppProvider', verbose_name=_('Providers') ) class Meta: verbose_name = _('Virtual app') def __str__(self): return self.name @property def path(self): return default_storage.path('virtual_apps/{}'.format(self.name)) @lazyproperty def readme(self): readme_file = os.path.join(self.path, 'README.md') if os.path.isfile(readme_file): with open(readme_file, 'r') as f: return f.read() return '' @property def icon(self): path = os.path.join(self.path, 'icon.png') if not os.path.exists(path): return None return os.path.join(settings.MEDIA_URL, 'virtual_apps', self.name, 'icon.png') @staticmethod def validate_pkg(d): files = ['manifest.yml', 'icon.png', ] for name in files: path = safe_join(d, name) if not os.path.exists(path): raise ValidationError({'error': _('Applet pkg not valid, Missing file {}').format(name)}) with open(safe_join(d, 'manifest.yml'), encoding='utf8') as f: manifest = yaml_load_with_i18n(f) if not manifest.get('name', ''): raise ValidationError({'error': 'Missing name in manifest.yml'}) return manifest @classmethod def install_from_dir(cls, path): from terminal.serializers import VirtualAppSerializer manifest = cls.validate_pkg(path) name = manifest['name'] instance = cls.objects.filter(name=name).first() serializer = VirtualAppSerializer(instance=instance, data=manifest) serializer.is_valid(raise_exception=True) instance = serializer.save() pkg_path = default_storage.path('virtual_apps/{}'.format(name)) if os.path.exists(pkg_path): shutil.rmtree(pkg_path) shutil.copytree(path, pkg_path) return instance, serializer class VirtualAppPublication(JMSBaseModel): provider = models.ForeignKey( 'AppProvider', on_delete=models.CASCADE, related_name='publications', verbose_name=_('App Provider') ) app = models.ForeignKey( 'VirtualApp', on_delete=models.CASCADE, related_name='publications', verbose_name=_('Virtual app') ) status = models.CharField(max_length=16, default='pending', verbose_name=_('Status')) class Meta: verbose_name = _('Virtual app publication') unique_together = ('provider', 'app')