14
14
__metaclass__ = type
16
17
"AdminNodeWithMACAddressesForm",
17
19
"CommissioningForm",
18
20
"CommissioningScriptForm",
19
21
"DownloadProgressForm",
22
"NodeGroupInterfaceForeignDHCPForm",
21
24
"get_node_edit_form",
22
25
"get_node_create_form",
29
"NetworksListingForm",
26
31
"NodeGroupInterfaceForm",
27
32
"NodeGroupWithInterfacesForm",
29
33
"NodeWithMACAddressesForm",
757
class NodeGroupInterfaceForeignDHCPForm(ModelForm):
758
"""A form to update a nodegroupinterface's foreign_dhcp_ip field."""
761
model = NodeGroupInterface
751
763
'foreign_dhcp_ip',
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
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)
755
785
INTERFACES_VALIDATION_ERROR_MESSAGE = (
1031
class InstanceListField(UnconstrainedMultipleChoiceField):
1032
"""A multiple-choice field used to list model instances."""
1034
def __init__(self, model_class, field_name,
1035
text_for_invalid_object=None,
1037
"""Instantiate an InstanceListField.
1039
Build an InstanceListField to deal with a list of instances of
1040
the class `model_class`, identified their field named
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
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
1063
def clean(self, value):
1064
"""Clean the list of field values.
1066
Assert that each field value corresponds to an instance of the class
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}
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))
1084
raise forms.ValidationError(error)
1002
1088
class SetZoneBulkAction:
1003
1089
"""A custom action we only offer in bulk: "Set physical zone."
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.
1285
class NetworksListingForm(forms.Form):
1286
"""Form for the networks listing API."""
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="
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={
1296
"Invalid parameter: list of node system IDs required.",
1299
def filter_networks(self, networks):
1300
"""Filter (and order) the given networks by the form's criteria.
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.
1306
nodes = self.cleaned_data.get('node')
1307
if nodes is not None:
1309
networks = networks.filter(node=node)
1310
return networks.order_by('name')