~allenap/maas/xxx-a-thon

« back to all changes in this revision

Viewing changes to src/maasserver/forms.py

  • Committer: LaMont Jones
  • Date: 2016-03-07 23:20:52 UTC
  • mfrom: (4657.1.84 maas)
  • mto: (4657.1.93 maas)
  • mto: This revision was merged to the branch mainline in revision 4660.
  • Revision ID: lamont@canonical.com-20160307232052-rgfxbq7dujj6s093
MergeĀ fromĀ trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2012-2015 Canonical Ltd.  This software is licensed under the
 
1
# Copyright 2012-2016 Canonical Ltd.  This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
3
 
4
4
"""Forms."""
5
5
 
6
6
__all__ = [
 
7
    "AdminMachineForm",
 
8
    "AdminMachineWithMACAddressesForm",
7
9
    "AdminNodeForm",
8
 
    "AdminNodeWithMACAddressesForm",
9
10
    "BootSourceForm",
10
11
    "BootSourceSelectionForm",
11
12
    "BootSourceSettingsForm",
13
14
    "ClaimIPForMACForm",
14
15
    "CommissioningForm",
15
16
    "CommissioningScriptForm",
 
17
    "get_machine_edit_form",
 
18
    "get_machine_create_form",
16
19
    "CreatePhysicalBlockDeviceForm",
17
 
    "get_node_create_form",
18
20
    "get_node_edit_form",
19
21
    "list_all_usable_architectures",
20
22
    "MAASAndNetworkForm",
21
 
    "MountFilesystemForm",
22
23
    "NetworksListingForm",
23
 
    "NodeWithMACAddressesForm",
 
24
    "NodeChoiceField",
 
25
    "MachineWithMACAddressesForm",
 
26
    "CreatePhysicalBlockDeviceForm",
24
27
    "ReleaseIPForm",
25
28
    "SSHKeyForm",
26
29
    "SSLKeyForm",
35
38
from functools import partial
36
39
import pipes
37
40
import re
38
 
from typing import Optional
39
41
 
40
42
from django import forms
41
43
from django.contrib import messages
102
104
    CacheSet,
103
105
    Config,
104
106
    Device,
 
107
    Domain,
105
108
    Filesystem,
106
109
    Interface,
107
110
    LargeFile,
108
111
    LicenseKey,
 
112
    Machine,
109
113
    Node,
110
114
    Partition,
111
115
    PartitionTable,
153
157
    valid_ipv6,
154
158
)
155
159
from provisioningserver.logger import get_maas_logger
156
 
from provisioningserver.utils import typed
157
160
from provisioningserver.utils.network import make_network
158
161
from provisioningserver.utils.twisted import (
159
162
    asynchronous,
371
374
class NodeForm(MAASModelForm):
372
375
    def __init__(self, request=None, *args, **kwargs):
373
376
        super(NodeForm, self).__init__(*args, **kwargs)
 
377
 
374
378
        # Even though it doesn't need it and doesn't use it, this form accepts
375
379
        # a parameter named 'request' because it is used interchangingly
376
 
        # with AdminNodeForm which actually uses this parameter.
377
 
 
 
380
        # with AdminMachineForm which actually uses this parameter.
378
381
        instance = kwargs.get('instance')
379
382
        if instance is None or instance.owner is None:
380
383
            self.has_owner = False
384
387
        # Are we creating a new node object?
385
388
        self.new_node = (instance is None)
386
389
 
387
 
        self.set_up_architecture_field()
388
 
        if self.has_owner:
389
 
            self.set_up_osystem_and_distro_series_fields(instance)
390
 
 
391
390
        self.fields['disable_ipv4'] = forms.BooleanField(
392
391
            label="", required=False)
393
392
 
394
 
        # We only want the license key field to render in the UI if the `OS`
395
 
        # and `Release` fields are also present.
396
 
        if self.has_owner:
397
 
            self.fields['license_key'] = forms.CharField(
398
 
                label="License Key", required=False, help_text=(
399
 
                    "License key for operating system"),
400
 
                max_length=30)
401
 
        else:
402
 
            self.fields['license_key'] = forms.CharField(
403
 
                label="", required=False, widget=forms.HiddenInput())
404
 
 
405
 
    def set_up_architecture_field(self):
406
 
        """Create the `architecture` field.
407
 
 
408
 
        This needs to be done on the fly so that we can pass a dynamic list of
409
 
        usable architectures.
410
 
        """
411
 
        architectures = list_all_usable_architectures()
412
 
        default_arch = pick_default_architecture(architectures)
413
 
        if len(architectures) == 0:
414
 
            choices = [BLANK_CHOICE]
415
 
        else:
416
 
            choices = list_architecture_choices(architectures)
417
 
        invalid_arch_message = compose_invalid_choice_text(
418
 
            'architecture', choices)
419
 
        self.fields['architecture'] = forms.ChoiceField(
420
 
            choices=choices, required=False, initial=default_arch,
421
 
            error_messages={'invalid_choice': invalid_arch_message})
422
 
 
423
 
    def set_up_osystem_and_distro_series_fields(self, instance):
424
 
        """Create the `osystem` and `distro_series` fields.
425
 
 
426
 
        This needs to be done on the fly so that we can pass a dynamic list of
427
 
        usable operating systems and distro_series.
428
 
        """
429
 
        osystems = list_all_usable_osystems()
430
 
        releases = list_all_usable_releases(osystems)
431
 
        if self.has_owner:
432
 
            os_choices = list_osystem_choices(osystems)
433
 
            distro_choices = list_release_choices(releases)
434
 
            invalid_osystem_message = compose_invalid_choice_text(
435
 
                'osystem', os_choices)
436
 
            invalid_distro_series_message = compose_invalid_choice_text(
437
 
                'distro_series', distro_choices)
438
 
            self.fields['osystem'] = forms.ChoiceField(
439
 
                label="OS", choices=os_choices, required=False, initial='',
440
 
                error_messages={'invalid_choice': invalid_osystem_message})
441
 
            self.fields['distro_series'] = forms.ChoiceField(
442
 
                label="Release", choices=distro_choices,
443
 
                required=False, initial='',
444
 
                error_messages={
445
 
                    'invalid_choice': invalid_distro_series_message})
446
 
        else:
447
 
            self.fields['osystem'] = forms.ChoiceField(
448
 
                label="", required=False, widget=forms.HiddenInput())
449
 
            self.fields['distro_series'] = forms.ChoiceField(
450
 
                label="", required=False, widget=forms.HiddenInput())
451
 
        if instance is not None:
452
 
            initial_value = get_distro_series_initial(osystems, instance)
453
 
            if instance is not None:
454
 
                self.initial['distro_series'] = initial_value
455
 
 
456
 
    def clean_distro_series(self):
457
 
        return clean_distro_series_field(self, 'distro_series', 'osystem')
458
 
 
459
393
    def clean_disable_ipv4(self):
460
394
        # Boolean fields only show up in UI form submissions as "true" (if the
461
395
        # box was checked) or not at all (if the box was not checked).  This
495
429
        except ValueError:
496
430
            raise ValidationError('Invalid size for swap: %s' % swap_size)
497
431
 
 
432
    def clean_domain(self):
 
433
        domain = self.cleaned_data.get('domain')
 
434
        if not domain:
 
435
            return None
 
436
        try:
 
437
            return Domain.objects.get(id=int(domain))
 
438
        except ValueError:
 
439
            try:
 
440
                return Domain.objects.get(name=domain)
 
441
            except Domain.DoesNotExist:
 
442
                raise ValidationError("Unable to find domain %s" % domain)
 
443
 
 
444
    hostname = forms.CharField(
 
445
        label="Host name", required=False, help_text=(
 
446
            "The hostname of the machine"))
 
447
 
 
448
    domain = forms.CharField(
 
449
        label="Domain name", required=False, help_text=(
 
450
            "The domain name of the machine."))
 
451
 
 
452
    swap_size = forms.CharField(
 
453
        label="Swap size", required=False, help_text=(
 
454
            "The size of the swap file in bytes. The field also accepts K, M, "
 
455
            "G and T meaning kilobytes, megabytes, gigabytes and terabytes."))
 
456
 
 
457
    class Meta:
 
458
        model = Node
 
459
 
 
460
        # Fields that the form should generate automatically from the
 
461
        # model:
 
462
        # Note: fields have to be added here even if they were defined manually
 
463
        # elsewhere in the form
 
464
        fields = (
 
465
            'hostname',
 
466
            'domain',
 
467
            'disable_ipv4',
 
468
            'swap_size',
 
469
            )
 
470
 
 
471
 
 
472
class MachineForm(NodeForm):
 
473
    def __init__(self, request=None, *args, **kwargs):
 
474
        super(MachineForm, self).__init__(*args, **kwargs)
 
475
 
 
476
        # Even though it doesn't need it and doesn't use it, this form accepts
 
477
        # a parameter named 'request' because it is used interchangingly
 
478
        # with AdminMachineForm which actually uses this parameter.
 
479
        instance = kwargs.get('instance')
 
480
 
 
481
        self.set_up_architecture_field()
 
482
        # We only want the license key field to render in the UI if the `OS`
 
483
        # and `Release` fields are also present.
 
484
        if self.has_owner:
 
485
            self.set_up_osystem_and_distro_series_fields(instance)
 
486
            self.fields['license_key'] = forms.CharField(
 
487
                label="License Key", required=False, help_text=(
 
488
                    "License key for operating system"),
 
489
                max_length=30)
 
490
        else:
 
491
            self.fields['license_key'] = forms.CharField(
 
492
                label="", required=False, widget=forms.HiddenInput())
 
493
 
 
494
    def set_up_architecture_field(self):
 
495
        """Create the `architecture` field.
 
496
 
 
497
        This needs to be done on the fly so that we can pass a dynamic list of
 
498
        usable architectures.
 
499
        """
 
500
        architectures = list_all_usable_architectures()
 
501
        default_arch = pick_default_architecture(architectures)
 
502
        if len(architectures) == 0:
 
503
            choices = [BLANK_CHOICE]
 
504
        else:
 
505
            choices = list_architecture_choices(architectures)
 
506
        invalid_arch_message = compose_invalid_choice_text(
 
507
            'architecture', choices)
 
508
        self.fields['architecture'] = forms.ChoiceField(
 
509
            choices=choices, required=False, initial=default_arch,
 
510
            error_messages={'invalid_choice': invalid_arch_message})
 
511
 
 
512
    def set_up_osystem_and_distro_series_fields(self, instance):
 
513
        """Create the `osystem` and `distro_series` fields.
 
514
 
 
515
        This needs to be done on the fly so that we can pass a dynamic list of
 
516
        usable operating systems and distro_series.
 
517
        """
 
518
        osystems = list_all_usable_osystems()
 
519
        releases = list_all_usable_releases(osystems)
 
520
        if self.has_owner:
 
521
            os_choices = list_osystem_choices(osystems)
 
522
            distro_choices = list_release_choices(releases)
 
523
            invalid_osystem_message = compose_invalid_choice_text(
 
524
                'osystem', os_choices)
 
525
            invalid_distro_series_message = compose_invalid_choice_text(
 
526
                'distro_series', distro_choices)
 
527
            self.fields['osystem'] = forms.ChoiceField(
 
528
                label="OS", choices=os_choices, required=False, initial='',
 
529
                error_messages={'invalid_choice': invalid_osystem_message})
 
530
            self.fields['distro_series'] = forms.ChoiceField(
 
531
                label="Release", choices=distro_choices,
 
532
                required=False, initial='',
 
533
                error_messages={
 
534
                    'invalid_choice': invalid_distro_series_message})
 
535
        else:
 
536
            self.fields['osystem'] = forms.ChoiceField(
 
537
                label="", required=False, widget=forms.HiddenInput())
 
538
            self.fields['distro_series'] = forms.ChoiceField(
 
539
                label="", required=False, widget=forms.HiddenInput())
 
540
        if instance is not None:
 
541
            initial_value = get_distro_series_initial(osystems, instance)
 
542
            if instance is not None:
 
543
                self.initial['distro_series'] = initial_value
 
544
 
 
545
    def clean_distro_series(self):
 
546
        return clean_distro_series_field(self, 'distro_series', 'osystem')
 
547
 
498
548
    def clean_min_hwe_kernel(self):
499
549
        min_hwe_kernel = self.cleaned_data.get('min_hwe_kernel')
500
550
        if self.new_node and not min_hwe_kernel:
503
553
        return validate_min_hwe_kernel(min_hwe_kernel)
504
554
 
505
555
    def clean(self):
506
 
        cleaned_data = super(NodeForm, self).clean()
 
556
        cleaned_data = super(MachineForm, self).clean()
507
557
 
508
558
        if not self.instance.hwe_kernel:
509
559
            osystem = cleaned_data.get('osystem')
520
570
        return cleaned_data
521
571
 
522
572
    def is_valid(self):
523
 
        is_valid = super(NodeForm, self).is_valid()
 
573
        is_valid = super(MachineForm, self).is_valid()
524
574
        if not is_valid:
525
575
            return False
526
576
        if len(list_all_usable_architectures()) == 0:
585
635
        self.is_bound = True
586
636
        self.data['hwe_kernel'] = hwe_kernel
587
637
 
588
 
    hostname = forms.CharField(
589
 
        label="Host name", required=False, help_text=(
590
 
            "The FQDN (Fully Qualified Domain Name) is derived from the "
591
 
            "host name: If the cluster controller for this node is managing "
592
 
            "DNS then the domain part in the host name (if any) is replaced "
593
 
            "by the domain defined on the cluster; if the cluster controller "
594
 
            "does not manage DNS, then the host name as entered will be the "
595
 
            "FQDN."))
596
 
 
597
 
    swap_size = forms.CharField(
598
 
        label="Swap size", required=False, help_text=(
599
 
            "The size of the swap file in bytes. The field also accepts K, M, "
600
 
            "G and T meaning kilobytes, megabytes, gigabytes and terabytes."))
601
 
 
602
638
    class Meta:
603
 
        model = Node
 
639
        model = Machine
604
640
 
605
 
        # Fields that the form should generate automatically from the
606
 
        # model:
607
 
        # Note: fields have to be added here even if they were defined manually
608
 
        # elsewhere in the form
609
 
        fields = (
610
 
            'hostname',
 
641
        fields = NodeForm.Meta.fields + (
611
642
            'architecture',
612
643
            'osystem',
613
644
            'distro_series',
614
645
            'license_key',
615
 
            'disable_ipv4',
616
 
            'swap_size',
617
646
            'min_hwe_kernel',
618
 
            'hwe_kernel'
619
 
            )
620
 
 
621
 
 
622
 
class DeviceForm(MAASModelForm):
 
647
            'hwe_kernel',
 
648
        )
 
649
 
 
650
 
 
651
class DeviceForm(NodeForm):
623
652
    parent = forms.ModelChoiceField(
624
653
        required=False, initial=None,
625
654
        queryset=Node.objects.all(), to_field_name='system_id')
627
656
    class Meta:
628
657
        model = Device
629
658
 
630
 
        fields = (
631
 
            'hostname',
 
659
        fields = NodeForm.Meta.fields + (
632
660
            'parent',
633
661
        )
634
662
 
637
665
        self.request = request
638
666
 
639
667
        instance = kwargs.get('instance')
640
 
        # Are we creating a new device object?
641
 
        self.new_device = (instance is None)
642
668
        self.set_up_initial_device(instance)
643
669
 
644
670
    def set_up_initial_device(self, instance):
652
678
    def save(self, commit=True):
653
679
        device = super(DeviceForm, self).save(commit=False)
654
680
        device.node_type = NODE_TYPE.DEVICE
655
 
        if self.new_device:
 
681
        if self.new_node:
656
682
            # Set the owner: devices are owned by their creator.
657
683
            device.owner = self.request.user
658
684
        device.save()
659
685
        return device
660
686
 
 
687
 
661
688
CLUSTER_NOT_AVAILABLE = mark_safe(
662
689
    "The cluster controller for this node is not responding; power type "
663
690
    "validation is not available. "
673
700
 
674
701
class AdminNodeForm(NodeForm):
675
702
    """A `NodeForm` which includes fields that only an admin may change."""
676
 
 
677
703
    zone = forms.ModelChoiceField(
678
704
        label="Physical zone", required=False,
679
705
        initial=Zone.objects.get_default_zone,
699
725
            data=data, instance=instance, **kwargs)
700
726
        self.request = request
701
727
        self.set_up_initial_zone(instance)
702
 
        AdminNodeForm.set_up_power_type(self, data, instance)
703
728
        # The zone field is not required because we want to be able
704
729
        # to omit it when using that form in the API.
705
730
        # We don't want the UI to show an entry for the 'empty' zone,
718
743
        if instance is not None:
719
744
            self.initial['zone'] = instance.zone.name
720
745
 
 
746
    def save(self, *args, **kwargs):
 
747
        """Persist the node into the database."""
 
748
        node = super(AdminNodeForm, self).save(commit=False)
 
749
        zone = self.cleaned_data.get('zone')
 
750
        if zone:
 
751
            node.zone = zone
 
752
        if kwargs.get('commit', True):
 
753
            node.save(*args, **kwargs)
 
754
            self.save_m2m()  # Save many to many relations.
 
755
        return node
 
756
 
 
757
 
 
758
class AdminMachineForm(MachineForm, AdminNodeForm):
 
759
    """A `MachineForm` which includes fields that only an admin may change."""
 
760
 
 
761
    class Meta:
 
762
        model = Machine
 
763
 
 
764
        # Fields that the form should generate automatically from the
 
765
        # model:
 
766
        fields = MachineForm.Meta.fields + (
 
767
            'cpu_count',
 
768
            'memory',
 
769
        )
 
770
 
 
771
    def __init__(self, data=None, instance=None, request=None, **kwargs):
 
772
        super(AdminMachineForm, self).__init__(
 
773
            data=data, instance=instance, **kwargs)
 
774
        AdminMachineForm.set_up_power_type(self, data, instance)
 
775
 
721
776
    @staticmethod
722
 
    def _get_power_type(form, data, node):
 
777
    def _get_power_type(form, data, machine):
723
778
        if data is None:
724
779
            data = {}
725
780
 
726
781
        power_type = data.get('power_type', form.initial.get('power_type'))
727
782
 
728
 
        # If power_type is None (this is a node creation form or this
 
783
        # If power_type is None (this is a machine creation form or this
729
784
        # form deals with an API call which does not change the value of
730
 
        # 'power_type') or invalid: get the node's current 'power_type'
731
 
        # value or the default value if this form is not linked to a node.
 
785
        # 'power_type') or invalid: get the machine's current 'power_type'
 
786
        # value or the default value if this form is not linked to a machine.
732
787
        try:
733
788
            power_types = get_power_types()
734
789
        except ClusterUnavailable as e:
741
796
            return ''
742
797
 
743
798
        if power_type not in power_types:
744
 
            return '' if node is None else node.power_type
 
799
            return '' if machine is None else machine.power_type
745
800
        return power_type
746
801
 
747
802
    @staticmethod
748
 
    def set_up_power_type(form, data, node=None):
 
803
    def set_up_power_type(form, data, machine=None):
749
804
        """Set up the 'power_type' and 'power_parameters' fields.
750
805
 
751
806
        This can't be done at the model level because the choices need to
752
807
        be generated on the fly by get_power_type_choices().
753
808
        """
754
 
        power_type = AdminNodeForm._get_power_type(form, data, node)
 
809
        power_type = AdminMachineForm._get_power_type(form, data, machine)
755
810
        choices = [BLANK_CHOICE] + get_power_type_choices()
756
811
        form.fields['power_type'] = forms.ChoiceField(
757
812
            required=False, choices=choices, initial=power_type)
782
837
        return cleaned_data
783
838
 
784
839
    def clean(self):
785
 
        cleaned_data = super(AdminNodeForm, self).clean()
786
 
        return AdminNodeForm.check_power_type(self, cleaned_data)
 
840
        cleaned_data = super(AdminMachineForm, self).clean()
 
841
        return AdminMachineForm.check_power_type(self, cleaned_data)
787
842
 
788
843
    @staticmethod
789
 
    def set_power_type(form, node):
790
 
        """Persist the node into the database."""
 
844
    def set_power_type(form, machine):
 
845
        """Persist the machine into the database."""
791
846
        power_type = form.cleaned_data.get('power_type')
792
847
        if power_type is not None:
793
 
            node.power_type = power_type
 
848
            machine.power_type = power_type
794
849
        power_parameters = form.cleaned_data.get('power_parameters')
795
850
        if power_parameters is not None:
796
 
            node.power_parameters = power_parameters
 
851
            machine.power_parameters = power_parameters
797
852
 
798
853
    def save(self, *args, **kwargs):
799
854
        """Persist the node into the database."""
800
 
        node = super(AdminNodeForm, self).save(commit=False)
 
855
        machine = super(AdminMachineForm, self).save(commit=False)
801
856
        zone = self.cleaned_data.get('zone')
802
857
        if zone:
803
 
            node.zone = zone
804
 
        AdminNodeForm.set_power_type(self, node)
 
858
            machine.zone = zone
 
859
        AdminMachineForm.set_power_type(self, machine)
805
860
        if kwargs.get('commit', True):
806
 
            node.save(*args, **kwargs)
 
861
            machine.save(*args, **kwargs)
807
862
            self.save_m2m()  # Save many to many relations.
808
 
        return node
 
863
        return machine
 
864
 
 
865
 
 
866
def get_machine_edit_form(user):
 
867
    if user.is_superuser:
 
868
        return AdminMachineForm
 
869
    else:
 
870
        return MachineForm
809
871
 
810
872
 
811
873
def get_node_edit_form(user):
1008
1070
    """A form mixin which dynamically adds power_type and power_parameters to
1009
1071
    the list of fields.  This mixin also overrides the 'save' method to persist
1010
1072
    these fields and is intended to be used with a class inheriting from
1011
 
    NodeForm.
 
1073
    MachineForm.
1012
1074
    """
1013
1075
 
1014
1076
    def __init__(self, *args, **kwargs):
1015
1077
        super(WithPowerMixin, self).__init__(*args, **kwargs)
1016
1078
        self.data = self.data.copy()
1017
 
        AdminNodeForm.set_up_power_type(self, self.data)
 
1079
        AdminMachineForm.set_up_power_type(self, self.data)
1018
1080
 
1019
1081
    def clean(self):
1020
1082
        cleaned_data = super(WithPowerMixin, self).clean()
1021
 
        return AdminNodeForm.check_power_type(self, cleaned_data)
 
1083
        return AdminMachineForm.check_power_type(self, cleaned_data)
1022
1084
 
1023
1085
    def save(self, *args, **kwargs):
1024
1086
        """Persist the node into the database."""
1025
1087
        node = super(WithPowerMixin, self).save()
1026
 
        AdminNodeForm.set_power_type(self, node)
 
1088
        AdminMachineForm.set_power_type(self, node)
1027
1089
        node.save()
1028
1090
        return node
1029
1091
 
1030
1092
 
1031
 
class AdminNodeWithMACAddressesForm(WithMACAddressesMixin, AdminNodeForm):
1032
 
    """A version of the AdminNodeForm which includes the multi-MAC address
 
1093
class AdminMachineWithMACAddressesForm(
 
1094
        WithMACAddressesMixin, AdminMachineForm):
 
1095
    """A version of the AdminMachineForm which includes the multi-MAC address
1033
1096
    field.
1034
1097
    """
1035
1098
 
1036
1099
 
1037
 
class NodeWithMACAddressesForm(WithMACAddressesMixin, NodeForm):
1038
 
    """A version of the NodeForm which includes the multi-MAC address field.
 
1100
class MachineWithMACAddressesForm(WithMACAddressesMixin, MachineForm):
 
1101
    """A version of the MachineForm which includes the multi-MAC address field.
1039
1102
    """
1040
1103
 
1041
1104
 
1042
 
class NodeWithPowerAndMACAddressesForm(
1043
 
        WithPowerMixin, NodeWithMACAddressesForm):
1044
 
    """A version of the NodeForm which includes the power fields.
 
1105
class MachineWithPowerAndMACAddressesForm(
 
1106
        WithPowerMixin, MachineWithMACAddressesForm):
 
1107
    """A version of the MachineForm which includes the power fields.
1045
1108
    """
1046
1109
 
1047
1110
 
1050
1113
    """
1051
1114
 
1052
1115
 
1053
 
def get_node_create_form(user):
 
1116
def get_machine_create_form(user):
1054
1117
    if user.is_superuser:
1055
 
        return AdminNodeWithMACAddressesForm
 
1118
        return AdminMachineWithMACAddressesForm
1056
1119
    else:
1057
 
        return NodeWithPowerAndMACAddressesForm
 
1120
        return MachineWithPowerAndMACAddressesForm
1058
1121
 
1059
1122
 
1060
1123
class ProfileForm(MAASModelForm):
2291
2354
        return self.block_device
2292
2355
 
2293
2356
 
2294
 
class MountFilesystemForm(Form):
2295
 
    """Form used to mount a filesystem."""
2296
 
 
2297
 
    @typed
2298
 
    def __init__(self, filesystem: Optional[Filesystem], *args, **kwargs):
2299
 
        super(MountFilesystemForm, self).__init__(*args, **kwargs)
2300
 
        self.filesystem = filesystem
2301
 
        self.setup()
2302
 
 
2303
 
    def setup(self):
2304
 
        if self.filesystem is not None:
2305
 
            if self.filesystem.uses_mount_point:
2306
 
                self.fields["mount_point"] = AbsolutePathField(required=True)
2307
 
            self.fields["mount_options"] = StrippedCharField(required=False)
2308
 
 
2309
 
    def clean(self):
2310
 
        cleaned_data = super(MountFilesystemForm, self).clean()
2311
 
        if self.filesystem is None:
2312
 
            self.add_error(
2313
 
                None, "Cannot mount an unformatted partition "
2314
 
                "or block device.")
2315
 
        elif self.filesystem.filesystem_group is not None:
2316
 
            self.add_error(
2317
 
                None, "Filesystem is part of a filesystem group, "
2318
 
                "and cannot be mounted.")
2319
 
        return cleaned_data
2320
 
 
2321
 
    def save(self):
2322
 
        if "mount_point" in self.cleaned_data:
2323
 
            self.filesystem.mount_point = self.cleaned_data['mount_point']
2324
 
        else:
2325
 
            self.filesystem.mount_point = "none"  # e.g. for swap.
2326
 
        if "mount_options" in self.cleaned_data:
2327
 
            self.filesystem.mount_options = self.cleaned_data['mount_options']
2328
 
        self.filesystem.save()
2329
 
 
2330
 
 
2331
2357
class AddPartitionForm(Form):
2332
2358
    """Form used to add a partition to block device."""
2333
2359