Merge lp:~rvb/maas/maas-basic-model into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Merged at revision: 14
Proposed branch: lp:~rvb/maas/maas-basic-model
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 128 lines (+95/-12)
2 files modified
src/maasserver/models.py (+53/-12)
src/maasserver/tests/test_models.py (+42/-0)
To merge this branch: bzr merge lp:~rvb/maas/maas-basic-model
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+88890@code.launchpad.net

Commit message

Update Node and MACAddress.

Description of the change

This branch updates the model classes: Node and MACAddress and adds model tests.

= Tests =

./bin/test

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks good. Lots of questions though :)

[1]

+ def save(self):
+ if not self.id:
+ self.created = datetime.date.today()
+ self.updated = datetime.datetime.today()

I assume this needs a sprinkling of pytz? Or does Django mysteriously
DTRT here? I think we should prefer explicit when dealing with dates.

[2]

+def generate_node_system_id():
+ return 'node-' + str(uuid1())

    return 'node-%s' % uuid1()

might be prettier :) No other reason.

[3]

+ def __unicode__(self):
+ return self.system_id

Is this used by Django, or is it for your own information?

Does Django provide a decent repr around this? In other words, it
would be nice to have a decent repr :)

[4]

+mac_re = re.compile(r'^([0-9a-fA-F]{2}(:|$)){6}$')

I'm not sure about the $ in the middle. It will also match
"12:34:56:78:90:12:" even though the max_length setting on mac_address
will catch that.

The following might be clearer:

mac_re = re.compile(r'^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')

Or even:

mac_re = re.compile(
   r"^ (?:[0-9a-fA-F]{2}:){5} [0-9a-fA-F]{2} $", re.VERBOSE)

Note also the non-capturing group I added: (?:...)

[5]

+ self.assertTrue(node.system_id.startswith('node-'))

Perhaps this is the time to introduce testtools and use StartsWith?

[6]

Docstrings needed!

[7]

+ mac = MACAddress(mac_address='AA:BB:CCXDD:EE:FF', node=node)
+ self.assertRaises(ValidationError, mac.full_clean)

Wait! What? It doesn't blow up until later?

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

> [1]
>
> + def save(self):
> + if not self.id:
> + self.created = datetime.date.today()
> + self.updated = datetime.datetime.today()
>
> I assume this needs a sprinkling of pytz? Or does Django mysteriously
> DTRT here? I think we should prefer explicit when dealing with dates.

It does something somewhat right now: it stores the date with the tz. jtv would like all the dates to be stored without a timezone but I'll let him do it :).

> [2]
>
> +def generate_node_system_id():
> + return 'node-' + str(uuid1())
>
> return 'node-%s' % uuid1()
>
> might be prettier :) No other reason.

Sure.

> [3]
>
> + def __unicode__(self):
> + return self.system_id
>
> Is this used by Django, or is it for your own information?

It is used by django when as the default __repr__ for instance when printing an object. It is just convenient to have the system_id.

> Does Django provide a decent repr around this? In other words, it
> would be nice to have a decent repr :)

The default __repr__ is <Node Ox1234>.

> [4]
>
> +mac_re = re.compile(r'^([0-9a-fA-F]{2}(:|$)){6}$')
>
> I'm not sure about the $ in the middle. It will also match
> "12:34:56:78:90:12:" even though the max_length setting on mac_address
> will catch that.
>
> The following might be clearer:
>
> mac_re = re.compile(r'^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')
>
> Or even:
>
> mac_re = re.compile(
> r"^ (?:[0-9a-fA-F]{2}:){5} [0-9a-fA-F]{2} $", re.VERBOSE)
>
> Note also the non-capturing group I added: (?:...)

I prefer the former.

> [5]
>
> + self.assertTrue(node.system_id.startswith('node-'))
>
> Perhaps this is the time to introduce testtools and use StartsWith?

ah! I'll skip that one for now.

> [6]
>
> Docstrings needed!

Docstrings added.

>
>
> [7]
>
> + mac = MACAddress(mac_address='AA:BB:CCXDD:EE:FF', node=node)
> + self.assertRaises(ValidationError, mac.full_clean)
>
> Wait! What? It doesn't blow up until later?

That's the way it is, the validation happens when you call full_clean on the model (see https://docs.djangoproject.com/en/dev/ref/models/instances/#validating-objects).

Revision history for this message
Gavin Panella (allenap) wrote :

Okay, looks good.

[1]

> It does something somewhat right now: it stores the date with the
> tz.

Yes, but which timezone? It's not specified anywhere.

[3]

>> Does Django provide a decent repr around this? In other words, it
>> would be nice to have a decent repr :)
>
> The default __repr__ is <Node Ox1234>.

Okay, not very exciting. I guess we could introduce our own magical
base class at some point.

[7]

> That's the way it is, the validation happens when you call
> full_clean on the model (see
> https://docs.djangoproject.com/en/dev/ref/models/instances/#validating-objects).

Cool, thanks for the link.

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

> Yes, but which timezone? It's not specified anywhere.

Your timezone. Well, what your browser says your timezone is :).

Revision history for this message
Gavin Panella (allenap) wrote :

>> Yes, but which timezone? It's not specified anywhere.
>
> Your timezone. Well, what your browser says your timezone is :).

Gulp. That sounds bad, but if Django *guarantees* that it gets it
right every time then maybe it's okay. My instinct is still to store
all dates as UTC.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Why aren't we using the macaddr data type for storing MACs in the database?

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

Good point. I've created this model that works and I'll create all the externally exposed methods (like add_mac_address & co) on the Node object in such a way that how we store MACs is a well confined implementation detail. I'll investigate what you suggest (I suspect it only requires wrapping it so that Django can use it) and also consider using an array to store the MAC Addresses.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Tuesday 17 January 2012 18:05:26 Gavin Panella wrote:
> >> Yes, but which timezone? It's not specified anywhere.
> >
> > Your timezone. Well, what your browser says your timezone is :).
>
> Gulp. That sounds bad, but if Django *guarantees* that it gets it
> right every time then maybe it's okay. My instinct is still to store
> all dates as UTC.

Mine too. The uncertainty here is concerning.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/models.py'
2--- src/maasserver/models.py 2012-01-16 16:25:49 +0000
3+++ src/maasserver/models.py 2012-01-17 17:22:24 +0000
4@@ -1,6 +1,29 @@
5 import datetime
6+import re
7+from uuid import uuid1
8+
9 from django.db import models
10 from django.contrib import admin
11+from django.core.validators import RegexValidator
12+
13+
14+class CommonInfo(models.Model):
15+ created = models.DateField(editable=False)
16+ updated = models.DateTimeField(editable=False)
17+
18+ class Meta:
19+ abstract = True
20+
21+ def save(self):
22+ if not self.id:
23+ self.created = datetime.date.today()
24+ self.updated = datetime.datetime.today()
25+ super(CommonInfo, self).save()
26+
27+
28+def generate_node_system_id():
29+ return 'node-%s' % uuid1()
30+
31
32 NODE_STATUS_CHOICES = (
33 (u'NEW', u'New'),
34@@ -11,17 +34,35 @@
35 )
36
37
38-class Node(models.Model):
39- name = models.CharField(max_length=30)
40+class Node(CommonInfo):
41+ system_id = models.CharField(
42+ max_length=41, unique=True, editable=False,
43+ default=generate_node_system_id)
44+ hostname = models.CharField(max_length=255)
45 status = models.CharField(max_length=10, choices=NODE_STATUS_CHOICES)
46- created = models.DateField(editable=False)
47- updated = models.DateTimeField(editable=False)
48-
49- def save(self):
50- if not self.id:
51- self.created = datetime.date.today()
52- self.updated = datetime.datetime.today()
53- super(Node, self).save()
54-
55-
56+
57+ def __unicode__(self):
58+ return self.system_id
59+
60+
61+mac_re = re.compile(r'^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$')
62+
63+
64+validate_mac_address = RegexValidator(
65+ regex = mac_re,
66+ message = u'Enter a valid MAC address (e.g. AA:BB:CC:DD:EE:FF).')
67+
68+
69+class MACAddress(CommonInfo):
70+ mac_address = models.CharField(
71+ max_length=17, validators=[validate_mac_address])
72+ node = models.ForeignKey(Node)
73+
74+ def __unicode__(self):
75+ return self.mac_address
76+
77+
78+# Register the models in the admin site.
79 admin.site.register(Node)
80+admin.site.register(MACAddress)
81+
82
83=== added file 'src/maasserver/tests/test_models.py'
84--- src/maasserver/tests/test_models.py 1970-01-01 00:00:00 +0000
85+++ src/maasserver/tests/test_models.py 2012-01-17 17:22:24 +0000
86@@ -0,0 +1,42 @@
87+"""
88+Test maasserver models.
89+"""
90+
91+from django.test import TestCase
92+from maasserver.models import Node, MACAddress
93+from django.core.exceptions import ValidationError
94+
95+
96+class NodeTest(TestCase):
97+
98+ def test_system_id(self):
99+ """
100+ The generated system_id looks good.
101+
102+ """
103+ node = Node()
104+ self.assertEqual(len(node.system_id), 41)
105+ self.assertTrue(node.system_id.startswith('node-'))
106+
107+
108+class MACAddressTest(TestCase):
109+
110+ def test_mac_address_invalid(self):
111+ """
112+ An invalid MAC address does not pass the model validation phase.
113+
114+ """
115+ node = Node()
116+ node.save()
117+ mac = MACAddress(mac_address='AA:BB:CCXDD:EE:FF', node=node)
118+ self.assertRaises(ValidationError, mac.full_clean)
119+
120+ def test_mac_address_valid(self):
121+ """
122+ A valid MAC address passes the model validation phase.
123+
124+ """
125+ node = Node()
126+ node.save()
127+ mac = MACAddress(mac_address='AA:BB:CC:DD:EE:FF', node=node)
128+ mac.full_clean() # No exception.