Code

Ticket #811: ipv6-9781.diff

File ipv6-9781.diff, 12.4 KB (added by jqueuniet, 5 years ago)

IPv6 patch for trunk, revision 9781

Line 
1Index: django/db/models/fields/__init__.py
2===================================================================
3--- django/db/models/fields/__init__.py (révision 9781)
4+++ django/db/models/fields/__init__.py (copie de travail)
5@@ -710,6 +710,21 @@
6         defaults.update(kwargs)
7         return super(IPAddressField, self).formfield(**defaults)
8 
9+class IP6AddressField(Field):
10+    empty_strings_allowed = False
11+    def __init__(self, *args, **kwargs):
12+        kwargs['max_length'] = 39
13+        Field.__init__(self, *args, **kwargs)
14+
15+    def get_internal_type(self):
16+        return "IP6AddressField"
17+
18+    def formfield(self, **kwargs):
19+        defaults = {'form_class': forms.IP6AddressField}
20+        defaults.update(kwargs)
21+        return super(IP6AddressField, self).formfield(**defaults)
22+
23+
24 class NullBooleanField(Field):
25     empty_strings_allowed = False
26     def __init__(self, *args, **kwargs):
27Index: django/db/backends/postgresql/introspection.py
28===================================================================
29--- django/db/backends/postgresql/introspection.py      (révision 9781)
30+++ django/db/backends/postgresql/introspection.py      (copie de travail)
31@@ -8,7 +8,7 @@
32         23: 'IntegerField',
33         25: 'TextField',
34         701: 'FloatField',
35-        869: 'IPAddressField',
36+        869: 'IP6AddressField',
37         1043: 'CharField',
38         1082: 'DateField',
39         1083: 'TimeField',
40Index: django/db/backends/postgresql/creation.py
41===================================================================
42--- django/db/backends/postgresql/creation.py   (révision 9781)
43+++ django/db/backends/postgresql/creation.py   (copie de travail)
44@@ -19,6 +19,7 @@
45         'FloatField':        'double precision',
46         'IntegerField':      'integer',
47         'IPAddressField':    'inet',
48+        'IP6AddressField':   'inet',
49         'NullBooleanField':  'boolean',
50         'OneToOneField':     'integer',
51         'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
52Index: django/db/backends/sqlite3/creation.py
53===================================================================
54--- django/db/backends/sqlite3/creation.py      (révision 9781)
55+++ django/db/backends/sqlite3/creation.py      (copie de travail)
56@@ -20,6 +20,7 @@
57         'FloatField':                   'real',
58         'IntegerField':                 'integer',
59         'IPAddressField':               'char(15)',
60+        'IP6AddressField':              'char(39)',
61         'NullBooleanField':             'bool',
62         'OneToOneField':                'integer',
63         'PositiveIntegerField':         'integer unsigned',
64Index: django/db/backends/mysql/creation.py
65===================================================================
66--- django/db/backends/mysql/creation.py        (révision 9781)
67+++ django/db/backends/mysql/creation.py        (copie de travail)
68@@ -19,6 +19,7 @@
69         'FloatField':        'double precision',
70         'IntegerField':      'integer',
71         'IPAddressField':    'char(15)',
72+        'IP6AddressField':   'char(39)',
73         'NullBooleanField':  'bool',
74         'OneToOneField':     'integer',
75         'PositiveIntegerField': 'integer UNSIGNED',
76@@ -63,4 +64,4 @@
77                 field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
78             ]
79         return table_output, deferred
80-       
81\ Pas de fin de ligne à la fin du fichier
82+       
83Index: django/db/backends/oracle/creation.py
84===================================================================
85--- django/db/backends/oracle/creation.py       (révision 9781)
86+++ django/db/backends/oracle/creation.py       (copie de travail)
87@@ -28,6 +28,7 @@
88         'FloatField':                   'DOUBLE PRECISION',
89         'IntegerField':                 'NUMBER(11)',
90         'IPAddressField':               'VARCHAR2(15)',
91+        'IP6AddressField':              'VARCHAR2(39)',
92         'NullBooleanField':             'NUMBER(1) CHECK ((%(qn_column)s IN (0,1)) OR (%(qn_column)s IS NULL))',
93         'OneToOneField':                'NUMBER(11)',
94         'PositiveIntegerField':         'NUMBER(11) CHECK (%(qn_column)s >= 0)',
95Index: django/forms/fields.py
96===================================================================
97--- django/forms/fields.py      (révision 9781)
98+++ django/forms/fields.py      (copie de travail)
99@@ -26,6 +26,7 @@
100 import django.core.exceptions
101 from django.utils.translation import ugettext_lazy as _
102 from django.utils.encoding import smart_unicode, smart_str
103+from django.utils.http import ip6_normalize
104 
105 from util import ErrorList, ValidationError
106 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
107@@ -40,7 +41,7 @@
108     'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
109     'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
110     'SplitDateTimeField', 'IPAddressField', 'FilePathField', 'SlugField',
111-    'TypedChoiceField'
112+    'TypedChoiceField', 'IP6AddressField'
113 )
114 
115 # These values, if given to to_python(), will trigger the self.required check.
116@@ -881,6 +882,25 @@
117     def __init__(self, *args, **kwargs):
118         super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs)
119 
120+ipv6_re = re.compile(r'^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$')
121+
122+class IP6AddressField(CharField):
123+    def __init__(self, *args, **kwargs):
124+        super(IP6AddressField, self).__init__(39, 3, *args, **kwargs)
125+
126+    def clean(self, value):
127+        value = super(IP6AddressField, self).clean(value)
128+        if value == u'':
129+            return value
130+        if not ipv4_re.search(value):
131+            try:
132+                ip = ip6_normalize(value)
133+                if not ipv6_re.search(ip):
134+                    raise ValidationError(_(u'Enter a valid IP address.'))
135+            except ValueError:
136+                raise ValidationError(_(u'Enter a valid IP address.'))
137+        return value
138+
139 slug_re = re.compile(r'^[-\w]+$')
140 
141 class SlugField(RegexField):
142Index: django/utils/http.py
143===================================================================
144--- django/utils/http.py        (révision 9781)
145+++ django/utils/http.py        (copie de travail)
146@@ -94,3 +94,55 @@
147         i = i % j
148         factor -= 1
149     return ''.join(base36)
150+
151+
152+def ip6_normalize(addr):
153+    """
154+    Normalize an IPv6 address to allow easy regexp validation
155+    mostly checks the length, and gets ride of tricky things
156+    like IPv4 mapped addresses and :: shortcuts
157+   
158+    Outputs a string
159+    """
160+    # Some basic error checking
161+    if addr.count('::') > 2 or ':::' in addr:
162+        raise ValueError
163+
164+    ip = addr.split(':')
165+    nbfull = len([elem for elem in ip if elem != ''])
166+    nb = len(ip)
167+
168+    if nbfull >= 1 and '.' in ip[-1]:
169+        # Convert IPv4 mapped addresses to full hexa
170+        ipv4 = ip[-1].split('.')
171+        hex1 = (int(ipv4[0]) << 8) + int(ipv4[1])
172+        hex2 = (int(ipv4[2]) << 8) + int(ipv4[3])
173+        ip[-1:] = [hex(hex1)[2:], hex(hex2)[2:]]
174+        nbfull = nbfull + 1
175+        nb = nb + 1
176+
177+    if nbfull == 8 or nbfull == nb:
178+        # No need to bother
179+        return addr
180+    elif nbfull > 8:
181+        # Has to be invalid anyway
182+        raise ValueError
183+
184+    # Begin normalization
185+    start, end, index = (None, None, 0)
186+    for elem in ip:
187+        if elem == '':
188+            if start is None:
189+                start = index
190+                end = index
191+            else:
192+                end = index
193+        index += 1
194+    pad = 8 - nbfull
195+    if end != start:
196+        ip[start:end-start+1] = ['0'] * pad
197+    else:
198+        ip[start] = '0'
199+        if pad > 1:
200+            ip[start:1] = ['0'] * (pad - 1)
201+    return ':'.join([item for item in ip if len(item) > 0])
202Index: tests/regressiontests/forms/extra.py
203===================================================================
204--- tests/regressiontests/forms/extra.py        (révision 9781)
205+++ tests/regressiontests/forms/extra.py        (copie de travail)
206@@ -377,6 +377,85 @@
207 ...
208 ValidationError: [u'Enter a valid IPv4 address.']
209 
210+# IP6AddressField ##################################################################
211+
212+>>> f = IP6AddressField()
213+>>> f.clean('')
214+Traceback (most recent call last):
215+...
216+ValidationError: [u'This field is required.']
217+>>> f.clean(None)
218+Traceback (most recent call last):
219+...
220+ValidationError: [u'This field is required.']
221+>>> f.clean('127.0.0.1')
222+u'127.0.0.1'
223+>>> f.clean('2a01:05d8:25ae:9451:020d:39bc:21e6:8cab')
224+u'2a01:05d8:25ae:9451:020d:39bc:21e6:8cab'
225+>>> f.clean('2a01:5d8:25ae:9451:20d:39bc:21e6:8cab')
226+u'2a01:5d8:25ae:9451:20d:39bc:21e6:8cab'
227+>>> f.clean('::1')
228+u'::1'
229+>>> f.clean('::ffff:88.191.32.1')
230+u'::ffff:88.191.32.1'
231+>>> f.clean('foo')
232+Traceback (most recent call last):
233+...
234+ValidationError: [u'Enter a valid IP address.']
235+>>> f.clean('127.0.0.')
236+Traceback (most recent call last):
237+...
238+ValidationError: [u'Enter a valid IP address.']
239+>>> f.clean('::1:')
240+Traceback (most recent call last):
241+...
242+ValidationError: [u'Enter a valid IP address.']
243+>>> f.clean('1.2.3.4.5')
244+Traceback (most recent call last):
245+...
246+ValidationError: [u'Enter a valid IP address.']
247+>>> f.clean('2a01:5d8:25ae:9451:20d:39bc:21e6:8cab:fb2c')
248+Traceback (most recent call last):
249+...
250+ValidationError: [u'Ensure this value has at most 39 characters (it has 42).']
251+>>> f.clean('2a01:5d8:25ae:9451:20d:39bc:1e6:cab:b2c')
252+Traceback (most recent call last):
253+...
254+ValidationError: [u'Enter a valid IP address.']
255+>>> f.clean('256.125.1.5')
256+Traceback (most recent call last):
257+...
258+ValidationError: [u'Enter a valid IP address.']
259+>>> f.clean('::12345')
260+Traceback (most recent call last):
261+...
262+ValidationError: [u'Enter a valid IP address.']
263+
264+>>> f = IP6AddressField(required=False)
265+>>> f.clean('')
266+u''
267+>>> f.clean(None)
268+u''
269+>>> f.clean('127.0.0.1')
270+u'127.0.0.1'
271+>>> f.clean('foo')
272+Traceback (most recent call last):
273+...
274+ValidationError: [u'Enter a valid IP address.']
275+>>> f.clean('127.0.0.')
276+Traceback (most recent call last):
277+...
278+ValidationError: [u'Enter a valid IP address.']
279+>>> f.clean('1.2.3.4.5')
280+Traceback (most recent call last):
281+...
282+ValidationError: [u'Enter a valid IP address.']
283+>>> f.clean('256.125.1.5')
284+Traceback (most recent call last):
285+...
286+ValidationError: [u'Enter a valid IP address.']
287+
288+
289 #################################
290 # Tests of underlying functions #
291 #################################
292Index: docs/topics/forms/modelforms.txt
293===================================================================
294--- docs/topics/forms/modelforms.txt    (révision 9781)
295+++ docs/topics/forms/modelforms.txt    (copie de travail)
296@@ -60,6 +60,7 @@
297     ``ImageField``                   ``ImageField``
298     ``IntegerField``                 ``IntegerField``
299     ``IPAddressField``               ``IPAddressField``
300+    ``IP6AddressField``              ``IP6AddressField``
301     ``ManyToManyField``              ``ModelMultipleChoiceField`` (see
302                                      below)
303     ``NullBooleanField``             ``CharField``
304Index: docs/ref/models/fields.txt
305===================================================================
306--- docs/ref/models/fields.txt  (révision 9781)
307+++ docs/ref/models/fields.txt  (copie de travail)
308@@ -626,9 +626,18 @@
309 
310 .. class:: IPAddressField([**options])
311 
312-An IP address, in string format (e.g. "192.0.2.30"). The admin represents this
313+An IPv4 address, in string format (e.g. "192.0.2.30"). The admin represents this
314 as an ``<input type="text">`` (a single-line input).
315 
316+``IP6AddressField``
317+------------------
318+
319+.. class:: IP6AddressField([**options])
320+
321+An IPv4 or IPv6 address, in string format (e.g. "192.0.2.30" or
322+"2a01:5d8:25ae:9451:20d:39bc:1e6:cab:b2c"). The admin represents this as an
323+``<input type="text">`` (a single-line input).
324+
325 ``NullBooleanField``
326 --------------------
327 
328Index: docs/ref/forms/fields.txt
329===================================================================
330--- docs/ref/forms/fields.txt   (révision 9781)
331+++ docs/ref/forms/fields.txt   (copie de travail)
332@@ -623,6 +623,19 @@
333       expression.
334     * Error message keys: ``required``, ``invalid``
335 
336+``IP6AddressField``
337+~~~~~~~~~~~~~~~~~~
338+
339+.. class:: IP6AddressField(**kwargs)
340+
341+    * Default widget: ``TextInput``
342+    * Empty value: ``''`` (an empty string)
343+    * Normalizes to: A Unicode object.
344+    * Validates that the given value is a valid IPv4 or IPv6 address, using
345+      a regular expression.
346+    * Error message keys: ``required``, ``invalid``
347+
348+
349 ``MultipleChoiceField``
350 ~~~~~~~~~~~~~~~~~~~~~~~
351