English 中文(简体)
Can Django models use MySQL functions?
原标题:

Is there a way to force Django models to pass a field to a MySQL function every time the model data is read or loaded? To clarify what I mean in SQL, I want the Django model to produce something like the following:

On model load: SELECT AES_DECRYPT(fieldname, password) FROM tablename

On model save: INSERT INTO tablename VALUES (AES_ENCRYPT(userinput, password))

最佳回答

I would define a custom modelfield for the column you want encrypted/decrypted. Override the to_python method to run the decryption when the model is loaded, and get_db_prep_value to run the encryption on saving.

Remember to set the field s metaclass to models.SubfieldBase otherwise these methods won t be called.

问题回答

Instead of on model load, you can create a property on your model, and when the property is accessed, it can read the database:

def _get_foobar(self):
    if not hasattr(self,  _foobar ):

        cursor = connection.cursor()
        self._foobar = cursor.execute( SELECT AES_DECRYPT(fieldname, password) FROM tablename )[0]
    return self._foobar
foobar = property(_get_foobar)

Now after loading, you can refer to mything.foobar, and the first access will retrieve the decryption from the database, holding onto it for later accesses.

This also has the advantage that if some of your code has no use for the decryption, it won t happen.

Here is a working solution, based in part on (http://www.djangosnippets.org/snippets/824/):

class Employee(models.Model):
   social_security_number = models.CharField(max_length=32)

   def _get_ssn(self):
       cursor = connection.cursor()
       cursor.execute("SELECT AES_DECRYPT(UNHEX(social_security_number), %s) as ssn FROM tablename WHERE id=%s", [settings.SECRET_KEY, self.id])
       return cursor.fetchone()[0]

   def _set_ssn(self, ssn_value):
       cursor = connection.cursor()
       cursor.execute("SELECT HEX(AES_ENCRYPT(%s, %s)) as ssn", [ssn_value, settings.SECRET_KEY])
       self.social_security_number = cursor.fetchone()[0]

   ssn = property(_get_ssn, _set_ssn)

And the results:

>>> from foo.bar.models import Employee
>>> p=Employee.objects.create(ssn= 123-45-6789 )
>>> p.ssn
 123-45-6789 

mysql> select * from foo_employee;
+----+----------------------------------+
| id | social_security_number           |
+----+----------------------------------+
| 31 | 41DF2D946C9186BEF77DD3307B85CC8C |
+----+----------------------------------+
1 row in set (0.00 sec)

It s definitely hackish, but it seems Django won t let you do it any other way at the moment. It s also worth noting that to_python will be called every time you change the value in python in addition to when it is first loaded.

from django.db import connection, models
import re

class EncryptedField(models.TextField):
    __metaclass__ = models.SubfieldBase

    def to_python(self, value):
        if not re.match( ^*some pattern here*$ , value):
            cursor = connection.cursor()
            cursor.execute( SELECT AES_DECRYPT(%s, %s) , [value, settings.SECRET_KEY])
            return cursor.fetchone()[0]
        return value

    def get_db_prep_value(self, value):
        cursor = connection.cursor()
        cursor.execute( SELECT AES_ENCRYPT(%s, %s) , [value, settings.SECRET_KEY])
        return cursor.fetchone()[0]


class Encrypt(models.Model):
    encrypted = EncryptedField(max_length = 32)

After deep search in the implementation of Django ORM,

I found that it can be solved by something like this:

class EncryptedField(models.BinaryField):
    @staticmethod
    def _pad(value):
        return value + (AES.block_size - len(value) % AES.block_size) * b( x00 )

    def _encrypt(self, data):
        if not data:
            return None
        return self.cipher.encrypt(self._pad(data.encode( utf8 )))

    def _decrypt(self, data):
        if not data:
            return None
        return self.cipher.decrypt(force_bytes(data)).rstrip(b x00 ).decode( utf8 )

    @property
    def cipher(self):
        return AES.new(KEY, mode=AES.MODE_CBC, IV=self._iv)

    def get_db_prep_value(self, value, connection, prepared=False):
        if value is not None:
            value = self._encrypt(value)
            if value:
                value = binascii.hexlify(value)
        return value

    def get_placeholder(self, value, compiler, connection):
        return  unhex(%s) 

Using Django signals you can do stuff when a model instance is saved, but as far as I know you can t trigger anything on read.

EDIT: My bad, it seems you can do stuff when initializing a model instance.





相关问题
SQL SubQuery getting particular column

I noticed that there were some threads with similar questions, and I did look through them but did not really get a convincing answer. Here s my question: The subquery below returns a Table with 3 ...

please can anyone check this while loop and if condition

<?php $con=mysql_connect("localhost","mts","mts"); if(!con) { die( unable to connect . mysql_error()); } mysql_select_db("mts",$con); /* date_default_timezone_set ("Asia/Calcutta"); $date = ...

php return a specific row from query

Is it possible in php to return a specific row of data from a mysql query? None of the fetch statements that I ve found return a 2 dimensional array to access specific rows. I want to be able to ...

Character Encodings in PHP and MySQL

Our website was developed with a meta tag set to... <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> This works fine for M-dashes and special quotes, etc. However, I ...

Pagination Strategies for Complex (slow) Datasets

What are some of the strategies being used for pagination of data sets that involve complex queries? count(*) takes ~1.5 sec so we don t want to hit the DB for every page view. Currently there are ~...

Averaging a total in mySQL

My table looks like person_id | car_id | miles ------------------------------ 1 | 1 | 100 1 | 2 | 200 2 | 3 | 1000 2 | 4 | 500 I need to ...

热门标签