> [2]
> + for k, v in error.message_dict.items():
> s/items/iteritems/
Nice.
> [3]
> + if type(v) is list:
> if isinstance(v, list):
k
> [4]
> + return get_object_or_404(Node, system_id=system_id)
> Django is a massive layer violation isn't it? ;)
Well, you'll admit it's convenient and avoids boiler plate code…
> [5]
> + def read(self, request, system_id=None):
> + if system_id is not None:
> + return get_object_or_404(Node, system_id=system_id)
> + else:
> + return Node.objects.all().order_by('id')
>
> Does it make sense to s/Node/self.model/ above (and elsewhere)?
I think it's clearer like it is.
> [6]
>
> + node = Node(status = 'NEW', **dict(request.data.items()))
>
> Pyflakes ought to be moaning about the spaces around the =, bit it's
> curiously silent. Ah, maybe it's pep8 that ought to be moaning...
Good spot. I wonder why I use pyflake when I have pynella :)
> [7]
> + if 'status' in request.data:
> + return bad_request('Cannot set the status for a node.')
>
> This worries me. Do we need to check every bit of data coming in by
> hand? I don't want to have to do that, otherwise we're going to have
> to check handlers every time we update the model.
Well, this is only required because this is not checked at the model level, but I suppose we could put that in the model.
If you don't mind, I'll leave it like this for now because I think we will have to revisit that shortly anyway.
> [8]
> How do we layer authorization onto this?
That's my next task :)
> [9]
>
> - hostname = models.CharField(max_length=255)
> + hostname = models.CharField(max_length=255, null=True, default=None)
>
> Is there a min_length too? A hostname of "" is as good as null, so
> it's probably better to have just one or the other.
> [10]
>
> + mac = MACAddress(mac_address=mac_address, node=self)
> + mac.full_clean()
> + mac.save()
>
> Does mac.save() not imply full_clean()? Seems like a common pattern,
> and it's strange that Django doesn't just do it.
I agree. Model validation was only added in Django 1.2. Before that the validation was done using Forms. I think the reason for the manual call to full_clean is historic… but I might be wrong.
> [11]
>
> + def addMACAddress(self, mac_address):
> ...
> + def removeMACAddress(self, mac_address):
>
> We have a chance to adopt PEP8 naming (i.e. add_mac_address,
> remove_mac_address). Perhaps we ought to.
True, I've been infected by the lp way to name methods.
> [12]
>
> === added file 'src/maasserver/tests/factory.py'
>
> This should probably like in testing/factory.py.
>
>
> [13]
>
> +class Factory():
> ...
> + def createNode(self, hostname=None, set_hostname=False, status=None,
>
> Should we maintain the Launchpad naming here? i.e. makeSomething (or
> make_something if we're going PEP8). createSomething feels like proper
> API, if you know what I mean.
Okay, let's do this properly since we are starting fresh.
> [18]
> + self.assertEqual(
> + nb_macs + 1,
> + MACAddress.objects.filter(node = self.node).count())
>
> Like above, show the MACs instead of just the count.
Thanks for the thorough review Gavin!
> [1]
Okay.
> [2] dict.items( ):
> + for k, v in error.message_
> s/items/iteritems/
Nice.
> [3]
> + if type(v) is list:
> if isinstance(v, list):
k
> [4] or_404( Node, system_ id=system_ id)
> + return get_object_
> Django is a massive layer violation isn't it? ;)
Well, you'll admit it's convenient and avoids boiler plate code…
> [5] or_404( Node, system_ id=system_ id) all().order_ by('id' )
> + def read(self, request, system_id=None):
> + if system_id is not None:
> + return get_object_
> + else:
> + return Node.objects.
>
> Does it make sense to s/Node/self.model/ above (and elsewhere)?
I think it's clearer like it is.
> [6] request. data.items( )))
>
> + node = Node(status = 'NEW', **dict(
>
> Pyflakes ought to be moaning about the spaces around the =, bit it's
> curiously silent. Ah, maybe it's pep8 that ought to be moaning...
Good spot. I wonder why I use pyflake when I have pynella :)
> [7]
> + if 'status' in request.data:
> + return bad_request('Cannot set the status for a node.')
>
> This worries me. Do we need to check every bit of data coming in by
> hand? I don't want to have to do that, otherwise we're going to have
> to check handlers every time we update the model.
Well, this is only required because this is not checked at the model level, but I suppose we could put that in the model.
If you don't mind, I'll leave it like this for now because I think we will have to revisit that shortly anyway.
> [8]
> How do we layer authorization onto this?
That's my next task :)
> [9] CharField( max_length= 255) CharField( max_length= 255, null=True, default=None)
>
> - hostname = models.
> + hostname = models.
>
> Is there a min_length too? A hostname of "" is as good as null, so
> it's probably better to have just one or the other.
Good point, Django agrees with you: https:/ /docs.djangopro ject.com/ en/dev/ ref/models/ fields/ #django. db.models. Field.null
> [10] mac_address= mac_address, node=self)
>
> + mac = MACAddress(
> + mac.full_clean()
> + mac.save()
>
> Does mac.save() not imply full_clean()? Seems like a common pattern,
> and it's strange that Django doesn't just do it.
I agree. Model validation was only added in Django 1.2. Before that the validation was done using Forms. I think the reason for the manual call to full_clean is historic… but I might be wrong.
> [11] s(self, mac_address): mac_address) . Perhaps we ought to.
>
> + def addMACAddress(self, mac_address):
> ...
> + def removeMACAddres
>
> We have a chance to adopt PEP8 naming (i.e. add_mac_address,
> remove_
True, I've been infected by the lp way to name methods.
> [12] /tests/ factory. py'
>
> === added file 'src/maasserver
>
> This should probably like in testing/factory.py.
>
>
> [13]
>
> +class Factory():
> ...
> + def createNode(self, hostname=None, set_hostname=False, status=None,
>
> Should we maintain the Launchpad naming here? i.e. makeSomething (or
> make_something if we're going PEP8). createSomething feels like proper
> API, if you know what I mean.
Okay, let's do this properly since we are starting fresh.
> [14] l(node1. hostname, parsed_ result[ 0]['hostname' ]) l(node1. system_ id, parsed_ result[ 0]['system_ id']) l(node2. hostname, parsed_ result[ 1]['hostname' ]) l(node2. system_ id, parsed_ result[ 1]['system_ id']) l(expected, parsed_result)
>
> + self.assertEqual(2, len(parsed_result))
> + self.assertEqua
> + self.assertEqua
> + self.assertEqua
> + self.assertEqua
> This might be clearer and simpler as:
> expected = [
> {"hostname": node1.hostname, "system_id": node1.system_id},
> {"hostname": node2.hostname, "system_id": node2.system_id},
> ]
> self.assertEqua
ok.
> [15]
>
> + """
> + The api allows to fetch the list of Nodes.
>
> s/allows to fetch/allows for fetching/
>
> (Elsewhere too.)
Done.
> [16] DELETE( self): filter( system_ id = system_id).count()) objects. filter( system_ id=system_ id)))
>
> + def test_node_
> ...
> + self.assertEqual(
> + 0,
> + Node.objects.
>
> The following might be clearer:
>
> + self.assertEqual(
> + [], list(Node.
>
> (Is the list() needed?)
Okay. yes, lazy evaluation.
> [17] createNode( ) addMACAddress( 'aa:bb: cc:dd:ee: ff') addMACAddress( '22:bb: cc:dd:aa: ff')
> + def setUp(self):
> + self.node = factory.
> + self.mac1 = self.node.
> + self.mac2 = self.node.
>
> Don't forget to up-call.
Right.
> [18] objects. filter( node = self.node).count())
> + self.assertEqual(
> + nb_macs + 1,
> + MACAddress.
>
> Like above, show the MACs instead of just the count.
Nah, I don't like macs :)
> [19] DELETE_ mac(self) :
>
> + def xtest_macs_
>
> s/xtest/test/ ?
Yep, en route for the 31th test…
> [20]
>
> + When deleting a MAC Address, the api returns a 'Bad Request' (400)
> + error the provided MAC Address is not valid.
>
> s/error/error if/
Done.
> [21]
>
> Why all the trailing slashes in the URL configuration?
Well, spotted, this is actually intentional: see https:/ /docs.djangopro ject.com/ en/dev/ ref/middleware/ #django. middleware. common. CommonMiddlewar e.