How to get django ModelForm to not clean a ManyToManyField before running clean_ for that field? - django-forms

class MyModel(models.Model) :
people = models.ManyToManyField(User,related_name='people')
...
class MyForm(ModelForm) :
class Meta :
model = MyModel
widgets = {'people':TextInput(),}
def clean_people(self) :
# turn a comma-separated list of names into a Python list
return [name0,name1,...]
def clean(self) :
# if no other errors, turn list of names into list of Users
This doesn't work, because clean_people doesn't get called before field.clean gets called, where field is an instance of ModelMultipleChoiceField, which checks for a Python list or tuple and so raises a ValidationError and skips clean_people.
Is there a reason why the order of calls is the way it is, and is there some standard provision for avoiding this problem? I could set field.clean to lambda x:x in each instance of MyForm, but that seems really ugly. Is perhaps the right thing to do to explicitly define the people field in MyForm as a CharField ?

why don't you override clean's behaviour?
class MyForm(ModelForm):
class Meta:
model = MyModel
widgets = {'people':TextInput(),}
def clean_people(self):
# turn a comma-separated list of names into a Python list
return [name0,name1,...]
# override below!
def clean(self):
super(MyForm, self).clean()
# do stuff you need, eg: call clean_people(self)
return self.cleaned_data
I'm not sure I understood your problem 100%, but just by defining clean_people() before clean() doesn't mean that's going to be called before, or even called at all.

It seems that the right? thing is to define the people field in the form, overriding the model's idea of that field
class MyModel(models.Model) :
people = models.ManyToManyField(User,related_name='people')
...
class MyForm(ModelForm):
people = CharField(...)
class Meta :
model = MyModel
def clean_people(self) :
# turn a comma-separated list of names into a Python list
return [name0,name1,...]
def clean(self) :
# if no other errors, turn list of names into list of Users

Related

conditional FilterSets in DRF 3.7 autogen docs: can I add a queryparam filter for a route (but only for certain HTTP verbs)

(DRF v3.7, django-filters v1.1.0)
Hi! I have a working FilterSet that lets me filter my results via a query parameter, e.g. http://localhost:9000/mymodel?name=FooOnly
This is working just fine.
class MyNameFilter(FilterSet):
name = CharFilter(field_name='name', help_text='Filter by name')
class Meta:
model = MyModel
fields = ('name',)
class MyModel(...):
...
filter_backends = (DjangoFilterBackend,)
filter_class = MyNameFilter
But when I render the built-in auto-generated docs for my API, I am seeing this query parameter documented for all methods in my route, e.g. GET, PUT, PATCH, etc.
I only intend to filter via this query parameter for some of these HTTP verbs, as it doesn't make sense for others, e.g. PUT
Is there a good way to make my FilterSet conditional in this manner? Conditional on route method.
I tried applying this logic at both the Router level (a misguided idea). Also at the ViewSet level -- but there is no get_filter_class override method in the same way there is e.g. get_serializer_class.
Thanks for the help.
you'll get get_filter_class in DjangoFilterBackend. You need to create a new FilterBackend which overrides the filter_queryset method.
class GETFilterBackend(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
if request.method == 'GET':
return super().filter_queryset(request, queryset, view)
return queryset
class MyModel(...):
...
filter_backends = (GETFilterBackend,)
filter_class = MyNameFilter
Figured this out, with help from Carlton G. on the django-filters Google Groups forum (thank you, Carlton).
My solution was to go up a level and intercept the CoreAPI schema that came out of the AutoSchema inspection, but before it made its way into the auto-generated docs.
At this point of interception, I override _allows_filters to apply only on my HTTP verbs of interest. (Despite being prefixed with a _ and thus intended as a private method not meant for overriding, the method's comments explicitly encourage this. Introduced in v3.7: Initially "private" (i.e. with leading underscore) to allow changes based on user experience.
My code below:
from rest_framework.schemas import AutoSchema
# see https://www.django-rest-framework.org/api-guide/schemas/#autoschema
# and https://www.django-rest-framework.org/api-guide/filtering/
class LimitedFilteringViewSchema(AutoSchema):
# Initially copied from lib/python2.7/site-packages/rest_framework/schemas/inspectors.py:352,
# then modified to restrict our filtering by query-parameters to only certain view
# actions or HTTP verbs
def _allows_filters(self, path, method):
if getattr(self.view, 'filter_backends', None) is None:
return False
if hasattr(self.view, 'action'):
return self.view.action in ["list"] # original code: ["list", "retrieve", "update", "partial_update", "destroy"]
return method.lower() in ["get"] # original code: ["get", "put", "patch", "delete"]
And then, at my APIView level:
class MyViewSchema(LimitedFilteringViewSchema):
# note to StackOverflow: this was some additional schema repair work I
# needed to do, again adding logic conditional on the HTTP verb.
# Not related to the original question posted here, but hopefully relevant
# all the same.
def get_serializer_fields(self, path, method):
fields = super(MyViewSchema, self).get_serializer_fields(path, method)
# The 'name' parameter is set in MyModelListItemSerializer as not being required.
# However, when creating an access-code-pool, it must be required -- and in DRF v3.7, there's
# no clean way of encoding this conditional logic, short of what you see here:
#
# We override the AutoSchema inspection class, so we can intercept the CoreAPI Fields it generated,
# on their way out but before they make their way into the auto-generated api docs.
#
# CoreAPI Fields are named tuples, hence the poor man's copy constructor below.
if path == u'/v1/domains/{domain_name}/access-code-pools' and method == 'POST':
# find the index of our 'name' field in our fields list
i = next((i for i, f in enumerate(fields) if (lambda f: f.name == 'name')(f)), -1)
if i >= 0:
name_field = fields[i]
fields[i] = Field(name=name_field.name, location=name_field.location,
schema=name_field.schema, description=name_field.description,
type=name_field.type, example=name_field.example,
required=True) # all this inspection, just to set this here boolean.
return fields
class MyNameFilter(FilterSet):
name = CharFilter(field_name='name', help_text='Filter returned access code pools by name')
class Meta:
model = MyModel
fields = ('name',)
class MyAPIView(...)
schema = MyViewSchema()
filter_backends = (DjangoFilterBackend,)
filter_class = MyNameFilter

rails string substitution or similar solution in controller

I'm building a site with users in all 50 states. We need to display information for each user that is specific to their situation, e.g., the number of events they completed in that state. Each state's view (a partial) displays state-specific information and, therefore, relies upon state-specific calculations in a state-specific model. We'd like to do something similar to this:
##{user.state} = #{user.state.capitalize}.new(current_user)
in the users_controller instead of
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
.... [and the remaining 49 states]
#wisconsin = Wisconsin.new(current_user) if (#user.state == 'wisconsin')
to trigger the Illinois.rb model and, in turn, drive the view defined in the users_controller by
def user_state_view
#user = current_user
#events = Event.all
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
end
I'm struggling to find a better way to do this / refactor it. Thanks!
I would avoid dynamically defining instance variables if you can help it. It can be done with instance_variable_set but it's unnecessary. There's no reason you need to define the variable as #illinois instead of just #user_state or something like that. Here is one way to do it.
First make a static list of states:
def states
%{wisconsin arkansas new_york etc}
end
then make a dictionary which maps those states to their classes:
def state_classes
states.reduce({}) do |memo, state|
memo[state] = state.camelize.constantize
memo
end
end
# = { 'illinois' => Illinois, 'wisconsin' => Wisconsin, 'new_york' => NewYork, etc }
It's important that you hard-code a list of state identifiers somewhere, because it's not a good practice to pass arbitrary values to contantize.
Then instantiating the correct class is a breeze:
#user_state = state_classes[#user.state].new(current_user)
there are definitely other ways to do this (for example, it could be added on the model layer instead)

How to use polymorphism to remove a switch statement which compares strings?

I am new to Ruby, so let me describe the context of my problem first:
I have a json as input which has the following key / value pair:
{
"service": "update"
}
The value has many different values for example: insert,delete etc.
Next there is a method x which handles the different requests:
def x(input)
case input[:service]
services = GenericService.new
when "update"
result = services.service(UpdateService.new,input)
when "insert"
result = services.service(InsertService.new,input)
when "delete"
result = services.service(DeleteService.new,input)
....
....
else
raise "Unknown service"
end
puts JSON.pretty_generate(result)
end
What is bothering me is that I still need to use a switch statement to check the String values (reminds me of 'instance of' ugh..). Is there a cleaner way (not need to use a switch)?
Finally I tried to search for an answer to my question and did not succeed, if however I missed it feel free to comment the related question.
Update: I was thinking to maybe cast the string to the related class name as follows: How do I create a class instance from a string name in ruby? and then call result = services.services(x.constantize.new,input) , then the class names ofcourse needs to match the input of the json.
You can try something like:
def x(input)
service_class_name = "#{input[:service].capitalize}Service"
service_class = Kernel.const_get(service_class_name)
service_class.new(input).process
end
In addition you might want to check if this is a valid Service class name at all.
I don't understand why you want to pass the service to GenericService this seems strange. let the service do it's job.
If you're trying to instatiate a class by it's name you're actually speaking about Reflection rather than Polymorphism.
In Ruby you can achieve this in this way:
byName = Object.const_get('YourClassName')
or if you are in a Rails app
byName= 'YourClassName'.constantize
Hope this helps
Just first thoughts, but you can do:
eval(services.service("#{input[:service].capitalize}Service.new, #{input})") if valid_service? input[:service]
def valid_service?
w%(delete update insert).include? input[:service]
end
As folks will no doubt shout, eval needs to be used with alot of care

TurboGears 2.3 #validte in two steps

I'm using TurboGears 2.3 and working on validating forms with formencode and need some guidance
I have a form which covers 2 different objects. They are a almost the same, but with some difference
When i submit my form, I want to validate 2 things
Some basic data
Some specific data for the specific object
Here are my schemas:
class basicQuestionSchema(Schema):
questionType = validators.OneOf(['selectQuestion', 'yesNoQuestion', 'amountQuestion'])
allow_extra_fields = True
class amount_or_yes_no_question_Schema(Schema):
questionText = validators.NotEmpty()
product_id_radio = object_exist_by_id(entity=Product, not_empty=True)
allow_extra_fields = True
class selectQuestionSchema(Schema):
questionText = validators.NotEmpty()
product_ids = validators.NotEmpty()
allow_extra_fields = True
And here are my controller's methods:
#expose()
#validate(validators=basicQuestionSchema(), error_handler=questionEditError)
def saveQuestion(self,**kw):
type = kw['questionType']
if type == 'selectQuestion':
self.save_select_question(**kw)
else:
self.save_amount_or_yes_no_question(**kw)
#validate(validators=selectQuestionSchema(),error_handler=questionEditError)
def save_select_question(self,**kw):
...
Do stuff
...
#validate(validators=amount_or_yes_no_question_Schema(),error_handler=questionEditError)
def save_amount_or_yes_no_question(self,**kw):
...
Do other stuff
...
What I wanted to do was validate twice, with different schemas. This doesn't work, as only the first #validate is validated, and the other are not (maybe ignored)
So, what am i doning wrong?
Thanks for the help
#validate is part of the request flow, so when manually calling a controller it is not executed (it is not a standard python decorator, all TG2 decorators actually only register an hook using tg.hooks so they are bound to request flow).
What you are trying to achieve should be done during validation phase itself, you can then call save_select_question and save_amount_or_yes_no_question as plain object methods after validation.
See http://runnable.com/VF_2-W1dWt9_fkPr/conditional-validation-in-turbogears-for-python for a working example of conditional validation.

Ruby: How to get a class based on class name and how can I get object's field based on field name?

Question 1
How to get a class given a class name as a string ?
For example, say Product class has do_something method:
str = "product"
<what should be here based on str?>.do_something
Question 2
How to get object's field given a field name as a string ?
For example, say Product class has price field:
str = "price"
product = Product.new
product.<what should be here based on str?> = 1200
Jacob's answer to the first question assumes that you're using Rails and will work fine if you are. In case you're not you can call Kernel::const_get(str) to find an existing constant by name.
send is a pure ruby. There's no need to intern your strings with send though (convert them to symbols), straight strings work fine.
Use capitalize and constantize:
str.capitalize.constantize.do_something
Use send:
product.send(str + '=', 1200)

Resources