如何指示哪个模型的字段引发了ValidationError?

时间:2019-05-27 20:13:46

标签: python django django-models

假设我有一个简单的Django模型:

class Transaction(models.Model):    
    description = models.CharField('description', max_length=150,
                                   validators=[MinLengthValidator(2, 'Description\'s min length is 2'), ])
    amount = models.DecimalField('amount', max_digits=10, decimal_places=2, 
                                 validators=[MinValueValidator(1, 'Min value is 1'), ])
    user = models.ForeignKey(User)

    # to trigger model fields' validation
    def clean(self, *args, **kwargs):
        super(Transaction, self).clean(*args, **kwargs)

    def save(self, *args, **kwargs):
        self.full_clean()
        super(Transaction, self).save(*args, **kwargs)

我想进行单元测试,它可以精确地检查ValidationError是否由 description 字段而不是 amount 字段(或任何其他字段)引起其他)。

所以我有一段测试,它以原始方式检查e.exception中是否存在 description 字段:

def test_model_requires_description_min_2_characters(self):
    with self.assertRaises(ValidationError) as e:
        Transaction.objects.create(description='a', amount="50", user=self.user1)
    err_dict = eval(str(e.exception))
    self.assertIn('description', err_dict.keys())

但是我真的不喜欢使用eval(),并且我相信还有一种更优雅的方式来表明ValidationError的来源。我该怎么办?

编辑:我的模型类还包含重写的clean()save()方法,因此验证程序运行良好。

2 个答案:

答案 0 :(得分:2)

我会做这样的事情,Exception in thread "Thread-1" java.lang.IllegalArgumentException: Object must be initiated on a crossroad. at com.google.common.base.Preconditions.checkArgument(Preconditions.java:135) at com.github.rinde.rinsim.core.model.road.GraphRoadModelImpl.addObjectAt(GraphRoadModelImpl.java:106) at be.kuleuven.cs.LuysterborgMonsecour.executionContext.ExecutionContext.addSmartMessage(ExecutionContext.java:68) at be.kuleuven.cs.LuysterborgMonsecour.agent.DeliveryAgent.tickImpl(DeliveryAgent.java:157) ... 具有属性ValidationError,我们已经可以使用它来进行测试

error_dict

答案 1 :(得分:0)

并非所有library(shiny) library(magrittr) # for pipe operator, %>%, used with debounce(). debounceDelay = 2000 # milliseconds ui <- fluidPage( titlePanel("Data affect Slider, both affect Subsequent Long Computation"), sidebarLayout( sidebarPanel( # Data input: textAreaInput( inputId = "dataText" , label = "Type data, then click Submit:" , value = "10 20 30 40" , width = "200px" , height = "100px" ) , p(actionButton(inputId = "dataSubmit" , label = "Submit Data")) , # Slider input, to be updated by data: sliderInput( inputId = "slider1" , label = HTML("Constant to Add to Mean of Data (after debounce delay):") , min=3000 , max=5000 , value=4000 , round = FALSE , step = 1 , ticks = FALSE ) ) , # end sidebarPanel mainPanel(textOutput("theOutput")) ) # end sidebarLayout ) # end ui fluidPage server <- function(input, output, session) { sliderUpdates <- reactiveValues(latestProgrammatic = Sys.time(), timeDiff = 0) # Parse data values out of data text: theData = reactive({ input$dataSubmit# establish dependency on dataSubmit button yText = isolate(input$dataText) # remove dependency on dataText y = as.numeric(unlist(strsplit(yText, "\\s+")[[1]])) if (any(is.na(y)) | length(y) < 2) { y = c(-12.3, 45.6, 78.9) # arbitrary replacement values updateTextAreaInput(session , inputId = "dataText" , value = paste(as.character(y), collapse = " ")) } return(y) }) # Computation on data for using in slider update: upUI <- reactive({ low = min(theData()) val = median(theData()) high = max(theData()) return(list( low = low , val = val , high = high )) }) # Update slider based on data values: observeEvent(upUI(), { sliderUpdates$latestProgrammatic <- Sys.time() print(paste("Programmatic slider update was triggered:" , sliderUpdates$latestProgrammatic)) updateSliderInput( session , inputId = "slider1" , min = upUI()$low , max = upUI()$high , value = upUI()$val ) }) # Debounce the slider value so it doesn't instantly trigger a cascade of long # computations sliderValue <- reactive({ latestUnkown <- Sys.time() print(paste("Slider was updated:" , latestUnkown)) sliderUpdates$timeDiff <- latestUnkown - sliderUpdates$latestProgrammatic req(input$slider1) return(input$slider1) }) %>% debounce(debounceDelay) # Compute output: output$theOutput <- renderText({ req(theData(), sliderValue(), req(isolate(sliderUpdates$timeDiff))) print(paste("Elapsed time since the last programmatic slider update:", isolate(sliderUpdates$timeDiff))) if(isolate(sliderUpdates$timeDiff) > 0.2){ Sys.sleep(3) # simulate lengthy computation time return( paste( "Time-consuming computation... Mean of data plus slider value: " , mean(theData()) + sliderValue() ) ) } else { NULL } }) # end of renderText } # end server shinyApp(ui = ui, server = server) 对象都 ValidationError。我们可以从implementation of the constructor of the ValidationError [GitHub]中得出。这取决于error_dict(构造的第一个参数)是否是字典。

我们可以要做的是为此使用getattr(..) [Python-doc],其后备值如下:

message

因此,鉴于def test_model_requires_description_min_2_characters(self): with self.assertRaises(ValidationError) as e: Transaction.objects.create(description='a', amount="50", user=self.user1) self.assertIn('description', getattr(e.exception, 'err_dict', {}))不存在,我们将让error_dict返回一个空字典,因此getattr(..)将失败。

我们还可以为此实现一个实用程序功能,例如:

assertIn

因此,您可以在提供额外的断言功能的实用程序类中将此类_singleton = object() class SomeTestCase(TestCase): def assertKeyInErrorDict(self, key, error): error_dict = getattr(error, 'err_dict', _singleton) if error_dict is _singleton: self.fail('The error {} has no error_dict'.format(error)) else: self.assertIn(key, error_dict) def test_model_requires_description_min_2_characters(self): with self.assertRaises(ValidationError) as e: Transaction.objects.create(description='a', amount="50", user=self.user1) self.assertKeyInErrorDict('description', e.exception)关联起来,然后在所有子类中使用它,从而删除许多 boilerplate 代码。