Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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
@claudep claudep authored
View
1  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)',
View
1  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)',
View
1  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)',
View
1  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)',
View
27 django/db/models/fields/__init__.py
@@ -1291,3 +1291,30 @@ def formfield(self, **kwargs):
}
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
View
4 django/utils/six.py
@@ -394,10 +394,14 @@ def with_metaclass(meta, base=object):
_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):
View
16 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``
----------------
View
6 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
~~~~~~~~~~~~~~
View
4 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
View
26 tests/model_fields/tests.py
@@ -12,8 +12,8 @@
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 @@ def test_changed(self):
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)
Please sign in to comment.
Something went wrong with that request. Please try again.