I'm using Django (1.10.5) together with DRF (3.5.4) to create API. I've defined a view to handle file upload and call the foo method with the data passed in the post request and the path to temporary file where the uploaded file is being stored.
class FooView(APIView):
parser_classes = (FormParser, MultiPartParser)
def post(self, request, format=None):
serializer = NewFooSerializer(data=request.data)
if serializer.is_valid():
file_obj = serializer.validated_data["my_file"]
name = serializer.validated_data["name"]
foo_id = my_module.foo(name=name, my_file=file_obj.temporary_file_path())
result_url = reverse('api:foo', kwargs={"foo_id": foo_id})
return Response({"result_url": result_url}, status=status.HTTP_202_ACCEPTED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
My serializer to validate incoming data looks like this
class NewFooSerializer(serializers.Serializer):
name = serializers.CharField()
my_file = serializers.FileField()
Basically I'd like to test this view without the filesystem interaction using mock library. My current test method is defined below
#mock.patch('my_module.foo')
def test_post_should_invoke_foo_method(self, mocked_foo):
mocked_foo.return_value = "123456"
self.client.post(self.VIEW_URL,
{'my_file': self.file_mock,
'name': 'Test file'
}, format='multipart')
mocked_new.assert_called_with(name="Test file", my_file="/some/dummy/path"
This one of course raises AssertionError
AssertionError: Expected call: foo(my_file='/some/dummy/path', name='Test file')
Actual call: foo(my_file='/tmp/tmp0VrYgR.upload', name=u'Test file')
I couldn't find any solution how to mock TemporaryUploadedFile so I tried to mock tempfile.NamedTemporaryFile which is being used by this class. So I've added these lines before TestCase
named_temporary_file_mock = mock.MagicMock(spec=tempfile.NamedTemporaryFile)
named_temporary_file_mock.name = mock.MagicMock()
named_temporary_file_mock.name.return_value = "/some/dummy/path"
and then decorate my test method with
#mock.patch('tempfile.NamedTemporaryFile', named_temporary_file_mock)
But unfortunately this results in the same error as previous version. What I might be missing? Should I take another approach to avoid interaction with the filesystem?
Related
Say I have the following url
path('clients/by_<str:order>', BrowseClients.as_view(), name='browse_clients')
and its corresponding view
#method_decorator(login_required, name='dispatch')
class BrowseClients(TemplateView):
template_name = "console/browse_clients.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['clients'] = Client.objects.filter(
owner=self.request.user.id).order_by(self.kwargs["order"])
context['form'] = AddClientForm()
return context
How can I test what is in the context?
class TestBrowseClientsView(TestCase, GeneralViewTest):
fixtures = ['users.yaml', 'clients.yaml']
def setUp(self):
self.request = RequestFactory().get('/console/clients/by_inscription')
self.request.user = User.objects.get(pk=1)
def test_return_client_ordered_by_inscription_date(self):
view = BrowseClients()
view.setup(self.request)
context = view.get_context_data()
Naively, I thought that view.setup(self.request) would "feed" .get_context_data() with the relevant kwargs based on the pattern found in self.request. But it does not seem to be the case.
======================================================================
ERROR: test_return_client_ordered_by_inscription_date (console.tests.TestBrowseClientsView)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/src/jengu/console/tests.py", line 164, in test_return_client_ordered_by_inscription_date
context = view.get_context_data()
File "/usr/src/jengu/console/views.py", line 34, in get_context_data
owner=self.request.user.id).order_by(self.kwargs["order"])
KeyError: 'order'
----------------------------------------------------------------------
Why is that the case? I managed to fix my problem by passing status and order explicitly but it looks a bit ad hoc:
def get_context_data(self, status, order, **kwargs):
def test_return_clients_ordered_by_parameter(self):
view = BrowseClients()
view.setup(self.request)
context = view.get_context_data("all", "inscription")
Among the different options mentioned here, which one is the more canonical? Am I taking a wrong path, explicitly using variables when defining get_context_data()?
If you want to check what will be in the context of a response, first you need to work with a response object (and you are not, you are just making an instance of your view, not getting the response generated by the view). I don't know about RequestFactory, but I'm sure you'll find out how to adapt my answer to your use case.
So, it would be something like:
def test_your_context(self):
user = User.objects.get(pk=1)
self.client.force_login(user) # because of the login_required decorator
response = self.client.get(reverse("browse_clients"))
assert response.context['your_context_key'] == "Anything you want to check"
Just a few things to go further:
the definition of your get_context_data method seems ok to me,
if you use Class Based View, I would recommand you to use also a Base view if you want to check if user is logged in or not (LoginRequiredMixin)
you gave a name to your url, so just use it instead of writing its raw form (that's what I did in my answer).
If you use the test client it will take care of running middleware and initialising the view.
When you use setup() and call the view directly, the URL handler does not run, so it's up to you to pass the kwargs.
def test_return_client_ordered_by_inscription_date(self):
view = BrowseClients()
view.setup(self.request, order='inscription')
context = view.get_context_data()
I want to call a post of a class as shown below by instantiating a view as shown below., i need to write a unit test that calls the post method of this class and not through URL.
class SequencingRequestSpreadsheetView(GenericAPIView):
parser_classes = (JSONParser,)
serializer_class = SequencingRequestSerializer
permission_classes = (IsBioUser, )
suffix = '.xls'
path = settings.SEQUENCE_REQUEST_SUBMISSION
def post(self, request, format=None, simulation_mode = False):
I need to know how do I create a request object and pass it to this function.
iam instantiating this view class and I tried passing a request data as json and also tried dictionary but did not work.
how do I create a request object and pass it to this method.
resp = SequencingRequestSpreadsheetView().post(request)
You can use RequestFactory for achieve what you want.
factory = RequestFactory()
# Build a post request.
request = factory.post(post_url, data, ...)
# Note here that, I don't call the class view directly
# with:
# SequencingRequestSpreadsheetView().post(request)
# instead I get the view with as_view(), and then pass
# a post request to it.
view = SequencingRequestSpreadsheetView.as_view()
response = view(request, ...)
See Making requests here to get a better understanding on how RequestFactory works.
So, after reading the Django REST Framework document, and a bunch of tutorials, I am still having trouble understanding how to use the Django serializers to convert incoming POST (JSON) data into a Python object (sorry, I'm new).
Given that I am posting a JSON string to, say, api/foo/bar, how do I write its serializer?
Example JSON:
{ 'name': 'Mr. Foo', address:'Bar Street' }
My controller, Foo contains a bar method as follows:
#detail_route(
methods=['post']
)
def bar(self, request, uuid=None):
serializer = MySampleSerializer(data=request.DATA)
something.clone(serializer.object)
return Response(status=status.HTTP_201_CREATED)
Can somebody explain to me what should my serializer look like? And how do I access the serialized data from the serializer?
As you do not want to use a model, you have to create the serializer from scratch. Something like this should maybe work:
class MySerializer(serializers.Serializer):
name = serializers.CharField(max_length = 100)
adress = serializers.CharField(max_length = 100)
And then you could use it in a request like this:
def bar(self, request, uuid=None):
data = JSONParser().parse(request)
serializer = MySerializer(data = data)
return Response(status=status.HTTP_201_CREATED)
Note however, as you have not created an Django model, you will not be able to save the serialized data (and thus nothing will be saved in the database)
Basically, you pass in the JSON data to the serializer, and then access the data field which will return an ordered dictionary.
def bar(self, request, uuid=None):
serializer = MySampleSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
my_object = serializer.data # Grab the dict of values
To define a serializer:
class MySampleSerializer(serializers.Serializer):
name = serializers.CharField(max_length=30)
address = serializers.CharField(max_length=30)
You do not have to use the ModelSerializer:
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
and access:
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
by the way, all the above is from the DRF website
I'm trying to make a PATCH request using to the Django Rest Framework but get the following error:
{"image_data": [{"non_field_errors": ["Invalid data"]}]
I understand that JSONField() could give some issues so I have taken care of that by adding to_native and from_native, But, I'm still running into this issue. I don't think JSONField() is the problem here at all, but still worth mentioning.
I believe I'm doing something fundamentally wrong in how I am trying to update the related field.
Code below...
Models:
class Photo(models.Model):
user = models.ForeignKey(AppUser, help_text="Item belongs to.")
image_data = models.ForeignKey("PhotoData", null=True, blank=True)
class PhotoData(models.Model):
thisdata = JSONField()
Serializers:
class ExternalJSONField(serializers.WritableField):
def to_native(self, obj):
return json.dumps(obj)
def from_native(self, value):
try:
val = json.loads(value)
except TypeError:
raise serializers.ValidationError(
"Could not load json <{}>".format(value)
)
return val
class PhotoDataSerializer(serializers.ModelSerializer):
thisdata = ExternalJSONField()
class Meta:
model = PhotoData
fields = ("id", "thisdata")
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataSerializer()
class Meta:
model = Photo
fields = ("id","user", "image_data")
PATCH:
> payload = {"image_data": {"thisdata": "{}"}}
> requests.patch("/photo/123/",payload )
I have also tried:
> payload = {"image_data": [{"thisdata": "{}"}]}
> requests.patch("/photo/123/",payload )
But again giving the same error:
[{"non_field_errors": ["Invalid data"]}]
The original idea of Django Rest Framework's serialization of relations is to not change values of related fields.
It means that your payload should contain a pk of PhotoData object, not a dataset for it.
It's like in models you can't assign a dict to a foreign key field.
Good (works only with serializers.PrimaryKeyRelatedField which contains problems itself):
payload = {"image_data": 2}
Bad (not works in DRF by default):
payload = {"image_data": {'thisdata': '{}'}}
Actually the data model that you provided doesn't need PhotoData at all (you can move thisdata field to Photo), but let's assume you have a special case even when Zen of Python says Special cases aren't special enough to break the rules..
So, here is some possible ways:
Using fields serializers (your original way)
What you want to do now is possible but is very ugly solution.
You may create a PhotoDataField (works for me, but not ready to use code, only for demonstration)
class PhotoDataField(serializers.PrimaryKeyRelatedField):
def field_to_native(self, *args):
"""
Use field_to_native from RelatedField for correct `to_native` result
"""
return super(serializers.RelatedField, self).field_to_native(*args)
# Prepare value to output
def to_native(self, obj):
if isinstance(obj, PhotoData):
return obj.thisdata
return super(PhotoDataField, self).to_native(obj)
# Handle input value
def field_from_native(self, data, files, field_name, into):
try:
int(data['image_data'])
except ValueError:
# Looks like we have a data for `thisdata` field here.
# So let's do write this to PhotoData model right now.
# Why? Because you can't do anything with `image_data` in further.
if not self.root.object.image_data:
# Create a new `PhotoData` instance and use it.
self.root.object.image_data = PhotoData.objects.create()
self.root.object.image_data.thisdata = data['image_data']
self.root.object.image_data.save()
return data['image_data']
except KeyError:
pass
# So native behaviour works (e.g. via web GUI)
return super(PhotoDataField, self).field_from_native(data, files, field_name, into)
and use it in PhotoSerializer
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataField(read_only=False, source='image_data')
class Meta:
model = Photo
fields = ("id", "user", "image_data")
so the request will works well
payload = {"image_data": '{}'}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/"), payload)
and the "good" request also
photodata = PhotoData.objects.get(pk=1)
payload = {"image_data": photodata.pk}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/"), payload)
and in result you will see in GET request "image_data": <photodata's thisdata value>,.
But, even if you will fix the validation problems with this approach it still be a huge pain in ass as you can see from my code (this is only thing DRF can offers you when you want to "break a normal workflow", Tastypie offers more).
Normalize your code and use #action (recommended)
class PhotoDataSerializer(serializers.ModelSerializer):
class Meta:
model = PhotoData
fields = ("id", "thisdata")
class PhotoSerializer(serializers.ModelSerializer):
image_data = PhotoDataSerializer() # or serializers.RelatedField
class Meta:
model = Photo
fields = ("id", "user", "image_data", "test")
and now define a specific method in your api's view that you will be able to use to set the data for any photo
from rest_framework import viewsets, routers, generics
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework import status
# ViewSets define the view behavior.
class PhotoViewSet(viewsets.ModelViewSet):
model = Photo
serializer_class = PhotoSerializer
#action(methods=['PATCH'])
def set_photodata(self, request, pk=None):
photo = self.get_object()
serializer = PhotoDataSerializer(data=request.DATA)
if serializer.is_valid():
if not photo.image_data:
photo.image_data = PhotoData.objects.create()
photo.save()
photo.image_data.thisdata = serializer.data
photo.image_data.save()
return Response({'status': 'ok'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
Now you can do almost the same request as you doing now, but you have much more extensibility and division of responsibilities in code.
See the URL, it's appended when you have #action's wrapped method.
payload = {"thisdata": '{"test": "ok"}'}
resp = requests.patch(request.build_absolute_uri("/api/photo/1/set_photodata/"), payload)
Hope this helps.
Since my model's custom save method takes request.user as an argument I'm unable to do POST/PUT requests.
TypeError at /api/obsadmin/observation/23
save() takes at least 2 arguments (1 given)
I'm using SessionAuthentication() and have included the CSRF token.
Here's the relevant model part:
def save(self, user, owner=None, *args, **kwargs):
self.updated_by = user.id
super(ObsModel, self).save(*args, **kwargs)
And the resource:
class ObservationResource2(ModelResource):
comments = fields.ToManyField(CommentResource2, 'comments', full=True, null=True)
class Meta:
queryset = Observation.objects.filter(is_verified=True)
authentication = SessionAuthentication()
authorization = DjangoAuthorization()
resource_name = 'observation'
always_return_data = True
I've just achieved this same end goal by using the built-in hydrate methods to modify the data prior to saving. The current request is available in bundle.request inside the hydrate methods. See the docs here.
I have a Friend model exposed via FriendResource that I want to link to the creating Django user via a user ForeignKey field.
My example Resource code:
class FriendResource(ModelResource):
class Meta:
queryset = Friend.objects.all()
resource_name = 'friend'
excludes = ['slug',]
authentication = SessionAuthentication()
authorization = DjangoAuthorization()
always_return_data = True
def get_object_list(self, request):
return super(FriendResource, self).get_object_list(request).filter(user=request.user)
def hydrate(self, bundle):
bundle.obj.user = bundle.request.user
return bundle
Hope that helps!
You could override the default save() method on your ModelResource subclass. Looking at the default implementation shows that save() is called with a bundle object which has both the request and the object to be saved.
Unfortunately, there's no easy way to change this without copying most of that code because changing a Django model's save() signature is fairly uncommon. You might be able to do something like this, although I'd recommend testing it carefully:
from functools import partial
try:
old_save = bundle.obj.save
bundle.obj.save = partial(old_save, user=bundle.request.user)
return super(FooResource, self).save(bundle)
finally:
bundle.obj.save = old_save
References:
obj_create: docs source
obj_update: docs source
save: source