I am a newbie at Groovy and I want to modify one example I've found at the Internet (the blog one).
I've defined two classes Post and Comment as follows:
class Post {
static hasMany = [comments:Comment]
String title
String teaser
String content
Date lastUpdated
SortedSet comments
static constraints = {
title(nullable:false, blank:false, length:1..50)
teaser(length:0..100)
content(nullable:false, blank:false)
lastUpdated(nullable:true)
}
String toString() {title}
}
Post post
String comment
String name
String url
String email
Date dateCreated = new Date()
static belongsTo = Post
static constraints = {
comment(nullable:false, blank:false)
name(nullable:false, blank:false)
url(nullable:true, blank:true, url:true)
email(nullable:true, blank:true, email:true)
dateCreated(nullable:true)
post(nullable:false)
}
public int compareTo(Object o) {
return dateCreated.compareTo(o.dateCreated)
}
String toString() {comment}
}
Up to now all OK.
I've defined the controllers as follows:
class CommentController {
def edit = {
render(view:'edit',
model:[ comment:new Comment(),
postId:params.postId])
}
def save = {
def comment = new Comment(params)
comment.dateCreated = new Date()
comment.post = Post.get(params.postId)
if(comment.save()) {
redirect(
controller:'post',
action:'list',
id:params.postId)
}
}
}
class PostController {
def defaultAction = 'list'
def index = {
render("En el norte de Alabama")
}
def edit = {
render(view:'edit', model:[post:loadPost(params.id)])
}
def save = {
def post = loadPost(params.id)
post.lastUpdated = new Date()
post.properties = params
if(post.save()) {
print "id " + post.id + "\n"
def offset = post.id - 1
print "offset " + offset
redirect(action:'list', offset:offset)
} else {
render(view:'edit', model:[post:post])
}
}
def list = {
if(!params.max)params.max = 1
[ postList: Post.list( params ) ]
}
def view = {
render(view:'view', model:[post:Post.get(params.id)])
}
private loadPost(id) {
def post = new Post();
if(id) {
post = Post.get(id)
}
return post
}
}
Finally I've defined the views/post/list
as follows:
<%# page contentType="text/html;charset=UTF-8" %>
<html>
<head><title>My postList</title>
</head>
<body>
<div class="nav">
<g:link controller="post" action="edit">New post</g:link>
</div>
<span class="menuButton"><H1>Last posts</H1></span>
<table border = 1><tr><td>
<g:each in="${postList}" var="post">
<div>
<h2>${post.title}</h2>
<p>Last update: <g:formatDate date="${post.lastUpdated}"
format="yyyy-MMM-dd HH:mm"/></p>
<p><i>${post?.content}</i></p>
</div>
<div class="paginateButtons">
<g:link controller="comment" action="edit" params="[postId:post.id]">
Add comment
</g:link>
</div>
<g:each in="${post.comments}" var="comt">
${comt.comment} <br />
</g:each>
</g:each>
<td></tr></table>
<div class="paginateButtons">
<g:paginate max="1" next="Next" prev="Prev."
total="${postCount == null ? Post.count(): postCount}" />
</div>
</body>
</html>
I can generate new Posts without any problem, but when I want to put comments to my posts I can put at most ONE comment for each post. When trying to put more I get a ClassCastException as follows:
Message: Comment
Caused by: Error processing GroovyPageView: Comment
Class: /WEB-INF/grails-app/views/post/list.gsp
At Line: [-1]
org.codehaus.groovy.grails.web.pages.exceptions.GroovyPagesException: Error processing GroovyPageView: Comment
at org.codehaus.groovy.grails.web.servlet.view.GroovyPageView.handleException(GroovyPageView.java:134)
at org.codehaus.groovy.grails.web.servlet.view.GroovyPageView.renderWithTemplateEngine(GroovyPageView.java:112)
at org.codehaus.groovy.grails.web.servlet.view.GroovyPageView.renderMergedOutputModel(GroovyPageView.java:86)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:257)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1183)
at org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet.doDispatch(GrailsDispatcherServlet.java:294)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:807)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:571)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:501)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:707)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1124)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:70)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:70)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417)
at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:334)
at org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:126)
at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:293)
at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:269)
at org.codehaus.groovy.grails.web.util.WebUtils.forwardRequestForUrlMappingInfo(WebUtils.java:261)
at org.codehaus.groovy.grails.web.mapping.filter.UrlMappingsFilter.doFilterInternal(UrlMappingsFilter.java:181)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)
at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.obtainContent(GrailsPageFilter.java:171)
at org.codehaus.groovy.grails.web.sitemesh.GrailsPageFilter.doFilter(GrailsPageFilter.java:110)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)
at org.codehaus.groovy.grails.web.servlet.filter.GrailsReloadServletFilter.doFilterInternal(GrailsReloadServletFilter.java:101)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)
at org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:65)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:324)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:534)
at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:864)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:533)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:207)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:403)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:522)
Caused by: java.lang.ClassCastException: Comment
at java.util.TreeMap.compare(TreeMap.java:1093)
at java.util.TreeMap.put(TreeMap.java:465)
at java.util.TreeSet.add(TreeSet.java:210)
at java.util.AbstractCollection.addAll(AbstractCollection.java:318)
at java.util.TreeSet.addAll(TreeSet.java:258)
at D__Workspace_groovypublish_grails_app_views_post_list_gsp$_run_closure5.doCall(D__Workspace_groovypublish_grails_app_views_post_list_gsp:52)
at D__Workspace_groovypublish_grails_app_views_post_list_gsp.run(D__Workspace_groovypublish_grails_app_views_post_list_gsp:21)
Seems to be that the problem is at the list.gsp file when trying to put nested "each"s
Any hint
Thanks in advance
Luis
Well, from what I can read here, you're using SortedSet as your container type for Comments in a Post. This is fine, but you have to make Comments implement Comparable, as that's a requirement for all SortedSet-contained objects. You'd probably want your comments sorted by dateCreated anyway, so you have to SAY that somewhere (like in the compareTo function you'll need to satisfy the interface).
Any reason you're not just using a List? Seems like you wouldn't want to add Comments with earlier dates anyway.
Anyway, if this isn't it, comment and I'll take a longer look.
I agree with Bill James but I think that you should note am improvement in your analysis of this problem. Your post had good data surrounding your question. In fact you had everything you needed. Lets look at that stacktrace.
Here is the code from TreeMap.java: 1093
private int compare(K k1, K k2) {
return (comparator==null ? ((Comparable</*-*/K>)k1).compareTo(k2)
: comparator.compare((K)k1, (K)k2));
}
From here you can see that Comment being cast to Comparable was the problem. A little look into TreeMap, TreeSet and Comparable would have probably pushed you through this problem.
Please don't interpret this post as in anyway impugning your question or problem. Your question is good, and well asked. This hopefully is another bit of data in helping you learn to fish.
Related
I'm trying to perform a GraphQL query using Django and Graphene. To query one single object using the id I did the following:
{
samples(id:"U2FtcGxlU2V0VHlwZToxMjYw") {
edges {
nodes {
name
}
}
}
}
And it just works fine. Problem arise when I try to query with more than one id, like the following:
{
samples(id_In:"U2FtcGxlU2V0VHlwZToxMjYw, U2FtcGxlU2V0VHlwZToxMjYx") {
edges {
nodes {
name
}
}
}
}
In the latter case I got the following error:
argument should be a bytes-like object or ASCII string, not 'list'
And this is a sketch of how defined the Type and Query in django-graphene
class SampleType(DjangoObjectType):
class Meta:
model = Sample
filter_fields = {
'id': ['exact', 'in'],
}
interfaces = (graphene.relay.Node,)
class Query(object):
samples = DjangoFilterConnectionField(SampleType)
def resolve_sample_sets(self, info, **kwargs):
return Sample.objects.all()
GlobalIDMultipleChoiceFilter from django-graphene kinda solves this issue, if you put "in" in the field name. You can create filters like
from django_filters import FilterSet
from graphene_django.filter import GlobalIDMultipleChoiceFilter
class BookFilter(FilterSet):
author = GlobalIDMultipleChoiceFilter()
and use it by
{
books(author: ["<GlobalID1>", "<GlobalID2>"]) {
edges {
nodes {
name
}
}
}
}
Still not perfect, but the need for custom code is minimized.
You can easily use a Filter just put this with your nodes.
class ReportFileFilter(FilterSet):
id = GlobalIDMultipleChoiceFilter()
Then in your query just use -
class Query(graphene.ObjectType):
all_report_files = DjangoFilterConnectionField(ReportFileNode, filterset_class=ReportFileFilter)
This is for relay implementation of graphql django.
None of the existing answers seemed to work for me as they were presented, however with some slight changes I managed to resolve my problem as follows:
You can create a custom FilterSet class for your object type, and filter the field by using the GlobalIDMultipleChoiceFilter. for example:
from django_filters import FilterSet
from graphene_django.filter import GlobalIDFilter, GlobalIDMultipleChoiceFilter
class SampleFilter(FilterSet):
id = GlobalIDFilter()
id__in = GlobalIDMultipleChoiceFilter(field_name="id")
class Meta:
model = Sample
fields = (
"id_in",
"id",
)
Something I came cross is that you can not have filter_fields defined with this approach. Instead, you have to only rely on the custom FilterSet class exclusively, making your object type effectively look like this:
from graphene import relay
from graphene_django import DjangoObjectType
class SampleType(DjangoObjectType):
class Meta:
model = Sample
filterset_class = SampleFilter
interfaces = (relay.Node,)
I had trouble implementing the 'in' filter as well--it appears to be misimplemented in graphene-django right now and does not work as expected. Here are the steps to make it work:
Remove the 'in' filter from your filter_fields
Add an input value to your DjangoFilterConnectionField entitled 'id__in' and make it a list of IDs
Rename your resolver to match the 'samples' field.
Handle filtering by 'id__in' in your resolver for the field. For you this will look as follows:
from base64 import b64decode
def get_pk_from_node_id(node_id: str):
"""Gets pk from node_id"""
model_with_pk = b64decode(node_id).decode('utf-8')
model_name, pk = model_with_pk.split(":")
return pk
class SampleType(DjangoObjectType):
class Meta:
model = Sample
filter_fields = {
'id': ['exact'],
}
interfaces = (graphene.relay.Node,)
class Query(object):
samples = DjangoFilterConnectionField(SampleType, id__in=graphene.List(graphene.ID))
def resolve_samples(self, info, **kwargs):
# filter_field for 'in' seems to not work, this hack works
id__in = kwargs.get('id__in')
if id__in:
node_ids = kwargs.pop('id__in')
pk_list = [get_pk_from_node_id(node_id) for node_id in node_ids]
return Sample._default_manager.filter(id__in=pk_list)
return Sample._default_manager.all()
This will allow you to call the filter with the following api. Note the use of an actual array in the signature (I think this is a better API than sending a comma separated string of values). This solution still allows you to add other filters to the request and they will chain together correctly.
{
samples(id_In: ["U2FtcGxlU2V0VHlwZToxMjYw", "U2FtcGxlU2V0VHlwZToxMjYx"]) {
edges {
nodes {
name
}
}
}
}
Another way is to tell the Relay filter of graphene_django to also deals with a list. This filter is register in a mixin in graphene_django and applied to any filter you define.
So here my solution:
from graphene_django.filter.filterset import (
GlobalIDFilter,
GrapheneFilterSetMixin,
)
from graphql_relay import from_global_id
class CustomGlobalIDFilter(GlobalIDFilter):
"""Allow __in lookup for IDs"""
def filter(self, qs, value):
if isinstance(value, list):
value_lst = [from_global_id(v)[1] for v in value]
return super(GlobalIDFilter, self).filter(qs, value_lst)
else:
return super().filter(qs, value)
# Fix the mixin defaults
GrapheneFilterSetMixin.FILTER_DEFAULTS.update({
AutoField: {"filter_class": CustomGlobalIDFilter},
OneToOneField: {"filter_class": CustomGlobalIDFilter},
ForeignKey: {"filter_class": CustomGlobalIDFilter},
})
I am having a weird issue. I have a Domain object:
class MyClass {
String name
Boolean new = true
String number
String type
Byte[] data
Date dateCreated
Date lastUpdated
static belongsTo = [
other: MyOtherClass
]
static mapping = {
table 'my_classes'
data column: "data", sqlType: "MEDIUMBLOB"
}
static constraints = {
data maxSize: 8000 * 66
number nullable: true
}
}
In the Controller I have (edited to show entire method):
def list = {
def myOtherClasses = MyOtherClass.getAll()
if ( !params.max ) params.max = 20
if ( !params.sort && !params.order ) {
params.sort = "new"
params.order= "desc"
}
def myClassCount = MyClass.createCriteria().count() {
'in'( 'other', myOtherClasses )
order( params.sort, params.order )
}
def myClassList = MyClass.createCriteria().list() {
'in'( 'other', myOtherClasses )
order( params.sort, params.order )
}
return [ myClassList: myClassList, myClassCount: myClassCount ]
}
The result if fine and the view is correct. But each time this code runs, the data property isDirty, so version is incremented, and lastUpdated is updated.
The data property is holding audio data, but I don't think that is relevant.
I can't figure out what is going on here. So my question is, how do I make it stop updating?
Using:
Grails 2.4.4
Hibernate 3.6.10.18
MySQL 5.7.9
Thanks in advance :)
After much research and testing, and a few great articles, I have found a solution:
Instead of using type Byte[] in the Domain Object, I use java.sql.Blob, and removed the sqlType in the mapping.
In the controller, I had to make a few changes to access the Byte[] data from the Blob, but that was easy.
I still don't know why this was happening, and I couldn't find any info on it, but it is working as expected now.
I have to keep 2 fields(question,options) of questions table in encrypted form
core.php
Configure::write('Security.ekey', '1234567890~`!##$%^&*()_+-={}[]<>,./?qwertrpoiuytlkskhsffgzxmnxcbvLAKSJHFJPOQWUTERLKDJAD');
in Question modal i have
public $encryptedFields = array('question', 'options');
public function beforeSave($options = array()) {
foreach($this->encryptedFields as $fieldName){
if(!empty($this->data[$this->alias][$fieldName])){
$this->data[$this->alias][$fieldName] = Security::rijndael(
$this->data[$this->alias][$fieldName],
Configure::read('Security.ekey'), 'encrypt');
}
}
return true;
}
public function afterFind($results, $primary = false) {
//debug($results); this return nothing while data is being displayed having question and option fields null
foreach($this->encryptedFields as $fieldName){
if(!empty($results[$this->alias][$fieldName])){
$results[$this->alias][$fieldName] = Security::rijndael(
$results[$this->alias][$fieldName],
Configure::read('Security.ekey'), 'decrypt');
}
}
return $results;
}
I have searched different blogs and stackoverflow questions
also tried changing datatype of fields to (text,blob,longblob)
length of these fields may be long (equalent to text datatype) please also suggest which datatype will be best to keep its encrypted string.
I Have two problem in this Case :
I want to pass a JSON object that i created in Zend Controller
public function exampleAction() {
$answers = array();
for($i = 0 ; $i < 3 ; $i++)
{
$answer = new Answer();
$answer->answer_id = 5 ; // for example
$answer->thanked = 'true';// for example
$answers[] = $answer;
}
echo Zend_Json_Encoder::encode($answers);
}
the Jquery Post function is :
$.post(
"/memberactions/getthanks/",
{values:values},
function(res){
alert(123);
}
, 'json')
First Question :
why the return response is HTML ? the response must be in JSON ?
Second Question
the HTML response is like this
[{"__className":"Answer","thanked":"true","answer_id":"5"}]
How can I make the response like this :
- answer
thanked : true
answer_id : 5
as a JSON Object without the __className:"Answer" (does it hurt to have the class name in the response) ?
Have you disabled layout, viewRenderer etc.? Also, you should send appropriate headers. You can do all this at once using the JSON action helper:
$this->_helper->json($answers);
You could provide a toArray() method in Answer, which would return an array of relevant properties and then use it in your action:
$answers[] = $answer->toArray();
How do I get the collection of errors in a view?
I don't want to use the Html Helper Validation Summary or Validation Message. Instead I want to check for errors and if any display them in specific format. Also on the input controls I want to check for a specific property error and add a class to the input.
P.S. I'm using the Spark View Engine but the idea should be the same.
So I figured I could do something like...
<if condition="${ModelState.Errors.Count > 0}">
DisplayErrorSummary()
</if>
....and also...
<input type="text" value="${Model.Name}"
class="?{ModelState.Errors["Name"] != string.empty} error" />
....
Or something like that.
UPDATE
My final solution looked like this:
<input type="text" value="${ViewData.Model.Name}"
class="text error?{!ViewData.ModelState.IsValid &&
ViewData.ModelState["Name"].Errors.Count() > 0}"
id="Name" name="Name" />
This only adds the error css class if this property has an error.
<% ViewData.ModelState.IsValid %>
or
<% ViewData.ModelState.Values.Any(x => x.Errors.Count >= 1) %>
and for a specific property...
<% ViewData.ModelState["Property"].Errors %> // Note this returns a collection
To just get the errors from the ModelState, use this Linq:
var modelStateErrors = this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors);
Condensed version of #ChrisMcKenzie's answer:
var modelStateErrors = this.ModelState.Values.SelectMany(m => m.Errors);
This will give you one string with all the errors with comma separating
string validationErrors = string.Join(",",
ModelState.Values.Where(E => E.Errors.Count > 0)
.SelectMany(E => E.Errors)
.Select(E => E.ErrorMessage)
.ToArray());
Putting together several answers from above, this is what I ended up using:
var validationErrors = ModelState.Values.Where(E => E.Errors.Count > 0)
.SelectMany(E => E.Errors)
.Select(E => E.ErrorMessage)
.ToList();
validationErrors ends up being a List<string> that contains each error message. From there, it's easy to do what you want with that list.
Thanks Chad! To show all the errors associated with the key, here's what I came up with. For some reason the base Html.ValidationMessage helper only shows the first error associated with the key.
<%= Html.ShowAllErrors(mykey) %>
HtmlHelper:
public static String ShowAllErrors(this HtmlHelper helper, String key) {
StringBuilder sb = new StringBuilder();
if (helper.ViewData.ModelState[key] != null) {
foreach (var e in helper.ViewData.ModelState[key].Errors) {
TagBuilder div = new TagBuilder("div");
div.MergeAttribute("class", "field-validation-error");
div.SetInnerText(e.ErrorMessage);
sb.Append(div.ToString());
}
}
return sb.ToString();
}
Here is the VB.
Dim validationErrors As String = String.Join(",", ModelState.Values.Where(Function(E) E.Errors.Count > 0).SelectMany(Function(E) E.Errors).[Select](Function(E) E.ErrorMessage).ToArray())
If you don't know what property caused the error, you can, using reflection, loop over all properties:
public static String ShowAllErrors<T>(this HtmlHelper helper) {
StringBuilder sb = new StringBuilder();
Type myType = typeof(T);
PropertyInfo[] propInfo = myType.GetProperties();
foreach (PropertyInfo prop in propInfo) {
foreach (var e in helper.ViewData.ModelState[prop.Name].Errors) {
TagBuilder div = new TagBuilder("div");
div.MergeAttribute("class", "field-validation-error");
div.SetInnerText(e.ErrorMessage);
sb.Append(div.ToString());
}
}
return sb.ToString();
}
Where T is the type of your "ViewModel".
Got this from BrockAllen's answer that worked for me, it displays the keys that have errors:
var errors =
from item in ModelState
where item.Value.Errors.Count > 0
select item.Key;
var keys = errors.ToArray();
Source: https://forums.asp.net/t/1805163.aspx?Get+the+Key+value+of+the+Model+error