Merge lp:~rvb/maas/default-zone 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: 1820
Proposed branch: lp:~rvb/maas/default-zone
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 2062 lines (+980/-346)
23 files modified
src/maasserver/forms.py (+18/-9)
src/maasserver/migrations/0060_add_zone_object.py (+2/-1)
src/maasserver/migrations/0061_add_ref_from_node_to_zone.py (+0/-229)
src/maasserver/migrations/0063_create_default_zone.py (+237/-0)
src/maasserver/migrations/0064_set_default_zone.py (+236/-0)
src/maasserver/migrations/0065_set_default_zone_as_model_default.py (+234/-0)
src/maasserver/models/node.py (+4/-2)
src/maasserver/models/tests/test_zone.py (+25/-3)
src/maasserver/models/zone.py (+42/-13)
src/maasserver/templates/maasserver/nodes_listing.html (+1/-5)
src/maasserver/templates/maasserver/zone_detail.html (+10/-2)
src/maasserver/templates/maasserver/zone_list.html (+4/-0)
src/maasserver/testing/factory.py (+2/-2)
src/maasserver/testing/tests/test_factory.py (+0/-6)
src/maasserver/tests/test_api_node.py (+13/-0)
src/maasserver/tests/test_api_nodes.py (+1/-14)
src/maasserver/tests/test_api_zone.py (+37/-3)
src/maasserver/tests/test_api_zones.py (+9/-3)
src/maasserver/tests/test_forms.py (+53/-27)
src/maasserver/tests/test_node_constraint_filter_forms.py (+1/-1)
src/maasserver/tests/test_views_nodes.py (+1/-17)
src/maasserver/tests/test_views_zones.py (+48/-8)
src/maasserver/views/zones.py (+2/-1)
To merge this branch: bzr merge lp:~rvb/maas/default-zone
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+200635@code.launchpad.net

Commit message

Add default zone object; fix node<->zone relation to not allow the zone to be None; remove the zone.delete() hack.

Description of the change

The main goal of this branch is to get nodes to be linked to a default zone instead of having their 'zone' field to be None by default. This branch is a bit big but only because it contains a couple of autogenerated migrations.

This work has been a bit complicated by the fact that we released a version of MAAS with the "None-zone" logic implemented so I had to create new migrations to transition to the new model instead of fixing the existing migrations. Turned out it wasn't that bad but I still meant more work and more testing.

The migration src/maasserver/migrations/0061_add_ref_from_node_to_zone.py has been deleted (because I originally started my work by refactoring that migration before switching to simply add new migrations —see my explanation above) and then re-added. This is exactly the same file but I didn't know how to un-remove that file with bzr so I took a shortcut and simply re-added the same file. The result is that the diff is a bit longer than it should have been.

Drive-by fix: remove the hack in zone.py:Zone.delete(). Instead of manually re-attaching the nodes connected to a zone about to be deleted, use Django's built-in on_delete=SET_DEFAULT feature (see the change to node.py and the migration /0065_set_default_zone_as_model_default.py).

I tested upgrading from the current Trusty package to a package built from this branch and it behaved as expected: the default zone was created and nodes with a 'None' zone filed were connected to the default zone after the migration took place.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Good stuff, though I tried to squeeze out a few notes:

.

Having an entire "Is this the default zone" column with a "Yes" in exactly one row seems a bit officious. Normally I'd say: just append "(default)" to the name. But with the default zone's name fixed to "default," that would look ridiculous as well. How about we just leave it out? We could make the default zone look different by italicising its displayed name.
.

Similarly, it looks a bit... clerical to say "Default / No" on every zone's details page and "Default / Yes" on the default zone's. Shouldn't we just have a notice like "This is the default zone. It cannot be renamed or deleted." on the default zone's page, and nothing on the rest? There may not even be a way to click through to that page without first noticing that there is a default zone.

.

Typo — test_updates_default_zone_description_works should lose either the "_works" suffix, of the first "s".

.

Finally, several things about test_rejects_deletion_of_default_zone:

    def test_rejects_deletion_of_default_zone(self):
        try:
            self.client.post(
                reverse('zone-del', args=[DEFAULT_ZONE_NAME]),
                {'post': 'yes'})
        except ValidationError:
            # XXX: Right now, this generates an error because the deletion

If you're going to have an XXX here, add your name and the date. Frankly though I'm not sure it's worth one — we have so many bigger fish to fry!

            # is prevented in the model code and not at the form level.
            # This is not so bad because we make sure that the deletion link
            # for the default zone isn't showed anywhere.

It's "shown," not "showed."

            # Still, ideally, once the validation happens at the form level,
            # this try/except statement should be removed and this test
            # should be extended further to check that the reponse to
            # the above self.client.post indicates a failure to validate
            # the data.
            pass

English has the extra "s" in "response." Keep technical English direct, and avoid the passive voice where possible! For example, I think this version takes less reading to understand: "If we move validation to the form level, this exception goes away and we'll have to check the HTTP response for a validation failure."

        # TODO: check that the page failed to validate the data.

If you want to verify that we actually got the ValidationError, use ExpectedException:

        with ExpectedException(ValidationError):
            self.client.post(
                ...)

This will make the test fail if the expected exception is not raised. Arguably that makes the test more brittle if we ever abolish the ValidationError, but in that case the comments will need revisiting anyway so it's appropriate for the test to call attention to itself.

Julian has been saying he would like us to use TODO markers only for reminders to ourselves, and never land them. It may be a little late for that though.

Anyway, thanks for the branch. I'll be happy to see this item closed!

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

> Having an entire "Is this the default zone" column with a "Yes" in exactly one
> row seems a bit officious. Normally I'd say: just append "(default)" to the
> name. But with the default zone's name fixed to "default," that would look
> ridiculous as well. How about we just leave it out? We could make the
> default zone look different by italicising its displayed name.

Yeah, good point. I tried italicising the name of the default zone but it really didn't look right. I think it's fine: it's named default and it doesn't have the icons to edit and remove it at the end of the line so it's pretty clear what it is and that it's special.

> Similarly, it looks a bit... clerical to say "Default / No" on every zone's
> details page and "Default / Yes" on the default zone's. Shouldn't we just
> have a notice like "This is the default zone. It cannot be renamed or
> deleted." on the default zone's page, and nothing on the rest? There may not
> even be a way to click through to that page without first noticing that there
> is a default zone.

True, done.

> Typo — test_updates_default_zone_description_works should lose either the
> "_works" suffix, of the first "s".

Done.

> Finally, several things about test_rejects_deletion_of_default_zone:
[..]
>

Fixed.

> If you want to verify that we actually got the ValidationError, use
> ExpectedException:
>
> with ExpectedException(ValidationError):
> self.client.post(
> ...)
>
> This will make the test fail if the expected exception is not raised.
> Arguably that makes the test more brittle if we ever abolish the
> ValidationError, but in that case the comments will need revisiting anyway so
> it's appropriate for the test to call attention to itself.

Well, I think it's a bit strange to code a test for something that is actually wrong so I left the try/except statement.

> Julian has been saying he would like us to use TODO markers only for reminders
> to ourselves, and never land them. It may be a little late for that though.

Okay, I've removed this TODO.

Thanks for the review!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/forms.py'
2--- src/maasserver/forms.py 2014-01-16 02:57:53 +0000
3+++ src/maasserver/forms.py 2014-01-16 08:48:57 +0000
4@@ -217,6 +217,7 @@
5
6 zone = forms.ModelChoiceField(
7 label="Physical zone", required=False,
8+ initial=Zone.objects.get_default_zone(),
9 queryset=Zone.objects.all(), to_field_name='name')
10
11 cpu_count = forms.IntegerField(
12@@ -242,16 +243,13 @@
13 def __init__(self, data=None, instance=None, **kwargs):
14 super(AdminNodeForm, self).__init__(
15 data=data, instance=instance, **kwargs)
16- self.set_initial_zone(instance)
17 self.set_up_power_parameters_field(data, instance)
18-
19- def set_initial_zone(self, instance):
20- if instance is not None:
21- zone = instance.zone
22- if zone is None:
23- self.initial['zone'] = ''
24- else:
25- self.initial['zone'] = zone.name
26+ # The zone field is not required because we want to be able
27+ # to omit it when using that form in the API.
28+ # We don't want the UI to show an entry for the 'empty' zone,
29+ # in the zones dropdown. This is why we set 'empty_label' to
30+ # None to force Django not to display that empty entry.
31+ self.fields['zone'].empty_label = None
32
33 def set_up_power_parameters_field(self, data, node):
34 """Setup the 'power_parameter' field based on the value for the
35@@ -969,6 +967,7 @@
36 # This adds an input field: the zone.
37 self.fields['zone'] = forms.ModelChoiceField(
38 label="Physical zone", required=False,
39+ initial=Zone.objects.get_default_zone(),
40 queryset=Zone.objects.all(), to_field_name='name')
41 self.fields['action'] = forms.ChoiceField(
42 required=True, choices=action_choices)
43@@ -1108,3 +1107,13 @@
44 'name',
45 'description',
46 )
47+
48+ def clean_name(self):
49+ new_name = self.cleaned_data['name']
50+ renaming_instance = (
51+ self.instance is not None and self.instance.is_default() and
52+ self.instance.name != new_name)
53+ if renaming_instance:
54+ raise forms.ValidationError(
55+ "This zone is the default zone, it cannot be renamed.")
56+ return self.cleaned_data['name']
57
58=== modified file 'src/maasserver/migrations/0060_add_zone_object.py'
59--- src/maasserver/migrations/0060_add_zone_object.py 2013-12-10 11:42:13 +0000
60+++ src/maasserver/migrations/0060_add_zone_object.py 2014-01-16 08:48:57 +0000
61@@ -1,8 +1,9 @@
62 # -*- coding: utf-8 -*-
63 import datetime
64+
65+from django.db import models
66 from south.db import db
67 from south.v2 import SchemaMigration
68-from django.db import models
69
70
71 class Migration(SchemaMigration):
72
73=== added file 'src/maasserver/migrations/0061_add_ref_from_node_to_zone.py'
74--- src/maasserver/migrations/0061_add_ref_from_node_to_zone.py 1970-01-01 00:00:00 +0000
75+++ src/maasserver/migrations/0061_add_ref_from_node_to_zone.py 2014-01-16 08:48:57 +0000
76@@ -0,0 +1,229 @@
77+# -*- coding: utf-8 -*-
78+import datetime
79+from south.db import db
80+from south.v2 import SchemaMigration
81+from django.db import models
82+
83+
84+class Migration(SchemaMigration):
85+
86+ def forwards(self, orm):
87+ # Adding field 'Node.zone'
88+ db.add_column(u'maasserver_node', 'zone',
89+ self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['maasserver.Zone'], null=True, blank=True),
90+ keep_default=False)
91+
92+
93+ def backwards(self, orm):
94+ # Deleting field 'Node.zone'
95+ db.delete_column(u'maasserver_node', 'zone_id')
96+
97+
98+ models = {
99+ u'auth.group': {
100+ 'Meta': {'object_name': 'Group'},
101+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
102+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
103+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
104+ },
105+ u'auth.permission': {
106+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
107+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
108+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
109+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
110+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
111+ },
112+ u'auth.user': {
113+ 'Meta': {'object_name': 'User'},
114+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
115+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
116+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
117+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
118+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
119+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
120+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
121+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
122+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
123+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
124+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
125+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
126+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
127+ },
128+ u'contenttypes.contenttype': {
129+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
130+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
131+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
132+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
133+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
134+ },
135+ u'maasserver.bootimage': {
136+ 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'},
137+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
138+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
139+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
140+ 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
141+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
142+ 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'})
143+ },
144+ u'maasserver.componenterror': {
145+ 'Meta': {'object_name': 'ComponentError'},
146+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
147+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
148+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
149+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
150+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
151+ },
152+ u'maasserver.config': {
153+ 'Meta': {'object_name': 'Config'},
154+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
155+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
156+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
157+ },
158+ u'maasserver.dhcplease': {
159+ 'Meta': {'object_name': 'DHCPLease'},
160+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
161+ 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
162+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
163+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
164+ },
165+ u'maasserver.downloadprogress': {
166+ 'Meta': {'object_name': 'DownloadProgress'},
167+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
168+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
169+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
170+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
171+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
172+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
173+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
174+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
175+ },
176+ u'maasserver.filestorage': {
177+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
178+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
179+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
180+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
181+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'87c6b1a2-6338-11e3-9837-9c4e363b1c94'", 'unique': 'True', 'max_length': '36'}),
182+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
183+ },
184+ u'maasserver.macaddress': {
185+ 'Meta': {'object_name': 'MACAddress'},
186+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
187+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
188+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
189+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
190+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
191+ },
192+ u'maasserver.node': {
193+ 'Meta': {'object_name': 'Node'},
194+ 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
195+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
196+ 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}),
197+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
198+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
199+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
200+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
201+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
202+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
203+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
204+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
205+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
206+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
207+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
208+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
209+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
210+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
211+ 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
212+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-87c5d5f2-6338-11e3-9837-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}),
213+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
214+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
215+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
216+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.Zone']", 'null': 'True', 'blank': 'True'})
217+ },
218+ u'maasserver.nodegroup': {
219+ 'Meta': {'object_name': 'NodeGroup'},
220+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
221+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
222+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
223+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
224+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
225+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
226+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
227+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
228+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
229+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
230+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
231+ },
232+ u'maasserver.nodegroupinterface': {
233+ 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
234+ 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
235+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
236+ 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
237+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
238+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
239+ 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
240+ 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
241+ 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
242+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
243+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
244+ 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
245+ 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
246+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
247+ },
248+ u'maasserver.sshkey': {
249+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
250+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
251+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
252+ 'key': ('django.db.models.fields.TextField', [], {}),
253+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
254+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
255+ },
256+ u'maasserver.tag': {
257+ 'Meta': {'object_name': 'Tag'},
258+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
259+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
260+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
261+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
262+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
263+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
264+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
265+ },
266+ u'maasserver.userprofile': {
267+ 'Meta': {'object_name': 'UserProfile'},
268+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
269+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
270+ },
271+ u'maasserver.zone': {
272+ 'Meta': {'object_name': 'Zone'},
273+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
274+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
275+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
276+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
277+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
278+ },
279+ u'piston.consumer': {
280+ 'Meta': {'object_name': 'Consumer'},
281+ 'description': ('django.db.models.fields.TextField', [], {}),
282+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
283+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
284+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
285+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
286+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
287+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
288+ },
289+ u'piston.token': {
290+ 'Meta': {'object_name': 'Token'},
291+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
292+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
293+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
294+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
295+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
296+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
297+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
298+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1386858019L'}),
299+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
300+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
301+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
302+ }
303+ }
304+
305+ complete_apps = ['maasserver']
306\ No newline at end of file
307
308=== removed file 'src/maasserver/migrations/0061_add_ref_from_node_to_zone.py'
309--- src/maasserver/migrations/0061_add_ref_from_node_to_zone.py 2013-12-12 14:22:42 +0000
310+++ src/maasserver/migrations/0061_add_ref_from_node_to_zone.py 1970-01-01 00:00:00 +0000
311@@ -1,229 +0,0 @@
312-# -*- coding: utf-8 -*-
313-import datetime
314-from south.db import db
315-from south.v2 import SchemaMigration
316-from django.db import models
317-
318-
319-class Migration(SchemaMigration):
320-
321- def forwards(self, orm):
322- # Adding field 'Node.zone'
323- db.add_column(u'maasserver_node', 'zone',
324- self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['maasserver.Zone'], null=True, blank=True),
325- keep_default=False)
326-
327-
328- def backwards(self, orm):
329- # Deleting field 'Node.zone'
330- db.delete_column(u'maasserver_node', 'zone_id')
331-
332-
333- models = {
334- u'auth.group': {
335- 'Meta': {'object_name': 'Group'},
336- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
337- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
338- 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
339- },
340- u'auth.permission': {
341- 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
342- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
343- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
344- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
345- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
346- },
347- u'auth.user': {
348- 'Meta': {'object_name': 'User'},
349- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
350- 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
351- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
352- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
353- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
354- 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
355- 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
356- 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
357- 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
358- 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
359- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
360- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
361- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
362- },
363- u'contenttypes.contenttype': {
364- 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
365- 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
366- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
367- 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
368- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
369- },
370- u'maasserver.bootimage': {
371- 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'},
372- 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
373- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
374- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
375- 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
376- 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
377- 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'})
378- },
379- u'maasserver.componenterror': {
380- 'Meta': {'object_name': 'ComponentError'},
381- 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
382- 'created': ('django.db.models.fields.DateTimeField', [], {}),
383- 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
384- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
385- 'updated': ('django.db.models.fields.DateTimeField', [], {})
386- },
387- u'maasserver.config': {
388- 'Meta': {'object_name': 'Config'},
389- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
390- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
391- 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
392- },
393- u'maasserver.dhcplease': {
394- 'Meta': {'object_name': 'DHCPLease'},
395- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
396- 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
397- 'mac': ('maasserver.fields.MACAddressField', [], {}),
398- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
399- },
400- u'maasserver.downloadprogress': {
401- 'Meta': {'object_name': 'DownloadProgress'},
402- 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
403- 'created': ('django.db.models.fields.DateTimeField', [], {}),
404- 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
405- 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
406- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
407- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
408- 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
409- 'updated': ('django.db.models.fields.DateTimeField', [], {})
410- },
411- u'maasserver.filestorage': {
412- 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
413- 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
414- 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
415- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
416- 'key': ('django.db.models.fields.CharField', [], {'default': "u'87c6b1a2-6338-11e3-9837-9c4e363b1c94'", 'unique': 'True', 'max_length': '36'}),
417- 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
418- },
419- u'maasserver.macaddress': {
420- 'Meta': {'object_name': 'MACAddress'},
421- 'created': ('django.db.models.fields.DateTimeField', [], {}),
422- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
423- 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
424- 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
425- 'updated': ('django.db.models.fields.DateTimeField', [], {})
426- },
427- u'maasserver.node': {
428- 'Meta': {'object_name': 'Node'},
429- 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
430- 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
431- 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}),
432- 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
433- 'created': ('django.db.models.fields.DateTimeField', [], {}),
434- 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
435- 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
436- 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
437- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
438- 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
439- 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
440- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
441- 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
442- 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
443- 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
444- 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
445- 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
446- 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
447- 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-87c5d5f2-6338-11e3-9837-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}),
448- 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
449- 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
450- 'updated': ('django.db.models.fields.DateTimeField', [], {}),
451- 'zone': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.Zone']", 'null': 'True', 'blank': 'True'})
452- },
453- u'maasserver.nodegroup': {
454- 'Meta': {'object_name': 'NodeGroup'},
455- 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
456- 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
457- 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
458- 'created': ('django.db.models.fields.DateTimeField', [], {}),
459- 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
460- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
461- 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
462- 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
463- 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
464- 'updated': ('django.db.models.fields.DateTimeField', [], {}),
465- 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
466- },
467- u'maasserver.nodegroupinterface': {
468- 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
469- 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
470- 'created': ('django.db.models.fields.DateTimeField', [], {}),
471- 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
472- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
473- 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
474- 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
475- 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
476- 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
477- 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
478- 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
479- 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
480- 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
481- 'updated': ('django.db.models.fields.DateTimeField', [], {})
482- },
483- u'maasserver.sshkey': {
484- 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
485- 'created': ('django.db.models.fields.DateTimeField', [], {}),
486- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
487- 'key': ('django.db.models.fields.TextField', [], {}),
488- 'updated': ('django.db.models.fields.DateTimeField', [], {}),
489- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
490- },
491- u'maasserver.tag': {
492- 'Meta': {'object_name': 'Tag'},
493- 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
494- 'created': ('django.db.models.fields.DateTimeField', [], {}),
495- 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
496- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
497- 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
498- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
499- 'updated': ('django.db.models.fields.DateTimeField', [], {})
500- },
501- u'maasserver.userprofile': {
502- 'Meta': {'object_name': 'UserProfile'},
503- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
504- 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
505- },
506- u'maasserver.zone': {
507- 'Meta': {'object_name': 'Zone'},
508- 'created': ('django.db.models.fields.DateTimeField', [], {}),
509- 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
510- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
511- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
512- 'updated': ('django.db.models.fields.DateTimeField', [], {})
513- },
514- u'piston.consumer': {
515- 'Meta': {'object_name': 'Consumer'},
516- 'description': ('django.db.models.fields.TextField', [], {}),
517- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
518- 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
519- 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
520- 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
521- 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
522- 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
523- },
524- u'piston.token': {
525- 'Meta': {'object_name': 'Token'},
526- 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
527- 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
528- 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
529- u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
530- 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
531- 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
532- 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
533- 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1386858019L'}),
534- 'token_type': ('django.db.models.fields.IntegerField', [], {}),
535- 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
536- 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
537- }
538- }
539-
540- complete_apps = ['maasserver']
541\ No newline at end of file
542
543=== added file 'src/maasserver/migrations/0063_create_default_zone.py'
544--- src/maasserver/migrations/0063_create_default_zone.py 1970-01-01 00:00:00 +0000
545+++ src/maasserver/migrations/0063_create_default_zone.py 2014-01-16 08:48:57 +0000
546@@ -0,0 +1,237 @@
547+# -*- coding: utf-8 -*-
548+import datetime
549+
550+from maasserver.models.zone import DEFAULT_ZONE_NAME
551+from south.v2 import DataMigration
552+
553+
554+class Migration(DataMigration):
555+
556+ def forwards(self, orm):
557+ now = datetime.datetime.now()
558+ ct, created = orm['maasserver.Zone'].objects.get_or_create(
559+ name=DEFAULT_ZONE_NAME,
560+ defaults={
561+ 'name': DEFAULT_ZONE_NAME,
562+ 'created': now,
563+ 'updated': now,
564+ })
565+
566+ def backwards(self, orm):
567+ pass
568+
569+ models = {
570+ u'auth.group': {
571+ 'Meta': {'object_name': 'Group'},
572+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
573+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
574+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
575+ },
576+ u'auth.permission': {
577+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
578+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
579+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
580+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
581+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
582+ },
583+ u'auth.user': {
584+ 'Meta': {'object_name': 'User'},
585+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
586+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
587+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
588+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
589+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
590+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
591+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
592+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
593+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
594+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
595+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
596+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
597+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
598+ },
599+ u'contenttypes.contenttype': {
600+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
601+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
602+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
603+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
604+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
605+ },
606+ u'maasserver.bootimage': {
607+ 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'},
608+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
609+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
610+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
611+ 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
612+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
613+ 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'})
614+ },
615+ u'maasserver.componenterror': {
616+ 'Meta': {'object_name': 'ComponentError'},
617+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
618+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
619+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
620+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
621+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
622+ },
623+ u'maasserver.config': {
624+ 'Meta': {'object_name': 'Config'},
625+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
626+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
627+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
628+ },
629+ u'maasserver.dhcplease': {
630+ 'Meta': {'object_name': 'DHCPLease'},
631+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
632+ 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
633+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
634+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
635+ },
636+ u'maasserver.downloadprogress': {
637+ 'Meta': {'object_name': 'DownloadProgress'},
638+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
639+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
640+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
641+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
642+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
643+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
644+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
645+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
646+ },
647+ u'maasserver.filestorage': {
648+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
649+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
650+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
651+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
652+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'81461798-7de9-11e3-ad6a-9c4e363b1c94'", 'unique': 'True', 'max_length': '36'}),
653+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
654+ },
655+ u'maasserver.macaddress': {
656+ 'Meta': {'object_name': 'MACAddress'},
657+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
658+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
659+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
660+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
661+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
662+ },
663+ u'maasserver.node': {
664+ 'Meta': {'object_name': 'Node'},
665+ 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
666+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
667+ 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}),
668+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
669+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
670+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
671+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
672+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
673+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
674+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
675+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
676+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
677+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
678+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
679+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
680+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
681+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
682+ 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
683+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-81450ee8-7de9-11e3-ad6a-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}),
684+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
685+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
686+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
687+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.Zone']", 'null': 'True', 'blank': 'True'})
688+ },
689+ u'maasserver.nodegroup': {
690+ 'Meta': {'object_name': 'NodeGroup'},
691+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
692+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
693+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
694+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
695+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
696+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
697+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
698+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
699+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
700+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
701+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
702+ },
703+ u'maasserver.nodegroupinterface': {
704+ 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
705+ 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
706+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
707+ 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
708+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
709+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
710+ 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
711+ 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
712+ 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
713+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
714+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
715+ 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
716+ 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
717+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
718+ },
719+ u'maasserver.sshkey': {
720+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
721+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
722+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
723+ 'key': ('django.db.models.fields.TextField', [], {}),
724+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
725+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
726+ },
727+ u'maasserver.tag': {
728+ 'Meta': {'object_name': 'Tag'},
729+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
730+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
731+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
732+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
733+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
734+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
735+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
736+ },
737+ u'maasserver.userprofile': {
738+ 'Meta': {'object_name': 'UserProfile'},
739+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
740+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
741+ },
742+ u'maasserver.vlan': {
743+ 'Meta': {'object_name': 'Vlan'},
744+ 'description': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
745+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
746+ 'tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True'})
747+ },
748+ u'maasserver.zone': {
749+ 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
750+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
751+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
752+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
753+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
754+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
755+ },
756+ u'piston.consumer': {
757+ 'Meta': {'object_name': 'Consumer'},
758+ 'description': ('django.db.models.fields.TextField', [], {}),
759+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
760+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
761+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
762+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
763+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
764+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
765+ },
766+ u'piston.token': {
767+ 'Meta': {'object_name': 'Token'},
768+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
769+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
770+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
771+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
772+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
773+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
774+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
775+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1389792759L'}),
776+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
777+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
778+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
779+ }
780+ }
781+
782+ complete_apps = ['maasserver']
783+ symmetrical = True
784
785=== added file 'src/maasserver/migrations/0064_set_default_zone.py'
786--- src/maasserver/migrations/0064_set_default_zone.py 1970-01-01 00:00:00 +0000
787+++ src/maasserver/migrations/0064_set_default_zone.py 2014-01-16 08:48:57 +0000
788@@ -0,0 +1,236 @@
789+# -*- coding: utf-8 -*-
790+import datetime
791+
792+from django.db import models
793+from maasserver.models.zone import DEFAULT_ZONE_NAME
794+from south.db import db
795+from south.v2 import DataMigration
796+
797+
798+class Migration(DataMigration):
799+
800+ def forwards(self, orm):
801+ default_zone = orm['maasserver.Zone'].objects.get(
802+ name=DEFAULT_ZONE_NAME)
803+ nodes_with_none_zone = orm['maasserver.Node'].objects.filter(
804+ zone=None)
805+ nodes_with_none_zone.update(zone=default_zone)
806+
807+ def backwards(self, orm):
808+ pass
809+
810+ models = {
811+ u'auth.group': {
812+ 'Meta': {'object_name': 'Group'},
813+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
814+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
815+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
816+ },
817+ u'auth.permission': {
818+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
819+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
820+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
821+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
822+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
823+ },
824+ u'auth.user': {
825+ 'Meta': {'object_name': 'User'},
826+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
827+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
828+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
829+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
830+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
831+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
832+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
833+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
834+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
835+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
836+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
837+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
838+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
839+ },
840+ u'contenttypes.contenttype': {
841+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
842+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
843+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
844+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
845+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
846+ },
847+ u'maasserver.bootimage': {
848+ 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'},
849+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
850+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
851+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
852+ 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
853+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
854+ 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'})
855+ },
856+ u'maasserver.componenterror': {
857+ 'Meta': {'object_name': 'ComponentError'},
858+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
859+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
860+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
861+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
862+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
863+ },
864+ u'maasserver.config': {
865+ 'Meta': {'object_name': 'Config'},
866+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
867+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
868+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
869+ },
870+ u'maasserver.dhcplease': {
871+ 'Meta': {'object_name': 'DHCPLease'},
872+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
873+ 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
874+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
875+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
876+ },
877+ u'maasserver.downloadprogress': {
878+ 'Meta': {'object_name': 'DownloadProgress'},
879+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
880+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
881+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
882+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
883+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
884+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
885+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
886+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
887+ },
888+ u'maasserver.filestorage': {
889+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
890+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
891+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
892+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
893+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'f816df92-7de9-11e3-acc1-9c4e363b1c94'", 'unique': 'True', 'max_length': '36'}),
894+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
895+ },
896+ u'maasserver.macaddress': {
897+ 'Meta': {'object_name': 'MACAddress'},
898+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
899+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
900+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
901+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
902+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
903+ },
904+ u'maasserver.node': {
905+ 'Meta': {'object_name': 'Node'},
906+ 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
907+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
908+ 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}),
909+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
910+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
911+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
912+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
913+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
914+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
915+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
916+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
917+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
918+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
919+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
920+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
921+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
922+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
923+ 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
924+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-f818607e-7de9-11e3-acc1-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}),
925+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
926+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
927+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
928+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.Zone']", 'null': 'True', 'blank': 'True'})
929+ },
930+ u'maasserver.nodegroup': {
931+ 'Meta': {'object_name': 'NodeGroup'},
932+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
933+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
934+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
935+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
936+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
937+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
938+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
939+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
940+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
941+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
942+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
943+ },
944+ u'maasserver.nodegroupinterface': {
945+ 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
946+ 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
947+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
948+ 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
949+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
950+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
951+ 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
952+ 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
953+ 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
954+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
955+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
956+ 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
957+ 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
958+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
959+ },
960+ u'maasserver.sshkey': {
961+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
962+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
963+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
964+ 'key': ('django.db.models.fields.TextField', [], {}),
965+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
966+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
967+ },
968+ u'maasserver.tag': {
969+ 'Meta': {'object_name': 'Tag'},
970+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
971+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
972+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
973+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
974+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
975+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
976+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
977+ },
978+ u'maasserver.userprofile': {
979+ 'Meta': {'object_name': 'UserProfile'},
980+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
981+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
982+ },
983+ u'maasserver.vlan': {
984+ 'Meta': {'object_name': 'Vlan'},
985+ 'description': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
986+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
987+ 'tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True'})
988+ },
989+ u'maasserver.zone': {
990+ 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
991+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
992+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
993+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
994+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
995+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
996+ },
997+ u'piston.consumer': {
998+ 'Meta': {'object_name': 'Consumer'},
999+ 'description': ('django.db.models.fields.TextField', [], {}),
1000+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1001+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
1002+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
1003+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
1004+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
1005+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
1006+ },
1007+ u'piston.token': {
1008+ 'Meta': {'object_name': 'Token'},
1009+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
1010+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
1011+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
1012+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1013+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
1014+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
1015+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
1016+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1389792959L'}),
1017+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
1018+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
1019+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
1020+ }
1021+ }
1022+
1023+ complete_apps = ['maasserver']
1024+ symmetrical = True
1025
1026=== added file 'src/maasserver/migrations/0065_set_default_zone_as_model_default.py'
1027--- src/maasserver/migrations/0065_set_default_zone_as_model_default.py 1970-01-01 00:00:00 +0000
1028+++ src/maasserver/migrations/0065_set_default_zone_as_model_default.py 2014-01-16 08:48:57 +0000
1029@@ -0,0 +1,234 @@
1030+# -*- coding: utf-8 -*-
1031+import datetime
1032+
1033+from django.db import models
1034+from south.db import db
1035+from south.v2 import SchemaMigration
1036+
1037+
1038+class Migration(SchemaMigration):
1039+
1040+ def forwards(self, orm):
1041+
1042+ # Changing field 'Node.zone'
1043+ db.alter_column(u'maasserver_node', 'zone_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['maasserver.Zone'], on_delete=models.SET_DEFAULT))
1044+
1045+ def backwards(self, orm):
1046+
1047+ # Changing field 'Node.zone'
1048+ db.alter_column(u'maasserver_node', 'zone_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['maasserver.Zone'], null=True))
1049+
1050+ models = {
1051+ u'auth.group': {
1052+ 'Meta': {'object_name': 'Group'},
1053+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1054+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
1055+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
1056+ },
1057+ u'auth.permission': {
1058+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
1059+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
1060+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
1061+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1062+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
1063+ },
1064+ u'auth.user': {
1065+ 'Meta': {'object_name': 'User'},
1066+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
1067+ 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}),
1068+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
1069+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
1070+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1071+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
1072+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
1073+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
1074+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
1075+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
1076+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
1077+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
1078+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
1079+ },
1080+ u'contenttypes.contenttype': {
1081+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
1082+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
1083+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1084+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
1085+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
1086+ },
1087+ u'maasserver.bootimage': {
1088+ 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'},
1089+ 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
1090+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1091+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
1092+ 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
1093+ 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
1094+ 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'})
1095+ },
1096+ u'maasserver.componenterror': {
1097+ 'Meta': {'object_name': 'ComponentError'},
1098+ 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
1099+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1100+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
1101+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1102+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
1103+ },
1104+ u'maasserver.config': {
1105+ 'Meta': {'object_name': 'Config'},
1106+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1107+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
1108+ 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'})
1109+ },
1110+ u'maasserver.dhcplease': {
1111+ 'Meta': {'object_name': 'DHCPLease'},
1112+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1113+ 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}),
1114+ 'mac': ('maasserver.fields.MACAddressField', [], {}),
1115+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"})
1116+ },
1117+ u'maasserver.downloadprogress': {
1118+ 'Meta': {'object_name': 'DownloadProgress'},
1119+ 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
1120+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1121+ 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}),
1122+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
1123+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1124+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
1125+ 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
1126+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
1127+ },
1128+ u'maasserver.filestorage': {
1129+ 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'},
1130+ 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}),
1131+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
1132+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1133+ 'key': ('django.db.models.fields.CharField', [], {'default': "u'8498ac20-7dea-11e3-b413-9c4e363b1c94'", 'unique': 'True', 'max_length': '36'}),
1134+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'})
1135+ },
1136+ u'maasserver.macaddress': {
1137+ 'Meta': {'object_name': 'MACAddress'},
1138+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1139+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1140+ 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}),
1141+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}),
1142+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
1143+ },
1144+ u'maasserver.node': {
1145+ 'Meta': {'object_name': 'Node'},
1146+ 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
1147+ 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
1148+ 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}),
1149+ 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
1150+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1151+ 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'null': 'True', 'blank': 'True'}),
1152+ 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
1153+ 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}),
1154+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1155+ 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
1156+ 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
1157+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}),
1158+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}),
1159+ 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}),
1160+ 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}),
1161+ 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}),
1162+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}),
1163+ 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
1164+ 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-8497005a-7dea-11e3-b413-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}),
1165+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}),
1166+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}),
1167+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
1168+ 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'})
1169+ },
1170+ u'maasserver.nodegroup': {
1171+ 'Meta': {'object_name': 'NodeGroup'},
1172+ 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}),
1173+ 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}),
1174+ 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}),
1175+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1176+ 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
1177+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1178+ 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
1179+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}),
1180+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
1181+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
1182+ 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'})
1183+ },
1184+ u'maasserver.nodegroupinterface': {
1185+ 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'},
1186+ 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
1187+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1188+ 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
1189+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1190+ 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
1191+ 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
1192+ 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
1193+ 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
1194+ 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
1195+ 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}),
1196+ 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
1197+ 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}),
1198+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
1199+ },
1200+ u'maasserver.sshkey': {
1201+ 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'},
1202+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1203+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1204+ 'key': ('django.db.models.fields.TextField', [], {}),
1205+ 'updated': ('django.db.models.fields.DateTimeField', [], {}),
1206+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
1207+ },
1208+ u'maasserver.tag': {
1209+ 'Meta': {'object_name': 'Tag'},
1210+ 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
1211+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1212+ 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
1213+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1214+ 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
1215+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
1216+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
1217+ },
1218+ u'maasserver.userprofile': {
1219+ 'Meta': {'object_name': 'UserProfile'},
1220+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1221+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
1222+ },
1223+ u'maasserver.vlan': {
1224+ 'Meta': {'object_name': 'Vlan'},
1225+ 'description': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}),
1226+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1227+ 'tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True'})
1228+ },
1229+ u'maasserver.zone': {
1230+ 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'},
1231+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
1232+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
1233+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1234+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}),
1235+ 'updated': ('django.db.models.fields.DateTimeField', [], {})
1236+ },
1237+ u'piston.consumer': {
1238+ 'Meta': {'object_name': 'Consumer'},
1239+ 'description': ('django.db.models.fields.TextField', [], {}),
1240+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1241+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
1242+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
1243+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
1244+ 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}),
1245+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"})
1246+ },
1247+ u'piston.token': {
1248+ 'Meta': {'object_name': 'Token'},
1249+ 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
1250+ 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
1251+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}),
1252+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
1253+ 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
1254+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}),
1255+ 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
1256+ 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1389793194L'}),
1257+ 'token_type': ('django.db.models.fields.IntegerField', [], {}),
1258+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}),
1259+ 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'})
1260+ }
1261+ }
1262+
1263+ complete_apps = ['maasserver']
1264\ No newline at end of file
1265
1266=== modified file 'src/maasserver/models/node.py'
1267--- src/maasserver/models/node.py 2014-01-16 02:57:53 +0000
1268+++ src/maasserver/models/node.py 2014-01-16 08:48:57 +0000
1269@@ -38,6 +38,7 @@
1270 IntegerField,
1271 Manager,
1272 ManyToManyField,
1273+ SET_DEFAULT,
1274 Q,
1275 )
1276 from django.shortcuts import get_object_or_404
1277@@ -438,8 +439,9 @@
1278 agent_name = CharField(max_length=255, default='', blank=True, null=True)
1279
1280 zone = ForeignKey(
1281- Zone, verbose_name="Physical zone", default=None, blank=True,
1282- null=True, editable=True, db_index=True)
1283+ Zone, verbose_name="Physical zone",
1284+ default=Zone.objects.get_default_zone, editable=True, db_index=True,
1285+ on_delete=SET_DEFAULT)
1286
1287 # Juju expects the following standard constraints, which are stored here
1288 # as a basic optimisation over querying the lshw output.
1289
1290=== modified file 'src/maasserver/models/tests/test_zone.py'
1291--- src/maasserver/models/tests/test_zone.py 2013-12-18 11:57:24 +0000
1292+++ src/maasserver/models/tests/test_zone.py 2014-01-16 08:48:57 +0000
1293@@ -1,4 +1,4 @@
1294-# Copyright 2013 Canonical Ltd. This software is licensed under the
1295+# Copyright 2013-2014 Canonical Ltd. This software is licensed under the
1296 # GNU Affero General Public License version 3 (see the file LICENSE).
1297
1298 """Test Zone objects."""
1299@@ -14,12 +14,28 @@
1300 __metaclass__ = type
1301 __all__ = []
1302
1303-from maasserver.models.zone import Zone
1304+from maasserver.models.zone import (
1305+ DEFAULT_ZONE_NAME,
1306+ Zone,
1307+ )
1308 from maasserver.testing import reload_object
1309 from maasserver.testing.factory import factory
1310 from maasserver.testing.testcase import MAASServerTestCase
1311
1312
1313+class TestZoneManager(MAASServerTestCase):
1314+ """Tests for `Zone` manager."""
1315+
1316+ def test_get_default_zone_returns_default_zone(self):
1317+ self.assertEqual(
1318+ DEFAULT_ZONE_NAME, Zone.objects.get_default_zone().name)
1319+
1320+ def test_get_default_zone_ignores_other_zones(self):
1321+ factory.make_zone()
1322+ self.assertEqual(
1323+ DEFAULT_ZONE_NAME, Zone.objects.get_default_zone().name)
1324+
1325+
1326 class TestZone(MAASServerTestCase):
1327 """Tests for :class:`Zone`."""
1328
1329@@ -56,4 +72,10 @@
1330 self.assertIsNone(reload_object(zone))
1331 node = reload_object(node)
1332 self.assertIsNotNone(node)
1333- self.assertIsNone(node.zone)
1334+ self.assertEqual(Zone.objects.get_default_zone(), node.zone)
1335+
1336+ def test_is_default_returns_True_for_default_zone(self):
1337+ self.assertTrue(Zone.objects.get_default_zone().is_default())
1338+
1339+ def test_is_default_returns_False_for_normal_zone(self):
1340+ self.assertFalse(factory.make_zone().is_default())
1341
1342=== modified file 'src/maasserver/models/zone.py'
1343--- src/maasserver/models/zone.py 2013-12-18 17:47:51 +0000
1344+++ src/maasserver/models/zone.py 2014-01-16 08:48:57 +0000
1345@@ -1,4 +1,4 @@
1346-# Copyright 2013 Canonical Ltd. This software is licensed under the
1347+# Copyright 2013-2014 Canonical Ltd. This software is licensed under the
1348 # GNU Affero General Public License version 3 (see the file LICENSE).
1349
1350 """Physical zone objects."""
1351@@ -13,13 +13,18 @@
1352
1353 __metaclass__ = type
1354 __all__ = [
1355+ "DEFAULT_ZONE_NAME",
1356 "Zone",
1357 "ZONE_NAME_VALIDATOR",
1358 ]
1359
1360+import datetime
1361+
1362+from django.core.exceptions import ValidationError
1363 from django.core.validators import RegexValidator
1364 from django.db.models import (
1365 CharField,
1366+ Manager,
1367 TextField,
1368 )
1369 from maasserver import DefaultMeta
1370@@ -29,12 +34,38 @@
1371
1372 ZONE_NAME_VALIDATOR = RegexValidator('^[\w-]+$')
1373
1374+# Name of the special, default zone. This zone can be neither deleted nor
1375+# renamed.
1376+DEFAULT_ZONE_NAME = 'default'
1377+
1378+
1379+class ZoneManager(Manager):
1380+ """Manager for :class:`Zone` model.
1381+
1382+ Don't import or instantiate this directly; access as `<Class>.objects` on
1383+ the model class it manages.
1384+ """
1385+
1386+ def get_default_zone(self):
1387+ """Return the default zone."""
1388+ now = datetime.datetime.now()
1389+ zone, _ = self.get_or_create(
1390+ name=DEFAULT_ZONE_NAME,
1391+ defaults={
1392+ 'name': DEFAULT_ZONE_NAME,
1393+ 'created': now,
1394+ 'updated': now,
1395+ }
1396+ )
1397+ return zone
1398+
1399
1400 class Zone(CleanSave, TimestampedModel):
1401 """A `Zone` is an entity used to logically group nodes together.
1402
1403 :ivar name: The short-human-identifiable name for this zone.
1404 :ivar description: Free-form description for this zone.
1405+ :ivar objects: An instance of the class :class:`ZoneManager`.
1406 """
1407
1408 class Meta(DefaultMeta):
1409@@ -43,25 +74,23 @@
1410 verbose_name_plural = "Physical zones"
1411 ordering = ["name"]
1412
1413+ objects = ZoneManager()
1414+
1415 name = CharField(
1416 max_length=256, unique=True, editable=True,
1417 validators=[ZONE_NAME_VALIDATOR])
1418+
1419 description = TextField(blank=True, editable=True)
1420
1421 def __unicode__(self):
1422 return self.name
1423
1424+ def is_default(self):
1425+ """Is this the default zone?"""
1426+ return self.name == DEFAULT_ZONE_NAME
1427+
1428 def delete(self):
1429- """Delete zone, but keep its nodes!
1430-
1431- Deleting a zone, by default, deletes all the nodes that are in that
1432- zone. Use `delete` instead: it will clear those nodes' `zone` fields
1433- so they'll be zoneless, but not gone.
1434- """
1435- # Avoid circular imports.
1436- from maasserver.models.node import Node
1437-
1438- # Clear nodes' references to this zone.
1439- Node.objects.filter(zone=self).update(zone=None)
1440- # That being done, defer to the reckless default implementation.
1441+ if self.is_default():
1442+ raise ValidationError(
1443+ "This zone is the default zone, it cannot be deleted.")
1444 super(Zone, self).delete()
1445
1446=== modified file 'src/maasserver/templates/maasserver/nodes_listing.html'
1447--- src/maasserver/templates/maasserver/nodes_listing.html 2013-12-18 11:16:29 +0000
1448+++ src/maasserver/templates/maasserver/nodes_listing.html 2014-01-16 08:48:57 +0000
1449@@ -52,11 +52,7 @@
1450 </td>
1451 <td>{{ node.display_status }}</td>
1452 <td class="zone-column">
1453- {% if node.zone %}
1454- <a href="{% url 'zone-view' node.zone.name %}">{{ node.zone }}</a>
1455- {% else %}
1456- (Default)
1457- {% endif %}
1458+ <a href="{% url 'zone-view' node.zone.name %}">{{ node.zone }}</a>
1459 </td>
1460 </tr>
1461 {% endfor %}
1462
1463=== modified file 'src/maasserver/templates/maasserver/zone_detail.html'
1464--- src/maasserver/templates/maasserver/zone_detail.html 2013-12-20 13:05:55 +0000
1465+++ src/maasserver/templates/maasserver/zone_detail.html 2014-01-16 08:48:57 +0000
1466@@ -15,9 +15,11 @@
1467 <a href="{% url 'zone-edit' zone.name %}" class="button secondary">
1468 Edit zone
1469 </a>
1470+ {% if not zone.is_default %}
1471 <a href="{% url 'zone-del' zone.name %}" class="button secondary">
1472 Delete zone
1473 </a>
1474+ {% endif %}
1475 {% endif %}
1476 {% endblock %}
1477
1478@@ -29,14 +31,20 @@
1479 <h4>Name</h4>
1480 <span>{{ zone.name }}</span>
1481 </li>
1482- <li class="block size1">
1483+ <li class="block size3">
1484 <h4>Nodes</h4>
1485 <span id="#nodecount">
1486 {{ zone.node_set.count }}
1487 (<a href="{{ node_list_link }}">view</a>)
1488 </span>
1489 </li>
1490- <li class="block size12 first">
1491+ <li class="block size3">
1492+ <h4>Default</h4>
1493+ <span id="#default">
1494+ {% if zone.default %}Yes{% else %}No{% endif %}
1495+ </span>
1496+ </li>
1497+ <li class="block size12 first">
1498 {% if zone.description %}
1499 <h4>Description</h4>
1500 <pre>{{ zone.description }}</pre>
1501
1502=== modified file 'src/maasserver/templates/maasserver/zone_list.html'
1503--- src/maasserver/templates/maasserver/zone_list.html 2013-12-20 13:05:55 +0000
1504+++ src/maasserver/templates/maasserver/zone_list.html 2014-01-16 08:48:57 +0000
1505@@ -15,6 +15,7 @@
1506 <tr>
1507 <th>Name</th>
1508 <th>Description</th>
1509+ <th>Default</th>
1510 <th>Nodes</th>
1511 {% if user.is_superuser %}
1512 <th></th>
1513@@ -31,12 +32,14 @@
1514 </a>
1515 </td>
1516 <td>{{ zone_item.description|truncatechars:40 }}</td>
1517+ <td>{% if zone_item.is_default %}Yes{% endif %}</td>
1518 <td>
1519 {{ zone_item.node_set.count }}
1520 (<a href="{% url 'node-list' %}?query=zone%3D{{ zone_item.name }}">view</a>)
1521 </td>
1522 {% if user.is_superuser %}
1523 <td>
1524+ {% if not zone_item.is_default %}
1525 <a href="{% url 'zone-edit' zone_item.name %}"
1526 title="Edit zone {{ zone_item.name }}"
1527 class="icon">
1528@@ -55,6 +58,7 @@
1529 <input type="hidden" name="name"
1530 value="{{ zone_item.name }}" />
1531 </form>
1532+ {% endif %}
1533 </td>
1534 {% endif %}
1535 </tr>
1536
1537=== modified file 'src/maasserver/testing/factory.py'
1538--- src/maasserver/testing/factory.py 2014-01-16 02:59:03 +0000
1539+++ src/maasserver/testing/factory.py 2014-01-16 08:48:57 +0000
1540@@ -149,7 +149,7 @@
1541
1542 def make_node(self, mac=False, hostname=None, status=None,
1543 architecture=ARCHITECTURE.i386, updated=None,
1544- created=None, nodegroup=None, routers=None, zone=NO_VALUE,
1545+ created=None, nodegroup=None, routers=None, zone=None,
1546 **kwargs):
1547 # hostname=None is a valid value, hence the set_hostname trick.
1548 if hostname is None:
1549@@ -160,7 +160,7 @@
1550 nodegroup = self.make_node_group()
1551 if routers is None:
1552 routers = [self.make_MAC()]
1553- if zone == NO_VALUE:
1554+ if zone is None:
1555 zone = self.make_zone()
1556 node = Node(
1557 hostname=hostname, status=status, architecture=architecture,
1558
1559=== modified file 'src/maasserver/testing/tests/test_factory.py'
1560--- src/maasserver/testing/tests/test_factory.py 2013-12-18 17:35:45 +0000
1561+++ src/maasserver/testing/tests/test_factory.py 2014-01-16 08:48:57 +0000
1562@@ -71,12 +71,6 @@
1563 self.assertEqual(
1564 nodegroup, factory.make_node(nodegroup=nodegroup).nodegroup)
1565
1566- def test_make_node_sets_zone_by_default(self):
1567- self.assertIsNotNone(factory.make_node().zone)
1568-
1569- def test_make_node_sets_no_zone_if_zone_is_None(self):
1570- self.assertIsNone(factory.make_node(zone=None).zone)
1571-
1572 def test_make_zone_returns_physical_zone(self):
1573 self.assertIsNotNone(factory.make_zone())
1574
1575
1576=== modified file 'src/maasserver/tests/test_api_node.py'
1577--- src/maasserver/tests/test_api_node.py 2014-01-16 02:57:53 +0000
1578+++ src/maasserver/tests/test_api_node.py 2014-01-16 08:48:57 +0000
1579@@ -703,6 +703,19 @@
1580 node = reload_object(node)
1581 self.assertEqual(new_zone, node.zone)
1582
1583+ def test_PUT_does_not_set_zone_if_not_present(self):
1584+ self.become_admin()
1585+ new_name = factory.make_name()
1586+ node = factory.make_node()
1587+ old_zone = node.zone
1588+
1589+ response = self.client_put(
1590+ self.get_node_uri(node), {'hostname': new_name})
1591+
1592+ self.assertEqual(httplib.OK, response.status_code)
1593+ node = reload_object(node)
1594+ self.assertEqual((old_zone, new_name), (node.zone, node.hostname))
1595+
1596 #@skip(
1597 # "XXX: JeroenVermeulen 2013-12-11 bug=1259872: Clearing the zone "
1598 # "field does not work..")
1599
1600=== modified file 'src/maasserver/tests/test_api_nodes.py'
1601--- src/maasserver/tests/test_api_nodes.py 2013-12-18 02:36:56 +0000
1602+++ src/maasserver/tests/test_api_nodes.py 2014-01-16 08:48:57 +0000
1603@@ -1,4 +1,4 @@
1604-# Copyright 2013 Canonical Ltd. This software is licensed under the
1605+# Copyright 2013-2014 Canonical Ltd. This software is licensed under the
1606 # GNU Affero General Public License version 3 (see the file LICENSE).
1607
1608 """Tests for the nodes API."""
1609@@ -1044,19 +1044,6 @@
1610 node = reload_object(node)
1611 self.assertEqual(zone, node.zone)
1612
1613- def test_POST_set_zone_clears_zone_on_nodes(self):
1614- self.become_admin()
1615- node = factory.make_node()
1616- response = self.client.post(
1617- reverse('nodes_handler'),
1618- {
1619- 'op': 'set_zone',
1620- 'nodes': [node.system_id],
1621- })
1622- self.assertEqual(httplib.OK, response.status_code)
1623- node = reload_object(node)
1624- self.assertIsNone(node.zone)
1625-
1626 def test_POST_set_zone_does_not_affect_other_nodes(self):
1627 self.become_admin()
1628 node = factory.make_node()
1629
1630=== modified file 'src/maasserver/tests/test_api_zone.py'
1631--- src/maasserver/tests/test_api_zone.py 2013-12-18 17:35:45 +0000
1632+++ src/maasserver/tests/test_api_zone.py 2014-01-16 08:48:57 +0000
1633@@ -1,4 +1,4 @@
1634-# Copyright 2013 Canonical Ltd. This software is licensed under the
1635+# Copyright 2013-2014 Canonical Ltd. This software is licensed under the
1636 # GNU Affero General Public License version 3 (see the file LICENSE).
1637
1638 """Tests for physical `Zone` API."""
1639@@ -18,6 +18,8 @@
1640 import json
1641
1642 from django.core.urlresolvers import reverse
1643+from maasserver.models import Zone
1644+from maasserver.models.zone import DEFAULT_ZONE_NAME
1645 from maasserver.testing import reload_object
1646 from maasserver.testing.api import APITestCase
1647 from maasserver.testing.factory import factory
1648@@ -85,6 +87,17 @@
1649 zone = reload_object(zone)
1650 self.assertEqual(new_name, zone.name)
1651
1652+ def test_PUT_rejects_change_of_default_zone_name(self):
1653+ self.become_admin()
1654+ zone = Zone.objects.get_default_zone()
1655+
1656+ response = self.client_put(
1657+ get_zone_uri(zone),
1658+ {'name': factory.make_name('zone')})
1659+ self.assertEqual(httplib.BAD_REQUEST, response.status_code)
1660+ zone = reload_object(zone)
1661+ self.assertEqual(DEFAULT_ZONE_NAME, zone.name)
1662+
1663 def test_PUT_changing_name_maintains_foreign_keys(self):
1664 self.become_admin()
1665 zone = factory.make_zone()
1666@@ -106,12 +119,33 @@
1667 self.assertEqual(httplib.NO_CONTENT, response.status_code)
1668 self.assertIsNone(reload_object(zone))
1669
1670+ def test_DELETE_rejects_deletion_of_default_zone(self):
1671+ self.become_admin()
1672+ response = self.client.delete(
1673+ get_zone_uri(Zone.objects.get_default_zone()))
1674+ self.assertEqual(httplib.BAD_REQUEST, response.status_code)
1675+ self.assertIsNotNone(Zone.objects.get_default_zone())
1676+
1677 def test_DELETE_requires_admin(self):
1678 zone = factory.make_zone()
1679 response = self.client.delete(get_zone_uri(zone))
1680 self.assertEqual(httplib.FORBIDDEN, response.status_code)
1681
1682- def test_DELETE_clears_foreign_keys(self):
1683+ def test_DELETE_cannot_delete_default_zone(self):
1684+ self.become_admin()
1685+ zone = Zone.objects.get_default_zone()
1686+
1687+ response = self.client.delete(get_zone_uri(zone))
1688+
1689+ self.assertEqual(
1690+ (
1691+ httplib.BAD_REQUEST,
1692+ "This zone is the default zone, it cannot be deleted.",
1693+ ),
1694+ (response.status_code, response.content))
1695+
1696+ def test_DELETE_sets_foreign_keys_to_default(self):
1697+ default_zone = Zone.objects.get_default_zone()
1698 self.become_admin()
1699 zone = factory.make_zone()
1700 node = factory.make_node(zone=zone)
1701@@ -121,7 +155,7 @@
1702
1703 node = reload_object(node)
1704 self.assertIsNotNone(node)
1705- self.assertIsNone(node.zone)
1706+ self.assertEquals(default_zone, node.zone)
1707
1708 def test_DELETE_is_idempotent(self):
1709 self.become_admin()
1710
1711=== modified file 'src/maasserver/tests/test_api_zones.py'
1712--- src/maasserver/tests/test_api_zones.py 2013-12-18 17:35:45 +0000
1713+++ src/maasserver/tests/test_api_zones.py 2014-01-16 08:48:57 +0000
1714@@ -58,7 +58,8 @@
1715 httplib.FORBIDDEN, response.status_code, response.content)
1716
1717 def test_list_returns_zone_list(self):
1718- zones = [factory.make_zone() for i in range(3)]
1719+ [factory.make_zone() for i in range(3)]
1720+ zones = Zone.objects.all()
1721 response = self.client.get(
1722 reverse('zones_handler'),
1723 {})
1724@@ -77,7 +78,8 @@
1725 for zone in parsed_result])
1726
1727 def test_list_returns_sorted_zone_list(self):
1728- zones = [factory.make_zone() for i in range(10)]
1729+ [factory.make_zone() for i in range(10)]
1730+ zones = Zone.objects.all()
1731 response = self.client.get(
1732 reverse('zones_handler'),
1733 {})
1734@@ -85,5 +87,9 @@
1735 parsed_result = json.loads(response.content)
1736 # Sorting is case-insensitive.
1737 self.assertEqual(
1738- sorted([zone.name for zone in zones], key=lambda s: s.lower()),
1739+ sorted(
1740+ [
1741+ zone.name
1742+ for zone in zones
1743+ ], key=lambda s: s.lower()),
1744 [zone.get('name') for zone in parsed_result])
1745
1746=== modified file 'src/maasserver/tests/test_forms.py'
1747--- src/maasserver/tests/test_forms.py 2014-01-14 00:41:50 +0000
1748+++ src/maasserver/tests/test_forms.py 2014-01-16 08:48:57 +0000
1749@@ -1,4 +1,4 @@
1750-# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
1751+# Copyright 2012-2014 Canonical Ltd. This software is licensed under the
1752 # GNU Affero General Public License version 3 (see the file LICENSE).
1753
1754 """Test forms."""
1755@@ -55,6 +55,7 @@
1756 remove_None_values,
1757 UnconstrainedMultipleChoiceField,
1758 ValidatorMultipleChoiceField,
1759+ ZoneForm,
1760 )
1761 from maasserver.models import (
1762 Config,
1763@@ -62,6 +63,7 @@
1764 Node,
1765 NodeGroup,
1766 NodeGroupInterface,
1767+ Zone,
1768 )
1769 from maasserver.models.config import DEFAULT_CONFIG
1770 from maasserver.node_action import (
1771@@ -447,17 +449,6 @@
1772 # The form saved without error, but the nodegroup change was ignored.
1773 self.assertEqual(old_nodegroup, node.nodegroup)
1774
1775- def test_AdminForm_sets_zone_initial_value(self):
1776- zone = factory.make_zone()
1777- node = factory.make_node(zone=zone)
1778- form = AdminNodeForm(instance=node)
1779- self.assertEqual(zone.name, form.initial['zone'])
1780-
1781- def test_AdminForm_sets_zone_initial_empty_value(self):
1782- node = factory.make_node(zone=None)
1783- form = AdminNodeForm(instance=node)
1784- self.assertEqual('', form.initial['zone'])
1785-
1786 def test_get_node_edit_form_returns_NodeForm_if_non_admin(self):
1787 user = factory.make_user()
1788 self.assertEqual(NodeForm, get_node_edit_form(user))
1789@@ -1195,21 +1186,7 @@
1790 "set_zone is not one of the available choices.",
1791 form._errors['action'])
1792
1793- def test_set_zone_can_set_no_zone(self):
1794- node = factory.make_node()
1795- form = BulkNodeActionForm(
1796- user=factory.make_admin(),
1797- data={
1798- 'action': 'set_zone',
1799- 'zone': None,
1800- 'system_id': [node.system_id],
1801- })
1802- self.assertTrue(form.is_valid(), form._errors)
1803- self.assertEqual((1, 0, 0), form.save())
1804- node = reload_object(node)
1805- self.assertIsNone(node.zone)
1806-
1807- def test_set_zone_leaves_unselected_zones_alone(self):
1808+ def test_set_zone_leaves_unselected_nodes_alone(self):
1809 unselected_node = factory.make_node()
1810 original_zone = unselected_node.zone
1811 form = BulkNodeActionForm(
1812@@ -1276,3 +1253,52 @@
1813 self.assertIsNone(
1814 DownloadProgressForm.get_download(
1815 factory.make_node_group(), factory.getRandomString(), 1))
1816+
1817+
1818+class TestZoneForm(MAASServerTestCase):
1819+ """Tests for `ZoneForm`."""
1820+
1821+ def test_creates_zone(self):
1822+ name = factory.make_name('zone')
1823+ description = factory.getRandomString()
1824+ form = ZoneForm(data={'name': name, 'description': description})
1825+ form.save()
1826+ zone = Zone.objects.get(name=name)
1827+ self.assertIsNotNone(zone)
1828+ self.assertEqual(description, zone.description)
1829+
1830+ def test_updates_zone(self):
1831+ zone = factory.make_zone()
1832+ new_description = factory.getRandomString()
1833+ form = ZoneForm(data={'description': new_description}, instance=zone)
1834+ form.save()
1835+ zone = reload_object(zone)
1836+ self.assertEqual(new_description, zone.description)
1837+
1838+ def test_renames_zone(self):
1839+ zone = factory.make_zone()
1840+ new_name = factory.make_name('zone')
1841+ form = ZoneForm(data={'name': new_name}, instance=zone)
1842+ form.save()
1843+ zone = reload_object(zone)
1844+ self.assertEqual(new_name, zone.name)
1845+ self.assertEqual(zone, Zone.objects.get(name=new_name))
1846+
1847+ def test_updates_default_zone_description_works(self):
1848+ zone = Zone.objects.get_default_zone()
1849+ new_description = factory.getRandomString()
1850+ form = ZoneForm(data={'description': new_description}, instance=zone)
1851+ self.assertTrue(form.is_valid(), form._errors)
1852+ form.save()
1853+ zone = reload_object(zone)
1854+ self.assertEqual(new_description, zone.description)
1855+
1856+ def test_disallows_renaming_default_zone(self):
1857+ zone = Zone.objects.get_default_zone()
1858+ form = ZoneForm(
1859+ data={'name': factory.make_name('zone')},
1860+ instance=zone)
1861+ self.assertFalse(form.is_valid())
1862+ self.assertEqual(
1863+ {'name': ["This zone is the default zone, it cannot be renamed."]},
1864+ form.errors)
1865
1866=== modified file 'src/maasserver/tests/test_node_constraint_filter_forms.py'
1867--- src/maasserver/tests/test_node_constraint_filter_forms.py 2013-12-18 17:35:45 +0000
1868+++ src/maasserver/tests/test_node_constraint_filter_forms.py 2014-01-16 08:48:57 +0000
1869@@ -285,7 +285,7 @@
1870
1871 def test_not_in_zone_excludes_given_zones(self):
1872 ineligible_nodes = [factory.make_node() for _ in range(2)]
1873- eligible_nodes = [factory.make_node(), factory.make_node(zone=None)]
1874+ eligible_nodes = [factory.make_node() for _ in range(2)]
1875 self.assertConstrainedNodes(
1876 eligible_nodes,
1877 {'not_in_zone': [node.zone.name for node in ineligible_nodes]})
1878
1879=== modified file 'src/maasserver/tests/test_views_nodes.py'
1880--- src/maasserver/tests/test_views_nodes.py 2014-01-14 00:50:25 +0000
1881+++ src/maasserver/tests/test_views_nodes.py 2014-01-16 08:48:57 +0000
1882@@ -1,4 +1,4 @@
1883-# Copyright 2012, 2013 Canonical Ltd. This software is licensed under the
1884+# Copyright 2012-2014 Canonical Ltd. This software is licensed under the
1885 # GNU Affero General Public License version 3 (see the file LICENSE).
1886
1887 """Test maasserver nodes views."""
1888@@ -275,12 +275,6 @@
1889 [zone_link],
1890 get_content_links(response, '.zone-column'))
1891
1892- def test_node_list_shows_placeholder_for_zone_if_none_set(self):
1893- factory.make_node(zone=None)
1894- response = self.client.get(reverse('node-list'))
1895- [zone_field] = fromstring(response.content).cssselect('.zone-column')
1896- self.assertEqual("(Default)", zone_field.text_content().strip())
1897-
1898 def test_node_list_displays_sorted_list_of_nodes(self):
1899 # Nodes are sorted on the node list page, newest first.
1900 nodes = [factory.make_node() for i in range(3)]
1901@@ -456,16 +450,6 @@
1902 [reverse('zone-view', args=[node.zone.name])],
1903 get_content_links(response, '#zone'))
1904
1905- def test_view_node_shows_no_physical_zone_if_not_set(self):
1906- node = factory.make_node(zone=None)
1907- node_link = reverse('node-view', args=[node.system_id])
1908-
1909- response = self.client.get(node_link)
1910- self.assertEqual(httplib.OK, response.status_code)
1911-
1912- doc = fromstring(response.content)
1913- self.assertEqual([], doc.cssselect('#zone'))
1914-
1915 def test_view_node_displays_link_to_edit_if_user_owns_node(self):
1916 node = factory.make_node(owner=self.logged_in_user)
1917 node_link = reverse('node-view', args=[node.system_id])
1918
1919=== modified file 'src/maasserver/tests/test_views_zones.py'
1920--- src/maasserver/tests/test_views_zones.py 2013-12-20 13:05:55 +0000
1921+++ src/maasserver/tests/test_views_zones.py 2014-01-16 08:48:57 +0000
1922@@ -1,4 +1,4 @@
1923-# Copyright 2013 Canonical Ltd. This software is licensed under the
1924+# Copyright 2013-2014 Canonical Ltd. This software is licensed under the
1925 # GNU Affero General Public License version 3 (see the file LICENSE).
1926
1927 """Test maasserver zones views."""
1928@@ -18,9 +18,11 @@
1929 import httplib
1930 from urllib import urlencode
1931
1932+from django.core.exceptions import ValidationError
1933 from django.core.urlresolvers import reverse
1934 from lxml.html import fromstring
1935 from maasserver.models import Zone
1936+from maasserver.models.zone import DEFAULT_ZONE_NAME
1937 from maasserver.testing import (
1938 extract_redirect,
1939 get_content_links,
1940@@ -54,7 +56,8 @@
1941
1942 def test_zone_list_displays_zone_details(self):
1943 # Zone listing displays the zone name and the zone description.
1944- zones = [factory.make_zone() for i in range(3)]
1945+ [factory.make_zone() for i in range(3)]
1946+ zones = Zone.objects.all()
1947 response = self.client.get(reverse('zone-list'))
1948 zone_names = [zone.name for zone in zones]
1949 truncated_zone_descriptions = [
1950@@ -65,7 +68,8 @@
1951
1952 def test_zone_list_displays_sorted_list_of_zones(self):
1953 # Zones are alphabetically sorted on the zone list page.
1954- zones = [factory.make_zone() for i in range(3)]
1955+ [factory.make_zone() for i in range(3)]
1956+ zones = Zone.objects.all()
1957 sorted_zones = sorted(zones, key=lambda x: x.name.lower())
1958 response = self.client.get(reverse('zone-list'))
1959 zone_links = [
1960@@ -77,7 +81,8 @@
1961 if link.startswith('/zones/')])
1962
1963 def test_zone_list_displays_links_to_zone_node(self):
1964- zones = [factory.make_zone() for i in range(3)]
1965+ [factory.make_zone() for i in range(3)]
1966+ zones = Zone.objects.all()
1967 sorted_zones = sorted(zones, key=lambda x: x.name.lower())
1968 response = self.client.get(reverse('zone-list'))
1969 zone_node_links = [
1970@@ -117,14 +122,21 @@
1971
1972 def test_zone_list_contains_edit_links(self):
1973 zones = [factory.make_zone() for i in range(3)]
1974- response = self.client.get(reverse('zone-list'))
1975+ default_zone = Zone.objects.get_default_zone()
1976 zone_edit_links = [
1977 reverse('zone-edit', args=[zone.name]) for zone in zones]
1978 zone_delete_links = [
1979 reverse('zone-del', args=[zone.name]) for zone in zones]
1980+ zone_default_edit = reverse('zone-edit', args=[default_zone])
1981+ zone_default_delete = reverse('zone-del', args=[default_zone])
1982+
1983+ response = self.client.get(reverse('zone-list'))
1984 all_links = get_content_links(response)
1985- self.assertThat(all_links, ContainsAll(zone_edit_links))
1986- self.assertThat(all_links, ContainsAll(zone_delete_links))
1987+
1988+ self.assertThat(all_links, ContainsAll(
1989+ zone_edit_links + zone_delete_links))
1990+ self.assertThat(all_links, Not(Contains(zone_default_edit)))
1991+ self.assertThat(all_links, Not(Contains(zone_default_delete)))
1992
1993 def test_zone_list_contains_add_link(self):
1994 response = self.client.get(reverse('zone-list'))
1995@@ -231,6 +243,12 @@
1996 zone_delete_link = reverse('zone-del', args=[zone.name])
1997 self.assertIn(zone_delete_link, get_content_links(response))
1998
1999+ def test_zone_detail_for_default_zone_does_not_contain_delete_link(self):
2000+ response = self.client.get(
2001+ reverse('zone-view', args=[DEFAULT_ZONE_NAME]))
2002+ zone_delete_link = reverse('zone-del', args=[DEFAULT_ZONE_NAME])
2003+ self.assertNotIn(zone_delete_link, get_content_links(response))
2004+
2005
2006 class ZoneEditNonAdminTest(LoggedInTestCase):
2007
2008@@ -281,6 +299,28 @@
2009 self.assertEqual(httplib.FOUND, response.status_code)
2010 self.assertIsNone(reload_object(zone))
2011
2012+ def test_rejects_deletion_of_default_zone(self):
2013+ try:
2014+ self.client.post(
2015+ reverse('zone-del', args=[DEFAULT_ZONE_NAME]),
2016+ {'post': 'yes'})
2017+ except ValidationError:
2018+ # XXX: Right now, this generates an error because the deletion
2019+ # is prevented in the model code and not at the form level.
2020+ # This is not so bad because we make sure that the deletion link
2021+ # for the default zone isn't showed anywhere.
2022+ # Still, ideally, once the validation happens at the form level,
2023+ # this try/except statement should be removed and this test
2024+ # should be extended further to check that the reponse to
2025+ # the above self.client.post indicates a failure to validate
2026+ # the data.
2027+ pass
2028+
2029+ # TODO: check that the page failed to validate the data.
2030+
2031+ # The default zone is still there.
2032+ self.assertIsNotNone(Zone.objects.get_default_zone())
2033+
2034 def test_redirects_to_listing(self):
2035 zone = factory.make_zone()
2036 response = self.client.post(
2037@@ -298,4 +338,4 @@
2038 self.assertIsNone(reload_object(zone))
2039 node = reload_object(node)
2040 self.assertIsNotNone(node)
2041- self.assertIsNone(node.zone)
2042+ self.assertEqual(Zone.objects.get_default_zone(), node.zone)
2043
2044=== modified file 'src/maasserver/views/zones.py'
2045--- src/maasserver/views/zones.py 2013-12-18 17:35:45 +0000
2046+++ src/maasserver/views/zones.py 2014-01-16 08:48:57 +0000
2047@@ -82,6 +82,7 @@
2048 """View for editing a physical zone."""
2049
2050 model = Zone
2051+ form_class = ZoneForm
2052 template_name = 'maasserver/zone_edit.html'
2053
2054 def get_object(self):
2055@@ -95,7 +96,7 @@
2056 class ZoneDelete(HelpfulDeleteView):
2057 """View for deleting a physical zone."""
2058
2059- template_name = 'maasserver/zone_configm_delete.html'
2060+ template_name = 'maasserver/zone_confirm_delete.html'
2061 context_object_name = 'zone_to_delete'
2062 model = Zone
2063