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.
Related
(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
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)
I have a REST api url endpoint that represents a Song within an Album:
/api/album/(?P<album_id>)/song/(?P<id>)/
and I want to refer to it from another resource, e.g. Chart that contains Top-1000 songs ever. Here's an implementation of ChartSerializer:
class ChartSerializer(HyperlinkedModelSerializer):
songs = HyperlinkedRelatedField(
queryset=Song.objects.all(),
view_name='api:song-detail',
lookup_field='id'
)
class Meta:
model = Chart
fields = ('songs', )
Clearly, I can pass id as lookup_field, but it seems to me that I won't be able to pass album_id by any means. I'm looking into HyperlinkedModelSerializer.get_url() method:
def get_url(self, obj, view_name, request, format):
"""
Given an object, return the URL that hyperlinks to the object.
May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
attributes are not configured to correctly match the URL conf.
"""
# Unsaved objects will not yet have a valid URL.
if hasattr(obj, 'pk') and obj.pk in (None, ''):
return None
lookup_value = getattr(obj, self.lookup_field)
kwargs = {self.lookup_url_kwarg: lookup_value}
return self.reverse(view_name, kwargs=kwargs, request=request, format=format)
As you can see, it constructs kwargs for reverse url lookup from scratch and doesn't allow to pass additional parameters to it. Am I right that this is not supported?
UPDATE:
Found a reference to this problem in the issue list of DRF: https://github.com/tomchristie/django-rest-framework/issues/3204
So, the answer is YES. There is even a paragraph about this issue in the DRF documentation:
http://www.django-rest-framework.org/api-guide/relations/#custom-hyperlinked-fields
It's a pretty standard task in Django REST Framework to supply additional args/kwargs to a serializer to set values of fields set not via request.data, but via the value in url parameters or cookies. For instance, I need to set user field of my Comment model equal to request.user upon POST request. Those additional arguments are called context.
Several questions (1, 2) on StackOverflow suggest that I override get_serializer_context() method of my ModelViewSet. I did and it doesn't help. I tried to understand, what's wrong, and found out that I don't understand from the source code, how this context system is supposed to work in general. (documentation on this matter is missing, too)
Can anyone explain, where serializer adds context to normal request data? I found two places, where it saves the values from context.
serializer.save(), method, which mixes kwargs with validated data, but it is usually called with no arguments (e.g. by ModelMixins).
fields.__new__(), which caches args and kwargs, but it seems that nobody ever reads them later.
Whenever you use generic views or viewsets, DRF(3.3.2) adds request object, view object and format to the serializer context. You can use serializer.context to access, lets say request.user in the serializer.
This is added when get_serializer_class() is called. Inside that, it calls get_serializer_context() method where all these parameters are added to its context.
DRF source code for reference:
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
to set values of fields set not via request.data, but via the value in url parameters or cookies. For instance, I need to set user field of my Comment model equal to request.user upon POST request.
This is how I handle both cases in my ModelViewSet:
def perform_create(self, serializer):
# Get article id from url e.g. http://myhost/article/1/comments/
# obviously assumes urls.py is setup right etc etc
article_pk = self.kwargs['article_pk']
article = get_object_or_404(Article.objects.all(), pk=article_pk)
# Get user from request
serializer.save(author=self.request.user, article=article)
Unfortunately the nested objects is not standard for DRF but that's besides the point. :)
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