Swagger not working with Django BaseSerializer object - python

I'm using django-rest-swagger to document and test an API and it has been working very well up to now but the following error has occured:
AttributeError at /docs/api-docs/app
'PeriodSerializer' object has no attribute 'get_fields'
'PeriodSerializer' inherits from serializers.BaseSerializer:
class PeriodSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return {
'lower': instance.lower,
'upper': instance.upper
}
def to_internal_value(self, data):
data = json.loads(data)
date_lower = self.date_from_str(data["lower"])
date_upper = self.date_from_str(data["upper"])
# some code omitted for brevity
return DateTimeTZRange(lower=date_lower, upper=date_upper)
#staticmethod
def date_from_str(datestr):
# code omitted for brevity
The code itself works fine, it's just that django-rest-swagger appears to have a problem with it. I'm using:
Python 3.4.0
Django 1.8.2
DRF 3.1.3
django-rest-swagger 0.3.2
Any help would be much appreciated.

Django Rest Framework's BaseSerializer doesn't have a get_fields function. You can see that in that in the source.
Short answer: Use Serializer, not BaseSerializer. Your code will work the same, and you won't have to worry about it. If for some reason you need to use BaseSerializer and django-rest-swagger together, you'll have to implement get_fields yourself.
If you look at the implementation of get_fields in a higher-level serializer (like Serializer) you'll see get_fields is defined like so:
def get_fields(self):
"""
Returns a dictionary of {field_name: field_instance}.
"""
# Every new serializer is created with a clone of the field instances.
# This allows users to dynamically modify the fields on a serializer
# instance without affecting every other serializer class.
return copy.deepcopy(self._declared_fields)
Using BaseSerializer, you won't have access to self._declared_fields either. You can see how that works in the linked source above, but the gist of it is that it returns a dictionary of attributes of the Field type.
Any instances of Field included as attributes on either the class
or on any of its superclasses will be include in the
_declared_fields dictionary.
I hope this helps answer your question!

Though late to the party but what works for me is something like this:
#six.add_metaclass(serializers.SerializerMetaclass)
class PeriodSerializer(serializers.BaseSerializer):
def get_fields(self):
"""
Returns a dictionary of {field_name: field_instance}.
"""
# Every new serializer is created with a clone of the field instances.
# This allows users to dynamically modify the fields on a serializer
# instance without affecting every other serializer class.
return copy.deepcopy(self._declared_fields)
i.e you need to decorate the class with a meta class which provides for _declared_field and then you can implement this method.

You could also try out drf-yasg. It is another swagger generator with support for the newer versions of Django Rest Framework.

Related

Sanitize input in Django Rest Framework

If I send something like
{
"description": "Hello World <script>alert('hacked');</script>"
}
to my django rest framework view, I want to get rid of the the script tags.
Is there a convenient way to do this, that does not involve overwriting all the things and add strip_tags?
What else is to do to sanitize input?
Did I really overread that section in the drf docs or isn't that covered?
Ignore the answers here, they are terrible.
Use bleach. You won't get every edge case. This is the situation to use a library in. Your client has control of the client side by definition.
You can override the view's perform_create method and with a bit of regular expression do something like so
import re
class MyView(generics.CreateAPIView):
......
......
def perform_create(self, serializer):
replacement=re.sub('</*script>','',serializer.validated_data.get('description'))
serializer.save(description=replacement)
This is assuming you are using CreateAPIView or one of its mixins.
Or you could create a custom serializer field,
class MyCustomField(serializers.CharField):
def to_internal_value(self, data):
data=re.sub('</*script>','',data)
return super(MyCustomField,self).to_internal_value(data)
and
class MySerializer(serializer.ModelSerializer):
description=MyCustomField()
class Meta:
model= MyModel

Canonical way to do bulk create in django-rest-framework 3.x?

In 2.x we had a serializer which looked like:
class FooSerializer(serializers.ModelSerializer):
bar = serializers.PrimaryKeyRelatedField()
class Meta:
model = Foo
This effectively was able to handle bulk creates (passing a list as the body of a JSON post request). In 3.x, this endpoint is broken. I've tried to implement something similar to the docs on DRF
class FooListSerializer(serializers.ListSerializer):
def create(self, validated_data):
foos = [Foo(**item) for item in validated_data]
return Foo.objects.bulk_create(foos)
class FooSerializer(serializers.ModelSerializer):
bar = serializers.PrimaryKeyRelatedField(
queryset=Bar.objects.all()
)
class Meta:
model = Foo
list_serializer_class = FooListSerializer
And while this works for a single create request, when I attempt to pass a list I get the error:
AttributeError: 'FooListSerializer' object has no attribute 'object'
I've seen some hacks where __init__ is super'd, but it seems with the creation of the ListSerializer class in 3.x there has to be a cleaner way to do this. Thanks in advance.
You don't show how your code is making a FooSerializer instance. The Django REST Framework 3 documentation says:
To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.
So, it seems that your code should detect whether the data contains one instance or many, and:
serializer = FooSerializer() to handle one instance, or
serializer = FooSerializer(many=True) to handle a list of many instances.
Explicit is better than implicit :-)

Django ORM: wrapper for model objects

I am looking for some way to define some wrapper that is called before i call to Model.objects.all().
I want whenever i call, Model.objects it call my method (wrapper) and then return the objects back to the query.
Lets take an example:
MyModel.objcts.filter(name="Jack")
Wrapper:
def mymodelWrapper(self):
return self.objects.annotate(size=Sum('id', field='order_size_weight*requested_selling_price'))
I want to run annotate in the background and also want to apply the filter.
I Know what i want to achieve, its the code i am looking for how to do that.
What you are talking about is perfectly doable with Django by using a custom model manager:
class MyModelManager(models.Manager):
def get_query_set(self):
return super(MyModelManager, self).get_query_set().annotate(size=Sum('id', field='order_size_weight*requested_selling_price'))
class MyModel(models.Model):
objects = MyModelManager()
# fields
Also see other similar topics:
Is it possible to override .objects on a django model?
Override djangos's object.all() with the request data
Django custom model managers

How to create a django User using DRF's ModelSerializer

In django, creating a User has a different and unique flow from the usual Model instance creation. You need to call create_user() which is a method of BaseUserManager.
Since django REST framework's flow is to do restore_object() and then save_object(), it's not possible to simply create Users using a ModelSerializer in a generic create API endpoint, without hacking you way through.
What would be a clean way to solve this? or at least get it working using django's built-in piping?
Edit:
Important to note that what's specifically not working is that once you try to authenticate the created user instance using django.contrib.auth.authenticate it fails if the instance was simply created using User.objects.create() and not .create_user().
Eventually I've overridden the serializer's restore_object method and made sure that the password being sent is then processes using instance.set_password(password), like so:
def restore_object(self, attrs, instance=None):
if not instance:
instance = super(RegisterationSerializer, self).restore_object(attrs, instance)
instance.set_password(attrs.get('password'))
return instance
Thanks everyone for help!
Another way to fix this is to overwrite pre_save(self, obj) method in your extension of viewsets.GenericViewSet like so:
def pre_save(self, obj):
""" We have to encode the password in the user object that will be
saved before saving it.
"""
viewsets.GenericViewSet.pre_save(self, obj)
# Password is raw right now, so set it properly (encoded password will
# overwrite the raw one then).
obj.user.set_password(obj.user.password)
Edit:
Note that the obj in the code above contains the instance of User class. If you use Django's user model class directly, replace obj.user with obj in the code (the last line in 2 places).
I'm working with DRF. And here is how I create users:
I have a Serializer with overrided save method:
def save(self, **kwargs ):
try:
user = create_new_user(self.init_data)
except UserDataValidationError as e:
raise FormValidationFailed(e.form)
self.object = user.user_profile
return self.object
create_new_user is just my function for user creation and in the view, I just have:
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
It seems like you should be overriding restore_object() in your serializer, not save(). This will allow you to create your object correctly.
However, it looks like you are trying to abuse the framework -- you are trying to make a single create() create two objects (the user and the profile). I am no DRF expert, but I suspect this may cause some problems.
You would probably do better by using a custom user model (which would also include the profile in the same object).

Overriding Django's RelatedManager methods

Django's ForeignRelatedObjectsDescriptor.create_manager(...) function dynamically creates the RelatedManager classes and subsequently initializes an instance of the dynamically created class.
If I wanted to override the RelatedManager.add(...) method, how would I do it?
The RelatedManager classes are created in file: django/db/models/fields/related.py.
An example of how I'd like to use a custom RelatedManager is...
class Record(Model):
string = CharField()
class Managed(Model):
record = ForeignKey('Record')
boolean = BooleanField()
def view_function(...):
record = Record(string='Example')
record.save()
record.managed_set.add(Managed(boolean=True)) # How to override add()?
Any suggestions would be appreciated.
I'm not sure what you need the override for - the default queryset already does what you want.
But to answer the question, you can define a custom Manager on the model and set use_for_related_fields=True to ensure it gets used as the automatic manager. See the documentation on controlling automatic Manager types.
I think I am having the same problem.
I have a custom manager that overrides self._db and get_query_set() to route it to different databases.
I dynamically created a model class, and has its _default_manager set with my custom manager.
This works for the class itself, but not for related field (foreign or many2many), even though I did set sets use_for_related_fields = True.
For related field, appending db_manager(dbname) (for example, record.managed_set.db_manager(dbname)) can fix all() method, but not for add() method.
To understand what I mean, see this django ticket: http://code.djangoproject.com/ticket/13358
I think it works for all(), but not add().
RelatedManager.add() calls RelatedManager._add_items() which calls Manager.bulk_create().
So if you extend Manager.bulk_create(), you might be able to achieve what you are after.

Categories