Index: django/db/models/fields/__init__.py =================================================================== --- django/db/models/fields/__init__.py (revision 9285) +++ django/db/models/fields/__init__.py (working copy) @@ -417,6 +417,26 @@ defaults.update(kwargs) return super(CharField, self).formfield(**defaults) + +class BinaryField(Field): + """Sometimes we have fields that need to store a small amount of binary + data. This is different then a varchar on postgresql at least because it + will not allow fields that are just nul bytes. + """ + + def get_internal_type(self): + return "BinaryField" + +class BlobField(Field): + """Sometimes we have fields that need to store a large amounts of binary + data. + """ + + def get_internal_type(self): + return "BlobField" + + + # TODO: Maybe move this into contrib, because it's specialized. class CommaSeparatedIntegerField(CharField): def formfield(self, **kwargs): Index: django/db/backends/postgresql/introspection.py =================================================================== --- django/db/backends/postgresql/introspection.py (revision 9285) +++ django/db/backends/postgresql/introspection.py (working copy) @@ -4,6 +4,7 @@ # Maps type codes to Django Field types. data_types_reverse = { 16: 'BooleanField', + 17: 'BlobField', 21: 'SmallIntegerField', 23: 'IntegerField', 25: 'TextField', Index: django/db/backends/postgresql/creation.py =================================================================== --- django/db/backends/postgresql/creation.py (revision 9285) +++ django/db/backends/postgresql/creation.py (working copy) @@ -8,6 +8,8 @@ # If a column type is set to None, it won't be included in the output. data_types = { 'AutoField': 'serial', + 'BinaryField': 'bytea', + 'BlobField': 'bytea', 'BooleanField': 'boolean', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', Index: django/db/backends/sqlite3/creation.py =================================================================== --- django/db/backends/sqlite3/creation.py (revision 9285) +++ django/db/backends/sqlite3/creation.py (working copy) @@ -9,6 +9,8 @@ # schema inspection is more useful. data_types = { 'AutoField': 'integer', + 'BinaryField': 'BLOB', + 'BlobField': 'BLOB', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', Index: django/db/backends/mysql/base.py =================================================================== --- django/db/backends/mysql/base.py (revision 9285) +++ django/db/backends/mysql/base.py (working copy) @@ -29,6 +29,7 @@ from django.db.backends.mysql.creation import DatabaseCreation from django.db.backends.mysql.introspection import DatabaseIntrospection from django.db.backends.mysql.validation import DatabaseValidation +from django.utils.encoding import smart_unicode # Raise exceptions for database warnings if DEBUG is on from django.conf import settings @@ -46,10 +47,22 @@ # behavior as they are signed and include days -- and Django expects time, so # we still need to override that. django_conversions = conversions.copy() + +binstr_conversion = [(FLAG.BINARY, buffer), + (FLAG.BLOB, smart_unicode), + ] + django_conversions.update({ FIELD_TYPE.TIME: util.typecast_time, FIELD_TYPE.DECIMAL: util.typecast_decimal, FIELD_TYPE.NEWDECIMAL: util.typecast_decimal, + FIELD_TYPE.TINY_BLOB: binstr_conversion, + FIELD_TYPE.MEDIUM_BLOB: binstr_conversion, + FIELD_TYPE.LONG_BLOB: binstr_conversion, + FIELD_TYPE.BLOB: binstr_conversion, + FIELD_TYPE.VARCHAR: binstr_conversion, + FIELD_TYPE.VAR_STRING: binstr_conversion, + FIELD_TYPE.STRING: binstr_conversion, }) # This should match the numerical portion of the version numbers (we can treat Index: django/db/backends/mysql/introspection.py =================================================================== --- django/db/backends/mysql/introspection.py (revision 9285) +++ django/db/backends/mysql/introspection.py (working copy) @@ -7,7 +7,10 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): data_types_reverse = { - FIELD_TYPE.BLOB: 'TextField', + FIELD_TYPE.BLOB: 'BlobField', + FIELD_TYPE.LONG_BLOB: 'BlobField', + FIELD_TYPE.TINY_BLOB: 'BlobField', + FIELD_TYPE.MEDIUM_BLOB: 'BlobField', FIELD_TYPE.CHAR: 'CharField', FIELD_TYPE.DECIMAL: 'DecimalField', FIELD_TYPE.NEWDECIMAL: 'DecimalField', Index: django/db/backends/mysql/creation.py =================================================================== --- django/db/backends/mysql/creation.py (revision 9285) +++ django/db/backends/mysql/creation.py (working copy) @@ -8,6 +8,8 @@ # If a column type is set to None, it won't be included in the output. data_types = { 'AutoField': 'integer AUTO_INCREMENT', + 'BinaryField': 'varbinary(%(max_length)s)', + 'BlobField': 'blob', 'BooleanField': 'bool', 'CharField': 'varchar(%(max_length)s)', 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', @@ -63,4 +65,4 @@ field.rel.to._meta.db_table, field.rel.to._meta.pk.column) ] return table_output, deferred - \ No newline at end of file + Index: django/db/backends/__init__.py =================================================================== --- django/db/backends/__init__.py (revision 9285) +++ django/db/backends/__init__.py (working copy) @@ -164,7 +164,11 @@ from django.utils.encoding import smart_unicode, force_unicode # Convert params to contain Unicode values. - to_unicode = lambda s: force_unicode(s, strings_only=True) + def to_unicode(s): + if isinstance(s,buffer): + return u'' % len(s) + return force_unicode(s, strings_only=True) + if isinstance(params, (list, tuple)): u_params = tuple([to_unicode(val) for val in params]) else: Index: tests/modeltests/binary/__init__.py =================================================================== Index: tests/modeltests/binary/tests.py =================================================================== --- tests/modeltests/binary/tests.py (revision 0) +++ tests/modeltests/binary/tests.py (revision 0) @@ -0,0 +1,70 @@ +#!/usr/bin/python +# encoding: utf-8 + +try: + from models import Document, BinaryFieldTest +except: # for local testing + from binary.models import Document, BinaryFieldTest + +import unittest + + +class BlobFieldTests( unittest.TestCase ): + + def test_blob(self): + test_title = u'Hi there! привет!' + test_descr = u'описание документа' + + s = '' + for i in range(0,256): + s += chr(i) + s+= '\xff\xff' + + + test_data = buffer(s) + + doc = Document(title = test_title, + description = test_descr, + document = test_data) + doc.save() + doc_id = doc.id + doc = None + doc = Document.objects.get(id=doc_id) + assert doc.title == test_title, type(doc.title) + assert doc.description == test_descr, type(doc.description) + assert doc.document == test_data, type(doc.document) + +class BinaryFieldTests( unittest.TestCase ): + + def test_binary(self): + test_title = u'Hi there! привет!' + + s = '' + for i in range(0,10): + s += chr(i) + + s+= '\xff\xff' + + test_data = buffer(s) + + + tst = BinaryFieldTest(title = test_title, + binfield = test_data) + tst.save() + tst_id = tst.id + tst = None + tst = BinaryFieldTest.objects.get(id=tst_id) + assert tst.title == test_title, type(tst.title) + assert tst.binfield == test_data, type(tst.binfield) + + +def test_suite(): + return unittest.TestSuite(( + unittest.makeSuite(BlobFieldTests), + unittest.makeSuite(BinaryFieldTests) + )) + + + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') Index: tests/modeltests/binary/models.py =================================================================== --- tests/modeltests/binary/models.py (revision 0) +++ tests/modeltests/binary/models.py (revision 0) @@ -0,0 +1,18 @@ +""" +BlobField and BinaryField tests + +""" + +from django.db import models + +# Create your models here. +class Document(models.Model): + title = models.CharField(max_length=50) + description = models.TextField() + document = models.BlobField() + +class BinaryFieldTest(models.Model): + title = models.CharField(max_length=20) + binfield = models.BinaryField(max_length=20) + +