I try to upload data to a grails app. This works very good and the object is created in the database and the uploaded document is also present. Unfortunately i get a 404 error directly after the creation.
I am using grails 2.3.5 with the following code:
Action to save:
#Transactional
def save(Book bookInstance) {
if (bookInstance == null) {
notFound()
return
}
if (bookInstance.hasErrors()) {
respond bookInstance.errors, view:'create'
return
}
if(!bookInstance.id){
bookInstance.id = UUID.randomUUID().toString()
}
bookInstance.save flush:true
request.withFormat {
form {
flash.message = message(code: 'default.created.message', args: [message(code: 'Book.label', default: 'Book'), bookInstance.id])
redirect bookInstance
}
'*' {
respond bookInstance, [status: CREATED]
}
}
}
GSP:
<g:uploadForm action="save" class="form-horizontal">
<g:render template="form"/>
<div class="form-actions margin-top-medium">
<g:submitButton name="create" class="btn btn-primary" value="${message(code: 'default.button.create.label', default: 'Create')}" />
<button class="btn" type="reset"><g:message code="default.button.reset.label" default="Reset" /></button>
</div>
</g:uploadForm>
When the tag is used instead of the it works. The enctype="multipart/form-data" causes the error.
What can I try to solve this?
Thanks
You should check conf/Config.groovy and check that multipartForm: 'multipart/form-data', is present as a value for grails.mime.types, e.g.:
grails.mime.types = [
...
form: 'application/x-www-form-urlencoded',
multipartForm: 'multipart/form-data',
]
You also need to specify that it's a multipart form in your withFormat declaration:
request.withFormat {
form multipartForm
You can try 'render' or 'forward' instead of "respond" .
Related
I am new to grails I am trying to pass an object Id to a form. I have domains called Beer and recipe. Beer hasMany recipes and recipe belongsTo beer. I want to create a link to a new recipe from the beer show page passing the beer id in the url in order to create the association. Right now I have been trying to use this link <g:link controller="recpie" action="create" params="[beerid: selectedBeer.id]">Create Recipe</g:link> and retrieve it in the form as a hidden field to submit with the recipe form using <g:field type="text" name="beer.id" value="${beer}"/>.(I know that the hidden property is not set). What am I doing wrong? Also is there a better way to create this association?
Beer Domain Class
class Beer {
String name
String style
Recipe recipe
String toString() {
name
}
static hasMany = [recipe : Recipe]
static constraints = {
}
}
Recipe Domain Class
class Recipe {
String name
Float grainAmount
String yeast
float boilTime
Float hopAmount
float og
float fg
float ogTemp
float fgTemp
float fermTime
Beer beer
static belongsTo = [ beer: Beer]
static constraints = {
beer nullable: true
}
String toString() {
name
}
}
Beer Controller
package com.example
import static org.springframework.http.HttpStatus.*
import grails.transaction.Transactional
#Transactional(readOnly = true)
class BeerController {
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond Beer.list(params), model:[beerCount: Beer.count()]
}
def show(Beer beer) {
respond beer
}
def create() {
respond new Beer(params)
}
#Transactional
def save(Beer beer) {
if (beer == null) {
transactionStatus.setRollbackOnly()
notFound()
return
}
if (beer.hasErrors()) {
transactionStatus.setRollbackOnly()
respond beer.errors, view:'create'
return
}
beer.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: 'beer.label', default: 'Beer'), beer.id])
redirect beer
}
'*' { respond beer, [status: CREATED] }
}
}
def edit(Beer beer) {
respond beer
}
#Transactional
def update(Beer beer) {
if (beer == null) {
transactionStatus.setRollbackOnly()
notFound()
return
}
if (beer.hasErrors()) {
transactionStatus.setRollbackOnly()
respond beer.errors, view:'edit'
return
}
beer.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'beer.label', default: 'Beer'), beer.id])
redirect beer
}
'*'{ respond beer, [status: OK] }
}
}
#Transactional
def delete(Beer beer) {
if (beer == null) {
transactionStatus.setRollbackOnly()
notFound()
return
}
beer.delete flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.deleted.message', args: [message(code: 'beer.label', default: 'Beer'), beer.id])
redirect action:"index", method:"GET"
}
'*'{ render status: NO_CONTENT }
}
}
protected void notFound() {
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'beer.label', default: 'Beer'), params.id])
redirect action: "index", method: "GET"
}
'*'{ render status: NOT_FOUND }
}
}
}
Recipe Controller
package com.example
import static org.springframework.http.HttpStatus.*
import grails.transaction.Transactional
#Transactional(readOnly = true)
class RecipeController {
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond Recipe.list(params), model:[recipeCount: Recipe.count()]
}
def show(Recipe recipe) {
respond recipe
}
def create() {
respond new Recipe(params)
}
#Transactional
def save(Recipe recipe) {
if (recipe == null) {
transactionStatus.setRollbackOnly()
notFound()
return
}
if (recipe.hasErrors()) {
transactionStatus.setRollbackOnly()
respond recipe.errors, view:'create'
return
}
recipe.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: 'recipe.label', default: 'Recipe'), recipe.id])
redirect recipe
}
'*' { respond recipe, [status: CREATED] }
}
}
def edit(Recipe recipe) {
respond recipe
}
#Transactional
def update(Recipe recipe) {
if (recipe == null) {
transactionStatus.setRollbackOnly()
notFound()
return
}
if (recipe.hasErrors()) {
transactionStatus.setRollbackOnly()
respond recipe.errors, view:'edit'
return
}
recipe.save flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'recipe.label', default: 'Recipe'), recipe.id])
redirect recipe
}
'*'{ respond recipe, [status: OK] }
}
}
#Transactional
def delete(Recipe recipe) {
if (recipe == null) {
transactionStatus.setRollbackOnly()
notFound()
return
}
recipe.delete flush:true
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.deleted.message', args: [message(code: 'recipe.label', default: 'Recipe'), recipe.id])
redirect action:"index", method:"GET"
}
'*'{ render status: NO_CONTENT }
}
}
protected void notFound() {
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'recipe.label', default: 'Recipe'), params.id])
redirect action: "index", method: "GET"
}
'*'{ render status: NOT_FOUND }
}
}
}
Beer/Show GSP
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main" />
<g:set var="entityName" value="${message(code: 'beer.label', default: 'Beer')}" />
<title><g:message code="default.show.label" args="[entityName]" /></title>
</head>
<body>
<g:message code="default.link.skip.label" default="Skip to content…"/>
<div class="nav" role="navigation">
<ul>
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
<li><g:link class="list" action="create" resource="recipe">New Recipe</g:link></li>
</ul>
</div>
<div id="show-beer" class="content scaffold-show" role="main">
<h1><g:message code="default.show.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<g:link controller="recpie" action="create" resource="recipe" params="[beerid: selectedBeer.id]">Create Recipe</g:link>
<f:display bean="beer" />
<g:form resource="${this.beer}" method="DELETE">
<fieldset class="buttons">
<g:link class="edit" action="edit" resource="${this.beer}"><g:message code="default.button.edit.label" default="Edit" /></g:link>
<input class="delete" type="submit" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />
</fieldset>
</g:form>
</div>
</body>
</html>
** Recipe/Create GSP**
<%# page import="com.example.Beer" %>
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main" />
<g:set var="entityName" value="${message(code: 'recipe.label', default: 'Recipe')}" />
<title><g:message code="default.create.label" args="[entityName]" /></title>
</head>
<body>
<g:message code="default.link.skip.label" default="Skip to content…"/>
<div class="nav" role="navigation">
<ul>
<li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
<li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
</ul>
</div>
<div id="create-recipe" class="content scaffold-create" role="main">
<h1><g:message code="default.create.label" args="[entityName]" /></h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<g:hasErrors bean="${this.recipe}">
<ul class="errors" role="alert">
<g:eachError bean="${this.recipe}" var="error">
<li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
</g:eachError>
</ul>
</g:hasErrors>
<g:form action="save" name="recipeForm">
<fieldset class="form">
Name:
<g:field type="text" name="name"/>
Grain Amount:
<g:field type="text" name="grainAmount"/>
Yeast:
<g:field type="text" name="yeast"/>
Boil Time:
<g:field type="text" name="boilTime"/>
Hop Amount:
<g:field type="text" name="hopAmount"/>
OG:
<g:field type="text" name="og"/>
FG:
<g:field type="text" name="fg"/>
OG Temp:
<g:field type="text" name="ogTemp"/>
FG Temp:
<g:field type="text" name="fgTemp"/>
Ferment Time:
<g:field type="text" name="fermTime"/>
beer:
<g:hiddenField name="beerid" value="${params.beerid}" />
</fieldset>
<fieldset class="buttons">
<g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
</fieldset>
</g:form>
</div>
</body>
</html>
You've submitted the beer ID from the beer show paged named beerid so you need to retrieve it from the params on the recipe create page as such:
beer - show.gsp
<g:link controller="recipe" action="create" resource="recipe" params="[beerid: beer.id]">Create Recipe</g:link>
recpie - create.gsp
<g:hiddenField name="beer.id" value="${params.beerid}"/>
i'm using HTML5 drag and drop multi-file upload plugin, to upload some files in my Grails application , but i want to save the uploaded files to DB but i don't know what is the object that is holding the uploaded files i searched in request and params , here is my the tag in the _form view:
<uploadr:add name="myUploadrName" controller="photos" action="save" direction="up" maxVisible="8" unsupported="/my/controller/action" rating="false" voting="false" colorPicker="false" maxSize="204800" />
here is the create view :
<g:form url="[resource:photosInstance]" enctype="multipart/form-data"><fieldset class="form">
<g:render template="form"/>
</fieldset>
<fieldset class="buttons">
<g:submitButton id = "submitBtn" name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
</fieldset>
</g:form>
here is the save action :
def save(Photos photosInstance) {
if (photosInstance == null) {
notFound()
return
}
if (photosInstance.hasErrors()) {
respond photosInstance.errors, view:'create'
return
}
request.getFileNames().each{
request.getFiles(it).each { file ->
// loop through all files selected
println "name: $file.name Originalfilename: $file.originalFilename contentType: $file.contentType"
photosInstance= new Photos(photo:file.bytes).save(failOnError:true)
}
}
/*request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [
message(code: 'photos.label', default: 'Photos'),
photosInstance.id
])
redirect photosInstance
}
'*' { respond photosInstance, [status: CREATED] }
}*/
}
A little hard to help when I don't have the controller code, but try this:
UploadedFile uploadedFile = UploadedFile.get(params.fileId)
if (!uploadedFile) response.sendError(404, "No uploaded file could be found matching id: ${params.fileId}.")
GridFSFile gridFSFile = uploadedFile.file
if (!gridFSFile) response.sendError(404, "No file attached to UploadedFile")
a have a problem with my code. how can i handle an error from service to my gsp? i tried it with render from service or controller, Validation error occured during call to save(): - Field error in object 'talent.CandidateProfile' on field 'core.db_email': rejected value []; codes with full exception trace. my sources:
def create() {
[candidateProfileInstance: new CandidateProfile(params)]
}
def save() {
def candidateProfileInstance = new CandidateProfile(params)
if (!candidateProfileInstance.save(flush: true)) {
render(view: "create", model: [candidateProfileInstance: candidateProfileInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'candidateProfile.label', default: 'CandidateProfile'), candidateProfileInstance.id])
redirect(action: "show", id: candidateProfileInstance.id)
}
My .gsp page code
<div class="full-filed">
<h3>Email Address:</h3>
</div>
<div class="fieldcontain ${hasErrors(bean: candidateProfileInstance, field: 'core.db_email', 'error')} ">
<label for="core.db_email" class="error_message"> <g:message
code="candidateProfile.core.db_email.label" default="Dbemail" />
</label>
</div>
<g:textField name="core.db_email" value="" class="loginTxtBox" placeholder="Email Address" />
i want just show to user an error, but not full exception trace
form your config.grooyPage
//grails.gorm.failOnError = true
just comment to this line...SO it will display your Custom Error
I have been struggling with trying to create/save multiple instances at once in Grails, now I am almost there with the following code but when I hit save an empty row of options is created, can anyone help me with this
please see these two questions to see what I want to achieve
How to save multiple object from one view using Grails
Grails one to many relationship view
<g:textField name="question" value="${multipleChoiceQuestionInstance?.question}"/><br/>
<div class="fieldcontain ${hasErrors(bean: multipleChoiceQuestionInstance, field: 'options', 'error')} ">
<label for="options">
<g:message code="multipleChoiceQuestion.options.label" default="Options" />
</label>
<ul class="one-to-many">
<g:set var="counter" value="${0}" />
<g:each status="i" in="${multipleChoiceQuestionInstance?.options?}" var="o">
<li>
<g:textField controller="multipleChoiceOption" name="options[${i}].answerOption" action="show" id="${o.id}" value="${o?.encodeAsHTML()}"/>
<g:checkBox name="options[${i}].correctOption" value="${o.correctOption}"/><br/>
</li>
<g:set var="counter" value="${++counter}" />
</g:each>
<li>
<g:textField name="options[${++counter}].answerOption" value=""/>
<g:checkBox name="options[${counter}].correctOption" /><br/>
</li>
<li class="add">
<g:link controller="multipleChoiceOption" action="create" params="['multipleChoiceQuestion.id': multipleChoiceQuestionInstance?.id]">${message(code: 'default.add.label', args: [message(code: 'multipleChoiceOption.label', default: 'MultipleChoiceOption')])}</g:link>
</li>
</ul>
</div>
If you prefer not to click on the link here are the domain classes
Class MultipleChoiceQuestion {
String question
static constraints = {
...
}
static hasMany = [options:MultipleChoiceOption]
class MultipleChoiceOption{
String answerOption
boolean correctOption
MultipleChoiceQuestion question
static constraints = {
...
}
}
}
I am using automatically generated code by grails for the controller, it is as bellow
def create() {
[multipleChoiceQuestionInstance: new MultipleChoiceQuestion(params)]
}
def save() {
println params
def multipleChoiceQuestionInstance = new MultipleChoiceQuestion(params)
if (!multipleChoiceQuestionInstance.save(flush: true)) {
render(view: "create", model: [multipleChoiceQuestionInstance: multipleChoiceQuestionInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'multipleChoiceQuestion.label', default: 'MultipleChoiceQuestion'), multipleChoiceQuestionInstance.id])
redirect(action: "show", id: multipleChoiceQuestionInstance.id)
}
def update() {
def multipleChoiceQuestionInstance = MultipleChoiceQuestion.get(params.id)
if (!multipleChoiceQuestionInstance) {
.... //deleted for real estate
return
}
if (params.version) {
//version checking stuff
}
}
multipleChoiceQuestionInstance.properties = params
if (!multipleChoiceQuestionInstance.save(flush: true)) {
render(view: "edit", model: [multipleChoiceQuestionInstance: multipleChoiceQuestionInstance])
return
}
flash.message = message(code: 'default.updated.message', args: [message(code: 'multipleChoiceQuestion.label', default: 'MultipleChoiceQuestion'), multipleChoiceQuestionInstance.id])
redirect(action: "show", id: multipleChoiceQuestionInstance.id)
}
The reason that you are getting an empty row of options is because you are leaving a textfield with name option[counter] blank. This empty value is passed as a parameter to the controller action and it creates a new row with these blank values.
You should remove any blank options before calling
multipleChoiceQuestionInstance.properties = params
You can use something like this :
def emptyOptions = params.options.findAll{!it.answerOption}
params.options.removeAll(emptyOptions)
I have this DomainClass:
package cm
class XXX{
String city
String website
static constraints = {
city(nullable: true)
website(nullable: true)
}
static mapping = {
id column:'xxx_id'
city column: 'xxx_city'
website column: 'xxx_website'
table "xxx_XXX"
version false
}
}
The Controller:
class ConferenceController {
static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
def index = {
redirect(action: "list", params: params)
}
def list = {
params.max = Math.min(params.max ? params.int('max') : 10, 100)
[XXXInstanceList: XXX.list(params), XXXInstanceTotal: XXX.count()]
}
def save = {
def XXXInstance= new XXX(params)
if (!XXXInstance.save(flush: true)) {
render view: 'add', model: [XXXInstance: XXXInstance]
return
}
//flash.message = "${message(code: 'default.created.message', args: [message(code: 'person.label', default: 'Person'), personInstance.id])}"
redirect(uri:"/index.gsp")
}
}
and my add.gsp page:
<head>
<title> xXXx</title>
<meta name="layout" content="main2" />
</head>
...
<g:form controller="XXX" action="save">
<g:hasErrors bean="${XXXInstance}">
<div class="errors">
<g:renderErrors bean="${XXXInstance}" as="list" />
</div>
</g:hasErrors>
year
<g:textField name="year" /><br>
website
<g:textField name="website" /><br>
<g:submitButton name="save2" value="Save" />
</g:form></div>
</div>
...
</body>
With this current code, everything works fine, but when a contraint is failed, it is showns the corresponding error but the written values are gone.. I cant figure this out - i'v tried a lot of things, but im sure the solution is as easy as my question -.- Please help.
I think you should check the *.properties file in YOUR_PROJECT\grails-app\i18n folder. They have the definition for translation. Investigate it for a time and refer to the document if need, you will understand how Grails perform translation.
And uncomment the following line in your controller:
//flash.message = "${message(code: 'default.created.message', args: [message(code: 'person.label', default: 'Person'), personInstance.id])}"