class IPMIForm(Form): # Max password length via IPMI v2.0 is 20 chars. We only support IPMI # v2.0+ compliant boards thus far. ipmi_password1 = forms.CharField(label=_("Password"), max_length=20, widget=forms.PasswordInput, required=False) ipmi_password2 = forms.CharField( label=_("Password confirmation"), max_length=20, widget=forms.PasswordInput, help_text=_("Enter the same password as above, for verification."), required=False) dhcp = forms.BooleanField( label=_("DHCP"), required=False, ) ipv4address = IP4AddressFormField( initial='', required=False, label=_("IPv4 Address"), ) ipv4netmaskbit = forms.ChoiceField( choices=choices.v4NetmaskBitList, required=False, label=_("IPv4 Netmask"), ) ipv4gw = IP4AddressFormField( initial='', required=False, label=_("IPv4 Default Gateway"), ) vlanid = forms.IntegerField( label=_("VLAN ID"), required=False, widget=forms.widgets.TextInput(), ) def __init__(self, *args, **kwargs): super(IPMIForm, self).__init__(*args, **kwargs) self.fields['dhcp'].widget.attrs['onChange'] = ( 'javascript:toggleGeneric(' '"id_dhcp", ["id_ipv4address", "id_ipv4netmaskbit"]);') channels = [] _n = notifier() for i in range(1, 17): try: data = _n.ipmi_get_lan(channel=i) except: continue if not data: continue channels.append((i, i)) self.fields['channel'] = forms.ChoiceField( choices=channels, label=_('Channel'), ) self.fields.keyOrder.remove('channel') self.fields.keyOrder.insert(0, 'channel') def clean_ipmi_password2(self): ipmi_password1 = self.cleaned_data.get("ipmi_password1", "") ipmi_password2 = self.cleaned_data["ipmi_password2"] if ipmi_password1 != ipmi_password2: raise forms.ValidationError( _("The two password fields didn't match.")) return ipmi_password2 def clean_ipv4netmaskbit(self): try: cidr = int(self.cleaned_data.get("ipv4netmaskbit")) except ValueError: return None bits = 0xffffffff ^ (1 << 32 - cidr) - 1 return socket.inet_ntoa(pack('>I', bits)) def clean_ipv4address(self): ipv4 = self.cleaned_data.get('ipv4address') if ipv4: ipv4 = str(ipv4) return ipv4 def clean_ipv4gw(self): ipv4 = self.cleaned_data.get('ipv4gw') if ipv4: ipv4 = str(ipv4) return ipv4
class DeviceForm(ModelForm): CDROM_path = PathField( label=_('CD-ROM (ISO)'), required=False, dirsonly=False, ) DISK_zvol = forms.ChoiceField( label=_('ZVol'), required=False, ) DISK_mode = forms.ChoiceField( label=_('Mode'), choices=choices.VM_DISKMODETYPES, required=False, initial='AHCI', ) DISK_raw = PathField( label=_('Raw File'), required=False, dirsonly=False, ) DISK_sectorsize = forms.IntegerField( label=_('Disk sectorsize'), required=False, initial=0, help_text=_("Logical and physical sector size in bytes of the emulated disk." "If 0, a sector size is not set."), ) NIC_type = forms.ChoiceField( label=_('Adapter Type'), choices=choices.VM_NICTYPES, required=False, initial='E1000', ) NIC_attach = forms.ChoiceField( label=_('Nic to attach'), choices=choices.NICChoices(exclude_configured=False), required=False, ) NIC_mac = forms.CharField( label=_('Mac Address'), required=False, help_text=_("You can specify the adapter MAC Address or let it be auto generated."), validators=[RegexValidator("^([0-9a-fA-F]{2}([::]?|$)){6}$", "Invalid MAC format.")], initial='00:a0:98:FF:FF:FF', ) VNC_resolution = forms.ChoiceField( label=_('Resolution'), choices=choices.VNC_RESOLUTION, required=False, initial='1024x768', ) VNC_port = forms.CharField( label=_('VNC port'), required=False, help_text=_("You can specify the VNC port or 0 for auto."), validators=[RegexValidator("^[0-9]*$", "Only integer is accepted")], initial=0, ) VNC_bind = forms.ChoiceField( label=_('Bind to'), choices=(), required=False, initial='0.0.0.0' ) VNC_wait = forms.BooleanField( label=_('Wait to boot'), required=False, ) class Meta: fields = '__all__' model = models.Device def __init__(self, *args, **kwargs): super(DeviceForm, self).__init__(*args, **kwargs) self.fields['dtype'].widget.attrs['onChange'] = ( "deviceTypeToggle();" ) self.fields['VNC_bind'].choices = self.ipv4_list() diskchoices = {} _n = notifier() used_zvol = [] for volume in Volume.objects.filter(): zvols = _n.list_zfs_vols(volume.vol_name, sort='name') for zvol, attrs in zvols.items(): if "zvol/" + zvol not in used_zvol: diskchoices["zvol/" + zvol] = "%s (%s)" % ( zvol, humanize_size(attrs['volsize'])) self.fields['DISK_zvol'].choices = diskchoices.items() if self.instance.id: if self.instance.dtype == 'CDROM': self.fields['CDROM_path'].initial = self.instance.attributes.get('path', '') elif self.instance.dtype == 'DISK': self.fields['DISK_zvol'].initial = self.instance.attributes.get('path', '').replace('/dev/', '') self.fields['DISK_mode'].initial = self.instance.attributes.get('type') self.fields['DISK_sectorsize'].initial = self.instance.attributes.get('sectorsize', 0) elif self.instance.dtype == "RAW": self.fields['DISK_raw'].initial = self.instance.attributes.get('path', '') self.fields['DISK_mode'].initial = self.instance.attributes.get('type') self.fields['DISK_sectorsize'].initial = self.instance.attributes.get('sectorsize', 0) elif self.instance.dtype == 'NIC': self.fields['NIC_type'].initial = self.instance.attributes.get('type') self.fields['NIC_mac'].initial = self.instance.attributes.get('mac') self.fields['NIC_attach'].initial = self.instance.attributes.get('nic_attach') elif self.instance.dtype == 'VNC': vnc_port = self.instance.attributes.get('vnc_port') vnc_port = 0 if vnc_port is None else vnc_port self.fields['VNC_wait'].initial = self.instance.attributes.get('wait') self.fields['VNC_port'].initial = vnc_port self.fields['VNC_resolution'].initial = self.instance.attributes.get('vnc_resolution') self.fields['VNC_bind'].initial = self.instance.attributes.get('vnc_bind') def ipv4_list(self): choices = (('0.0.0.0', '0.0.0.0'),) with client as c: ipv4_addresses = c.call('interfaces.ipv4_in_use') for ipv4_addr in ipv4_addresses: choices = choices + ((ipv4_addr, ipv4_addr),) return choices def clean(self): vm = self.cleaned_data.get('vm') vnc_port = self.cleaned_data.get('VNC_port') new_vnc_port = 5900 if vm and vnc_port == '0': new_vnc_port = new_vnc_port + int(vm.id) self.cleaned_data['VNC_port'] = str(new_vnc_port) return self.cleaned_data def save(self, *args, **kwargs): vm = self.cleaned_data.get('vm') kwargs['commit'] = False obj = super(DeviceForm, self).save(*args, **kwargs) if self.cleaned_data['dtype'] == 'DISK': obj.attributes = { 'path': '/dev/' + self.cleaned_data['DISK_zvol'], 'type': self.cleaned_data['DISK_mode'], 'sectorsize': self.cleaned_data['DISK_sectorsize'], } elif self.cleaned_data['dtype'] == 'RAW': obj.attributes = { 'path': self.cleaned_data['DISK_raw'], 'type': self.cleaned_data['DISK_mode'], 'sectorsize': self.cleaned_data['DISK_sectorsize'], } elif self.cleaned_data['dtype'] == 'CDROM': cdrom_path = self.cleaned_data['CDROM_path'] if cdrom_path: obj.attributes = { 'path': cdrom_path, } else: self._errors['CDROM_path'] = self.error_class([_('Please choose an ISO file.')]) elif self.cleaned_data['dtype'] == 'NIC': obj.attributes = { 'type': self.cleaned_data['NIC_type'], 'mac': self.cleaned_data['NIC_mac'], 'nic_attach': self.cleaned_data['NIC_attach'], } elif self.cleaned_data['dtype'] == 'VNC': if vm.bootloader == 'UEFI': obj.attributes = { 'wait': self.cleaned_data['VNC_wait'], 'vnc_port': self.cleaned_data['VNC_port'], 'vnc_resolution': self.cleaned_data['VNC_resolution'], 'vnc_bind': self.cleaned_data['VNC_bind'], } else: self._errors['dtype'] = self.error_class([_('VNC is only allowed for UEFI')]) self.cleaned_data.pop('VNC_port', None) self.cleaned_data.pop('VNC_wait', None) self.cleaned_data.pop('VNC_resolution', None) self.cleaned_data.pop('VNC_bind', None) return obj obj.save() return obj
class IPMIForm(Form): ipmi_password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput, required=False) ipmi_password2 = forms.CharField( label=_("Password confirmation"), widget=forms.PasswordInput, help_text=_("Enter the same password as above, for verification."), required=False) dhcp = forms.BooleanField( label=_("DHCP"), required=False, ) ipv4address = IP4AddressFormField( initial='', required=False, label=_("IPv4 Address"), ) ipv4netmaskbit = forms.ChoiceField( choices=choices.v4NetmaskBitList, required=False, label=_("IPv4 Netmask"), ) ipv4gw = IP4AddressFormField( initial='', required=False, label=_("IPv4 Default Gateway"), ) vlanid = forms.IntegerField( label=_("VLAN ID"), required=False, widget=forms.widgets.TextInput(), ) def __init__(self, *args, **kwargs): self.remote = kwargs.pop('remote', None) self.initial_fail = False with client as c: if self.remote: try: data = c.call('failover.call_remote', 'ipmi.query', [[('channel', '=', 1)]]) except Exception: self.initial_fail = True data = None else: data = c.call('ipmi.query', [('channel', '=', 1)]) if data: data = data[0] num, cidr = struct.unpack('>I', socket.inet_aton( data['netmask']))[0], 0 while num > 0: num = num << 1 & 0xffffffff cidr += 1 kwargs['initial'] = { 'dhcp': data['dhcp'], 'ipv4address': data.get('ipaddress'), 'ipv4gw': data.get('gateway'), 'ipv4netmaskbit': str(cidr), 'vlanid': data.get('vlan'), } super(IPMIForm, self).__init__(*args, **kwargs) self.fields['dhcp'].widget.attrs['onChange'] = ( 'javascript:toggleGeneric(' '"id_dhcp", ["id_ipv4address", "id_ipv4netmaskbit"]);') with client as c: channels = list(map(lambda i: (i, i), c.call('ipmi.channels'))) self.fields['channel'] = forms.ChoiceField( choices=channels, label=_('Channel'), ) key_order(self, 0, 'channel', instance=True) def clean_ipmi_password2(self): ipmi_password1 = self.cleaned_data.get("ipmi_password1", "") ipmi_password2 = self.cleaned_data["ipmi_password2"] if ipmi_password1 != ipmi_password2: raise forms.ValidationError( _("The two password fields didn't match.")) return ipmi_password2 def clean(self): # Max password length via IPMI v2.0 is 20 chars. We only support IPMI # v2.0+ compliant boards thus far. cleaned_data = self.cleaned_data if len(cleaned_data.get("ipmi_password1", "")) > 20: self._errors["ipmi_password1"] = self.error_class( [_("A maximum of 20 characters are allowed")]) return cleaned_data def clean_ipv4netmaskbit(self): try: cidr = int(self.cleaned_data.get("ipv4netmaskbit")) except ValueError: return None bits = 0xffffffff ^ (1 << 32 - cidr) - 1 return socket.inet_ntoa(pack('>I', bits)) def clean_ipv4address(self): ipv4 = self.cleaned_data.get('ipv4address') if ipv4: ipv4 = str(ipv4) return ipv4 def clean_ipv4gw(self): ipv4 = self.cleaned_data.get('ipv4gw') if ipv4: ipv4 = str(ipv4) return ipv4 def save(self): data = { 'dhcp': self.cleaned_data.get('dhcp'), 'ipaddress': self.cleaned_data.get('ipv4address'), 'netmask': self.cleaned_data.get('ipv4netmaskbit'), 'gateway': self.cleaned_data.get('ipv4gw'), 'password': self.cleaned_data.get('ipmi_password2'), } vlan = self.cleaned_data.get('vlanid') if vlan: data['vlan'] = vlan channel = self.cleaned_data.get('channel') with client as c: if self.remote: return c.call('failover.call_remote', 'ipmi.update', [channel, data]) else: return c.call('ipmi.update', channel, data)
class VMForm(ModelForm): root_password = forms.CharField( label=_("Root Password"), widget=forms.PasswordInput(render_value=True), required=False, ) path = PathField( label=_("Docker Disk File"), dirsonly=False, filesonly=False, ) size = forms.IntegerField( label=_("Size of Docker Disk File (GiB)"), initial=20, required=False, ) class Meta: fields = '__all__' model = models.VM def __init__(self, *args, **kwargs): super(VMForm, self).__init__(*args, **kwargs) if self.instance.id: for i in ('vm_type', 'root_password', 'path', 'size'): del self.fields[i] if self.instance.vm_type != 'Bhyve': del self.fields['bootloader'] else: self.fields['vm_type'].widget.attrs['onChange'] = ( "vmTypeToggle();") key_order(self, 0, 'vm_type', instance=True) def clean_name(self): name = self.cleaned_data.get('name') if name: name = name.replace(' ', '') return name def clean_root_password(self): vm_type = self.cleaned_data.get('vm_type') root_password = self.cleaned_data.get('root_password') if vm_type != 'Bhyve' and not root_password: raise forms.ValidationError(_('This field is required.')) return root_password def clean_path(self): vm_type = self.cleaned_data.get('vm_type') path = self.cleaned_data.get('path') if vm_type != 'Bhyve': if path and os.path.exists(path): raise forms.ValidationError(_('File must not exist.')) elif not path: raise forms.ValidationError(_('File path is required.')) return path def clean_size(self): vm_type = self.cleaned_data.get('vm_type') size = self.cleaned_data.get('size') if vm_type != 'Bhyve' and not size: raise forms.ValidationError(_('This field is required.')) return size def save(self, **kwargs): with client as c: cdata = self.cleaned_data # Container boot load is GRUB if self.instance.vm_type == 'Container Provider': cdata['bootloader'] = 'GRUB' if self.instance.id: c.call('vm.update', self.instance.id, cdata) else: if cdata['vm_type'] == 'Container Provider': cdata['devices'] = [ { 'dtype': 'NIC', 'attributes': { 'type': 'E1000' } }, { 'dtype': 'RAW', 'attributes': { 'path': cdata.pop('path'), 'type': 'AHCI', 'sectorsize': 0, 'size': cdata.pop('size'), 'exists': False, } }, ] cdata.pop('vm_type') cdata.pop('bootloader') cdata['type'] = 'RancherOS' return c.call('vm.create_container', cdata) cdata.pop('root_password') cdata.pop('path') cdata.pop('size') if cdata['bootloader'] == 'UEFI' and cdata[ 'vm_type'] == 'Bhyve': cdata['devices'] = [ { 'dtype': 'NIC', 'attributes': { 'type': 'E1000' } }, { 'dtype': 'VNC', 'attributes': { 'wait': False, 'vnc_web': False } }, ] else: cdata['devices'] = [ { 'dtype': 'NIC', 'attributes': { 'type': 'E1000' } }, ] self.instance = models.VM.objects.get( pk=c.call('vm.create', cdata)) return self.instance def delete(self, **kwargs): with client as c: c.call('vm.delete', self.instance.id)
class DeviceForm(ModelForm): CDROM_path = PathField( label=_('CD-ROM (ISO)'), required=False, dirsonly=False, ) DISK_zvol = forms.ChoiceField( label=_('ZVol'), required=False, ) DISK_mode = forms.ChoiceField( label=_('Mode'), choices=choices.VM_DISKMODETYPES, required=False, initial='AHCI', ) DISK_raw = PathField( label=_('Raw File'), required=False, dirsonly=False, ) DISK_raw_boot = forms.BooleanField( label=_('Disk boot'), widget=forms.widgets.HiddenInput(), required=False, initial=False, ) ROOT_password = forms.CharField( label=_('Password'), max_length=50, widget=forms.widgets.HiddenInput(), required=False, help_text=_("Set the password for the rancher user."), ) DISK_sectorsize = forms.IntegerField( label=_('Disk sectorsize'), required=False, initial=0, help_text=_( "Sector size of the emulated disk in bytes. Both logical and physical sector size are set to this value." "If 0, a sector size is not set."), ) DISK_raw_size = forms.CharField( label=_('Disk size'), widget=forms.widgets.HiddenInput(), required=False, initial=0, validators=[ RegexValidator( "^(\d*)\s?([M|G|T]?)$", "Enter M, G, or T after the value to use megabytes, gigabytes or terabytes." " When no suffix letter is entered, the units default to gigabytes." ) ], help_text= _("Resize the existing raw disk. Enter 0 to use the disk with the current size." ), ) NIC_type = forms.ChoiceField( label=_('Adapter Type'), choices=choices.VM_NICTYPES, required=False, initial='E1000', ) NIC_attach = forms.ChoiceField( label=_('NIC to attach'), choices=(), required=False, ) NIC_mac = forms.CharField( label=_('MAC Address'), required=False, help_text= _("Specify the adapter MAC Address or leave empty to be auto generated." ), validators=[ RegexValidator("^([0-9a-fA-F]{2}([::]?|$)){6}$", "Invalid MAC format.") ], initial='00:a0:98:FF:FF:FF', ) VNC_resolution = forms.ChoiceField( label=_('Resolution'), choices=choices.VNC_RESOLUTION, required=False, initial='1024x768', ) VNC_port = forms.CharField( label=_('VNC port'), required=False, help_text=_("Specify the VNC port or set to 0 for auto."), validators=[RegexValidator("^[0-9]*$", "Only integers are accepted")], initial=0, ) VNC_bind = forms.ChoiceField( label=_('Bind to'), choices=(), required=False, ) VNC_wait = forms.BooleanField( label=_('Wait to boot'), required=False, ) VNC_password = forms.CharField( label=_('Password'), max_length=8, widget=forms.PasswordInput(render_value=True, ), required=False, help_text=_("The VNC password authentication." "Maximum password length is 8 characters.")) VNC_web = forms.BooleanField( label=_('VNC Web'), required=False, ) class Meta: fields = '__all__' model = models.Device def __init__(self, *args, **kwargs): super(DeviceForm, self).__init__(*args, **kwargs) self.fields['dtype'].widget.attrs['onChange'] = ("deviceTypeToggle();") self.fields['VNC_bind'].choices = self.ipv4_list() self.fields['NIC_attach'].choices = choices.NICChoices( exclude_configured=False, include_vlan_parent=True, include_lagg_parent=False, ) diskchoices = {} with client as c: for zvol in c.call('pool.dataset.query', [('type', '=', 'VOLUME')]): diskchoices[f'zvol/{zvol["name"]}'] = "%s (%s)" % ( zvol['name'], humanize_size(zvol['volsize']['parsed'])) self.fields['DISK_zvol'].choices = diskchoices.items() if self.instance.id: if self.instance.dtype == 'CDROM': self.fields[ 'CDROM_path'].initial = self.instance.attributes.get( 'path', '') elif self.instance.dtype == 'DISK': self.fields[ 'DISK_zvol'].initial = self.instance.attributes.get( 'path', '').replace('/dev/', '') self.fields[ 'DISK_mode'].initial = self.instance.attributes.get('type') self.fields[ 'DISK_sectorsize'].initial = self.instance.attributes.get( 'sectorsize', 0) elif self.instance.dtype == 'RAW': self.fields['DISK_raw'].initial = self.instance.attributes.get( 'path', '') self.fields[ 'DISK_mode'].initial = self.instance.attributes.get('type') self.fields[ 'DISK_sectorsize'].initial = self.instance.attributes.get( 'sectorsize', 0) if self.instance.vm.vm_type == 'Container Provider': self.fields['DISK_raw_boot'].widget = forms.CheckboxInput() self.fields['DISK_raw_size'].widget = forms.TextInput() self.fields['ROOT_password'].widget = forms.PasswordInput( render_value=True, ) self.fields[ 'DISK_raw_boot'].initial = self.instance.attributes.get( 'boot', False) self.fields[ 'DISK_raw_size'].initial = self.instance.attributes.get( 'size', '') self.fields[ 'ROOT_password'].initial = self.instance.attributes.get( 'rootpwd', '') elif self.instance.dtype == 'NIC': self.fields['NIC_type'].initial = self.instance.attributes.get( 'type') self.fields['NIC_mac'].initial = self.instance.attributes.get( 'mac') self.fields[ 'NIC_attach'].initial = self.instance.attributes.get( 'nic_attach') elif self.instance.dtype == 'VNC': vnc_port = self.instance.attributes.get('vnc_port') vnc_port = 0 if vnc_port is None else vnc_port self.fields['VNC_wait'].initial = self.instance.attributes.get( 'wait') self.fields['VNC_port'].initial = vnc_port self.fields[ 'VNC_resolution'].initial = self.instance.attributes.get( 'vnc_resolution') self.fields['VNC_bind'].initial = self.instance.attributes.get( 'vnc_bind') self.fields[ 'VNC_password'].initial = self.instance.attributes.get( 'vnc_password') self.fields['VNC_web'].initial = self.instance.attributes.get( 'vnc_web') def ipv4_list(self): choices = () with client as c: ipv4_addresses = c.call('vm.get_vnc_ipv4') for ipv4_addr in ipv4_addresses: choices = choices + ((ipv4_addr, ipv4_addr), ) return choices def clean(self): vm = self.cleaned_data.get('vm') vnc_port = self.cleaned_data.get('VNC_port') new_vnc_port = 5900 if vm and vnc_port == '0': new_vnc_port = new_vnc_port + int(vm.id) self.cleaned_data['VNC_port'] = str(new_vnc_port) return self.cleaned_data def is_container(self, vm_type): if vm_type == 'Container Provider': return True else: return False def save(self, *args, **kwargs): vm = self.cleaned_data.get('vm') kwargs['commit'] = False obj = super(DeviceForm, self).save(*args, **kwargs) if self.cleaned_data['dtype'] == 'DISK': obj.attributes = { 'path': '/dev/' + self.cleaned_data['DISK_zvol'], 'type': self.cleaned_data['DISK_mode'], 'sectorsize': self.cleaned_data['DISK_sectorsize'], } elif self.cleaned_data['dtype'] == 'RAW': obj.attributes = { 'path': self.cleaned_data['DISK_raw'], 'type': self.cleaned_data['DISK_mode'], 'sectorsize': self.cleaned_data['DISK_sectorsize'], 'boot': self.cleaned_data['DISK_raw_boot'], 'size': self.cleaned_data['DISK_raw_size'], 'rootpwd': self.cleaned_data['ROOT_password'], } elif self.cleaned_data['dtype'] == 'CDROM': cdrom_path = self.cleaned_data['CDROM_path'] if cdrom_path: obj.attributes = { 'path': cdrom_path, } else: self._errors['CDROM_path'] = self.error_class( [_('Please choose an ISO file.')]) elif self.cleaned_data['dtype'] == 'NIC': obj.attributes = { 'type': self.cleaned_data['NIC_type'], 'mac': self.cleaned_data['NIC_mac'], 'nic_attach': self.cleaned_data['NIC_attach'], } elif self.cleaned_data['dtype'] == 'VNC': if vm.bootloader == 'UEFI' and self.is_container( vm.vm_type) is False: obj.attributes = { 'wait': self.cleaned_data['VNC_wait'], 'vnc_port': self.cleaned_data['VNC_port'], 'vnc_resolution': self.cleaned_data['VNC_resolution'], 'vnc_bind': self.cleaned_data['VNC_bind'], 'vnc_password': self.cleaned_data['VNC_password'], 'vnc_web': self.cleaned_data['VNC_web'], } else: self._errors['dtype'] = self.error_class( [_('VNC only works with UEFI VMs')]) self.cleaned_data.pop('VNC_port', None) self.cleaned_data.pop('VNC_wait', None) self.cleaned_data.pop('VNC_resolution', None) self.cleaned_data.pop('VNC_bind', None) self.cleaned_data.pop('VNC_password', None) self.cleaned_data.pop('VNC_web', None) return obj obj.save() return obj
class CloudSyncForm(ModelForm): attributes = forms.CharField( widget=CloudSyncWidget(), label=_('Provider'), ) transfers = forms.IntegerField( required=False, label=_('Transfers'), help_text= _('The number of file transfers to run in parallel. It can sometimes be useful to set this to a ' 'smaller number if the remote is giving a lot of timeouts or bigger if you have lots of bandwidth ' 'and a fast remote.'), widget=forms.widgets.TextInput(), ) bwlimit = forms.CharField( required=False, label=_('Bandwidth limit'), help_text= _('Either single bandwidth limit or bandwidth limit schedule in rclone format.<br />' 'Example: "08:00,512 12:00,10M 13:00,512 18:00,30M 23:00,off".<br />' 'Default unit is kilobytes.')) exclude = forms.CharField( required=False, label=_('Exclude'), help_text= _('Newline-separated list of files and directories to exclude from sync.<br />' 'See https://rclone.org/filtering/ for more details on --exclude option.' ), widget=forms.Textarea(), ) class Meta: exclude = ('credential', 'args') fields = '__all__' model = models.CloudSync widgets = { 'minute': CronMultiple(attrs={ 'numChoices': 60, 'label': _("minute") }), 'hour': CronMultiple(attrs={ 'numChoices': 24, 'label': _("hour") }), 'daymonth': CronMultiple(attrs={ 'numChoices': 31, 'start': 1, 'label': _("day of month"), }), 'dayweek': forms.CheckboxSelectMultiple(choices=choices.WEEKDAYS_CHOICES), 'month': forms.CheckboxSelectMultiple(choices=choices.MONTHS_CHOICES), } def __init__(self, *args, **kwargs): if "instance" in kwargs and kwargs["instance"].id: kwargs.setdefault("initial", {}) try: kwargs["initial"]["encryption_password"] = notifier( ).pwenc_decrypt(kwargs["instance"].encryption_password) except Exception: pass try: kwargs["initial"]["encryption_salt"] = notifier( ).pwenc_decrypt(kwargs["instance"].encryption_salt) except Exception: pass if len(kwargs["instance"].bwlimit ) == 1 and kwargs["instance"].bwlimit[0]["time"] == "00:00": if kwargs["instance"].bwlimit[0]['bandwidth'] is not None: kwargs["initial"]["bwlimit"] = humanize_size_rclone( kwargs["instance"].bwlimit[0]['bandwidth']) else: kwargs["initial"]["bwlimit"] = "" else: kwargs["initial"]["bwlimit"] = " ".join([ f"{limit['time']},{humanize_size_rclone(limit['bandwidth']) if limit['bandwidth'] else 'off'}" for limit in kwargs["instance"].bwlimit ]) kwargs["initial"]["exclude"] = "\n".join( kwargs["instance"].exclude) super(CloudSyncForm, self).__init__(*args, **kwargs) key_order(self, 2, 'attributes', instance=True) mchoicefield(self, 'month', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) mchoicefield(self, 'dayweek', [1, 2, 3, 4, 5, 6, 7]) if self.instance.id: self.fields['attributes'].initial = { 'credential': self.instance.credential.id, } self.fields['attributes'].initial.update(self.instance.attributes) self.fields['direction'].widget.attrs['onChange'] = ( "cloudSyncDirectionToggle();") def clean_attributes(self): attributes = self.cleaned_data.get('attributes') try: attributes = json.loads(attributes) except ValueError: raise forms.ValidationError(_('Invalid provider details.')) credential = attributes.get('credential') if not credential: raise forms.ValidationError(_('This field is required.')) qs = CloudCredentials.objects.filter(id=credential) if not qs.exists(): raise forms.ValidationError(_('Invalid credential.')) return attributes def clean_month(self): m = self.data.getlist('month') if len(m) == 12: return '*' m = ','.join(m) return m def clean_dayweek(self): w = self.data.getlist('dayweek') if w == '*': return w if len(w) == 7: return '*' w = ','.join(w) return w def clean_bwlimit(self): v = self.cleaned_data.get('bwlimit').strip() if v and "," not in v: v = f"00:00,{v}" bwlimit = [] for t in v.split(): try: time_, bandwidth = t.split(",", 1) except ValueError: raise forms.ValidationError(_('Invalid value: %r') % t) try: h, m = time_.split(":", 1) except ValueError: raise forms.ValidationError(_('Invalid time: %r') % time_) try: time(int(h), int(m)) except ValueError: raise forms.ValidationError(_('Invalid time: %r') % time_) if bandwidth == "off": bandwidth = None else: try: bandwidth = int(bandwidth) * 1024 except ValueError: try: bandwidth = int(bandwidth[:-1]) * { "b": 1, "k": 1024, "M": 1024 * 1024, "G": 1024 * 1024 * 1024 }[bandwidth[-1]] except (KeyError, ValueError): raise forms.ValidationError( _('Invalid bandwidth: %r') % bandwidth) bwlimit.append({ "time": time_, "bandwidth": bandwidth, }) for a, b in zip(bwlimit, bwlimit[1:]): if a["time"] >= b["time"]: raise forms.ValidationError( _('Invalid time order: %s, %s') % (a["time"], b["time"])) return bwlimit def clean_exclude(self): return list( filter( None, map(lambda s: s.strip(), self.cleaned_data.get('exclude').split('\n')))) def save(self, **kwargs): with client as c: cdata = self.cleaned_data cdata['credentials'] = cdata['attributes'].pop('credential') cdata['schedule'] = { 'minute': cdata.pop('minute'), 'hour': cdata.pop('hour'), 'dom': cdata.pop('daymonth'), 'month': cdata.pop('month'), 'dow': cdata.pop('dayweek') } if self.instance.id: c.call('cloudsync.update', self.instance.id, cdata) else: self.instance = models.CloudSync.objects.get( pk=c.call('cloudsync.create', cdata)['id']) return self.instance def delete(self, **kwargs): with client as c: c.call('cloudsync.delete', self.instance.id)