Mongoid validation validates_inclusion_of is not giving me an error - mongoid

I have a mongodb document, and I am trying to create a validation
class Calculator
include Mongoid::Document
field :a, type: String
field :b, type: String
field :operator, type: String
validates_inclusion_of :operator, in: %w[sum difference multiplication division]
Then when I create a document in the console with an invalid operator field it doesn't give me an error.
c = Calculator.new(a: 3, b: 4, operator: 'fdad')

Validations are performed on persistence, e.g. when you call save or save!. They are not performed when you instantiate models (new call).

Related

Wagtail ModelAdmin > How to use custom validation?

I am using wagtail ModelAdmin for some of my non page models and want to add some custom validation.
This is some of the code.
class EditPlanningView(EditView):
def publish_url(self):
return self.url_helper.get_action_url('publish', self.pk_quoted)
def unpublish_url(self):
return self.url_helper.get_action_url('unpublish', self.pk_quoted)
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
instance = form.save(commit=False)
if bool(request.POST.get('action-publish')):
try:
instance.publish(commit=True)
except PublishWithoutMeetingError as e:
form.add_error(
'planning_meeting',
e
)
return self.form_invalid(form)
When validation fails the invalid form is returned, but the error I added is not bound to the field. In stead a 'general error message' appears at the top.
Can someone help me out?
Cheers,
Robert
I think the error is in the following lines.
form.add_error(
'planning_meeting',
e
)
Actually can't say anything without knowing about PublishWithoutMeetingError, the type of e. Better to replace e with a string. And make sure the post method is not throwing any exceptions. Other than that, what you have done is correct. Read the following to also to check if you have missed any point.
Long Answer
There are two ways that you can achieve showing an error messages in forms.
Overriding the Form
Overriding the EditView
In both of these cases, you are going to use a method called add_error. That method takes 2 argument, field and error. From these two, error is the most important argument. The field simply state the field of the form that this error applies to. This can be None.
The error argument can be multiple types.
The error argument can be an instance of str. Then wagtail will assign the given error to the given field.
The error argument can be an instance of list of str. Then wagtail will assign the given list of errors to the given field.
The error argument can be an instance of dict with str keys and str or list of str values. In this case field should be None. The keys will be used as the fields for the errors given by values.
The error argument can be an instance of ValidationError exception. You can create a ValidationError using a str, list, or dict, which represent the above three cases.
Overriding the Form
In the form clean method need to be overridden in order to find errors.
from wagtail.admin.forms.models import WagtailAdminModelForm
class ExtraForm(WagtailAdminModelForm):
def clean(self):
cleaned_data = super().clean() # Get the already cleaned data. Same as self.cleaned_data in this case. But this way is better.
title = cleaned_data.get('title') # Get the cleaned title
if title is None: # Title is never None here, but still..
return cleaned_data
title = title.strip() # Do some formatting if needed
if title.startswith('A'): # Validation
self.add_error('title', 'Title cannot start with A') # Validation error
else:
cleaned_data['title'] = title # Use the formatted title
return cleaned_data
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=500, default='', blank=False)
# Or any other fields you have
base_form_class = ExtraForm # Tell wagtail to use ExtraForm instead of the default one
Overriding the EditView
This way is same as the way that you have mentioned in the question. You need to override post method. You need to check if the form associated with the EditView is valid or invalid and return the appropriate form.
To check validity, is_valid method of the form is used by default. That method will clean the form and check if there are errors added to the form.
If form is valid, you need to return self.valid_form and self.invalid_form otherwise.
Unlike overriding the Form, you can access the request here.
class MyEditView(EditView):
def post(self, request, *args, **kwargs):
form = self.get_form() # Get the form associated with this edit view
if form.is_valid(): # Check if the form pass the default checks
my_field = request.POST.get('my_field') # You can access the request
title = form.cleaned_data.get('title') # You can access the form data
if title != my_field: # Validation
form.add_error('title', 'Title must match my_field') # Validation error
return self.form_invalid(form) # Return invalid form if there are validation errors
return self.form_valid(form) # Return the valid form if there are no validation errors
else:
return self.form_invalid(form) # Return invalid form if default check failed
class MyModelAdmin(ModelAdmin):
model = MyModel
menu_label = 'My Model'
list_display = ('id', 'title')
search_fields = (
'title',
)
edit_view_class = MyEditView # Tell wagtail to use MyEditView instead of the default one.

How to find all document by association Mongoid 4

I have a model Tag which potentially belongs to several other models, but at the moment only one model Todo which in turn belongs to User like so:
class User
include Mongoid::Document
field: name, type: String
has_many :todos
end
class Todo
include Mongoid::Document
field: name, type: String
belongs_to :user
end
class Tag
include Mongoid::Document
field: name, type: String
belongs_to :todos
end
How can I query all Tags that belongs to a particular user? I've written the following:
todo_ids = Todo.where(user_id: '86876876787')
and then:
tags = Tag.where('todo_id.in': todo_ids)
But those didn't work. What am I missing?
You're missing two things:
Mongoid isn't ActiveRecord so it won't know what to do with todo_ids in the Tag query.
'todo_id.in' is a field path that is trying to look at the in field inside a todo_id hash, this isn't a use of MongoDB's $in operator.
You can only work with one collection at a time so to fix the first one, you need to pull an array of IDs out of MongoDB:
todo_ids = Todo.where(user_id: '86876876787').pluck(:id)
# -------------------------------------------^^^^^^^^^^^
To fix the second one, use the $in operator:
tags = Tag.where(todo_id: { '$in': todo_ids })
tags = Tag.where(:todo_id.in => todo_ids)
tags = Tag.in(todo_id: todo_ids)
#...

Appengine search language

I'm trying to implement search.FieldLoadSaver interface to be able to pick field language.
func (p *Product) Save() ([]search.Field, error) {
var fields []search.Field
// Add product.ID
fields = append(fields, search.Field{Name: "ID", Value: search.Atom(p.ID)})
// Add product.Name
fields = append(fields, search.Field{Name: "Name", Value: p.Name, Language: "en"})
return fields, nil
}
And i'm getting this error : errors.errorString{s:"search: INVALID_REQUEST: invalid language . Languages should be two letters."}
It seems that python devserver handles empty language field as an error.
EDIT: so the problem was that i was putting multiple fields with the same name and setting language to be empty. It appears that this isn't allowed, so when you're using multiple fields with same name, make sure you're putting language also.
I'm not sure what exactly your question is but here you can see that what you think (It seems that python devserver handles empty language field as an error.) is not true.
Code snippet from that documentation
type Field struct {
// Name is the field name. A valid field name matches /[A-Z][A-Za-z0-9_]*/.
// A field name cannot be longer than 500 characters.
Name string
// Value is the field value. The valid types are:
// - string,
// - search.Atom,
// - search.HTML,
// - time.Time (stored with millisecond precision),
// - float64,
// - appengine.GeoPoint.
Value interface{}
// Language is a two-letter ISO 693-1 code for the field's language,
// defaulting to "en" if nothing is specified. It may only be specified for
// fields of type string and search.HTML.
Language string
// Derived marks fields that were calculated as a result of a
// FieldExpression provided to Search. This field is ignored when saving a
// document.
Derived bool
}
As you can see, if you specify no language, it defaults to "en"
However, as can be seen in the search api source code:
class Field(object):
"""An abstract base class which represents a field of a document.
This class should not be directly instantiated.
"""
TEXT, HTML, ATOM, DATE, NUMBER, GEO_POINT = ('TEXT', 'HTML', 'ATOM', 'DATE',
'NUMBER', 'GEO_POINT')
_FIELD_TYPES = frozenset([TEXT, HTML, ATOM, DATE, NUMBER, GEO_POINT])
def __init__(self, name, value, language=None):
"""Initializer.
In particular This class should not be directly instantiated.
There are some other Field subclasses that you should be using instead. For your current example you should use (assuming that p.id is a string. Otherwise use NumberField)
class TextField(Field):
"""A Field that has text content.
and
class AtomField(Field):
"""A Field that has content to be treated as a single token for indexing.

Mongoid 3.1 eager loading, json, and field names

Recently updated to Mongoid 3.1 from 3.0.3 and this resulted in some broken code and confusion on my side.
Say you have a pair of classes with a belongs_to/has_many relationship, like so:
class Band
include Mongoid::Document
field :name, type: String
has_many :members, :autosave => true
end
class Member
include Mongoid::Document
field :name, type: String
belongs_to :band
end
Saving all this to the database like so:
b = Band.new
b.name = "Sonny and Cher"
b.members << Member.new(name: "Sonny")
b.members << Member.new(name: "Cher")
b.save
I would in my API, be able to return a 'member' object like so:
m = Member.where(name: "Sonny").first
m.to_json
which yields the following, as expected:
{"_id":"<removed>","band_id":"5151d89f5dd99dd9ec000002","name":"Sonny"}
My client can request the full band object with a subsequent call if it wants to. However, in some cases I DO want to include the referenced item directly. With 3.0.3, I would just do the following:
m = Member.where(name: "Sonny").first
m[:band] = m.band
m.to_json
and this would add a new field with the full band information to it. With 3.1, however (it may have started in earlier versions, but I didn't test), I now get this:
{"_id":"<removed>","band_id":{"_id":"5151dc025dd99d579e000002","name":"Sonny and Cher"},"name":"Sonny"}
So, it looks like the band info has been eager-loaded into the field? Why is it stored under the key ':band_id' and not ':band'? I guess ':band' is protected, but I still don't think the data should be stored under the ':band_id' key. I suspect I am missing something here. Any ideas?
You can specify an :include option for to_json like so:
m.to_json(include: :band)
The JSON will then have a key band with the Band object converted to JSON and band_id will still be present.

Griffon checkbox binding won't work

i am trying the following griffon code
on model:
#Bindable boolean hello1=false
on view:
checkBox(id:1,text: 'hello1', constraints:'wrap',selected:bind(target: model, targetProperty:'hello1'))
but it does say
ERROR org.codehaus.griffon.runtime.builder.UberBuilder - An error occurred while building test.TestView#1132e76
groovy.lang.MissingMethodException: No signature of method: java.lang.Object.setVariable() is applicable for argument types: (java.util.Collections$EmptyMap, java.util.Arrays$ArrayList) values: [[:], [1, javax.swing.JCheckBox[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.synth.SynthBorder#b101cf,flags=288,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=0,left=0,bottom=0,right=0],paintBorder=false,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=]]]8-mar-2012 12.03.41 groovy.util.FactoryBuilderSupport createNode
AVVERTENZA: Could not find match for name 'setVariable'
i dont get what's the deal, i copied that from working examples on internet....
Use a String instead of a number for the value of the id: property, like this
checkBox(id: 'c1', ...)

Resources