浏览代码

Add a BinaryField model field

Thanks Michael Jung, Charl Botha and Florian Apolloner for review
and help on the patch.
Claude Paroz 12 年之前
父节点
当前提交
8ee1eddb7e

+ 1 - 0
django/db/backends/mysql/creation.py

@@ -7,6 +7,7 @@ class DatabaseCreation(BaseDatabaseCreation):
     # If a column type is set to None, it won't be included in the output.
     data_types = {
         'AutoField':         'integer AUTO_INCREMENT',
+        'BinaryField':       'longblob',
         'BooleanField':      'bool',
         'CharField':         'varchar(%(max_length)s)',
         'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',

+ 1 - 0
django/db/backends/oracle/creation.py

@@ -17,6 +17,7 @@ class DatabaseCreation(BaseDatabaseCreation):
 
     data_types = {
         'AutoField':                    'NUMBER(11)',
+        'BinaryField':                  'BLOB',
         'BooleanField':                 'NUMBER(1) CHECK (%(qn_column)s IN (0,1))',
         'CharField':                    'NVARCHAR2(%(max_length)s)',
         'CommaSeparatedIntegerField':   'VARCHAR2(%(max_length)s)',

+ 1 - 0
django/db/backends/postgresql_psycopg2/creation.py

@@ -11,6 +11,7 @@ class DatabaseCreation(BaseDatabaseCreation):
     # If a column type is set to None, it won't be included in the output.
     data_types = {
         'AutoField':         'serial',
+        'BinaryField':       'bytea',
         'BooleanField':      'boolean',
         'CharField':         'varchar(%(max_length)s)',
         'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',

+ 1 - 0
django/db/backends/sqlite3/creation.py

@@ -9,6 +9,7 @@ class DatabaseCreation(BaseDatabaseCreation):
     # schema inspection is more useful.
     data_types = {
         'AutoField':                    'integer',
+        'BinaryField':                  'BLOB',
         'BooleanField':                 'bool',
         'CharField':                    'varchar(%(max_length)s)',
         'CommaSeparatedIntegerField':   'varchar(%(max_length)s)',

+ 27 - 0
django/db/models/fields/__init__.py

@@ -1291,3 +1291,30 @@ class URLField(CharField):
         }
         defaults.update(kwargs)
         return super(URLField, self).formfield(**defaults)
+
+class BinaryField(Field):
+    description = _("Raw binary data")
+
+    def __init__(self, *args, **kwargs):
+        kwargs['editable'] = False
+        super(BinaryField, self).__init__(*args, **kwargs)
+        if self.max_length is not None:
+            self.validators.append(validators.MaxLengthValidator(self.max_length))
+
+    def get_internal_type(self):
+        return "BinaryField"
+
+    def get_default(self):
+        if self.has_default() and not callable(self.default):
+            return self.default
+        default = super(BinaryField, self).get_default()
+        if default == '':
+            return b''
+        return default
+
+    def get_db_prep_value(self, value, connection, prepared=False):
+        value = super(BinaryField, self
+            ).get_db_prep_value(value, connection, prepared)
+        if value is not None:
+            return connection.Database.Binary(value)
+        return value

+ 4 - 0
django/utils/six.py

@@ -394,10 +394,14 @@ if PY3:
     _iterlists = "lists"
     _assertRaisesRegex = "assertRaisesRegex"
     _assertRegex = "assertRegex"
+    memoryview = memoryview
 else:
     _iterlists = "iterlists"
     _assertRaisesRegex = "assertRaisesRegexp"
     _assertRegex = "assertRegexpMatches"
+    # memoryview and buffer are not stricly equivalent, but should be fine for
+    # django core usage (mainly BinaryField)
+    memoryview = buffer
 
 
 def iterlists(d):

+ 16 - 0
docs/ref/models/fields.txt

@@ -347,6 +347,22 @@ A 64 bit integer, much like an :class:`IntegerField` except that it is
 guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The
 default form widget for this field is a :class:`~django.forms.TextInput`.
 
+``BinaryField``
+-------------------
+
+.. class:: BinaryField([**options])
+
+.. versionadded:: 1.6
+
+A field to store raw binary data. It only supports ``bytes`` assignment. Be
+aware that this field has limited functionality. For example, it is not possible
+to filter a queryset on a ``BinaryField`` value.
+
+.. admonition:: Abusing ``BinaryField``
+
+    Although you might think about storing files in the database, consider that
+    it is bad design in 99% of the cases. This field is *not* a replacement for
+    proper :ref.`static files <static-files> handling.
 
 ``BooleanField``
 ----------------

+ 6 - 0
docs/releases/1.6.txt

@@ -53,6 +53,12 @@ UTC. This limitation was lifted in Django 1.6. Use :meth:`QuerySet.datetimes()
 <django.db.models.query.QuerySet.datetimes>` to perform time zone aware
 aggregation on a :class:`~django.db.models.DateTimeField`.
 
+``BinaryField`` model field
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A new :class:`django.db.models.BinaryField` model field allows to store raw
+binary data in the database.
+
 Minor features
 ~~~~~~~~~~~~~~
 

+ 4 - 0
tests/model_fields/models.py

@@ -106,6 +106,10 @@ class VerboseNameField(models.Model):
 class DecimalLessThanOne(models.Model):
     d = models.DecimalField(max_digits=3, decimal_places=3)
 
+class DataModel(models.Model):
+    short_data = models.BinaryField(max_length=10, default=b'\x08')
+    data = models.BinaryField()
+
 ###############################################################################
 # FileField
 

+ 24 - 2
tests/model_fields/tests.py

@@ -12,8 +12,8 @@ from django.utils import six
 from django.utils import unittest
 
 from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post,
-    NullBooleanModel, BooleanModel, Document, RenamedField, VerboseNameField,
-    FksToBooleans)
+    NullBooleanModel, BooleanModel, DataModel, Document, RenamedField,
+    VerboseNameField, FksToBooleans)
 
 from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests,
     TwoImageFieldTests, ImageFieldNoDimensionsTests,
@@ -424,3 +424,25 @@ class FileFieldTests(unittest.TestCase):
         field = d._meta.get_field('myfile')
         field.save_form_data(d, 'else.txt')
         self.assertEqual(d.myfile, 'else.txt')
+
+
+class BinaryFieldTests(test.TestCase):
+    binary_data = b'\x00\x46\xFE'
+
+    def test_set_and_retrieve(self):
+        data_set = (self.binary_data, six.memoryview(self.binary_data))
+        for bdata in data_set:
+            dm = DataModel(data=bdata)
+            dm.save()
+            dm = DataModel.objects.get(pk=dm.pk)
+            self.assertEqual(bytes(dm.data), bytes(bdata))
+            # Resave (=update)
+            dm.save()
+            dm = DataModel.objects.get(pk=dm.pk)
+            self.assertEqual(bytes(dm.data), bytes(bdata))
+            # Test default value
+            self.assertEqual(bytes(dm.short_data), b'\x08')
+
+    def test_max_length(self):
+        dm = DataModel(short_data=self.binary_data*4)
+        self.assertRaises(ValidationError, dm.full_clean)