Add a BinaryField model field

Thanks Michael Jung, Charl Botha and Florian Apolloner for review
and help on the patch.
This commit is contained in:
Claude Paroz 2012-12-13 22:11:06 +01:00
parent 0f306cad84
commit 8ee1eddb7e
10 changed files with 85 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1291,3 +1291,30 @@ class URLField(CharField):
} }
defaults.update(kwargs) defaults.update(kwargs)
return super(URLField, self).formfield(**defaults) 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 File

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

View File

@ -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 guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807. The
default form widget for this field is a :class:`~django.forms.TextInput`. 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`` ``BooleanField``
---------------- ----------------

View File

@ -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 <django.db.models.query.QuerySet.datetimes>` to perform time zone aware
aggregation on a :class:`~django.db.models.DateTimeField`. 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 Minor features
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@ -106,6 +106,10 @@ class VerboseNameField(models.Model):
class DecimalLessThanOne(models.Model): class DecimalLessThanOne(models.Model):
d = models.DecimalField(max_digits=3, decimal_places=3) 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 # FileField

View File

@ -12,8 +12,8 @@ from django.utils import six
from django.utils import unittest from django.utils import unittest
from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post,
NullBooleanModel, BooleanModel, Document, RenamedField, VerboseNameField, NullBooleanModel, BooleanModel, DataModel, Document, RenamedField,
FksToBooleans) VerboseNameField, FksToBooleans)
from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests, from .imagefield import (ImageFieldTests, ImageFieldTwoDimensionsTests,
TwoImageFieldTests, ImageFieldNoDimensionsTests, TwoImageFieldTests, ImageFieldNoDimensionsTests,
@ -424,3 +424,25 @@ class FileFieldTests(unittest.TestCase):
field = d._meta.get_field('myfile') field = d._meta.get_field('myfile')
field.save_form_data(d, 'else.txt') field.save_form_data(d, 'else.txt')
self.assertEqual(d.myfile, '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)