Merge lp:~rvb/maas/ipfield into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 2525
Proposed branch: lp:~rvb/maas/ipfield
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 694 lines (+445/-29)
8 files modified
src/maasserver/fields.py (+27/-0)
src/maasserver/migrations/0088_ip_to_custom_field.py (+377/-0)
src/maasserver/models/dhcplease.py (+5/-3)
src/maasserver/models/network.py (+3/-3)
src/maasserver/models/nodegroupinterface.py (+11/-11)
src/maasserver/models/staticipaddress.py (+3/-12)
src/maasserver/tests/models.py (+6/-0)
src/maasserver/tests/test_fields.py (+13/-0)
To merge this branch: bzr merge lp:~rvb/maas/ipfield
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+225804@code.launchpad.net

Commit message

Re-do the fix for bug 1338452 in a more consistent way: work around the Django bug (see reference in the code) by defining a custom IP field. Update all the IP fields in the codebase to use the new field.

Description of the change

Although this is a bit of a hack, it's much safer than having a workaround in just one place of the code.

I've tested that an upgrade to the package built from this branch works fine (objects with IP address fields look okay) and I ran a test in the lab with this branch.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) :
review: Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Please let's not have a repeat of the TestCase debacle, and call this
*MAAS*IPAddressField instead.

Also, surprised it needs a migration just for a field name change.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/

iEYEARECAAYFAlO7RT4ACgkQWhGlTF8G/HdakQCgjBvFZWZz+LoW6eoa8NFqmDO/
bSEAnipHz21PGLPmeBy0zTqgg0eKsiFp
=E/ET
-----END PGP SIGNATURE-----

Revision history for this message
Raphaël Badin (rvb) wrote :

On 07/08/2014 03:12 AM, Julian Edwards wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Please let's not have a repeat of the TestCase debacle, and call this
> *MAAS*IPAddressField instead.

Fair point. Done.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/fields.py'
2--- src/maasserver/fields.py 2014-05-19 14:44:58 +0000
3+++ src/maasserver/fields.py 2014-07-08 07:55:43 +0000
4@@ -14,6 +14,7 @@
5 __metaclass__ = type
6 __all__ = [
7 "EditableBinaryField",
8+ "MAASIPAddressField",
9 "MAC",
10 "MACAddressField",
11 "MACAddressFormField",
12@@ -33,6 +34,7 @@
13 from django.db.models import (
14 BinaryField,
15 Field,
16+ GenericIPAddressField,
17 SubfieldBase,
18 )
19 from django.forms import (
20@@ -90,6 +92,7 @@
21 "^maasserver\.fields\.JSONObjectField",
22 "^maasserver\.fields\.XMLField",
23 "^maasserver\.fields\.EditableBinaryField",
24+ "^maasserver\.fields\.MAASIPAddressField",
25 ])
26
27
28@@ -385,3 +388,27 @@
29 def __init__(self, *args, **kwargs):
30 super(EditableBinaryField, self).__init__(*args, **kwargs)
31 self.editable = True
32+
33+
34+class MAASIPAddressField(GenericIPAddressField):
35+ """A version of GenericIPAddressField with a custom get_internal_type().
36+
37+ This class exists to work around a bug in Django that inserts a HOST() cast
38+ on the IP, causing the wrong comparison on the IP field. See
39+ https://code.djangoproject.com/ticket/11442 for details.
40+ """
41+
42+ def get_internal_type(self):
43+ """Returns a value different from 'GenericIPAddressField' and
44+ 'IPAddressField' to force Django not to use a HOST() case when
45+ performing operation on this field.
46+ """
47+ return "IPField"
48+
49+ def db_type(self, connection):
50+ """Returns the database column data type for IPAddressField.
51+
52+ Override the default implementation which uses get_internal_type()
53+ and force a 'inet' type field.
54+ """
55+ return 'inet'
56
57=== added file 'src/maasserver/migrations/0088_ip_to_custom_field.py'
58--- src/maasserver/migrations/0088_ip_to_custom_field.py 1970-01-01 00:00:00 +0000
59+++ src/maasserver/migrations/0088_ip_to_custom_field.py 2014-07-08 07:55:43 +0000
60@@ -0,0 +1,377 @@
61+from django.db import models
62+from south.db import db
63+# -*- coding: utf-8 -*-
64+from south.utils import datetime_utils as datetime
65+from south.v2 import SchemaMigration
66+
67+
68+class Migration(SchemaMigration):
69+
70+ def forwards(self, orm):
71+
72+ # Changing field 'NodeGroupInterface.ip_range_high'
73+ db.alter_column(u'maasserver_nodegroupinterface', 'ip_range_high', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39, null=True))
74+
75+ # Changing field 'NodeGroupInterface.static_ip_range_low'
76+ db.alter_column(u'maasserver_nodegroupinterface', 'static_ip_range_low', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39, null=True))
77+
78+ # Changing field 'NodeGroupInterface.ip'
79+ db.alter_column(u'maasserver_nodegroupinterface', 'ip', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39))
80+
81+ # Changing field 'NodeGroupInterface.ip_range_low'
82+ db.alter_column(u'maasserver_nodegroupinterface', 'ip_range_low', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39, null=True))
83+
84+ # Changing field 'NodeGroupInterface.subnet_mask'
85+ db.alter_column(u'maasserver_nodegroupinterface', 'subnet_mask', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39, null=True))
86+
87+ # Changing field 'NodeGroupInterface.broadcast_ip'
88+ db.alter_column(u'maasserver_nodegroupinterface', 'broadcast_ip', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39, null=True))
89+
90+ # Changing field 'NodeGroupInterface.router_ip'
91+ db.alter_column(u'maasserver_nodegroupinterface', 'router_ip', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39, null=True))
92+
93+ # Changing field 'NodeGroupInterface.foreign_dhcp_ip'
94+ db.alter_column(u'maasserver_nodegroupinterface', 'foreign_dhcp_ip', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39, null=True))
95+
96+ # Changing field 'NodeGroupInterface.static_ip_range_high'
97+ db.alter_column(u'maasserver_nodegroupinterface', 'static_ip_range_high', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39, null=True))
98+
99+ # Changing field 'StaticIPAddress.ip'
100+ db.alter_column(u'maasserver_staticipaddress', 'ip', self.gf('maasserver.fields.MAASIPAddressField')(unique=True, max_length=39))
101+
102+ # Changing field 'DHCPLease.ip'
103+ db.alter_column(u'maasserver_dhcplease', 'ip', self.gf('maasserver.fields.MAASIPAddressField')(unique=True, max_length=39))
104+
105+ # Changing field 'Network.ip'
106+ db.alter_column(u'maasserver_network', 'ip', self.gf('maasserver.fields.MAASIPAddressField')(unique=True, max_length=39))
107+
108+ # Changing field 'Network.netmask'
109+ db.alter_column(u'maasserver_network', 'netmask', self.gf('maasserver.fields.MAASIPAddressField')(max_length=39))
110+
111+ def backwards(self, orm):
112+
113+ # Changing field 'NodeGroupInterface.ip_range_high'
114+ db.alter_column(u'maasserver_nodegroupinterface', 'ip_range_high', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True))
115+
116+ # Changing field 'NodeGroupInterface.static_ip_range_low'
117+ db.alter_column(u'maasserver_nodegroupinterface', 'static_ip_range_low', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True))
118+
119+ # Changing field 'NodeGroupInterface.ip'
120+ db.alter_column(u'maasserver_nodegroupinterface', 'ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39))
121+
122+ # Changing field 'NodeGroupInterface.ip_range_low'
123+ db.alter_column(u'maasserver_nodegroupinterface', 'ip_range_low', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True))
124+
125+ # Changing field 'NodeGroupInterface.subnet_mask'
126+ db.alter_column(u'maasserver_nodegroupinterface', 'subnet_mask', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True))
127+
128+ # Changing field 'NodeGroupInterface.broadcast_ip'
129+ db.alter_column(u'maasserver_nodegroupinterface', 'broadcast_ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True))
130+
131+ # Changing field 'NodeGroupInterface.router_ip'
132+ db.alter_column(u'maasserver_nodegroupinterface', 'router_ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True))
133+
134+ # Changing field 'NodeGroupInterface.foreign_dhcp_ip'
135+ db.alter_column(u'maasserver_nodegroupinterface', 'foreign_dhcp_ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True))
136+
137+ # Changing field 'NodeGroupInterface.static_ip_range_high'
138+ db.alter_column(u'maasserver_nodegroupinterface', 'static_ip_range_high', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True))
139+
140+ # Changing field 'StaticIPAddress.ip'
141+ db.alter_column(u'maasserver_staticipaddress', 'ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, unique=True))
142+
143+ # Changing field 'DHCPLease.ip'
144+ db.alter_column(u'maasserver_dhcplease', 'ip', self.gf('django.db.models.fields.IPAddressField')(max_length=15, unique=True))
145+
146+ # Changing field 'Network.ip'
147+ db.alter_column(u'maasserver_network', 'ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, unique=True))
148+
149+ # Changing field 'Network.netmask'
150+ db.alter_column(u'maasserver_network', 'netmask', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39))
151+
152+ models = {
153+ u'auth.group': {
154+ 'Meta': {'object_name': 'Group'},
155+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
156+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
157+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
158+ },
159+ u'auth.permission': {
160+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
161+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
162+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
163+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
164+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
165+ },
166+ u'auth.user': {
167+ 'Meta': {'object_name': 'User'},
168+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
169+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
170+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
171+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
172+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
173+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
174+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
175+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
176+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
177+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
178+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
179+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
180+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
181+ },
182+ u'contenttypes.contenttype': {
183+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
184+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
185+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
186+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
187+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
188+ },
189+ u'maasserver.bootimage': {
190+ 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'},
191+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
192+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
193+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
194+ 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}),
195+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
196+ 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
197+ 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
198+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
199+ 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
200+ 'supported_subarches': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
201+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
202+ 'xinstall_path': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
203+ 'xinstall_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '30', 'null': 'True', 'blank': 'True'})
204+ },
205+ u'maasserver.bootsource': {
206+ 'Meta': {'object_name': 'BootSource'},
207+ 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
208+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
209+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
210+ 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}),
211+ 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
212+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
213+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
214+ },
215+ u'maasserver.bootsourceselection': {
216+ 'Meta': {'object_name': 'BootSourceSelection'},
217+ 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
218+ 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}),
219+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
220+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
221+ 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
222+ 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
223+ 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}),
224+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
225+ },
226+ u'maasserver.componenterror': {
227+ 'Meta': {'object_name': 'ComponentError'},
228+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
229+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
230+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
231+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
232+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
233+ },
234+ u'maasserver.config': {
235+ 'Meta': {'object_name': 'Config'},
236+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
237+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
238+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
239+ },
240+ u'maasserver.dhcplease': {
241+ 'Meta': {'object_name': 'DHCPLease'},
242+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
243+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
244+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
245+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
246+ },
247+ u'maasserver.downloadprogress': {
248+ 'Meta': {'object_name': 'DownloadProgress'},
249+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
250+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
251+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
252+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
253+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
254+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
255+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
256+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
257+ },
258+ u'maasserver.filestorage': {
259+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
260+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
261+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
262+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
263+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'1ed17c44-0672-11e4-aedf-9c4e363b1c94'", 'unique': 'True', 'max_length': '36'}),
264+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
265+ },
266+ u'maasserver.licensekey': {
267+ 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'},
268+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
269+ 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
270+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
271+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
272+ 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
273+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
274+ },
275+ u'maasserver.macaddress': {
276+ 'Meta': {'object_name': 'MACAddress'},
277+ 'cluster_interface': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.NodeGroupInterface']", 'null': 'True', 'blank': 'True'}),
278+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
279+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
280+ 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.StaticIPAddress']", 'symmetrical': 'False', 'through': u"orm['maasserver.MACStaticIPAddressLink']", 'blank': 'True'}),
281+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
282+ 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}),
283+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
284+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
285+ },
286+ u'maasserver.macstaticipaddresslink': {
287+ 'Meta': {'unique_together': "((u'ip_address', u'mac_address'),)", 'object_name': 'MACStaticIPAddressLink'},
288+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
289+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
290+ 'ip_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.StaticIPAddress']", 'unique': 'True'}),
291+ 'mac_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.MACAddress']"}),
292+ 'nic_alias': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
293+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
294+ },
295+ u'maasserver.network': {
296+ 'Meta': {'object_name': 'Network'},
297+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
298+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
299+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
300+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
301+ 'netmask': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
302+ 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'})
303+ },
304+ u'maasserver.node': {
305+ 'Meta': {'object_name': 'Node'},
306+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
307+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
308+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
309+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
310+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
311+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
312+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
313+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
314+ 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
315+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
316+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
317+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
318+ 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}),
319+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
320+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
321+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
322+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
323+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
324+ 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
325+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-1ececdb4-0672-11e4-aedf-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}),
326+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
327+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
328+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
329+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
330+ },
331+ u'maasserver.nodegroup': {
332+ 'Meta': {'object_name': 'NodeGroup'},
333+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
334+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
335+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
336+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
337+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
338+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
339+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
340+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
341+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
342+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
343+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
344+ },
345+ u'maasserver.nodegroupinterface': {
346+ 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
347+ 'broadcast_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
348+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
349+ 'foreign_dhcp_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
350+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
351+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
352+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}),
353+ 'ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
354+ 'ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
355+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
356+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
357+ 'router_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
358+ 'static_ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
359+ 'static_ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
360+ 'subnet_mask': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
361+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
362+ },
363+ u'maasserver.sshkey': {
364+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
365+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
366+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
367+ 'key': ('django.db.models.fields.TextField', [], {}),
368+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
369+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
370+ },
371+ u'maasserver.sslkey': {
372+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'},
373+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
374+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
375+ 'key': ('django.db.models.fields.TextField', [], {}),
376+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
377+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
378+ },
379+ u'maasserver.staticipaddress': {
380+ 'Meta': {'object_name': 'StaticIPAddress'},
381+ 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
382+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
383+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
384+ 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}),
385+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
386+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
387+ },
388+ u'maasserver.tag': {
389+ 'Meta': {'object_name': 'Tag'},
390+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
391+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
392+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
393+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
394+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
395+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
396+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
397+ },
398+ u'maasserver.userprofile': {
399+ 'Meta': {'object_name': 'UserProfile'},
400+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
401+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
402+ },
403+ u'maasserver.zone': {
404+ 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
405+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
406+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
407+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
408+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
409+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
410+ },
411+ u'piston.consumer': {
412+ 'Meta': {'object_name': 'Consumer'},
413+ 'description': ('django.db.models.fields.TextField', [], {}),
414+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
415+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
416+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
417+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
418+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
419+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
420+ },
421+ u'piston.token': {
422+ 'Meta': {'object_name': 'Token'},
423+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
424+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
425+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
426+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
427+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
428+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
429+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
430+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1404804793L'}),
431+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
432+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
433+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
434+ }
435+ }
436+
437+ complete_apps = ['maasserver']
438\ No newline at end of file
439
440=== modified file 'src/maasserver/models/dhcplease.py'
441--- src/maasserver/models/dhcplease.py 2014-07-03 12:29:46 +0000
442+++ src/maasserver/models/dhcplease.py 2014-07-08 07:55:43 +0000
443@@ -20,7 +20,6 @@
444 from django.db import connection
445 from django.db.models import (
446 ForeignKey,
447- IPAddressField,
448 Manager,
449 Model,
450 )
451@@ -28,7 +27,10 @@
452 from django.dispatch import receiver
453 from maasserver import DefaultMeta
454 from maasserver.enum import NODE_STATUS
455-from maasserver.fields import MACAddressField
456+from maasserver.fields import (
457+ MAASIPAddressField,
458+ MACAddressField,
459+ )
460 from maasserver.models.cleansave import CleanSave
461 from maasserver.models.macaddress import MACAddress
462 from maasserver.utils import strip_domain
463@@ -159,7 +161,7 @@
464 objects = DHCPLeaseManager()
465
466 nodegroup = ForeignKey('maasserver.NodeGroup', null=False, editable=False)
467- ip = IPAddressField(null=False, editable=False, unique=True)
468+ ip = MAASIPAddressField(null=False, editable=False, unique=True)
469 mac = MACAddressField(null=False, editable=False, unique=False)
470
471 def __unicode__(self):
472
473=== modified file 'src/maasserver/models/network.py'
474--- src/maasserver/models/network.py 2014-06-30 14:46:10 +0000
475+++ src/maasserver/models/network.py 2014-07-08 07:55:43 +0000
476@@ -27,13 +27,13 @@
477 from django.core.validators import RegexValidator
478 from django.db.models import (
479 CharField,
480- GenericIPAddressField,
481 Manager,
482 Model,
483 PositiveSmallIntegerField,
484 TextField,
485 )
486 from maasserver import DefaultMeta
487+from maasserver.fields import MAASIPAddressField
488 from maasserver.models.cleansave import CleanSave
489 from netaddr import IPAddress
490 from netaddr.core import AddrFormatError
491@@ -220,11 +220,11 @@
492 validators=[NETWORK_NAME_VALIDATOR],
493 help_text="Identifying name for this network.")
494
495- ip = GenericIPAddressField(
496+ ip = MAASIPAddressField(
497 blank=False, editable=True, unique=True, null=False,
498 help_text="Network address (e.g. 192.168.1.0).")
499
500- netmask = GenericIPAddressField(
501+ netmask = MAASIPAddressField(
502 blank=False, editable=True, null=False,
503 help_text="Network mask (e.g. 255.255.255.0).")
504
505
506=== modified file 'src/maasserver/models/nodegroupinterface.py'
507--- src/maasserver/models/nodegroupinterface.py 2014-07-07 09:31:31 +0000
508+++ src/maasserver/models/nodegroupinterface.py 2014-07-08 07:55:43 +0000
509@@ -23,7 +23,6 @@
510 from django.db.models import (
511 CharField,
512 ForeignKey,
513- GenericIPAddressField,
514 IntegerField,
515 )
516 from maasserver import DefaultMeta
517@@ -32,6 +31,7 @@
518 NODEGROUPINTERFACE_MANAGEMENT_CHOICES,
519 NODEGROUPINTERFACE_MANAGEMENT_CHOICES_DICT,
520 )
521+from maasserver.fields import MAASIPAddressField
522 from maasserver.models.cleansave import CleanSave
523 from maasserver.models.timestampedmodel import TimestampedModel
524 from netaddr import (
525@@ -55,7 +55,7 @@
526 unique_together = ('nodegroup', 'interface')
527
528 # Static IP of the interface.
529- ip = GenericIPAddressField(
530+ ip = MAASIPAddressField(
531 null=False, editable=True,
532 help_text="Static IP Address of the interface",
533 verbose_name="IP")
534@@ -72,33 +72,33 @@
535 interface = CharField(
536 blank=True, editable=True, max_length=255, default='',
537 help_text="Name of this interface (e.g. 'em1').")
538- subnet_mask = GenericIPAddressField(
539+ subnet_mask = MAASIPAddressField(
540 editable=True, unique=False, blank=True, null=True, default=None,
541 help_text="e.g. 255.255.255.0")
542- broadcast_ip = GenericIPAddressField(
543+ broadcast_ip = MAASIPAddressField(
544 editable=True, unique=False, blank=True, null=True, default=None,
545 verbose_name="Broadcast IP",
546 help_text="e.g. 192.168.1.255")
547- router_ip = GenericIPAddressField(
548+ router_ip = MAASIPAddressField(
549 editable=True, unique=False, blank=True, null=True, default=None,
550 verbose_name="Router IP",
551 help_text="IP of this network's router given to DHCP clients")
552- ip_range_low = GenericIPAddressField(
553+ ip_range_low = MAASIPAddressField(
554 editable=True, unique=False, blank=True, null=True, default=None,
555 verbose_name="DHCP dynamic IP range low value",
556 help_text="Lowest IP number of the range for dynamic IPs, used for "
557 "enlistment, commissioning and unknown devices.")
558- ip_range_high = GenericIPAddressField(
559+ ip_range_high = MAASIPAddressField(
560 editable=True, unique=False, blank=True, null=True, default=None,
561 verbose_name="DHCP dynamic IP range high value",
562 help_text="Highest IP number of the range for dynamic IPs, used for "
563 "enlistment, commissioning and unknown devices.")
564- static_ip_range_low = GenericIPAddressField(
565+ static_ip_range_low = MAASIPAddressField(
566 editable=True, unique=False, blank=True, null=True, default=None,
567 verbose_name="Static IP range low value",
568 help_text="Lowest IP number of the range for IPs given to allocated "
569 "nodes, must be in same network as dynamic range.")
570- static_ip_range_high = GenericIPAddressField(
571+ static_ip_range_high = MAASIPAddressField(
572 editable=True, unique=False, blank=True, null=True, default=None,
573 verbose_name="Static IP range high value",
574 help_text="Highest IP number of the range for IPs given to allocated "
575@@ -106,7 +106,7 @@
576
577 # Foreign DHCP server address, if any, that was detected on this
578 # interface.
579- foreign_dhcp_ip = GenericIPAddressField(
580+ foreign_dhcp_ip = MAASIPAddressField(
581 null=True, default=None, editable=True, blank=True, unique=False)
582
583 @property
584@@ -120,7 +120,7 @@
585 ip = self.ip
586 netmask = self.subnet_mask
587 # Nullness check for GenericIPAddress fields is deliberately kept
588- # vague: GenericIPAddressField seems to represent nulls as empty
589+ # vague: MAASIPAddressField seems to represent nulls as empty
590 # strings.
591 if netmask:
592 return make_network(ip, netmask).cidr
593
594=== modified file 'src/maasserver/models/staticipaddress.py'
595--- src/maasserver/models/staticipaddress.py 2014-07-07 10:14:12 +0000
596+++ src/maasserver/models/staticipaddress.py 2014-07-08 07:55:43 +0000
597@@ -28,13 +28,13 @@
598 from django.db import connection
599 from django.db.models import (
600 ForeignKey,
601- GenericIPAddressField,
602 IntegerField,
603 Manager,
604 )
605 from maasserver import DefaultMeta
606 from maasserver.enum import IPADDRESS_TYPE
607 from maasserver.exceptions import StaticIPAddressExhaustion
608+from maasserver.fields import MAASIPAddressField
609 from maasserver.models.cleansave import CleanSave
610 from maasserver.models.timestampedmodel import TimestampedModel
611 from maasserver.utils import strip_domain
612@@ -82,16 +82,7 @@
613 # When we do ipv6, this needs changing.
614 range_list = [ip.ipv4().format() for ip in static_range]
615
616- # We're using a raw query to bypass a bug in Django that inserts
617- # a HOST() cast on the IP, causing the wrong comparison on the
618- # IP field.
619- # https://code.djangoproject.com/ticket/11442
620- existing = StaticIPAddress.objects.raw(
621- """
622- SELECT * FROM maasserver_staticipaddress
623- WHERE IP >= %s AND IP <= %s
624- """,
625- [range_low, range_high])
626+ existing = self.filter(ip__gte=range_low, ip__lte=range_high)
627
628 # Calculate the set of available IPs. This will be inefficient
629 # with large sets, but it will do for now.
630@@ -169,7 +160,7 @@
631 verbose_name = "Static IP Address"
632 verbose_name_plural = "Static IP Addresses"
633
634- ip = GenericIPAddressField(
635+ ip = MAASIPAddressField(
636 unique=True, null=False, editable=False, blank=False,
637 verbose_name='IP')
638
639
640=== modified file 'src/maasserver/tests/models.py'
641--- src/maasserver/tests/models.py 2013-10-07 09:12:40 +0000
642+++ src/maasserver/tests/models.py 2014-07-08 07:55:43 +0000
643@@ -13,6 +13,7 @@
644
645 __metaclass__ = type
646 __all__ = [
647+ 'MAASIPAddressFieldModel',
648 'JSONFieldModel',
649 'FieldChangeTestModel',
650 ]
651@@ -24,6 +25,7 @@
652 )
653 from maasserver.fields import (
654 JSONObjectField,
655+ MAASIPAddressField,
656 XMLField,
657 )
658 from maasserver.models.managers import BulkManager
659@@ -67,3 +69,7 @@
660 parent = ForeignKey('BulkManagerParentTestModel', editable=False)
661
662 objects = BulkManager()
663+
664+
665+class MAASIPAddressFieldModel(Model):
666+ ip_address = MAASIPAddressField()
667
668=== modified file 'src/maasserver/tests/test_fields.py'
669--- src/maasserver/tests/test_fields.py 2014-07-02 07:05:24 +0000
670+++ src/maasserver/tests/test_fields.py 2014-07-08 07:55:43 +0000
671@@ -42,6 +42,7 @@
672 from maasserver.testing.testcase import MAASServerTestCase
673 from maasserver.tests.models import (
674 JSONFieldModel,
675+ MAASIPAddressFieldModel,
676 XMLFieldModel,
677 )
678 from maastesting.djangotestcase import TestModelMixin
679@@ -388,3 +389,15 @@
680
681 def test_is_editable(self):
682 self.assertTrue(EditableBinaryField().editable)
683+
684+
685+class TestMAASIPAddressField(TestModelMixin, MAASServerTestCase):
686+
687+ app = 'maasserver.tests'
688+
689+ def test_uses_ip_comparison(self):
690+ ip_object = MAASIPAddressFieldModel.objects.create(
691+ ip_address='192.0.2.99')
692+ results = MAASIPAddressFieldModel.objects.filter(
693+ ip_address__lte='192.0.2.100')
694+ self.assertItemsEqual([ip_object], results)