~lutostag/ubuntu/trusty/maas/1.5.4+keystone

« back to all changes in this revision

Viewing changes to src/maasserver/forms.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2014-02-15 12:08:23 UTC
  • mto: This revision was merged to the branch mainline in revision 48.
  • Revision ID: package-import@ubuntu.com-20140215120823-u7dkitfy0h8tbruh
Tags: upstream-1.5+bzr1948
ImportĀ upstreamĀ versionĀ 1.5+bzr1948

Show diffs side-by-side

added added

removed removed

Lines of Context:
13
13
 
14
14
__metaclass__ = type
15
15
__all__ = [
 
16
    "AdminNodeForm",
16
17
    "AdminNodeWithMACAddressesForm",
 
18
    "BulkNodeActionForm",
17
19
    "CommissioningForm",
18
20
    "CommissioningScriptForm",
19
21
    "DownloadProgressForm",
 
22
    "NodeGroupInterfaceForeignDHCPForm",
20
23
    "get_action_form",
21
24
    "get_node_edit_form",
22
25
    "get_node_create_form",
 
26
    "MAASAndNetworkForm",
23
27
    "MACAddressForm",
24
 
    "MAASAndNetworkForm",
25
 
    "BulkNodeActionForm",
 
28
    "NetworkForm",
 
29
    "NetworksListingForm",
 
30
    "NodeGroupEdit",
26
31
    "NodeGroupInterfaceForm",
27
32
    "NodeGroupWithInterfacesForm",
28
 
    "NodeGroupEdit",
29
33
    "NodeWithMACAddressesForm",
30
34
    "SSHKeyForm",
 
35
    "TagForm",
31
36
    "UbuntuForm",
32
 
    "AdminNodeForm",
33
 
    "TagForm",
34
37
    "ZoneForm",
35
38
    ]
36
39
 
748
751
            'router_ip',
749
752
            'ip_range_low',
750
753
            'ip_range_high',
 
754
            )
 
755
 
 
756
 
 
757
class NodeGroupInterfaceForeignDHCPForm(ModelForm):
 
758
    """A form to update a nodegroupinterface's foreign_dhcp_ip field."""
 
759
 
 
760
    class Meta:
 
761
        model = NodeGroupInterface
 
762
        fields = (
751
763
            'foreign_dhcp_ip',
752
 
            )
 
764
        )
 
765
 
 
766
    def save(self):
 
767
        foreign_dhcp_ip = self.cleaned_data['foreign_dhcp_ip']
 
768
        # Do this through an update, not a read/modify/write.  Updating
 
769
        # NodeGroupInterface client-side may inadvertently trigger Django
 
770
        # signals that cause a rewrite of the DHCP config, plus restart of
 
771
        # the DHCP server.
 
772
        # The inadvertent triggering has been known to happen because of race
 
773
        # conditions between read/modify/write transactions that were enabled
 
774
        # by Django defaulting to, and being designed for, the READ COMMITTED
 
775
        # isolation level; the ORM writing back even unmodified fields; and
 
776
        # GenericIPAddressField's default value being prone to problems where
 
777
        # NULL is sometimes represented as None, sometimes as an empty string,
 
778
        # and the difference being enough to convince the signal machinery
 
779
        # that these fields have changed when in fact they have not.
 
780
        query = NodeGroupInterface.objects.filter(id=self.instance.id)
 
781
        query.update(foreign_dhcp_ip=foreign_dhcp_ip)
 
782
        return NodeGroupInterface.objects.get(id=self.instance.id)
753
783
 
754
784
 
755
785
INTERFACES_VALIDATION_ERROR_MESSAGE = (
827
857
                % (networks[index - 1]['name'], networks[index]['name']))
828
858
 
829
859
 
830
 
# XXX JeroenVermeulen 2014-01-29 bug=1052339: This restriction is going away.
831
860
class NodeGroupWithInterfacesForm(ModelForm):
832
861
    """Create a NodeGroup with unmanaged interfaces."""
833
862
 
999
1028
        return values
1000
1029
 
1001
1030
 
 
1031
class InstanceListField(UnconstrainedMultipleChoiceField):
 
1032
    """A multiple-choice field used to list model instances."""
 
1033
 
 
1034
    def __init__(self, model_class, field_name,
 
1035
                 text_for_invalid_object=None,
 
1036
                 *args, **kwargs):
 
1037
        """Instantiate an InstanceListField.
 
1038
 
 
1039
        Build an InstanceListField to deal with a list of instances of
 
1040
        the class `model_class`, identified their field named
 
1041
        `field_name`.
 
1042
 
 
1043
        :param model_class:  The model class of the instances to list.
 
1044
        :param field_name:  The name of the field used to retrieve the
 
1045
            instances. This must be a unique field of the `model_class`.
 
1046
        :param text_for_invalid_object:  Option error message used to
 
1047
            create the validation error returned when any of the input
 
1048
            values doesn't match an existing object.  The default value
 
1049
            is "Unknown {obj_name}(s): {unknown_names}.".  A custom
 
1050
            message can use {obj_name} and {unknown_names} which will be
 
1051
            replaced by the name of the model instance and the list of
 
1052
            the names that didn't correspond to a valid instance
 
1053
            respectively.
 
1054
        """
 
1055
        super(InstanceListField, self).__init__(*args, **kwargs)
 
1056
        self.model_class = model_class
 
1057
        self.field_name = field_name
 
1058
        if text_for_invalid_object is None:
 
1059
            text_for_invalid_object = (
 
1060
                "Unknown {obj_name}(s): {unknown_names}.")
 
1061
        self.text_for_invalid_object = text_for_invalid_object
 
1062
 
 
1063
    def clean(self, value):
 
1064
        """Clean the list of field values.
 
1065
 
 
1066
        Assert that each field value corresponds to an instance of the class
 
1067
        `self.model_class`.
 
1068
        """
 
1069
        if value is None:
 
1070
            return None
 
1071
        # `value` is in fact a list of values since this field is a subclass of
 
1072
        # forms.MultipleChoiceField.
 
1073
        set_values = set(value)
 
1074
        filters = {'%s__in' % self.field_name: set_values}
 
1075
 
 
1076
        instances = self.model_class.objects.filter(**filters)
 
1077
        if len(instances) != len(set_values):
 
1078
            unknown = set_values.difference(
 
1079
                {getattr(instance, self.field_name) for instance in instances})
 
1080
            error = self.text_for_invalid_object.format(
 
1081
                obj_name=self.model_class.__name__.lower(),
 
1082
                unknown_names=', '.join(sorted(unknown))
 
1083
                )
 
1084
            raise forms.ValidationError(error)
 
1085
        return instances
 
1086
 
 
1087
 
1002
1088
class SetZoneBulkAction:
1003
1089
    """A custom action we only offer in bulk: "Set physical zone."
1004
1090
 
1005
 
    Looks just enough like a node action class for presentatoin purposes, but
 
1091
    Looks just enough like a node action class for presentation purposes, but
1006
1092
    isn't one of the actions we normally offer on the node page.  The
1007
1093
    difference is that this action takes an argument: the zone.
1008
1094
    """
1194
1280
            'netmask',
1195
1281
            'vlan_tag',
1196
1282
            )
 
1283
 
 
1284
 
 
1285
class NetworksListingForm(forms.Form):
 
1286
    """Form for the networks listing API."""
 
1287
 
 
1288
    # Multi-value parameter, but with a name in the singular.  This is going
 
1289
    # to be passed as a GET-style parameter in the URL, so repeated as "node="
 
1290
    # for every node.
 
1291
    node = InstanceListField(
 
1292
        model_class=Node, field_name='system_id',
 
1293
        label="Show only networks that are attached to all of these nodes.",
 
1294
        required=False, error_messages={
 
1295
            'invalid_list':
 
1296
            "Invalid parameter: list of node system IDs required.",
 
1297
            })
 
1298
 
 
1299
    def filter_networks(self, networks):
 
1300
        """Filter (and order) the given networks by the form's criteria.
 
1301
 
 
1302
        :param networks: A query set of :class:`Network`.
 
1303
        :return: A version of `networks` restricted and ordered according to
 
1304
            the criteria passed to the form.
 
1305
        """
 
1306
        nodes = self.cleaned_data.get('node')
 
1307
        if nodes is not None:
 
1308
            for node in nodes:
 
1309
                networks = networks.filter(node=node)
 
1310
        return networks.order_by('name')