Creating plain API which accepts files - python

I am building an API which accepts file and validates it, sends json response (not storing the file in db, so no need of model). I have created a class based view, in post function, request.FILES or request.POST doesn’t contain the file… If I make a form class, it will work. But, I don’t want any UI, it should be a simple API. Anyone knows how to do it?
class ValidateView(View):
def get(self, request, *args, **kwargs):
pass
def post(self, request, *args, **kwargs):
file = request.FILES
if not file:
return JsonResponse({"status_code": "400", "message": "a file is required", "status_response": "Bad Request"})
return JsonResponse({"status_code": "200", "message": "data validated", "status_response": "Success"})
#csrf_exempt
def dispatch(self, request, *args, **kwargs):
return super(ValidateView, self).dispatch(request, *args, **kwargs)
I used djangorestframework and come up with this
class ValidateView(views.APIView):
parser_classes = (FileUploadParser,)
def post(self, request, filename, format=None):
file_obj = request.data['file']
if is_csv_valid(file_obj):
return Response(status=200, data={"message": "valid file"})
else:
return Response(status=400, data={"message": "not valid"})
But, here the problem is I must build a url like this
re_path("validate/(?P<filename>[^/]+)$", ValidateView.as_view(), name="api-validate")
If I exclude filename in url, it throws an error. Also, file_obj contains some extra lines along with original data like this.
[b'----------------------------634867545113999762020341\r\n', b'Content-Disposition: form-data; name=""; filename="kafka_word_count_input.txt"\r\n', b'Content-Type: text/plain\r\n', 'original_data']
Someone help!!!

you can create the serializer
class FileSerializer(serializer.Serializers):
file = serializer.FileFiled()
def validate_file(self, value):
# write logic to validate
if not is_csv_valid(value):
raise serializer.ValidationError("wrong file")
else:
return value
class ValidateView(views.APIView):
serializer_class = FileSerializer
def post(self, request, filename, format=None):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True):
return Response(status=200, data={"message": "valid file"})
now send the reuqest in form data

Figured it out... The problem is I am not sending key file from postman. This I figured it out, when using curl... wowww, command line tools rocks...
also, I need to use request.FILES['file'] to access file and do my stuff...

Related

Class Based Views Form neither Valid nor Invalid (Django)

I'm new to Django Class Based Views and I can't get my form to pass through neither form_valid() nor form_invalid().
I have taken most of this code from the Django allauth module, so I extend some mixins (AjaxCapableProcessFormViewMixin & LogoutFunctionalityMixin) that I do not know well.
This form is meant to allow users to change their passwords upon receiving an email. As it is now, users are able to change their password but since the form_valid() function is never triggered, they do no get redirected to the success URL as is intended. Instead the password change is registered but the users stay on the same page.
The functions dispatch(), get_form_kwargs() & get_form_class() are all triggered and behave in the way that they should. Still, it's unclear to me why they execute in the order that they do (dispatch() is triggered first, then get_form_class() and finally get_form_kwargs(). I suppose they implicitely have an order as presented in this documentation: https://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/FormView/)
I am lacking some intuition about how this works, therefore I don't know if there is a way to redirect to the success URL without passing through form_valid() because that would also solve my problem.
As is mentionned in the title, neither form_valid() nor form_invalid() is triggered after submitting a new password. The last executed bit of code is the return kwargs from the get_form_kwargs() function.
Here is my code:
class PasswordResetFromKeyView(AjaxCapableProcessFormViewMixin, LogoutFunctionalityMixin, FormView):
template_name = "account/password_reset_from_key." + app_settings.TEMPLATE_EXTENSION
form_class = ResetPasswordKeyForm
success_url = '/'
reset_url_key = "set-password"
def get_form_class(self):
return get_form_class(
app_settings.FORMS, "reset_password_from_key", self.form_class
)
def dispatch(self, request, uuid, **kwargs):
self.request = request
token = get_object_or_404(ResetToken, token=uuid)
if token.redeemed == False:
self.reset_user = token.client
self.token = token
response = self.render_to_response(self.get_context_data(token_fail=False))
else:
return super(PasswordResetFromKeyView, self).dispatch(
request, uuid, **kwargs
)
return response
def get_form_kwargs(self, **kwargs):
kwargs = super(PasswordResetFromKeyView, self).get_form_kwargs(**kwargs)
kwargs["user"] = self.reset_user
if len(kwargs) > 3:
try:
if kwargs['data']['password1'] == kwargs['data']['password2']:
self.reset_user.set_password(kwargs['data']['password1'])
self.reset_user.save()
self.token.redeemed = True
self.token.date_redeemed = datetime.now()
self.token.save()
perform_login(
self.request,
self.reset_user,
email_verification=app_settings.EMAIL_VERIFICATION,
)
else:
pass
##passwords dont match
except:
##couldnt change the password
pass
return kwargs
def form_valid(self, form, **kwargs):
form.save()
return super(PasswordResetFromKeyView, self).form_valid(form)
def form_invalid(self, form):
response = super().form_invalid(form)
if self.request.accepts('text/html'):
return response
else:
return JsonResponse(form.errors, status=400)
If both methods are not triggered, it means - you requests.method is never is 'POST'.
The class FormView calls this two methods only if post.method == 'POST':
# code from django.views.generic.edit
...
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
By the way in dispatch, if token.redeemed == False you should return self.form_invalid().

Where/how to replace default upload handler in Django CBV?

I am trying to specify a specific method of handling file uploads for a class based view. Per the docs this can be achieved by something like:
from django.core.files.uploadhandler import TemporaryFileUploadHandler
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
If i specify this in post method of a FormView like so:
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return super().post(self, request, *args, **kwargs)
I get:
AttributeError: You cannot set the upload handlers after the upload has been processed.
Variants like yield the same result:
def post(self, request, *args, **kwargs):
self.request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
However when i do this in the get method this is ineffective:
def get(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=self.request)]
return super().get(self, request, *args, **kwargs)
If I upload a small file it still uses the default django.core.files.uploadhandler.MemoryFileUploadHandler.
What am I doing wrong?
EDIT
Also when i try to mirror what is suggested in the note, I get the same AttributeError:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
#csrf_exempt
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return self._post(request, *args, **kwargs)
#csrf_protect
def _post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Ok, finally got it to work (using the suggestions provided by #Alasdair). Setting a method decorator(crsf_exempt) on post is not engough it needs to be on dispatch. For anyone struggling with this in the future, it goes like this:
from django.views.generic import FormView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
#method_decorator(csrf_exempt, 'dispatch')
class UploadDataSetView(FormView):
def post(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
return self._post(request)
#method_decorator(csrf_protect)
def _post(self, request):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Also it will fail if you remove the {% csrf_token %} from your template (which is what you want).
Because you cannot change upload handler in view as it is something that gets invoked prior to your view function.
Get shouldn't collect post parameters so it behaves accordingly.
Upload Handlers
When a user uploads a file, Django passes off the file data to an
upload handler – a small class that handles file data as it gets
uploaded. Upload handlers are initially defined in the
FILE_UPLOAD_HANDLERS setting, which defaults to:
["django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler"]
If you want different upload handler you can change FILE_UPLOAD_HANDLERS settings
If this is not enough you can write your custom upload handler
Edit:
Also, request.POST is accessed byCsrfViewMiddleware which is enabled
by default. This means you will need to use csrf_exempt() on your view
to allow you to change the upload handlers.

How to return an empty form in ModelFormMixin

DetailStory subclasses DetailView and ModelFormMixin thus presenting the DetailView of a certain story and a form at the end. However, on filling the form and submitting the data, the data is saved in the databases but it is still shown on the form (in addition to the one now displayed on the DetailView). How do I present an empty form after submitting it? (Here is the code sample)
class DetailStory(DetailView, ModelFormMixin):
model = Story
template_name = 'stories/detail_story.html'
context_object_name = 'detail'
form_class = CommentForm
def get(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
return DetailView.get(self, request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
self.form = self.get_form(self.form_class)
if self.form.is_valid():
obj = self.form.save(commit=False)
obj.user = self.request.user
obj.memoir = self.get_object()
self.object = obj.save()
return self.get(request, *args, **kwargs)
def get_object(self):
item_id = crypt.decode(self.kwargs['story_id'])[0]
obj = get_object_or_404(Story, Q(privacy='public') | Q(user_id=self.request.user.id), pk=item_id)
return obj
get_form uses the request data to construct the form as per the docs
If the request is a POST or PUT, the request data (request.POST and request.FILES) will also be provided.
So simply don't make your post function go back through the get, just have it redirect to your required place or do anything differently to pointing it at the get function.
return redirect('mynamespace:story_detail', story_id=self.object.pk)
You may wish to read this answer for a list of technical details you should consider whilst making your application. In particular,
Redirect after a POST if that POST was successful, to prevent a refresh from submitting again.

How to catch a request in a DRF viewset?

So I have this:
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [TokenHasReadWriteScope]
queryset = User.objects.all()
serializer_class = UserSerializer
entity_name = 'user'
perm_type = {
'POST': 'create',
'GET': 'read',
'PATCH': 'update',
'DELETE': 'delete'
}
def check_permissions(self, request):
user = request.user
has_permissions = user.has_entity_permissions(
name=self.entity_name,
perm_type=self.perm_type[request.method]
)
if not has_permissions:
raise PermissionDenied
def create(self, request, *args, **kwargs):
self.check_permissions(request)
return super().create(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
self.check_permissions(request)
return super().list(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
self.check_permissions(request)
return super().update(request, *args, **kwargs)
I have a custom security server, the purpose is to centralize all the apps of the company, so when we have a new employee, we can give him access to the differents apps with different permissions in every entity and their properties from a single app instead of creating the user and give him permissions in every app.
So basically in the "check_permission" function I check for this, depending in the request method (perm_type associated a request method with a permission (CRUD))
The question:
there is a way to catch the request before enters into list, retrive, create, update or delete (Middlewears dont work because i need to know the entity type or endpoint, thats why I set the entity_name variable, but if you have a better idea is welcome)
Why overriding your action method when you already have check_permissions implemented by APIView class? (which ModelViewSet inherits from)
Simply add your peace of code by overriding it.

DjangoRestFramework upload file 'CSRF Verification Failed'

I am using DjangoRestFramework to make an API. Currently I have OAuth2 authentication working which means I can generate a valid access token to use the API.
How do I upload a user file? I need to upload a file and relate it to the user who uploaded it.
I am currently trying to do it this way
api/views.py:
class FileUploadView(APIView):
parser_classes = (FileUploadParser,)
def put(self, request, filename, format=None):
file_obj = request.FILES['file']
# do stuff
return Response(status=204)
api/urls.py contains this line:
url(r'^files/', 'api.views.FileUploadView'),
but when I try to upload a file I get an error stating that:
'CSRF verification failed. Request aborted'
'Reason given for failure: CSRF cookie not set'
when I try this curl command:
curl -XPUT http://localhost:8000/files/ -H 'Authorization: Bearer some_access_token' -F filedata=#localfile.txt
Here are my REST_FRAMEWORK defaults:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.OAuth2Authentication',
)
}
1) In my original code I was expecting a filename parameter to come into my view but I was not parsing it out of the url in urls.py. It should have looked something like this:
url(r'^uploads/(?P<filename>[\w.]{0,256})/$', views.UploadsView.as_view()),
2) I was also not providing the APIView as a view. If you notice above I am specifically calling the .as_view() method.
With the above fixed and implementing POST instead of PUT my code works as expected. Here is my current APIView:
class UploadsView(APIView):
parser_classes = (FileUploadParser,)
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
file_obj = request.FILES['file']
print("FILE OBJ: ", file_obj)
return Response(status=201)
Per the Django REST Framework Documentation, "If you're using SessionAuthentication you'll need to include valid CSRF tokens for any POST, PUT, PATCH or DELETE operations.
In order to make AJAX requests, you need to include CSRF token in the HTTP header, as described in the Django documentation."
Alternatively, you can attempt to make this view CSRF Exempt:
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
class FileUploadView(APIView):
parser_classes = (FileUploadParser,)
#method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(FileUploadView, self).dispatch(request, *args, **kwargs)
def put(self, request, filename, format=None):
file_obj = request.FILES['file']
# do stuff
return Response(status=204)

Categories

Resources