我的 Django 应用程序中有一个用户定义的异常:
class TemplateMatchError(Exception):
...
我有一段代码可以在常规中捕获此异常try ... except
:
try:
if template.match(text):
return attrs
except TemplateMatchError as e:
continue
我注意到在生产中,当DEBUG=True
这个错误未被捕获时,如果引发,我的浏览器会显示黄色的 Django 堆栈跟踪页面。时DEBUG=False
,异常被捕获。
我对这种行为感到惊讶,因为这意味着调试设置改变了普通 python 的行为try...except
。这是一个正确的解释,如果是这样,为什么 Django 会这样工作?
更新:根据我在下面发布的实际回溯的评论(名称与上面的玩具示例不同):
Environment:
Request Method: POST
Request URL: http://mysite.com/api/call/6/
Django Version: 1.4.2
Python Version: 2.7.3
Installed Applications:
('longerusername',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.sites',
'django.contrib.admin',
'south',
'django_extensions',
'django.contrib.staticfiles',
'crispy_forms',
'api',
'rest_framework')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware')
Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
111. response = callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/compat.py" in view
121. return self.dispatch(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/views/decorators/csrf.py" in wrapped_view
77. return view_func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/views.py" in dispatch
327. response = self.handle_exception(exc)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/views.py" in dispatch
324. response = handler(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/generics.py" in put
469. return self.update(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/mixins.py" in update
129. if serializer.is_valid():
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in is_valid
479. return not self.errors
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in errors
471. ret = self.from_native(data, files)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in from_native
867. instance = super(ModelSerializer, self).from_native(data, files)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in from_native
319. attrs = self.perform_validation(attrs)
File "/usr/local/lib/python2.7/dist-packages/rest_framework/serializers.py" in perform_validation
260. attrs = validate_method(attrs, source)
File "/home/uname/api/serializers.py" in validate_text
68. if template.match(text):
File "/home/uname/api/models.py" in match
135. raise TemplateMatchError()
Exception Type: TemplateMatchError at /api/call/6/
Exception Value: Not a match
这是我的models.py:
import re
from datetime import datetime
from django.db import models
from django.contrib.auth.models import User as AuthUser
from otalo.ao.models import User
from otalo.surveys.models import Survey
class XCall(models.Model):
created_on = models.DateTimeField(auto_now_add=True)
send_on = models.DateTimeField(default=datetime.now)
auth_user = models.ForeignKey(AuthUser, related_name='calls')
recipient = models.ForeignKey(User)
text = models.CharField(max_length=4096)
backup_calls = models.IntegerField(blank=True, null=True)
'''
' Associate with the survey, since
' all the calls can be traced through it.
' This billing/auditing purposes.
'
' This can be lazily created at the time of
' scheduling the call, so make it nullable
'
'''
survey = models.ForeignKey(Survey, null=True, blank=True)
def __unicode__(self):
return unicode(self.auth_user) + '-' + unicode(self.recipient)
class TemplateMatchError(Exception):
STD_MESSAGE = 'Not a match'
def __init__(self, msg=None):
if msg is not None:
self.msg = TemplateMatchError.STD_MESSAGE + ': ' + msg
else:
self.msg = TemplateMatchError.STD_MESSAGE
def __str__(self):
return self.msg
class XTemplate(models.Model):
VARTYPE_NUM = '_N_'
VARTYPE_WORD = '_W_'
VARTYPE_DATETIME = '_DT_'
VARTYPE_DATE = '_D_'
VARTYPE_TIME = '_T_'
# add grouping for regexes for easy extraction
VARTYPE_REGEXS = { VARTYPE_NUM: r'(\d+(?:\.\d{1,2})?)', # supports decimals up to two precision points. Could be more but then
# the prompting would start to sound weird
VARTYPE_WORD: r'(\w+)',
# Match dates and times as words
# so that your match function can
# try to convert to datetime for more
# detailed error messages
VARTYPE_DATETIME: r'(\w+)',
VARTYPE_DATE: r'(\w+)',
VARTYPE_TIME: r'(\w+)',
}
DATE_FORMATS = {
VARTYPE_DATETIME: '%d-%m-%y %H:%M',
VARTYPE_DATE: '%d-%m-%y',
VARTYPE_TIME: '%H:%M'
}
created_on = models.DateTimeField(auto_now_add=True)
auth_user = models.ForeignKey(AuthUser, related_name='templates')
'''
' Encodes the wildcards and their type.
' e.g. Your account number _N_ is of type _W_ expiring at time _D_
'''
text = models.CharField(max_length=4096)
'''
' For common prompts like numbers and dates and times
'''
language = models.CharField(max_length=24)
STATUS_PENDING = 0
STATUS_ACTIVE = 1
STATUS_INACTIVE = 2
STATUSES = (
(STATUS_PENDING, 'Pending'),
(STATUS_ACTIVE, 'Active'),
(STATUS_INACTIVE, 'Inactive'),
)
status = models.IntegerField(choices=STATUSES)
'''
' Compare the inupt text to this template's text;
' return the match object if it matches, else throw an error
'''
def match(self, input):
pattern = self.text
# first convert the template pattern into a regular expression
vars = [var.group() for var in re.finditer('_[A-Z]_', pattern)]
re_pattern = pattern
for vartype, regex in XTemplate.VARTYPE_REGEXS.iteritems():
re_pattern = re_pattern.replace(vartype, regex)
# now try an initial match of the structure of the input
match = re.match(re_pattern, input)
if match:
# make sure words are in the wordlist
# numbers are valid numbers
# and dates are in the proper format
vocab = [word.text for word in self.vocabulary.all()]
vals = match.groups()
for i in range(len(vars)):
if i > len(vals):
raise TemplateMatchError('Missing a variable in input')
var = vars[i]
val = vals[i]
if var == XTemplate.VARTYPE_NUM:
try:
float(val)
except ValueError as e:
raise TemplateMatchError('Invalid number')
elif var == XTemplate.VARTYPE_WORD:
if val not in vocab:
raise TemplateMatchError('Word not in vocabulary')
elif var == XTemplate.VARTYPE_DATETIME or var == XTemplate.VARTYPE_DATE or var == XTemplate.VARTYPE_TIME:
format = XTemplate.DATE_FORMATS[var]
try:
date = datetime.strptime(val, format)
except ValueError as e:
raise TemplateMatchError('Invalid date, time, or datetime format - ' + val)
return match
else:
raise TemplateMatchError()
def __unicode__(self):
return self.text + '-' + unicode(self.auth_user)
class XWord(models.Model):
text = models.CharField(max_length=128)
template = models.ForeignKey(XTemplate, related_name='vocabulary')
def __unicode__(self):
return self.text
有问题的序列化程序类:
class CallSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='call-detail',
)
recipient = PhoneNumberField(read_only=False)
status = SurveySerializer(source='survey', read_only=True)
def validate_text(self, attrs, source):
text = attrs['text']
auth = self.context['request'].user
templates = auth.templates.all()
for template in templates:
try:
if template.match(text):
return attrs
except TemplateMatchError as e:
continue
raise serializers.ValidationError("Call text does not match a registered template")
class Meta:
model = XCall
fields = ('url', 'id', 'text', 'recipient', 'send_on', 'backup_calls', 'status')
lookup_field= 'pk'