Skip to content

Commit efef8e8

Browse files
authored
Merge pull request svetlyak40wt#50 from nautilebleu/master
Add support for django 1.10
2 parents 43b8bbf + d84bf54 commit efef8e8

File tree

10 files changed

+258
-186
lines changed

10 files changed

+258
-186
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ parts/
1111
test.sqlite
1212
env
1313
pip-log.txt
14+
.tox

.travis.yml

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,19 @@
11
language: python
2-
script: ./run-tests.sh
2+
python:
3+
- "3.5"
4+
- "3.4"
5+
- "2.7"
6+
sudo: false
7+
env:
8+
- TOX_ENV=django19
9+
- TOX_ENV=django18
10+
- TOX_ENV=django110
11+
- TOX_ENV=djangomaster
12+
matrix:
13+
fast_finish: true
14+
allow_failures:
15+
- env: TOX_ENV=djangomaster
16+
install:
17+
- pip install tox
18+
script:
19+
- tox -e $TOX_ENV

requirements.txt

-5
This file was deleted.

run-tests.sh

-10
This file was deleted.

setup.py

+19-14
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,21 @@
33
version = '0.3.0'
44

55
setup(
6-
name = 'django-fields',
7-
version = version,
8-
description = 'Django-fields is an application which includes different kinds of models fields.',
9-
keywords = 'django apps tools collection',
10-
license = 'New BSD License',
11-
author = 'Alexander Artemenko',
12-
author_email = '[email protected]',
13-
url = 'https://github.com/svetlyak40wt/django-fields/',
14-
install_requires = ['pycrypto', ],
6+
name='django-fields',
7+
version=version,
8+
description='Django-fields is an application which includes different kinds of models fields.',
9+
keywords='django apps tools collection',
10+
license='New BSD License',
11+
author='Alexander Artemenko',
12+
author_email='[email protected]',
13+
url='https://github.com/svetlyak40wt/django-fields/',
14+
install_requires=[
15+
'django',
16+
'pycrypto',
17+
'nose',
18+
'django-nose==1.4.4',
19+
'tox',
20+
],
1521
classifiers=[
1622
'Development Status :: 2 - Pre-Alpha',
1723
'Environment :: Plugins',
@@ -21,9 +27,8 @@
2127
'Programming Language :: Python',
2228
'Topic :: Software Development :: Libraries :: Python Modules',
2329
],
24-
package_dir = {'': 'src'},
25-
packages = ['django_fields'],
26-
include_package_data = True,
30+
package_dir={'': 'src'},
31+
packages=['django_fields'],
32+
include_package_data=True,
33+
test_suite="runtests.runtests",
2734
)
28-
29-

src/django_fields/fields.py

+88-35
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import binascii
2+
import codecs
23
import datetime
34
import string
45
import sys
@@ -8,7 +9,6 @@
89
from django.forms import fields
910
from django.db import models
1011
from django.conf import settings
11-
from django.utils.encoding import smart_str, force_unicode
1212
from django.utils.translation import ugettext_lazy as _
1313
from Crypto import Random
1414
from Crypto.Random import random
@@ -28,6 +28,14 @@
2828
except:
2929
import pickle
3030

31+
if sys.version_info[0] == 3:
32+
PYTHON3 = True
33+
from django.utils.encoding import smart_str, force_text as force_unicode
34+
else:
35+
PYTHON3 = False
36+
from django.utils.encoding import smart_str, force_unicode
37+
38+
3139
class BaseEncryptedField(models.Field):
3240
'''This code is based on the djangosnippet #1095
3341
You can find the original at http://www.djangosnippets.org/snippets/1095/'''
@@ -76,15 +84,20 @@ def __init__(self, *args, **kwargs):
7684
super(BaseEncryptedField, self).__init__(*args, **kwargs)
7785

7886
def _is_encrypted(self, value):
79-
return isinstance(value, basestring) and value.startswith(self.prefix)
87+
if PYTHON3 is True:
88+
is_enc = isinstance(value, str) and value.startswith(self.prefix)
89+
return is_enc
90+
else:
91+
return isinstance(value, basestring) and value.startswith(
92+
self.prefix)
8093

8194
def _get_padding(self, value):
8295
# We always want at least 2 chars of padding (including zero byte),
8396
# so we could have up to block_size + 1 chars.
8497
mod = (len(value) + 2) % self.cipher.block_size
8598
return self.cipher.block_size - mod + 2
8699

87-
def to_python(self, value):
100+
def from_db_value(self, value, expression, connection, context):
88101
if self._is_encrypted(value):
89102
if self.block_type:
90103
self.iv = binascii.a2b_hex(value[len(self.prefix):])[:len(self.iv)]
@@ -96,31 +109,51 @@ def to_python(self, value):
96109
else:
97110
decrypt_value = binascii.a2b_hex(value[len(self.prefix):])
98111
return force_unicode(
99-
self.cipher.decrypt(decrypt_value).split('\0')[0]
112+
self.cipher.decrypt(decrypt_value).split(b'\0')[0]
100113
)
101114
return value
102115

103116
def get_db_prep_value(self, value, connection=None, prepared=False):
104117
if value is None:
105118
return None
106119

107-
value = smart_str(value)
120+
if PYTHON3 is True:
121+
value = bytes(value.encode('utf-8'))
122+
else:
123+
value = smart_str(value)
108124

109125
if not self._is_encrypted(value):
110126
padding = self._get_padding(value)
111127
if padding > 0:
112-
value += "\0" + ''.join([
113-
random.choice(string.printable)
114-
for index in range(padding-1)
115-
])
128+
if PYTHON3 is True:
129+
value += bytes("\0".encode('utf-8')) + bytes(
130+
''.encode('utf-8')).join([
131+
bytes(random.choice(
132+
string.printable).encode('utf-8'))
133+
for index in range(padding - 1)])
134+
else:
135+
value += "\0" + ''.join([
136+
random.choice(string.printable)
137+
for index in range(padding - 1)
138+
])
116139
if self.block_type:
117140
self.cipher = self.cipher_object.new(
118141
self.secret_key,
119142
getattr(self.cipher_object, self.block_type),
120143
self.iv)
121-
value = self.prefix + binascii.b2a_hex(self.iv + self.cipher.encrypt(value))
144+
if PYTHON3 is True:
145+
value = self.prefix + binascii.b2a_hex(
146+
self.iv + self.cipher.encrypt(value)).decode('utf-8')
147+
else:
148+
value = self.prefix + binascii.b2a_hex(
149+
self.iv + self.cipher.encrypt(value))
122150
else:
123-
value = self.prefix + binascii.b2a_hex(self.cipher.encrypt(value))
151+
if PYTHON3 is True:
152+
value = self.prefix + binascii.b2a_hex(
153+
self.cipher.encrypt(value)).decode('utf-8')
154+
else:
155+
value = self.prefix + binascii.b2a_hex(
156+
self.cipher.encrypt(value))
124157
return value
125158

126159
def deconstruct(self):
@@ -136,7 +169,6 @@ def deconstruct(self):
136169

137170

138171
class EncryptedTextField(BaseEncryptedField):
139-
__metaclass__ = models.SubfieldBase
140172

141173
def get_internal_type(self):
142174
return 'TextField'
@@ -148,7 +180,6 @@ def formfield(self, **kwargs):
148180

149181

150182
class EncryptedCharField(BaseEncryptedField):
151-
__metaclass__ = models.SubfieldBase
152183

153184
def get_internal_type(self):
154185
return "CharField"
@@ -191,6 +222,9 @@ def formfield(self, **kwargs):
191222
return super(BaseEncryptedDateField, self).formfield(**defaults)
192223

193224
def to_python(self, value):
225+
return self.from_db_value(value)
226+
227+
def from_db_value(self, value, expression, connection, context):
194228
# value is either a date or a string in the format "YYYY:MM:DD"
195229

196230
if value in fields.EMPTY_VALUES:
@@ -199,7 +233,8 @@ def to_python(self, value):
199233
if isinstance(value, self.date_class):
200234
date_value = value
201235
else:
202-
date_text = super(BaseEncryptedDateField, self).to_python(value)
236+
date_text = super(BaseEncryptedDateField, self).from_db_value(
237+
value, expression, connection, context)
203238
date_value = self.date_class(*map(int, date_text.split(':')))
204239
return date_value
205240

@@ -218,7 +253,6 @@ def get_db_prep_value(self, value, connection=None, prepared=False):
218253

219254

220255
class EncryptedDateField(BaseEncryptedDateField):
221-
__metaclass__ = models.SubfieldBase
222256
form_widget = forms.DateInput
223257
form_field = forms.DateField
224258
save_format = "%Y:%m:%d"
@@ -228,7 +262,6 @@ class EncryptedDateField(BaseEncryptedDateField):
228262

229263
class EncryptedDateTimeField(BaseEncryptedDateField):
230264
# FIXME: This doesn't handle time zones, but Python doesn't really either.
231-
__metaclass__ = models.SubfieldBase
232265
form_widget = forms.DateTimeInput
233266
form_field = forms.DateTimeField
234267
save_format = "%Y:%m:%d:%H:%M:%S:%f"
@@ -248,11 +281,15 @@ def get_internal_type(self):
248281
return 'CharField'
249282

250283
def to_python(self, value):
284+
return self.from_db_value(value)
285+
286+
def from_db_value(self, value, expression, connection, context):
251287
# value is either an int or a string of an integer
252288
if isinstance(value, self.number_type) or value == '':
253289
number = value
254290
else:
255-
number_text = super(BaseEncryptedNumberField, self).to_python(value)
291+
number_text = super(BaseEncryptedNumberField, self).from_db_value(
292+
value, expression, connection, context)
256293
number = self.number_type(number_text)
257294
return number
258295

@@ -267,47 +304,67 @@ def get_db_prep_value(self, value, connection=None, prepared=False):
267304

268305

269306
class EncryptedIntField(BaseEncryptedNumberField):
270-
__metaclass__ = models.SubfieldBase
271-
max_raw_length = len(str(-sys.maxint - 1))
307+
if PYTHON3 is True:
308+
max_raw_length = len(str(-sys.maxsize - 1))
309+
else:
310+
max_raw_length = len(str(-sys.maxint - 1))
272311
number_type = int
273312
format_string = "%d"
274313

275314

276315
class EncryptedLongField(BaseEncryptedNumberField):
277-
__metaclass__ = models.SubfieldBase
278316
max_raw_length = None # no limit
279-
number_type = long
317+
if PYTHON3 is True:
318+
number_type = int
319+
else:
320+
number_type = long
280321
format_string = "%d"
281322

282323
def get_internal_type(self):
283324
return 'TextField'
284325

285326

286327
class EncryptedFloatField(BaseEncryptedNumberField):
287-
__metaclass__ = models.SubfieldBase
288328
max_raw_length = 150 # arbitrary, but should be sufficient
289329
number_type = float
290330
# If this format is too long for some architectures, change it.
291331
format_string = "%0.66f"
292332

293333

294334
class PickleField(models.TextField):
295-
__metaclass__ = models.SubfieldBase
296-
297335
editable = False
298336
serialize = False
299337

300338
def get_db_prep_value(self, value, connection=None, prepared=False):
301-
return pickle.dumps(value)
339+
if PYTHON3 is True:
340+
# When PYTHON3, we convert data to base64 to prevent errors when
341+
# unpickling.
342+
val = codecs.encode(pickle.dumps(value), 'base64').decode()
343+
return val
344+
else:
345+
return pickle.dumps(value)
302346

303347
def to_python(self, value):
304-
if not isinstance(value, basestring):
305-
return value
348+
return self.from_db_value(value)
349+
350+
def from_db_value(self, value, expression, connection, context):
351+
if PYTHON3 is True:
352+
if not isinstance(value, str):
353+
return value
354+
else:
355+
if not isinstance(value, basestring):
356+
return value
306357

307358
# Tries to convert unicode objects to string, cause loads pickle from
308359
# unicode excepts ugly ``KeyError: '\x00'``.
309360
try:
310-
return pickle.loads(smart_str(value))
361+
if PYTHON3 is True:
362+
# When PYTHON3, data are in base64 to prevent errors when
363+
# unpickling.
364+
val = pickle.loads(codecs.decode(value.encode(), "base64"))
365+
return val
366+
else:
367+
return pickle.loads(smart_str(value))
311368
# If pickle could not loads from string it's means that it's Python
312369
# string saved to PickleField.
313370
except ValueError:
@@ -317,14 +374,12 @@ def to_python(self, value):
317374

318375

319376
class EncryptedUSPhoneNumberField(BaseEncryptedField):
320-
__metaclass__ = models.SubfieldBase
321-
322377
def get_internal_type(self):
323378
return "CharField"
324379

325380
def formfield(self, **kwargs):
326381
try:
327-
from localflavor.us.forms import USPhoneNumberField
382+
from localflavor.us.forms import USPhoneNumberField
328383
except ImportError:
329384
from django.contrib.localflavor.us.forms import USPhoneNumberField
330385

@@ -334,23 +389,21 @@ def formfield(self, **kwargs):
334389

335390

336391
class EncryptedUSSocialSecurityNumberField(BaseEncryptedField):
337-
__metaclass__ = models.SubfieldBase
338-
339392
def get_internal_type(self):
340393
return "CharField"
341394

342395
def formfield(self, **kwargs):
343396
try:
344397
from localflavor.us.forms import USSocialSecurityNumberField
345398
except ImportError:
346-
from django.contrib.localflavor.us.forms import USSocialSecurityNumberField
399+
from django.contrib.localflavor.us.forms import USSocialSecurityNumberField
347400

348401
defaults = {'form_class': USSocialSecurityNumberField}
349402
defaults.update(kwargs)
350403
return super(EncryptedUSSocialSecurityNumberField, self).formfield(**defaults)
351404

405+
352406
class EncryptedEmailField(BaseEncryptedField):
353-
__metaclass__ = models.SubfieldBase
354407
description = _("E-mail address")
355408

356409
def get_internal_type(self):

0 commit comments

Comments
 (0)