Django rest framework : different serializer following instance - python

I'm trying to implement a workflow process through the REST Api for an object to handle.
At the step 1, all fields could be written (except the status).
When calling a method (route on the viewset class), the status change (reflecting the change of the state of the object).
class ExaminationViewSet(viewsets.ModelViewSet):
model = Examination
#detail_route(methods=['GET'])
def invoice(self, request, pk=None):
current_examination = self.get_object()
current_examination.status = 1
current_examination.save()
return Response({'invoice':'waiting for paiment'})
Now it is not possible to edit some fields (but not all) on the object.
I thought that overriding get_serializer_class could be a solution, but the serializer class depends on the "state" of the instance. I'm not sure that it could be the way.
Do you have any idea to solve this problem ?

Related

How are the function/methods defined inside a class Based Views CBV are called?

I have been learning the Django framework for 3-4 months but there is 1 thing that bugs me the most and I am unable to find a satisfactory answer yet.When we define the functions/methods inside a Class based views, do they get called automatically when some object is created? Like we use
#action(detail=False)
def recent_users(self, request):
recent_users = User.objects.all().order_by('-last_login')
page = self.paginate_queryset(recent_users)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(recent_users, many=True)
return Response(serializer.data)
How this method will be executed? DO we have to make an object and call like obj.method?
And how those get() get_queryset() methods work given with the Class Based Views?? How do they process the data from models?
and one last question would be the mixture of these both.
Can I create a new method inside a ClassBasedView? If yes, How do I execute it ? Say in return and render data inside a template ?
Please do not provide any link. I just want to know in simple English terms. If anybody knows, it'll be very helpful.
When referencing a class based view in the path (url prior to 2.X) function we call the as_view class method. Looking at the source code will show that this essentially defines a function view that calls a class based view's dispatch method.
What this means is that the entry point in a class based view is the dispatch method. All other methods are called somewhere down the line from dispatch. The methods immediately called by dispatch are all going to be named according to HTTP methods (E.G. get, post, delete).
This is the typical path for a view inheriting from TemplateView.
as_view returns a function that calls dispatch.
dispatch calls get or, if request.method isn't GET, the http_method_not_allowed method.
get calls get_context_data and passes that as an argument to render_to_response.
render_to_response calls get_template_names and passes that as an argument to TemplateResponse.
You can define any method you want on your class based view, but it won't be called unless you call it somewhere that is already being called.
One common modification is to add something to get_context_data.
def get_context_data(self, **kwargs):
kwargs.setdefault('recent_users', self.recent_users())
return super().get_context_data(**kwargs)
def recent_users(self):
# self.request is accessible here.
...
https://ccbv.co.uk/ is a helpful resource for writing class based views. I reference that site all the time while I'm writing class based views.

Swagger not working with Django BaseSerializer object

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.

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).

Is it okay to set instance variables in a Django class based view?

I trying out Django's class based views (CBVs).
class BlahView(TemplateView):
template_name = 'blah/blah.html'
def get_context_data(self, **kwargs):
#code...
def get(self, request, **kwargs):
#more code...
Now, I know that I can get the request params from self.request. Now say I want to parse these request params and store them within the class. Can I store those in self.xxx? Now, obviously based on how classes work, this seems straightforward.
But I can't make out the flow of control, looking at the definition of View (superclass of TemplateView). The source mentions as_view() to be the 'entry-point'
I thought of setting my instance variables at the beginning of get_context_data() but that doesn't seem right to do initialization there.
Can I define an __init__() for my CBV?
If so, will there be threading issues or something where multiple page-accesses possibly work with a global instance of my parsed data?
I know this sounds a bit messy, but I'm just a bit confused with the code flow in CBVs.
According to the source of django.views.generic.base.View.as_view:
on django startup, as_view() returns a function view, which is not called
on request, view() is called, it instantiates the class and calls dispatch()
the class instance is thread safe
According to the source of django.views.generic.base.View.__init__, the request object is out of scope at this point so you can't parse it in your own constructor overload.
However, you could parse the request and set class view instance attributes in an overload of django.views.generic.base.View.dispatch, this is safe according to the source:
class YourView(SomeView):
def dispatch(self, request, *args, **kwargs):
# parse the request here ie.
self.foo = request.GET.get('foo', False)
# call the view
return super(YourView, self).dispatch(request, *args, **kwargs)
#jpic provided a great answer. Inspired from it, I would like to reference the following blog post where the author claims that:
... We cannot override view, as doing so would require overriding
as_view(). Overriding dispatch() is appealing (and what I did
originally when I presented this talk) because it offers a single
simple place to do so, but this defies the logic of dispatch().
Instead, it is best to call set_account() in overrides of both get()
and post(). ...
Therefore, one can override the get or post methods and set any self.whatever variables. It feels somehow cleaner.

How to manipulate form fields in Django dynamically within ModelAdmin?

I have a field (slug) that is "required" in the model, but want to change the field in the ModelAdmin class to be optional. If the user doesn't fill it in, it is automatically filled in by another field (name).
class SomeModel(model.Model):
name = model.CharField(max_length=255)
slug = model.SlugField(unique=True, max_length=255)
I tried to do this various ways, such as overriding get_form() within ModelAdmin or using the ModelForm class and specifying the form specifically.
class SomeModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(self.__class__, self).get_form(request, obj, **kwargs)
form.slug.required = False
return form
However, neither solution worked for me. Beyond manually creating the form, is there any other quicker solution?
I have a lot of these forms, and doing it by hand might be tedious and hard to maintain.
Found this page through Google when wrestling with the same problem myself. The following will also work in the ModelAdmin:
def get_form(self, *args, **kwargs):
form = super(SomeModelAdmin, self).get_form(*args, **kwargs)
form.base_fields['slug'].required = False
return form
Subsequent forms created from the updated ModelFormMetaclass will have the slug field unrequired.
This works better in my situation, where I have only a single class in which I need to unrequire the field, and don't need to do any data transformation on save. GoogleDroid's solution is better if you have a lot of classes, or where the data transformations are necessary.
In your get_form method, form.fields['slug'].required should work.
But the proper way to do this is to simply provide a custom ModelForm.
class SomeModelForm(forms.ModelForm):
slug = forms.CharField(required=False)
class SomeModelAdmin(admin.ModelAdmin):
form = SomeModelForm
Incidentally, please don't do super(self.__class__, self). You should always explicitly name the current class when using super, otherwise any subclass that inherits from yours and in turn calls super will break.
Edit form.fields, not forms.fields.
By saying self.__class__, you are explicitly stopping Python from working out the inheritance - because it always refers to the concrete class - ie the bottom of the inheritance tree. But if your method is the middle of that tree, then referring to the concrete class in super is wrong - because you want it to call the next level up from where you are, not one up from the bottom. That's why you should always name the class you're in - in this case, super(SomeModelAdmin, self).
I just wanted to report back in case others might find this useful.
I was never able to in get_form method do form.fields['slug'].required and never figured out why. However, I solved my problem by creating a new form inheriting from ModelForm.
I had to override init() to set self.fields['slug'].required = False after calling the parent constructor, then override clean_slug() to modify the slug field content if required by accessing self.data['slug'].
Hope this helps someone

Categories