I'am building an API using python 2.7 and django 1.7 and I'm facing a problem. I'm not an expert in Rest Framework but I understand the basic mechanisms.
I can resume my problem by giving you an example. I have a route lets say
/api/project/
Django Rest Framework provides me all basic operations for this route and I don't need to write them e.g:
POST, GET, PUT, DELETE => /api/project/
The fact is, I want to do some operation when I create a new project. I want to add the id of the user who has created the new project.
I want to add some kind of trigger/callback to the create function:
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def create(self, request, *args, **kwargs):
I want to keep the internal behavior of Rest Framework (I don't want to rewrite the route functions), but I want the route to do extra stuff and I need the request object in my trigger/callback. Something like
def callback(request, instance):
instance.created_by = request.user
instance.save()
Do you have any ideas?
Thank you.
You need to add creator_id field to your serializer as well as to model represented by the resource. Then you can do something like this in your view:-
import copy
class ProjectViewSet(viewsets.ModelViewSet):
...
def create(self, request, *args, **kwargs):
data = copy.deepcopy(request.data)
data['creator_id'] = request.user.id
request._data = data
return super(ProjectViewSet, self).create(request, *args, **kwargs)
Related
I'm working on an Django app that uses REST Framework together with Swagger. Also added some models, and one of them is called Example. Added some views based on mixins in views.py for the model previously mentioned.
In views.py, I've created two classes: ExampleList (that uses GET to get all the objects made out from that model and POST to add a new model) and ExampleIndividual, that uses methods such as individual GET, PUT and DELETE.
Anyways, this is how my ExampleList class looks like:
class ExampleList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
permission_classes = [IsAuthenticated]
queryset = ExampleModel.objects.all()
serializer_class = ExampleSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
In the settings.py file, in the REST_FRAMEWORK configuration, I've set:
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
Everything works fine at this moment. What I want to do, is, whenever I want to get the list of all objects from the Example model (access the get() method from the ExampleList() class, I want it to work only if I am authenticated (using the Authorize option from Swagger). If not, a status code like "FORBIDDEN" should be returned.
I tried using permission_classes = [IsAuthenticated] at the beginning of the method, but it didn't work. It seems that I can still GET all the objects without being authenticated into Swagger.
Any advice on how I can do that properly? Thank you.
Did you tried it with Token Authentication? With this method in each request made to the API, the token must be included in the Header Field Authorization. https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication to test it in Browser you can install an extension called Mod Header https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj there you can include the token in the header field.
I'm trying to make a custom permission using this guide
views.py
class CustomModelList(generics.ListAPIView):
queryset = CustomModel.objects.all()
serializer_class = CustomModelSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsCustomOrReadOnly]
def get(self, request, format=None):
# some logic
def post(self, request, format=None):
# some logic
Just for experiment I've created this permission not to apply anyway
pesmissions.py
class IsCustomOrReadOnly(BasePermission):
def has_object_permission(self, request, view, obj):
return False
But when POST request sends to server it takes no effect -- I'm able to create new model instance.
I think that since you are using a list view, custom object level permissions are not checked automatically.
Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you require object-level filtering of list views, you'll need to filter the queryset separately. See the filtering documentation for more details.
You can try overriding the has_permission method instead and see if that works, or check the permissions manually.
I have used Python for many of my projects but I am new to django and the django rest framework, which I am using to design and develop a set of Web APIs for my current project.
Weaving the backend, we are using Postgres for user information and DynamoDb for other set of resources users have to work upon.
In the basic implementation, I tried to write the viewset as below:
class WorkViewSet(viewsets.ViewSet):
serializer_class = serializers.WorkSerializer
permission_map = {
'create' : [IsAuthenticated, IsUser, ], # post
'list' : [IsAuthenticated, ], # get
'retrieve' : [IsAuthenticated, ], # get
'work_approval' : [IsAuthenticated, IsAdmin, ], # post
'work_disapproval' : [IsAuthenticated, IsAdmin, ], # post
}
def list(self, request):
...
def create(self, request):
...
def retrieve(self, request, pk=None):
...
#action(methods=['post'], detail=True, url_path='approve', url_name='work_approval')
def work_approval(self, request, pk=None, *args, **kwargs):
...
#action(methods=['post'], detail=True, url_path='disapprove', url_name='work_disapproval')
def work_disapproval(self, request, pk=None, *args, **kwargs):
...
def get_permissions(self):
try:
return [permission() for permission in self.permission_map[self.action]]
except KeyError:
return [permission() for permission in self.permission_classes]
and the serializer as below:
class WorkSerializer(serializers.Serializer):
STATUSES = (
'0',
'1',
'2',
)
work_id = serializers.IntegerField(read_only=True)
work_name = serializers.CharField(max_length=256)
work_type = serializers.CharField(max_length=256)
work_status = serializers.ChoiceField(choices=STATUSES, default='0')
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
This set of code is working absolutely fine for me, but with latest requirement changes, I need to configure the POST request to additionally accept a csv file and parse this file to extract the contents which must be pushed to database in additional fields (not as file field). I tried to look for the solutions to this problem and found this link but this solution mainly targets bulk submission of single type of resource which differs from my need.
I am using Python 3.6.5, Django 2.0.6 and Django Rest Framework 3.8.2
Please help me how should I proceed.
Extend your serializer to include a CSV file:
class WorkSerializer(serializers.Serializer):
csv_file = serializers.FileField()
in serializer's create function:
def create(self, validated_data):
csv_input = validated_data.pop("csv_file", None)
instance = super().create(validated_data)
if csv_input:
** Process your csv file **
return instance
Personally, i would suggest you to create a background to process csv files and update DB. Because thsi could be long running task.
So instead of processing the csv file directly during the POST request, you would schedule a task.
Updated to answer the comment
Background processing - it requires little bit of configuration and you have multiple ways you can choose from. Perhaps, the easiest is to use django background tasks
And this would serve your purpose well. You simple create a function, add background decorator and when it is called, a task is scheduled.
Do you think if this approach of using a csv file to post bulk data is a good one or we should use a huge json instead ?
Well that depends.
If you upload a file, you will need to configure a storage for it where your scheduled task has access to ( local or remote one , again depends on your use case ).
One huge json - hm, that depends how huge is huge. You would need to run some test to determine where are your limits.
Maybe a possible solution would be to upload your csv file directly to your storage from client ( if you'd use S3 - that would be easy ) and then just tell your server to process it from there.
Im kind of new to Django Rest Framework. I know it is possible to post data using the Browsable API, I just don't know how. I have this simple view:
class ProcessBill(APIView):
def post(self, request):
bill_data = request.data
print(bill_data)
return Response("just a test", status=status.HTTP_200_OK)
When I go to the url that points to this view, I get the rest_framework browsable api view with the response from the server method not allowed which is understandable cause I am not setting a def get() method. But ... how can I POST the data? I was expecting a form of some kind somewhere.
EDIT
This is a screenshot of how the browsable API looks for me, it is in spanish. The view is the same I wrote above but in spanish. As you can see ... no form for POST data :/ .
Since you are new I will recommend you to use Generic views, it will save you lot of time and make your life easier:
class ProcessBillListCreateApiView(generics.ListCreateAPIView):
model = ProcessBill
queryset = ProcessBill.objects.all()
serializer_class = ProcessBillSerializer
def create(self, request, *args, **kwargs):
bill_data = request.data
print(bill_data)
return bill_data
I will recommend to go also through DRF Tutorial to the different way to implement your endpoint and some advanced feature like Generic views, Permessions, etc.
Most likely the user has read-only permission. If this is the case make sure the user is properly authenticated or remove the configuration from the projects settings.py if it is not necessary as shown below.
```#'DEFAULT_PERMISSION_CLASSES': [
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
#],```
Read more on permissions here.
I am using Django REST as backend and angular as front end.
I have two serializers one for readong GET requests and other for POST, PUT requests.
This was because there are few fields like interval, etc which i am entering in integer by user but in the database i am saving as timedelta so i have to multitply them to make them as seconds on front end.
so e.g interval = 5 entered by user and i am posting 5*60*60 to server.
In order to read i have made ReadSerializer where i am diving that by 60*60 to again show to user what he added.
This is working find my problem is after saving my object to database the djnago rest frameework sends the object as it is saved which has interval = 5*60*60. inerval is just an example there 4-5 felds where i am changing them in front end before posting
Is there any way that response used my READ serializer before sending
class Serializer(serializers.ModelSerializer):
interval = serializers.SerializerMethodField('get_interval')
class Meta:
model = User
fields = ('id', 'interval')
def get_interval(self, obj):
return obj.interval/60*60
class WriteSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'interval')
This is the view
class UserListCreateView(generics.ListCreateAPIView):
queryset = User.objects.filter(is_deleted=False)
def get_serializer_class(self):
if self.request.method == 'GET':
return ReadSerializer
return WriteSerializer
You can use the transform_field method which allows you to customize the representation of a field when showing it.
Documentation about it: http://www.django-rest-framework.org/api-guide/serializers/#customizing-field-representation
Btw, careful if you are planning to upgrade to new versions of drf (>=3), these methods may disappear in favour of a unique to_representation method: https://groups.google.com/forum/?fromgroups#!topic/django-rest-framework/9kp5kXCssR4
I would just change it in JavaScript on the front end.
However I have a similar issue and solved it like this, (warning, I just started using DRF, so may not be the best way):
Just change the response of the method directly by first getting the original response, then editing the data property of it, which is a dict (pre-serialized) of the data to return:
class UserListCreateView(generics.ListCreateAPIView):
...
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
response.data['interval'] = response.data['interval'] / 60 / 60
return response