Merge lp:~rvb/maas/ui-update-fqdn-1.2 into lp:maas/1.2
- ui-update-fqdn-1.2
- Merge into 1.2
Proposed by
Raphaël Badin
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Raphaël Badin | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 1303 | ||||
Proposed branch: | lp:~rvb/maas/ui-update-fqdn-1.2 | ||||
Merge into: | lp:maas/1.2 | ||||
Diff against target: |
881 lines (+505/-58) 18 files modified
src/maasserver/forms.py (+9/-0) src/maasserver/migrations/0043_unique_hostname_preparation.py (+218/-0) src/maasserver/migrations/0044_node_hostname_unique.py (+201/-0) src/maasserver/models/node.py (+2/-1) src/maasserver/templates/maasserver/node_edit.html (+2/-0) src/maasserver/templates/maasserver/node_view.html (+4/-4) src/maasserver/templates/maasserver/nodes_listing.html (+9/-6) src/maasserver/testing/factory.py (+3/-3) src/maasserver/tests/test_api.py (+15/-7) src/maasserver/tests/test_dhcplease.py (+3/-3) src/maasserver/tests/test_dns.py (+1/-1) src/maasserver/tests/test_forms.py (+0/-12) src/maasserver/tests/test_node.py (+2/-3) src/maasserver/tests/test_node_constraint_filter.py (+2/-2) src/maasserver/tests/test_views_nodes.py (+18/-1) src/maasserver/tests/test_views_tags.py (+10/-14) src/maasserver/views/nodes.py (+2/-0) src/maasserver/views/tags.py (+4/-1) |
||||
To merge this branch: | bzr merge lp:~rvb/maas/ui-update-fqdn-1.2 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+134663@code.launchpad.net |
Commit message
Backport of 1335 (and required db migration merged in revision 1330).
Description of the change
Backport of 1335 (and required db migration merged in revision 1330).
To post a comment you must log in.
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 2012-11-16 07:43:42 +0000 | |||
3 | +++ src/maasserver/forms.py 2012-11-16 13:58:29 +0000 | |||
4 | @@ -152,6 +152,15 @@ | |||
5 | 152 | initial=ARCHITECTURE.i386, | 152 | initial=ARCHITECTURE.i386, |
6 | 153 | error_messages={'invalid_choice': INVALID_ARCHITECTURE_MESSAGE}) | 153 | error_messages={'invalid_choice': INVALID_ARCHITECTURE_MESSAGE}) |
7 | 154 | 154 | ||
8 | 155 | hostname = forms.CharField( | ||
9 | 156 | label="Host name", required=False, help_text=( | ||
10 | 157 | "The FQDN (Fully Qualified Domain Name) is derived from the " | ||
11 | 158 | "host name: If the cluster controller for this node is managing " | ||
12 | 159 | "DNS then the domain part in the host name (if any) is replaced " | ||
13 | 160 | "by the domain defined on the cluster; if the cluster controller " | ||
14 | 161 | "does not manage DNS, then the host name as entered will be the " | ||
15 | 162 | "FQDN.")) | ||
16 | 163 | |||
17 | 155 | class Meta: | 164 | class Meta: |
18 | 156 | model = Node | 165 | model = Node |
19 | 157 | 166 | ||
20 | 158 | 167 | ||
21 | === added file 'src/maasserver/migrations/0043_unique_hostname_preparation.py' | |||
22 | --- src/maasserver/migrations/0043_unique_hostname_preparation.py 1970-01-01 00:00:00 +0000 | |||
23 | +++ src/maasserver/migrations/0043_unique_hostname_preparation.py 2012-11-16 13:58:29 +0000 | |||
24 | @@ -0,0 +1,218 @@ | |||
25 | 1 | # -*- coding: utf-8 -*- | ||
26 | 2 | import datetime | ||
27 | 3 | |||
28 | 4 | from django.db import models | ||
29 | 5 | from django.db.models import Count | ||
30 | 6 | from south.db import db | ||
31 | 7 | from south.v2 import DataMigration | ||
32 | 8 | |||
33 | 9 | |||
34 | 10 | class Migration(DataMigration): | ||
35 | 11 | |||
36 | 12 | def forwards(self, orm): | ||
37 | 13 | # Find the nodes with duplicated hostnames. | ||
38 | 14 | duplicated_hostnames = orm['maasserver.node'].objects.values_list( | ||
39 | 15 | 'hostname', flat=True).annotate( | ||
40 | 16 | hostname_count=Count('hostname')).exclude(hostname_count=1) | ||
41 | 17 | # Rename the nodes with duplicated hostnames. | ||
42 | 18 | for duplicated_hostname in duplicated_hostnames: | ||
43 | 19 | nodes_with_duplicated_hostnames = ( | ||
44 | 20 | orm['maasserver.node'].objects.filter( | ||
45 | 21 | hostname=duplicated_hostname)) | ||
46 | 22 | # Rename all the nodes but one. | ||
47 | 23 | for node in nodes_with_duplicated_hostnames[1:]: | ||
48 | 24 | number = 1 | ||
49 | 25 | while True: | ||
50 | 26 | new_hostname = '%s-%d' % (node.hostname, number) | ||
51 | 27 | if not orm['maasserver.node'].objects.filter(hostname=new_hostname).exists(): | ||
52 | 28 | node.hostname = new_hostname | ||
53 | 29 | node.save() | ||
54 | 30 | break | ||
55 | 31 | number += 1 | ||
56 | 32 | |||
57 | 33 | |||
58 | 34 | def backwards(self, orm): | ||
59 | 35 | pass | ||
60 | 36 | |||
61 | 37 | models = { | ||
62 | 38 | 'auth.group': { | ||
63 | 39 | 'Meta': {'object_name': 'Group'}, | ||
64 | 40 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
65 | 41 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), | ||
66 | 42 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) | ||
67 | 43 | }, | ||
68 | 44 | 'auth.permission': { | ||
69 | 45 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, | ||
70 | 46 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
71 | 47 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), | ||
72 | 48 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
73 | 49 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | ||
74 | 50 | }, | ||
75 | 51 | 'auth.user': { | ||
76 | 52 | 'Meta': {'object_name': 'User'}, | ||
77 | 53 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
78 | 54 | 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), | ||
79 | 55 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
80 | 56 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), | ||
81 | 57 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
82 | 58 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
83 | 59 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
84 | 60 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
85 | 61 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
86 | 62 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
87 | 63 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | ||
88 | 64 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), | ||
89 | 65 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) | ||
90 | 66 | }, | ||
91 | 67 | 'contenttypes.contenttype': { | ||
92 | 68 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, | ||
93 | 69 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
94 | 70 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
95 | 71 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
96 | 72 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) | ||
97 | 73 | }, | ||
98 | 74 | u'maasserver.bootimage': { | ||
99 | 75 | 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, | ||
100 | 76 | 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
101 | 77 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
102 | 78 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), | ||
103 | 79 | 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
104 | 80 | 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
105 | 81 | 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) | ||
106 | 82 | }, | ||
107 | 83 | u'maasserver.componenterror': { | ||
108 | 84 | 'Meta': {'object_name': 'ComponentError'}, | ||
109 | 85 | 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), | ||
110 | 86 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
111 | 87 | 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), | ||
112 | 88 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
113 | 89 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
114 | 90 | }, | ||
115 | 91 | u'maasserver.config': { | ||
116 | 92 | 'Meta': {'object_name': 'Config'}, | ||
117 | 93 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
118 | 94 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
119 | 95 | 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) | ||
120 | 96 | }, | ||
121 | 97 | u'maasserver.dhcplease': { | ||
122 | 98 | 'Meta': {'object_name': 'DHCPLease'}, | ||
123 | 99 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
124 | 100 | 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), | ||
125 | 101 | 'mac': ('maasserver.fields.MACAddressField', [], {}), | ||
126 | 102 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) | ||
127 | 103 | }, | ||
128 | 104 | u'maasserver.filestorage': { | ||
129 | 105 | 'Meta': {'object_name': 'FileStorage'}, | ||
130 | 106 | 'content': ('metadataserver.fields.BinaryField', [], {}), | ||
131 | 107 | 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), | ||
132 | 108 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) | ||
133 | 109 | }, | ||
134 | 110 | u'maasserver.macaddress': { | ||
135 | 111 | 'Meta': {'object_name': 'MACAddress'}, | ||
136 | 112 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
137 | 113 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
138 | 114 | 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), | ||
139 | 115 | 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), | ||
140 | 116 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
141 | 117 | }, | ||
142 | 118 | u'maasserver.node': { | ||
143 | 119 | 'Meta': {'object_name': 'Node'}, | ||
144 | 120 | 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
145 | 121 | 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), | ||
146 | 122 | 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
147 | 123 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
148 | 124 | 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), | ||
149 | 125 | 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
150 | 126 | 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), | ||
151 | 127 | 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
152 | 128 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
153 | 129 | 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
154 | 130 | 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
155 | 131 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), | ||
156 | 132 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), | ||
157 | 133 | 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), | ||
158 | 134 | 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), | ||
159 | 135 | 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), | ||
160 | 136 | 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-6b5652fe-2412-11e2-9eff-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}), | ||
161 | 137 | 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), | ||
162 | 138 | 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), | ||
163 | 139 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
164 | 140 | }, | ||
165 | 141 | u'maasserver.nodegroup': { | ||
166 | 142 | 'Meta': {'object_name': 'NodeGroup'}, | ||
167 | 143 | 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), | ||
168 | 144 | 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), | ||
169 | 145 | 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), | ||
170 | 146 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
171 | 147 | 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
172 | 148 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
173 | 149 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), | ||
174 | 150 | 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
175 | 151 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | ||
176 | 152 | 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) | ||
177 | 153 | }, | ||
178 | 154 | u'maasserver.nodegroupinterface': { | ||
179 | 155 | 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, | ||
180 | 156 | 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
181 | 157 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
182 | 158 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
183 | 159 | 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
184 | 160 | 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), | ||
185 | 161 | 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
186 | 162 | 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
187 | 163 | 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
188 | 164 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), | ||
189 | 165 | 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
190 | 166 | 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
191 | 167 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
192 | 168 | }, | ||
193 | 169 | u'maasserver.sshkey': { | ||
194 | 170 | 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, | ||
195 | 171 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
196 | 172 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
197 | 173 | 'key': ('django.db.models.fields.TextField', [], {}), | ||
198 | 174 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | ||
199 | 175 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) | ||
200 | 176 | }, | ||
201 | 177 | u'maasserver.tag': { | ||
202 | 178 | 'Meta': {'object_name': 'Tag'}, | ||
203 | 179 | 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), | ||
204 | 180 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
205 | 181 | 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}), | ||
206 | 182 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
207 | 183 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), | ||
208 | 184 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
209 | 185 | }, | ||
210 | 186 | u'maasserver.userprofile': { | ||
211 | 187 | 'Meta': {'object_name': 'UserProfile'}, | ||
212 | 188 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
213 | 189 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) | ||
214 | 190 | }, | ||
215 | 191 | 'piston.consumer': { | ||
216 | 192 | 'Meta': {'object_name': 'Consumer'}, | ||
217 | 193 | 'description': ('django.db.models.fields.TextField', [], {}), | ||
218 | 194 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
219 | 195 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), | ||
220 | 196 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
221 | 197 | 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), | ||
222 | 198 | 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), | ||
223 | 199 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) | ||
224 | 200 | }, | ||
225 | 201 | 'piston.token': { | ||
226 | 202 | 'Meta': {'object_name': 'Token'}, | ||
227 | 203 | 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), | ||
228 | 204 | 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
229 | 205 | 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), | ||
230 | 206 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
231 | 207 | 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
232 | 208 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), | ||
233 | 209 | 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), | ||
234 | 210 | 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1351767230L'}), | ||
235 | 211 | 'token_type': ('django.db.models.fields.IntegerField', [], {}), | ||
236 | 212 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), | ||
237 | 213 | 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) | ||
238 | 214 | } | ||
239 | 215 | } | ||
240 | 216 | |||
241 | 217 | complete_apps = ['maasserver'] | ||
242 | 218 | symmetrical = True | ||
243 | 0 | 219 | ||
244 | === added file 'src/maasserver/migrations/0044_node_hostname_unique.py' | |||
245 | --- src/maasserver/migrations/0044_node_hostname_unique.py 1970-01-01 00:00:00 +0000 | |||
246 | +++ src/maasserver/migrations/0044_node_hostname_unique.py 2012-11-16 13:58:29 +0000 | |||
247 | @@ -0,0 +1,201 @@ | |||
248 | 1 | # -*- coding: utf-8 -*- | ||
249 | 2 | import datetime | ||
250 | 3 | |||
251 | 4 | from django.db import models | ||
252 | 5 | from south.db import db | ||
253 | 6 | from south.v2 import SchemaMigration | ||
254 | 7 | |||
255 | 8 | |||
256 | 9 | class Migration(SchemaMigration): | ||
257 | 10 | |||
258 | 11 | def forwards(self, orm): | ||
259 | 12 | # Adding unique constraint on 'Node', fields ['hostname'] | ||
260 | 13 | db.create_unique(u'maasserver_node', ['hostname']) | ||
261 | 14 | |||
262 | 15 | |||
263 | 16 | def backwards(self, orm): | ||
264 | 17 | # Removing unique constraint on 'Node', fields ['hostname'] | ||
265 | 18 | db.delete_unique(u'maasserver_node', ['hostname']) | ||
266 | 19 | |||
267 | 20 | |||
268 | 21 | models = { | ||
269 | 22 | 'auth.group': { | ||
270 | 23 | 'Meta': {'object_name': 'Group'}, | ||
271 | 24 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
272 | 25 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), | ||
273 | 26 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) | ||
274 | 27 | }, | ||
275 | 28 | 'auth.permission': { | ||
276 | 29 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, | ||
277 | 30 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
278 | 31 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), | ||
279 | 32 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
280 | 33 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | ||
281 | 34 | }, | ||
282 | 35 | 'auth.user': { | ||
283 | 36 | 'Meta': {'object_name': 'User'}, | ||
284 | 37 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
285 | 38 | 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), | ||
286 | 39 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
287 | 40 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), | ||
288 | 41 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
289 | 42 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
290 | 43 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
291 | 44 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
292 | 45 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), | ||
293 | 46 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), | ||
294 | 47 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), | ||
295 | 48 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), | ||
296 | 49 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) | ||
297 | 50 | }, | ||
298 | 51 | 'contenttypes.contenttype': { | ||
299 | 52 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, | ||
300 | 53 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
301 | 54 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
302 | 55 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | ||
303 | 56 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) | ||
304 | 57 | }, | ||
305 | 58 | u'maasserver.bootimage': { | ||
306 | 59 | 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, | ||
307 | 60 | 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
308 | 61 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
309 | 62 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), | ||
310 | 63 | 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
311 | 64 | 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
312 | 65 | 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) | ||
313 | 66 | }, | ||
314 | 67 | u'maasserver.componenterror': { | ||
315 | 68 | 'Meta': {'object_name': 'ComponentError'}, | ||
316 | 69 | 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), | ||
317 | 70 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
318 | 71 | 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), | ||
319 | 72 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
320 | 73 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
321 | 74 | }, | ||
322 | 75 | u'maasserver.config': { | ||
323 | 76 | 'Meta': {'object_name': 'Config'}, | ||
324 | 77 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
325 | 78 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
326 | 79 | 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) | ||
327 | 80 | }, | ||
328 | 81 | u'maasserver.dhcplease': { | ||
329 | 82 | 'Meta': {'object_name': 'DHCPLease'}, | ||
330 | 83 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
331 | 84 | 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), | ||
332 | 85 | 'mac': ('maasserver.fields.MACAddressField', [], {}), | ||
333 | 86 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) | ||
334 | 87 | }, | ||
335 | 88 | u'maasserver.filestorage': { | ||
336 | 89 | 'Meta': {'object_name': 'FileStorage'}, | ||
337 | 90 | 'content': ('metadataserver.fields.BinaryField', [], {}), | ||
338 | 91 | 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), | ||
339 | 92 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) | ||
340 | 93 | }, | ||
341 | 94 | u'maasserver.macaddress': { | ||
342 | 95 | 'Meta': {'object_name': 'MACAddress'}, | ||
343 | 96 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
344 | 97 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
345 | 98 | 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), | ||
346 | 99 | 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), | ||
347 | 100 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
348 | 101 | }, | ||
349 | 102 | u'maasserver.node': { | ||
350 | 103 | 'Meta': {'object_name': 'Node'}, | ||
351 | 104 | 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
352 | 105 | 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), | ||
353 | 106 | 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
354 | 107 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
355 | 108 | 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), | ||
356 | 109 | 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
357 | 110 | 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), | ||
358 | 111 | 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}), | ||
359 | 112 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
360 | 113 | 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
361 | 114 | 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | ||
362 | 115 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), | ||
363 | 116 | 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), | ||
364 | 117 | 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), | ||
365 | 118 | 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), | ||
366 | 119 | 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), | ||
367 | 120 | 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-763aa1fc-2412-11e2-be98-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}), | ||
368 | 121 | 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), | ||
369 | 122 | 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), | ||
370 | 123 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
371 | 124 | }, | ||
372 | 125 | u'maasserver.nodegroup': { | ||
373 | 126 | 'Meta': {'object_name': 'NodeGroup'}, | ||
374 | 127 | 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), | ||
375 | 128 | 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), | ||
376 | 129 | 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), | ||
377 | 130 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
378 | 131 | 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
379 | 132 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
380 | 133 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), | ||
381 | 134 | 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
382 | 135 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | ||
383 | 136 | 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) | ||
384 | 137 | }, | ||
385 | 138 | u'maasserver.nodegroupinterface': { | ||
386 | 139 | 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, | ||
387 | 140 | 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
388 | 141 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
389 | 142 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
390 | 143 | 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), | ||
391 | 144 | 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), | ||
392 | 145 | 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
393 | 146 | 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
394 | 147 | 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | ||
395 | 148 | 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), | ||
396 | 149 | 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
397 | 150 | 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), | ||
398 | 151 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
399 | 152 | }, | ||
400 | 153 | u'maasserver.sshkey': { | ||
401 | 154 | 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, | ||
402 | 155 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
403 | 156 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
404 | 157 | 'key': ('django.db.models.fields.TextField', [], {}), | ||
405 | 158 | 'updated': ('django.db.models.fields.DateTimeField', [], {}), | ||
406 | 159 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) | ||
407 | 160 | }, | ||
408 | 161 | u'maasserver.tag': { | ||
409 | 162 | 'Meta': {'object_name': 'Tag'}, | ||
410 | 163 | 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), | ||
411 | 164 | 'created': ('django.db.models.fields.DateTimeField', [], {}), | ||
412 | 165 | 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}), | ||
413 | 166 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
414 | 167 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), | ||
415 | 168 | 'updated': ('django.db.models.fields.DateTimeField', [], {}) | ||
416 | 169 | }, | ||
417 | 170 | u'maasserver.userprofile': { | ||
418 | 171 | 'Meta': {'object_name': 'UserProfile'}, | ||
419 | 172 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
420 | 173 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) | ||
421 | 174 | }, | ||
422 | 175 | 'piston.consumer': { | ||
423 | 176 | 'Meta': {'object_name': 'Consumer'}, | ||
424 | 177 | 'description': ('django.db.models.fields.TextField', [], {}), | ||
425 | 178 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
426 | 179 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), | ||
427 | 180 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), | ||
428 | 181 | 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), | ||
429 | 182 | 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), | ||
430 | 183 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) | ||
431 | 184 | }, | ||
432 | 185 | 'piston.token': { | ||
433 | 186 | 'Meta': {'object_name': 'Token'}, | ||
434 | 187 | 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), | ||
435 | 188 | 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
436 | 189 | 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), | ||
437 | 190 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | ||
438 | 191 | 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), | ||
439 | 192 | 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), | ||
440 | 193 | 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), | ||
441 | 194 | 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1351767248L'}), | ||
442 | 195 | 'token_type': ('django.db.models.fields.IntegerField', [], {}), | ||
443 | 196 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), | ||
444 | 197 | 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) | ||
445 | 198 | } | ||
446 | 199 | } | ||
447 | 200 | |||
448 | 201 | complete_apps = ['maasserver'] | ||
449 | 0 | \ No newline at end of file | 202 | \ No newline at end of file |
450 | 1 | 203 | ||
451 | === modified file 'src/maasserver/models/node.py' | |||
452 | --- src/maasserver/models/node.py 2012-11-16 07:43:42 +0000 | |||
453 | +++ src/maasserver/models/node.py 2012-11-16 13:58:29 +0000 | |||
454 | @@ -428,6 +428,7 @@ | |||
455 | 428 | :ivar power_type: The :class:`POWER_TYPE` that determines how this | 428 | :ivar power_type: The :class:`POWER_TYPE` that determines how this |
456 | 429 | node will be powered on. If not given, the default will be used as | 429 | node will be powered on. If not given, the default will be used as |
457 | 430 | configured in the `node_power_type` setting. | 430 | configured in the `node_power_type` setting. |
458 | 431 | :ivar nodegroup: The `NodeGroup` this `Node` belongs to. | ||
459 | 431 | :ivar tags: The list of :class:`Tag`s associated with this `Node`. | 432 | :ivar tags: The list of :class:`Tag`s associated with this `Node`. |
460 | 432 | :ivar objects: The :class:`NodeManager`. | 433 | :ivar objects: The :class:`NodeManager`. |
461 | 433 | 434 | ||
462 | @@ -440,7 +441,7 @@ | |||
463 | 440 | max_length=41, unique=True, default=generate_node_system_id, | 441 | max_length=41, unique=True, default=generate_node_system_id, |
464 | 441 | editable=False) | 442 | editable=False) |
465 | 442 | 443 | ||
467 | 443 | hostname = CharField(max_length=255, default='', blank=True) | 444 | hostname = CharField(max_length=255, default='', blank=True, unique=True) |
468 | 444 | 445 | ||
469 | 445 | status = IntegerField( | 446 | status = IntegerField( |
470 | 446 | max_length=10, choices=NODE_STATUS_CHOICES, editable=False, | 447 | max_length=10, choices=NODE_STATUS_CHOICES, editable=False, |
471 | 447 | 448 | ||
472 | === modified file 'src/maasserver/templates/maasserver/node_edit.html' | |||
473 | --- src/maasserver/templates/maasserver/node_edit.html 2012-07-10 16:03:27 +0000 | |||
474 | +++ src/maasserver/templates/maasserver/node_edit.html 2012-11-16 13:58:29 +0000 | |||
475 | @@ -30,6 +30,7 @@ | |||
476 | 30 | {% endblock %} | 30 | {% endblock %} |
477 | 31 | 31 | ||
478 | 32 | {% block content %} | 32 | {% block content %} |
479 | 33 | <div id="node-edit" class="block size7"> | ||
480 | 33 | <form action="." method="post" class="block auto-width"> | 34 | <form action="." method="post" class="block auto-width"> |
481 | 34 | {% csrf_token %} | 35 | {% csrf_token %} |
482 | 35 | <ul> | 36 | <ul> |
483 | @@ -62,4 +63,5 @@ | |||
484 | 62 | <input type="submit" value="Save node" class="right" /> | 63 | <input type="submit" value="Save node" class="right" /> |
485 | 63 | <a class="link-button" href="{% url 'node-view' node.system_id %}">Cancel</a> | 64 | <a class="link-button" href="{% url 'node-view' node.system_id %}">Cancel</a> |
486 | 64 | </form> | 65 | </form> |
487 | 66 | </div> | ||
488 | 65 | {% endblock %} | 67 | {% endblock %} |
489 | 66 | 68 | ||
490 | === modified file 'src/maasserver/templates/maasserver/node_view.html' | |||
491 | --- src/maasserver/templates/maasserver/node_view.html 2012-10-05 17:42:12 +0000 | |||
492 | +++ src/maasserver/templates/maasserver/node_view.html 2012-11-16 13:58:29 +0000 | |||
493 | @@ -1,8 +1,8 @@ | |||
494 | 1 | {% extends "maasserver/base.html" %} | 1 | {% extends "maasserver/base.html" %} |
495 | 2 | 2 | ||
496 | 3 | {% block nav-active-settings %}active{% endblock %} | 3 | {% block nav-active-settings %}active{% endblock %} |
499 | 4 | {% block title %}Node: {{ node.hostname }}{% endblock %} | 4 | {% block title %}Node: {{ node.fqdn }}{% endblock %} |
500 | 5 | {% block page-title %}Node: {{ node.hostname }}{% endblock %} | 5 | {% block page-title %}Node: {{ node.fqdn }}{% endblock %} |
501 | 6 | {% block layout-modifiers %}sidebar{% endblock %} | 6 | {% block layout-modifiers %}sidebar{% endblock %} |
502 | 7 | 7 | ||
503 | 8 | {% block sidebar %} | 8 | {% block sidebar %} |
504 | @@ -39,8 +39,8 @@ | |||
505 | 39 | {% block content %} | 39 | {% block content %} |
506 | 40 | <ul class="data-list"> | 40 | <ul class="data-list"> |
507 | 41 | <li class="block size3 first"> | 41 | <li class="block size3 first"> |
510 | 42 | <h4>Hostname</h4> | 42 | <h4><acronym title="Fully Qualified Domain Name">FQDN</acronym></h4> |
511 | 43 | <span>{{ node.hostname }}</span> | 43 | <span>{{ node.fqdn }}</span> |
512 | 44 | </li> | 44 | </li> |
513 | 45 | <li class="block size3"> | 45 | <li class="block size3"> |
514 | 46 | <h4>MAC addresses</h4> | 46 | <h4>MAC addresses</h4> |
515 | 47 | 47 | ||
516 | === modified file 'src/maasserver/templates/maasserver/nodes_listing.html' | |||
517 | --- src/maasserver/templates/maasserver/nodes_listing.html 2012-10-10 08:13:01 +0000 | |||
518 | +++ src/maasserver/templates/maasserver/nodes_listing.html 2012-11-16 13:58:29 +0000 | |||
519 | @@ -2,19 +2,22 @@ | |||
520 | 2 | <table class="list"> | 2 | <table class="list"> |
521 | 3 | <thead> | 3 | <thead> |
522 | 4 | <tr> | 4 | <tr> |
525 | 5 | <th>MAC</th> | 5 | <th><acronym title="Fully Qualified Domain Name">FQDN</acronym></th> |
526 | 6 | <th>Status</th> | 6 | <th><acronym |
527 | 7 | title="Media Access Control addresses">MAC</acronym></th> | ||
528 | 7 | </tr> | 8 | </tr> |
529 | 8 | </thead> | 9 | </thead> |
530 | 9 | {% for node in node_list %} | 10 | {% for node in node_list %} |
531 | 10 | <tr class="node {% cycle 'even' 'odd' %}"> | 11 | <tr class="node {% cycle 'even' 'odd' %}"> |
532 | 11 | <td> | 12 | <td> |
533 | 12 | <a href="{% url 'node-view' node.system_id %}"> | 13 | <a href="{% url 'node-view' node.system_id %}"> |
537 | 13 | {% for macaddress in node.macaddress_set.all reversed %} | 14 | {{ node.fqdn }} |
535 | 14 | {{ macaddress }}{% if not forloop.last %},{% endif %} | ||
536 | 15 | {% endfor %} | ||
538 | 16 | </a> | 15 | </a> |
540 | 17 | ({{ node.hostname }}) | 16 | </td> |
541 | 17 | <td> | ||
542 | 18 | {% for macaddress in node.macaddress_set.all reversed %} | ||
543 | 19 | {{ macaddress }}{% if not forloop.last %},{% endif %} | ||
544 | 20 | {% endfor %} | ||
545 | 18 | </td> | 21 | </td> |
546 | 19 | <td>{{ node.display_status }}</td> | 22 | <td>{{ node.display_status }}</td> |
547 | 20 | </tr> | 23 | </tr> |
548 | 21 | 24 | ||
549 | === modified file 'src/maasserver/testing/factory.py' | |||
550 | --- src/maasserver/testing/factory.py 2012-11-08 09:14:58 +0000 | |||
551 | +++ src/maasserver/testing/factory.py 2012-11-16 13:58:29 +0000 | |||
552 | @@ -96,11 +96,11 @@ | |||
553 | 96 | finally: | 96 | finally: |
554 | 97 | NODE_TRANSITIONS[None] = valid_initial_states | 97 | NODE_TRANSITIONS[None] = valid_initial_states |
555 | 98 | 98 | ||
558 | 99 | def make_node(self, mac=False, hostname='', set_hostname=False, | 99 | def make_node(self, mac=False, hostname=None, status=None, |
559 | 100 | status=None, architecture=ARCHITECTURE.i386, updated=None, | 100 | architecture=ARCHITECTURE.i386, updated=None, |
560 | 101 | created=None, nodegroup=None, **kwargs): | 101 | created=None, nodegroup=None, **kwargs): |
561 | 102 | # hostname=None is a valid value, hence the set_hostname trick. | 102 | # hostname=None is a valid value, hence the set_hostname trick. |
563 | 103 | if hostname is '' and set_hostname: | 103 | if hostname is None: |
564 | 104 | hostname = self.getRandomString(20) | 104 | hostname = self.getRandomString(20) |
565 | 105 | if status is None: | 105 | if status is None: |
566 | 106 | status = NODE_STATUS.DEFAULT_STATUS | 106 | status = NODE_STATUS.DEFAULT_STATUS |
567 | 107 | 107 | ||
568 | === modified file 'src/maasserver/tests/test_api.py' | |||
569 | --- src/maasserver/tests/test_api.py 2012-11-16 07:43:42 +0000 | |||
570 | +++ src/maasserver/tests/test_api.py 2012-11-16 13:58:29 +0000 | |||
571 | @@ -1170,7 +1170,7 @@ | |||
572 | 1170 | 1170 | ||
573 | 1171 | def test_GET_returns_node(self): | 1171 | def test_GET_returns_node(self): |
574 | 1172 | # The api allows for fetching a single Node (using system_id). | 1172 | # The api allows for fetching a single Node (using system_id). |
576 | 1173 | node = factory.make_node(set_hostname=True) | 1173 | node = factory.make_node() |
577 | 1174 | response = self.client.get(self.get_node_uri(node)) | 1174 | response = self.client.get(self.get_node_uri(node)) |
578 | 1175 | 1175 | ||
579 | 1176 | self.assertEqual(httplib.OK, response.status_code) | 1176 | self.assertEqual(httplib.OK, response.status_code) |
580 | @@ -1179,7 +1179,7 @@ | |||
581 | 1179 | self.assertEqual(node.system_id, parsed_result['system_id']) | 1179 | self.assertEqual(node.system_id, parsed_result['system_id']) |
582 | 1180 | 1180 | ||
583 | 1181 | def test_GET_returns_associated_tag(self): | 1181 | def test_GET_returns_associated_tag(self): |
585 | 1182 | node = factory.make_node(set_hostname=True) | 1182 | node = factory.make_node() |
586 | 1183 | tag = factory.make_tag() | 1183 | tag = factory.make_tag() |
587 | 1184 | node.tags.add(tag) | 1184 | node.tags.add(tag) |
588 | 1185 | response = self.client.get(self.get_node_uri(node)) | 1185 | response = self.client.get(self.get_node_uri(node)) |
589 | @@ -1424,6 +1424,15 @@ | |||
590 | 1424 | self.assertEqual(0, Node.objects.filter(hostname='diane').count()) | 1424 | self.assertEqual(0, Node.objects.filter(hostname='diane').count()) |
591 | 1425 | self.assertEqual(1, Node.objects.filter(hostname='francis').count()) | 1425 | self.assertEqual(1, Node.objects.filter(hostname='francis').count()) |
592 | 1426 | 1426 | ||
593 | 1427 | def test_PUT_omitted_hostname(self): | ||
594 | 1428 | hostname = factory.make_name('hostname') | ||
595 | 1429 | node = factory.make_node(hostname=hostname, owner=self.logged_in_user) | ||
596 | 1430 | response = self.client.put( | ||
597 | 1431 | self.get_node_uri(node), | ||
598 | 1432 | {'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES)}) | ||
599 | 1433 | self.assertEqual(httplib.OK, response.status_code, response.content) | ||
600 | 1434 | self.assertTrue(Node.objects.filter(hostname=hostname).exists()) | ||
601 | 1435 | |||
602 | 1427 | def test_PUT_ignores_unknown_fields(self): | 1436 | def test_PUT_ignores_unknown_fields(self): |
603 | 1428 | node = factory.make_node( | 1437 | node = factory.make_node( |
604 | 1429 | owner=self.logged_in_user, | 1438 | owner=self.logged_in_user, |
605 | @@ -1677,7 +1686,7 @@ | |||
606 | 1677 | def test_DELETE_deletes_node(self): | 1686 | def test_DELETE_deletes_node(self): |
607 | 1678 | # The api allows to delete a Node. | 1687 | # The api allows to delete a Node. |
608 | 1679 | self.become_admin() | 1688 | self.become_admin() |
610 | 1680 | node = factory.make_node(set_hostname=True, owner=self.logged_in_user) | 1689 | node = factory.make_node(owner=self.logged_in_user) |
611 | 1681 | system_id = node.system_id | 1690 | system_id = node.system_id |
612 | 1682 | response = self.client.delete(self.get_node_uri(node)) | 1691 | response = self.client.delete(self.get_node_uri(node)) |
613 | 1683 | 1692 | ||
614 | @@ -1699,14 +1708,14 @@ | |||
615 | 1699 | 1708 | ||
616 | 1700 | def test_DELETE_deletes_node_fails_if_not_admin(self): | 1709 | def test_DELETE_deletes_node_fails_if_not_admin(self): |
617 | 1701 | # Only superusers can delete nodes. | 1710 | # Only superusers can delete nodes. |
619 | 1702 | node = factory.make_node(set_hostname=True, owner=self.logged_in_user) | 1711 | node = factory.make_node(owner=self.logged_in_user) |
620 | 1703 | response = self.client.delete(self.get_node_uri(node)) | 1712 | response = self.client.delete(self.get_node_uri(node)) |
621 | 1704 | 1713 | ||
622 | 1705 | self.assertEqual(httplib.FORBIDDEN, response.status_code) | 1714 | self.assertEqual(httplib.FORBIDDEN, response.status_code) |
623 | 1706 | 1715 | ||
624 | 1707 | def test_DELETE_forbidden_without_edit_permission(self): | 1716 | def test_DELETE_forbidden_without_edit_permission(self): |
625 | 1708 | # A user without the edit permission cannot delete a Node. | 1717 | # A user without the edit permission cannot delete a Node. |
627 | 1709 | node = factory.make_node(set_hostname=True) | 1718 | node = factory.make_node() |
628 | 1710 | response = self.client.delete(self.get_node_uri(node)) | 1719 | response = self.client.delete(self.get_node_uri(node)) |
629 | 1711 | 1720 | ||
630 | 1712 | self.assertEqual(httplib.FORBIDDEN, response.status_code) | 1721 | self.assertEqual(httplib.FORBIDDEN, response.status_code) |
631 | @@ -1771,8 +1780,7 @@ | |||
632 | 1771 | # The api allows for fetching the list of Nodes. | 1780 | # The api allows for fetching the list of Nodes. |
633 | 1772 | node1 = factory.make_node() | 1781 | node1 = factory.make_node() |
634 | 1773 | node2 = factory.make_node( | 1782 | node2 = factory.make_node( |
637 | 1774 | set_hostname=True, status=NODE_STATUS.ALLOCATED, | 1783 | status=NODE_STATUS.ALLOCATED, owner=self.logged_in_user) |
636 | 1775 | owner=self.logged_in_user) | ||
638 | 1776 | response = self.client.get(self.get_uri('nodes/'), {'op': 'list'}) | 1784 | response = self.client.get(self.get_uri('nodes/'), {'op': 'list'}) |
639 | 1777 | parsed_result = json.loads(response.content) | 1785 | parsed_result = json.loads(response.content) |
640 | 1778 | 1786 | ||
641 | 1779 | 1787 | ||
642 | === modified file 'src/maasserver/tests/test_dhcplease.py' | |||
643 | --- src/maasserver/tests/test_dhcplease.py 2012-10-30 15:15:30 +0000 | |||
644 | +++ src/maasserver/tests/test_dhcplease.py 2012-11-16 13:58:29 +0000 | |||
645 | @@ -168,7 +168,7 @@ | |||
646 | 168 | expected_mapping = {} | 168 | expected_mapping = {} |
647 | 169 | for i in range(3): | 169 | for i in range(3): |
648 | 170 | node = factory.make_node( | 170 | node = factory.make_node( |
650 | 171 | nodegroup=nodegroup, set_hostname=True) | 171 | nodegroup=nodegroup) |
651 | 172 | mac = factory.make_mac_address(node=node) | 172 | mac = factory.make_mac_address(node=node) |
652 | 173 | factory.make_mac_address(node=node) | 173 | factory.make_mac_address(node=node) |
653 | 174 | lease = factory.make_dhcp_lease( | 174 | lease = factory.make_dhcp_lease( |
654 | @@ -193,7 +193,7 @@ | |||
655 | 193 | def test_get_hostname_ip_mapping_considers_only_first_mac(self): | 193 | def test_get_hostname_ip_mapping_considers_only_first_mac(self): |
656 | 194 | nodegroup = factory.make_node_group() | 194 | nodegroup = factory.make_node_group() |
657 | 195 | node = factory.make_node( | 195 | node = factory.make_node( |
659 | 196 | nodegroup=nodegroup, set_hostname=True) | 196 | nodegroup=nodegroup) |
660 | 197 | factory.make_mac_address(node=node) | 197 | factory.make_mac_address(node=node) |
661 | 198 | second_mac = factory.make_mac_address(node=node) | 198 | second_mac = factory.make_mac_address(node=node) |
662 | 199 | # Create a lease for the second MAC Address. | 199 | # Create a lease for the second MAC Address. |
663 | @@ -205,7 +205,7 @@ | |||
664 | 205 | def test_get_hostname_ip_mapping_considers_given_nodegroup(self): | 205 | def test_get_hostname_ip_mapping_considers_given_nodegroup(self): |
665 | 206 | nodegroup = factory.make_node_group() | 206 | nodegroup = factory.make_node_group() |
666 | 207 | node = factory.make_node( | 207 | node = factory.make_node( |
668 | 208 | nodegroup=nodegroup, set_hostname=True) | 208 | nodegroup=nodegroup) |
669 | 209 | mac = factory.make_mac_address(node=node) | 209 | mac = factory.make_mac_address(node=node) |
670 | 210 | factory.make_dhcp_lease( | 210 | factory.make_dhcp_lease( |
671 | 211 | nodegroup=nodegroup, mac=mac.mac_address) | 211 | nodegroup=nodegroup, mac=mac.mac_address) |
672 | 212 | 212 | ||
673 | === modified file 'src/maasserver/tests/test_dns.py' | |||
674 | --- src/maasserver/tests/test_dns.py 2012-10-11 01:17:24 +0000 | |||
675 | +++ src/maasserver/tests/test_dns.py 2012-11-16 13:58:29 +0000 | |||
676 | @@ -157,7 +157,7 @@ | |||
677 | 157 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) | 157 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
678 | 158 | interface = nodegroup.get_managed_interface() | 158 | interface = nodegroup.get_managed_interface() |
679 | 159 | node = factory.make_node( | 159 | node = factory.make_node( |
681 | 160 | nodegroup=nodegroup, set_hostname=True) | 160 | nodegroup=nodegroup) |
682 | 161 | mac = factory.make_mac_address(node=node) | 161 | mac = factory.make_mac_address(node=node) |
683 | 162 | ips = IPRange(interface.ip_range_low, interface.ip_range_high) | 162 | ips = IPRange(interface.ip_range_low, interface.ip_range_high) |
684 | 163 | lease_ip = str(islice(ips, lease_number, lease_number + 1).next()) | 163 | lease_ip = str(islice(ips, lease_number, lease_number + 1).next()) |
685 | 164 | 164 | ||
686 | === modified file 'src/maasserver/tests/test_forms.py' | |||
687 | --- src/maasserver/tests/test_forms.py 2012-11-09 18:12:36 +0000 | |||
688 | +++ src/maasserver/tests/test_forms.py 2012-11-16 13:58:29 +0000 | |||
689 | @@ -381,18 +381,6 @@ | |||
690 | 381 | form.save() | 381 | form.save() |
691 | 382 | self.assertEqual(old_name, reload_object(node).hostname) | 382 | self.assertEqual(old_name, reload_object(node).hostname) |
692 | 383 | 383 | ||
693 | 384 | def test_AdminNodeForm_accepts_omitted_hostname_on_allocated_node(self): | ||
694 | 385 | node = factory.make_node(status=NODE_STATUS.ALLOCATED) | ||
695 | 386 | old_name = node.hostname | ||
696 | 387 | form = AdminNodeForm( | ||
697 | 388 | data={ | ||
698 | 389 | 'architecture': node.architecture, | ||
699 | 390 | }, | ||
700 | 391 | instance=node) | ||
701 | 392 | self.assertTrue(form.is_valid()) | ||
702 | 393 | form.save() | ||
703 | 394 | self.assertEqual(old_name, reload_object(node).hostname) | ||
704 | 395 | |||
705 | 396 | def test_remove_None_values_removes_None_values_in_dict(self): | 384 | def test_remove_None_values_removes_None_values_in_dict(self): |
706 | 397 | random_input = factory.getRandomString() | 385 | random_input = factory.getRandomString() |
707 | 398 | self.assertEqual( | 386 | self.assertEqual( |
708 | 399 | 387 | ||
709 | === modified file 'src/maasserver/tests/test_node.py' | |||
710 | --- src/maasserver/tests/test_node.py 2012-11-16 07:43:42 +0000 | |||
711 | +++ src/maasserver/tests/test_node.py 2012-11-16 13:58:29 +0000 | |||
712 | @@ -313,7 +313,7 @@ | |||
713 | 313 | node: node.get_effective_power_type() | 313 | node: node.get_effective_power_type() |
714 | 314 | for node in nodes} | 314 | for node in nodes} |
715 | 315 | started_nodes = Node.objects.start_nodes( | 315 | started_nodes = Node.objects.start_nodes( |
717 | 316 | list(node_power_types.keys()), user) | 316 | [node.system_id for node in list(node_power_types.keys())], user) |
718 | 317 | successful_types = [node_power_types[node] for node in started_nodes] | 317 | successful_types = [node_power_types[node] for node in started_nodes] |
719 | 318 | self.assertItemsEqual(configless_power_types, successful_types) | 318 | self.assertItemsEqual(configless_power_types, successful_types) |
720 | 319 | 319 | ||
721 | @@ -636,8 +636,7 @@ | |||
722 | 636 | status = NODE_STATUS.READY | 636 | status = NODE_STATUS.READY |
723 | 637 | else: | 637 | else: |
724 | 638 | status = NODE_STATUS.ALLOCATED | 638 | status = NODE_STATUS.ALLOCATED |
727 | 639 | return factory.make_node( | 639 | return factory.make_node(status=status, owner=user, **kwargs) |
726 | 640 | set_hostname=True, status=status, owner=user, **kwargs) | ||
728 | 641 | 640 | ||
729 | 642 | def make_node_with_mac(self, user=None, **kwargs): | 641 | def make_node_with_mac(self, user=None, **kwargs): |
730 | 643 | node = self.make_node(user, **kwargs) | 642 | node = self.make_node(user, **kwargs) |
731 | 644 | 643 | ||
732 | === modified file 'src/maasserver/tests/test_node_constraint_filter.py' | |||
733 | --- src/maasserver/tests/test_node_constraint_filter.py 2012-10-11 06:59:33 +0000 | |||
734 | +++ src/maasserver/tests/test_node_constraint_filter.py 2012-11-16 13:58:29 +0000 | |||
735 | @@ -60,8 +60,8 @@ | |||
736 | 60 | self.assertConstrainedNodes([node1, node2], {}) | 60 | self.assertConstrainedNodes([node1, node2], {}) |
737 | 61 | 61 | ||
738 | 62 | def test_hostname(self): | 62 | def test_hostname(self): |
741 | 63 | node1 = factory.make_node(set_hostname=True) | 63 | node1 = factory.make_node() |
742 | 64 | node2 = factory.make_node(set_hostname=True) | 64 | node2 = factory.make_node() |
743 | 65 | self.assertConstrainedNodes([node1], {'hostname': node1.hostname}) | 65 | self.assertConstrainedNodes([node1], {'hostname': node1.hostname}) |
744 | 66 | self.assertConstrainedNodes([node2], {'hostname': node2.hostname}) | 66 | self.assertConstrainedNodes([node2], {'hostname': node2.hostname}) |
745 | 67 | self.assertConstrainedNodes([], {'hostname': 'unknown-name'}) | 67 | self.assertConstrainedNodes([], {'hostname': 'unknown-name'}) |
746 | 68 | 68 | ||
747 | === modified file 'src/maasserver/tests/test_views_nodes.py' | |||
748 | --- src/maasserver/tests/test_views_nodes.py 2012-10-17 05:20:13 +0000 | |||
749 | +++ src/maasserver/tests/test_views_nodes.py 2012-11-16 13:58:29 +0000 | |||
750 | @@ -25,6 +25,8 @@ | |||
751 | 25 | ARCHITECTURE_CHOICES, | 25 | ARCHITECTURE_CHOICES, |
752 | 26 | NODE_AFTER_COMMISSIONING_ACTION, | 26 | NODE_AFTER_COMMISSIONING_ACTION, |
753 | 27 | NODE_STATUS, | 27 | NODE_STATUS, |
754 | 28 | NODEGROUP_STATUS, | ||
755 | 29 | NODEGROUPINTERFACE_MANAGEMENT, | ||
756 | 28 | ) | 30 | ) |
757 | 29 | from maasserver.exceptions import ( | 31 | from maasserver.exceptions import ( |
758 | 30 | InvalidConstraint, | 32 | InvalidConstraint, |
759 | @@ -79,6 +81,21 @@ | |||
760 | 79 | enlist_preseed_link = reverse('enlist-preseed-view') | 81 | enlist_preseed_link = reverse('enlist-preseed-view') |
761 | 80 | self.assertIn(enlist_preseed_link, get_content_links(response)) | 82 | self.assertIn(enlist_preseed_link, get_content_links(response)) |
762 | 81 | 83 | ||
763 | 84 | def test_node_list_displays_fqdn_dns_not_managed(self): | ||
764 | 85 | nodes = [factory.make_node() for i in range(3)] | ||
765 | 86 | response = self.client.get(reverse('node-list')) | ||
766 | 87 | node_fqdns = [node.fqdn for node in nodes] | ||
767 | 88 | self.assertThat(response.content, ContainsAll(node_fqdns)) | ||
768 | 89 | |||
769 | 90 | def test_node_list_displays_fqdn_dns_managed(self): | ||
770 | 91 | nodegroup = factory.make_node_group( | ||
771 | 92 | status=NODEGROUP_STATUS.ACCEPTED, | ||
772 | 93 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) | ||
773 | 94 | nodes = [factory.make_node(nodegroup=nodegroup) for i in range(3)] | ||
774 | 95 | response = self.client.get(reverse('node-list')) | ||
775 | 96 | node_fqdns = [node.fqdn for node in nodes] | ||
776 | 97 | self.assertThat(response.content, ContainsAll(node_fqdns)) | ||
777 | 98 | |||
778 | 82 | def test_node_list_displays_sorted_list_of_nodes(self): | 99 | def test_node_list_displays_sorted_list_of_nodes(self): |
779 | 83 | # Nodes are sorted on the node list page, newest first. | 100 | # Nodes are sorted on the node list page, newest first. |
780 | 84 | nodes = [factory.make_node() for i in range(3)] | 101 | nodes = [factory.make_node() for i in range(3)] |
781 | @@ -493,7 +510,7 @@ | |||
782 | 493 | def test_preseedview_node_displays_message_if_commissioning(self): | 510 | def test_preseedview_node_displays_message_if_commissioning(self): |
783 | 494 | node = factory.make_node( | 511 | node = factory.make_node( |
784 | 495 | owner=self.logged_in_user, status=NODE_STATUS.COMMISSIONING, | 512 | owner=self.logged_in_user, status=NODE_STATUS.COMMISSIONING, |
786 | 496 | set_hostname=True) | 513 | ) |
787 | 497 | node_preseed_link = reverse('node-preseed-view', args=[node.system_id]) | 514 | node_preseed_link = reverse('node-preseed-view', args=[node.system_id]) |
788 | 498 | response = self.client.get(node_preseed_link) | 515 | response = self.client.get(node_preseed_link) |
789 | 499 | self.assertThat( | 516 | self.assertThat( |
790 | 500 | 517 | ||
791 | === modified file 'src/maasserver/tests/test_views_tags.py' | |||
792 | --- src/maasserver/tests/test_views_tags.py 2012-11-07 11:32:16 +0000 | |||
793 | +++ src/maasserver/tests/test_views_tags.py 2012-11-16 13:58:29 +0000 | |||
794 | @@ -15,15 +15,11 @@ | |||
795 | 15 | from django.core.urlresolvers import reverse | 15 | from django.core.urlresolvers import reverse |
796 | 16 | from lxml.etree import XPath | 16 | from lxml.etree import XPath |
797 | 17 | from lxml.html import fromstring | 17 | from lxml.html import fromstring |
802 | 18 | from maastesting.matchers import ContainsAll | 18 | from maasserver.testing import get_content_links |
799 | 19 | from maasserver.testing import ( | ||
800 | 20 | get_content_links, | ||
801 | 21 | ) | ||
803 | 22 | from maasserver.testing.factory import factory | 19 | from maasserver.testing.factory import factory |
807 | 23 | from maasserver.testing.testcase import ( | 20 | from maasserver.testing.testcase import LoggedInTestCase |
805 | 24 | LoggedInTestCase, | ||
806 | 25 | ) | ||
808 | 26 | from maasserver.views import tags as tags_views | 21 | from maasserver.views import tags as tags_views |
809 | 22 | from maastesting.matchers import ContainsAll | ||
810 | 27 | 23 | ||
811 | 28 | 24 | ||
812 | 29 | class TagViewsTest(LoggedInTestCase): | 25 | class TagViewsTest(LoggedInTestCase): |
813 | @@ -37,12 +33,12 @@ | |||
814 | 37 | response = self.client.get(tag_link) | 33 | response = self.client.get(tag_link) |
815 | 38 | doc = fromstring(response.content) | 34 | doc = fromstring(response.content) |
816 | 39 | content_text = doc.cssselect('#content')[0].text_content() | 35 | content_text = doc.cssselect('#content')[0].text_content() |
819 | 40 | self.assertThat(content_text, | 36 | self.assertThat( |
820 | 41 | ContainsAll([tag.comment, tag.definition])) | 37 | content_text, ContainsAll([tag.comment, tag.definition])) |
821 | 42 | 38 | ||
822 | 43 | def test_view_tag_includes_node_links(self): | 39 | def test_view_tag_includes_node_links(self): |
823 | 44 | tag = factory.make_tag() | 40 | tag = factory.make_tag() |
825 | 45 | node = factory.make_node(set_hostname=True) | 41 | node = factory.make_node() |
826 | 46 | node.tags.add(tag) | 42 | node.tags.add(tag) |
827 | 47 | mac = factory.make_mac_address(node=node).mac_address | 43 | mac = factory.make_mac_address(node=node).mac_address |
828 | 48 | tag_link = reverse('tag-view', args=[tag.name]) | 44 | tag_link = reverse('tag-view', args=[tag.name]) |
829 | @@ -50,8 +46,8 @@ | |||
830 | 50 | response = self.client.get(tag_link) | 46 | response = self.client.get(tag_link) |
831 | 51 | doc = fromstring(response.content) | 47 | doc = fromstring(response.content) |
832 | 52 | content_text = doc.cssselect('#content')[0].text_content() | 48 | content_text = doc.cssselect('#content')[0].text_content() |
835 | 53 | self.assertThat(content_text, | 49 | self.assertThat( |
836 | 54 | ContainsAll([mac, '(%s)' % node.hostname])) | 50 | content_text, ContainsAll([mac, '%s' % node.hostname])) |
837 | 55 | self.assertNotIn(node.system_id, content_text) | 51 | self.assertNotIn(node.system_id, content_text) |
838 | 56 | self.assertIn(node_link, get_content_links(response)) | 52 | self.assertIn(node_link, get_content_links(response)) |
839 | 57 | 53 | ||
840 | @@ -82,8 +78,8 @@ | |||
841 | 82 | 78 | ||
842 | 83 | def test_view_tag_hides_private_nodes(self): | 79 | def test_view_tag_hides_private_nodes(self): |
843 | 84 | tag = factory.make_tag() | 80 | tag = factory.make_tag() |
846 | 85 | node = factory.make_node(set_hostname=True) | 81 | node = factory.make_node() |
847 | 86 | node2 = factory.make_node(owner=factory.make_user(), set_hostname=True) | 82 | node2 = factory.make_node(owner=factory.make_user()) |
848 | 87 | node.tags.add(tag) | 83 | node.tags.add(tag) |
849 | 88 | node2.tags.add(tag) | 84 | node2.tags.add(tag) |
850 | 89 | tag_link = reverse('tag-view', args=[tag.name]) | 85 | tag_link = reverse('tag-view', args=[tag.name]) |
851 | 90 | 86 | ||
852 | === modified file 'src/maasserver/views/nodes.py' | |||
853 | --- src/maasserver/views/nodes.py 2012-10-17 05:20:13 +0000 | |||
854 | +++ src/maasserver/views/nodes.py 2012-11-16 13:58:29 +0000 | |||
855 | @@ -123,6 +123,8 @@ | |||
856 | 123 | except InvalidConstraint as e: | 123 | except InvalidConstraint as e: |
857 | 124 | self.query_error = e | 124 | self.query_error = e |
858 | 125 | return Node.objects.none() | 125 | return Node.objects.none() |
859 | 126 | nodes = nodes.prefetch_related('nodegroup') | ||
860 | 127 | nodes = nodes.prefetch_related('nodegroup__nodegroupinterface_set') | ||
861 | 126 | return nodes | 128 | return nodes |
862 | 127 | 129 | ||
863 | 128 | def get_context_data(self, **kwargs): | 130 | def get_context_data(self, **kwargs): |
864 | 129 | 131 | ||
865 | === modified file 'src/maasserver/views/tags.py' | |||
866 | --- src/maasserver/views/tags.py 2012-11-07 11:32:16 +0000 | |||
867 | +++ src/maasserver/views/tags.py 2012-11-16 13:58:29 +0000 | |||
868 | @@ -33,9 +33,12 @@ | |||
869 | 33 | return super(TagView, self).get(request, *args, **kwargs) | 33 | return super(TagView, self).get(request, *args, **kwargs) |
870 | 34 | 34 | ||
871 | 35 | def get_queryset(self): | 35 | def get_queryset(self): |
873 | 36 | return Tag.objects.get_nodes( | 36 | nodes = Tag.objects.get_nodes( |
874 | 37 | self.tag, user=self.request.user, prefetch_mac=True, | 37 | self.tag, user=self.request.user, prefetch_mac=True, |
875 | 38 | ).order_by('-created') | 38 | ).order_by('-created') |
876 | 39 | nodes = nodes.prefetch_related('nodegroup') | ||
877 | 40 | nodes = nodes.prefetch_related('nodegroup__nodegroupinterface_set') | ||
878 | 41 | return nodes | ||
879 | 39 | 42 | ||
880 | 40 | def get_context_data(self, **kwargs): | 43 | def get_context_data(self, **kwargs): |
881 | 41 | context = super(TagView, self).get_context_data(**kwargs) | 44 | context = super(TagView, self).get_context_data(**kwargs) |
This is a backport of 1335 (and the required db migration merged in revision 1330). Self-approving.