Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add a BinaryField model field

Thanks Michael Jung, Charl Botha and Florian Apolloner for review
and help on the patch.
  • Loading branch information...
commit 8ee1eddb7e148de89aebde9e68da495633fc1ec9 1 parent 0f306ca
Claude Paroz authored December 13, 2012
1  django/db/backends/mysql/creation.py
@@ -7,6 +7,7 @@ class DatabaseCreation(BaseDatabaseCreation):
7 7
     # If a column type is set to None, it won't be included in the output.
8 8
     data_types = {
9 9
         'AutoField':         'integer AUTO_INCREMENT',
  10
+        'BinaryField':       'longblob',
10 11
         'BooleanField':      'bool',
11 12
         'CharField':         'varchar(%(max_length)s)',
12 13
         'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
1  django/db/backends/oracle/creation.py
@@ -17,6 +17,7 @@ class DatabaseCreation(BaseDatabaseCreation):
17 17
 
18 18
     data_types = {
19 19
         'AutoField':                    'NUMBER(11)',
  20
+        'BinaryField':                  'BLOB',
20 21
         'BooleanField':                 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))',
21 22
         'CharField':                    'NVARCHAR2(%(max_length)s)',
22 23
         'CommaSeparatedIntegerField':   'VARCHAR2(%(max_length)s)',
1  django/db/backends/postgresql_psycopg2/creation.py
@@ -11,6 +11,7 @@ class DatabaseCreation(BaseDatabaseCreation):
11 11
     # If a column type is set to None, it won't be included in the output.
12 12
     data_types = {
13 13
         'AutoField':         'serial',
  14
+        'BinaryField':       'bytea',
14 15
         'BooleanField':      'boolean',
15 16
         'CharField':         'varchar(%(max_length)s)',
16 17
         'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
1  django/db/backends/sqlite3/creation.py
@@ -9,6 +9,7 @@ class DatabaseCreation(BaseDatabaseCreation):
9 9
     # schema inspection is more useful.
10 10
     data_types = {
11 11
         'AutoField':                    'integer',
  12
+        'BinaryField':                  'BLOB',
12 13
         'BooleanField':                 'bool',
13 14
         'CharField':                    'varchar(%(max_length)s)',
14 15
         'CommaSeparatedIntegerField':   'varchar(%(max_length)s)',
27  django/db/models/fields/__init__.py
@@ -1291,3 +1291,30 @@ def formfield(self, **kwargs):
1291 1291
         }
1292 1292
         defaults.update(kwargs)
1293 1293
         return super(URLField, self).formfield(**defaults)
  1294
+
  1295
+class BinaryField(Field):
  1296
+    description = _("Raw binary data")
  1297
+
  1298
+    def __init__(self, *args, **kwargs):
  1299
+        kwargs['editable'] = False
  1300
+        super(BinaryField, self).__init__(*args, **kwargs)
  1301
+        if self.max_length is not None:
  1302
+            self.validators.append(validators.MaxLengthValidator(self.max_length))
  1303
+
  1304
+    def get_internal_type(self):
  1305
+        return "BinaryField"
  1306
+
  1307
+    def get_default(self):
  1308
+        if self.has_default() and not callable(self.default):
  1309
+            return self.default
  1310
+        default = super(BinaryField, self).get_default()
  1311
+        if default == '':
  1312
+            return b''
  1313
+        return default
  1314
+
  1315
+    def get_db_prep_value(self, value, connection, prepared=False):
  1316
+        value = super(BinaryField, self
  1317
+            ).get_db_prep_value(value, connection, prepared)
  1318
+        if value is not None:
  1319
+            return connection.Database.Binary(value)
  1320
+        return value
4  django/utils/six.py
@@ -394,10 +394,14 @@ def with_metaclass(meta, base=object):
394 394
     _iterlists = "lists"
395 395
     _assertRaisesRegex = "assertRaisesRegex"
396 396
     _assertRegex = "assertRegex"
  397
+    memoryview = memoryview
397 398
 else:
398 399
     _iterlists = "iterlists"
399 400
     _assertRaisesRegex = "assertRaisesRegexp"
400 401
     _assertRegex = "assertRegexpMatches"
  402
+    # memoryview and buffer are not stricly equivalent, but should be fine for
  403
+    # django core usage (mainly BinaryField)
  404
+    memoryview = buffer
401 405
 
402 406
 
403 407
 def iterlists(d):
16  docs/ref/models/fields.txt
@@ -347,6 +347,22 @@ A 64 bit integer, much like an :class:`IntegerField` except that it is
347 347
 guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The
348 348
 default form widget for this field is a :class:`~django.forms.TextInput`.
349 349
 
  350
+``BinaryField``
  351
+-------------------
  352
+
  353
+.. class:: BinaryField([**options])
  354
+
  355
+.. versionadded:: 1.6
  356
+
  357
+A field to store raw binary data. It only supports ``bytes`` assignment. Be
  358
+aware that this field has limited functionality. For example, it is not possible
  359
+to filter a queryset on a ``BinaryField`` value.
  360
+
  361
+.. admonition:: Abusing ``BinaryField``
  362
+
  363
+    Although you might think about storing files in the database, consider that
  364
+    it is bad design in 99% of the cases. This field is *not* a replacement for
  365
+    proper :ref.`static files <static-files> handling.
350 366
 
351 367
 ``BooleanField``
352 368
 ----------------
6  docs/releases/1.6.txt
@@ -53,6 +53,12 @@ UTC. This limitation was lifted in Django 1.6. Use :meth:`QuerySet.datetimes()
53 53
 <django.db.models.query.QuerySet.datetimes>` to perform time zone aware
54 54
 aggregation on a :class:`~django.db.models.DateTimeField`.
55 55
 
  56
+``BinaryField`` model field
  57
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
  58
+
  59
+A new :class:`django.db.models.BinaryField` model field allows to store raw
  60
+binary data in the database.
  61
+
56 62
 Minor features
57 63
 ~~~~~~~~~~~~~~
58 64
 
4  tests/model_fields/models.py
@@ -106,6 +106,10 @@ class VerboseNameField(models.Model):
106 106
 class DecimalLessThanOne(models.Model):
107 107
     d = models.DecimalField(max_digits=3, decimal_places=3)
108 108
 
  109
+class DataModel(models.Model):
  110
+    short_data = models.BinaryField(max_length=10, default=b'\x08')
  111
+    data = models.BinaryField()
  112
+
109 113
 ###############################################################################
110 114
 # FileField
111 115
 
26  tests/model_fields/tests.py
@@ -12,8 +12,8 @@
12 12
 from django.utils import unittest
13 13
 
14 14
 from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post,
15  
-    NullBooleanModel, BooleanModel, Document, RenamedField, VerboseNameField,
16  
-    FksToBooleans)
  15
+    NullBooleanModel, BooleanModel, DataModel, Document, RenamedField,
  16
+    VerboseNameField, FksToBooleans)
17 17
 
18 18
 from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests,
19 19
     TwoImageFieldTests, ImageFieldNoDimensionsTests,
@@ -424,3 +424,25 @@ def test_changed(self):
424 424
         field = d._meta.get_field('myfile')
425 425
         field.save_form_data(d, 'else.txt')
426 426
         self.assertEqual(d.myfile, 'else.txt')
  427
+
  428
+
  429
+class BinaryFieldTests(test.TestCase):
  430
+    binary_data = b'\x00\x46\xFE'
  431
+
  432
+    def test_set_and_retrieve(self):
  433
+        data_set = (self.binary_data, six.memoryview(self.binary_data))
  434
+        for bdata in data_set:
  435
+            dm = DataModel(data=bdata)
  436
+            dm.save()
  437
+            dm = DataModel.objects.get(pk=dm.pk)
  438
+            self.assertEqual(bytes(dm.data), bytes(bdata))
  439
+            # Resave (=update)
  440
+            dm.save()
  441
+            dm = DataModel.objects.get(pk=dm.pk)
  442
+            self.assertEqual(bytes(dm.data), bytes(bdata))
  443
+            # Test default value
  444
+            self.assertEqual(bytes(dm.short_data), b'\x08')
  445
+
  446
+    def test_max_length(self):
  447
+        dm = DataModel(short_data=self.binary_data*4)
  448
+        self.assertRaises(ValidationError, dm.full_clean)

0 notes on commit 8ee1edd

Please sign in to comment.
Something went wrong with that request. Please try again.