Django Model __unicode__ raising exception when logging

血红的双手。 提交于 2019-12-23 12:32:29

问题


I have a model class that looks like the following:

class Address(models.Model):
    # taking length of address/city fields from existing UserProfile model
    address_1 = models.CharField(max_length=128,
                                 blank=False,
                                 null=False)

    address_2 = models.CharField(max_length=128,
                                 blank=True,
                                 null=True)

    address_3 = models.CharField(max_length=128,
                                 blank=True,
                                 null=True)

    unit = models.CharField(max_length=10,
                            blank=True,
                            null=True)

    city = models.CharField(max_length=128,
                            blank=False,
                            null=False)

    state_or_province = models.ForeignKey(StateOrProvince)

    postal_code = models.CharField(max_length=20,
                                   blank=False,
                                   null=False)

    phone = models.CharField(max_length=20,
                             blank=True,
                             null=True)

    is_deleted = models.BooleanField(default=False,
                                     null=False)

    def __unicode__(self):
        return u"{}, {} {}, {}".format(
            self.city, self.state_or_province.postal_abbrev, self.postal_code, self.address_1)

The key being the __unicode__ method. I have a customer model that has a foreign key field to this table, and I am doing the following logging:

log.debug(u'Generated customer [{}]'.format(vars(customer)))

This works fine, but if an address_1 field value contains a non ascii value, say

57562 Vån Ness Hwy

the system is throwing the following exception:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 345: ordinal not in range(128)

I tracked this down to a strange method in django/db/models/base.py:

def __repr__(self):
        try:
            u = six.text_type(self)
        except (UnicodeEncodeError, UnicodeDecodeError):
            u = '[Bad Unicode data]'
        return force_str('<%s: %s>' % (self.__class__.__name__, u))

as you can see, this method is getting called to force_str, which doesn't get handled correctly. is this a bug? if unicode is getting called on my object, shouldn't everything be in unicode?


回答1:


According to the docs, when a python object is passed as an argument to '{}'.format(obj),

A general convention is that an empty format string ("") [within the "{}"] produces the same result as if you had called str() on the value.

This means you're effectively calling str(vars(customer)), and vars(customer) returns a dict.

Calling str() on a dict will call repr() on its keys and values because otherwise you'd get ambiguous output (eg str(1) == str('1') == '1' but repr(1) == '1' and repr('1') == '"1"' (see Difference between __str__ and __repr__ in Python)

Therefore repr() is still being called on your Address, which returns a string.

Now returning unicode from repr() is not allowed in Python 2 - https://stackoverflow.com/a/3627835/648176, so you'll need to either override __str__() in your model to make it handle decoding into ascii (Django docs), or do something like:

string_dict = {str(k): str(v) for (k, v) in vars(customer).items()}
log.debug(u'Generated customer [{}]'.format(string_dict))



回答2:


Try decode for non utf-8 chars with:

def __unicode__(self):
        return u"{}, {} {}, {}".format(
            self.city, self.state_or_province.postal_abbrev, self.postal_code, self.address_1.decode('utf-8'))



回答3:


This is more of a hack that a pretty answer, but I'll still throw my two cents to the pile. Just subclass the "logging.Handler" you are using, and change the 'emit' method (if it is the one causing the exceptions).

Pros

Very easy to setup. After setup, no actions required with any model/data.

Cons

The result is that there will be no UnicodeErrors, but the log file will have "strange looking strings starting with a backslash" where ever there was a unicode mark. For example 🦄 will turn into '\xf0\x9f\xa6\x84\'. Perhaps you could use a script to translate the '\xf0\x9f\xa6\x84\' back to unicode inside the log file when needed.

The steps are

1) Make a "custom_logging.py", which you can import to your settings.py

from logging import FileHandler

class Utf8FileHandler(FileHandler):
    """
          This is a hack-around version of the logging.Filehandler

        Prevents errors of the type
        UnicodeEncodeError: 'charmap' codec can't encode character '\U0001f984' in position 150: character maps to <undefined>
    """
    def __init__(self, *args, **kwargs):
        FileHandler.__init__(self, *args, **kwargs)

    def emit(self, record):
        """
        Emit a record.

        If a formatter is specified, it is used to format the record.
        The record is then written to the stream with a trailing newline.  If
        exception information is present, it is formatted using
        traceback.print_exception and appended to the stream.  If the stream
        has an 'encoding' attribute, it is used to determine how to do the
        output to the stream.
        """
        try:
            msg = self.format(record)
            stream = self.stream
            stream.write(msg)
            stream.write(self.terminator)
            self.flush()
        except Exception:
            # The hack.
            try:
                stream.write(str(msg.encode('utf-8'))[2:-1])
                stream.write(self.terminator)
                self.flush()
            # End of the hack.
            except Exception:
                self.handleError(record)

2) In your settings.py, use your custom made filehandler, like this (set the LOGGING['handlers']['file']['class'] to point to the custom_logging module.):

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
    },
    'handlers': {
        'file': {
            'level': 'DEBUG',
            'class': 'config.custom_logging.Utf8FileHandler',
            'filename': secrets['DJANGO_LOG_FILE'],
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}


来源:https://stackoverflow.com/questions/32193797/django-model-unicode-raising-exception-when-logging

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!