django api post without bring json still work? - django

Because django rest framework did not support bulk create
So I write one
And I found a strange problem
if I POST the api with a json like :
[{'address':'1','name':'2','start':'3'},
{'address':'10','name':'20','start':'30'}]
it works!
But if I kust POST the api without bring json
I still got bulk create success message.
Why would this happen??
Where do I write wrong??
This is my API view
class BulkTestList(APIView):
def post(self, request, format=None):
duplicateList = []
for data in request.data:
message = {}
if not 'address' in data.keys():
message['address'] = [ "This field is required."]
elif not data['address']:
message["address"] = [ "This field may not be blank."]
if not 'name' in data.keys():
message["name"] = [ "This field is required."]
elif not data['name']:
message["name"]= [ "This field may not be blank."]
if not 'star' in data.keys():
message["star"] = [ "This field is required."]
elif not data['star']:
message["star"]= [ "This field may not be blank."]
if message:
return Response(message, status=status.HTTP_400_BAD_REQUEST)
for data in request.data:
address = data['address'].upper()
bulkCreateObjects = Data(address=address, name=data['name'], star=data['star'], datetime=datetime.datetime.now(pytz.utc))
bulkCreateObjects.save()
message = {"bulk create success"}
return Response(data=message, status=status.HTTP_201_CREATED)

Django REST framework doesn't have bulk out of the box but you have a third party app that does.
Your current view just doesn't call a serializer therefore you won't get the validation at any point. See http://www.django-rest-framework.org/tutorial/3-class-based-views/#rewriting-our-api-using-class-based-views
Note that for bulk you'll add a many=True to the serializer so it will be able to deal with list of data.

Your issue that the view returns a 201 "{bulk create success}" is due to the fact that you iteration over request.data does not check if reuqest.data is actually empty. A for loop over an empty list will just skip over the for block. As Linovia mentions, you need to add some validation to your view.

Related

Django: Validate GET request

I have written a few simple API endpoints (without Django REST). I have problems figuring out how to validate the input data.
class CarTypeForm(forms.Form):
car_type= forms.IntegerField(
validators=[MinValueValidator(0), MaxValueValidator(6)], required=False
)
def car_data_as_json(request):
"""
API Endpoint
"""
# Receive and validate variables from get request
car_type= request.GET.get("car_type", 0)
car_type_form = CarTypeForm()
car_type_form.car_type = int(car_type)
if car_type_form.is_valid():
do something
return JsonResponse(something)
else:
return JsonResponse({'err': 'invalid car_type'}, status=400)
I don't understand why if car_type_form.is_valid(): is not True despite the value being 5 for example.
Endpoint is called by javascript to an url: /api/car_data/?car_type=<number>
I'm using Django 2.1
You need to initialize the form with your data before calling is_valid:
car_type_form = CarTypeForm({'car_type': car_type})

Django rest serialization - Passing JSON to Javascript

Disclaimer - Django noob, especially REST Framework. I'm trying to create an app that for this purposes passes JSON to the template. I figured Django Rest would be ideal.
I have set up a user profile so the user can select various attributes (works fine), and now want to pass a JSON of all the user's selections to template. Think of it as a "my profile" page.
View:
profile = Profile.objects.filter(user=request.user)
serializer = ProfileSerializer(profile, many=True)
myteam = JsonResponse(serializer.data, safe=False)
print(myteam.content) ## to see what's being passed
context = {'myteam': myteam.content}
return render(request, 'main/myteam.html', context)
Template:
<script>
console.log({{myteam}});
<\script>
Django server output:
b'[{"user": "DAVE", "GK1": {"id": 1001, "ShortName": "J. Strummer", "Club": "CLUB", "Shirt": "SHIRT.png", "ShortClub": "ETC", "ShortPos": "FW", "CurrentVal": "10", "TotalPoints": 1}, "GK2": {"id": 320, "ShortName": "D. Jones", "Club": "CLUB2", "Shirt": "ETABE.png", "ShortClub": "ETABE", "ShortPos": "GK", "CurrentVal": "4.5",
Template - Google Chrome JS console:
Uncaught SyntaxError: Invalid or unexpected token
Template - Chrome details:
console.log(b'[{"user": "mikey", "GK1": {"id": 1001, "ShortName": "J. Strummer", "Club": "ETC", "Shirt": "SHIRT.png", "ShortClub": "ETC", "ShortPos": "FW", "CurrentVal": "10.0", "TotalPoints": 1}, // lot's more of this
Noob Conclusion:
I don't seem to be getting a "clean" JSON object passed to the template from the server. Possibly due to the b'[ .... is this bytes literal?
Perhaps I should be using another method to pass the JSON to the template?
TWIST - in my testing I followed the REST tutorial and was able to setup a simple view which returned JsonResponse:
player = Player.objects.all()
serializer = PlayerSerializer(player, many=True)
return JsonResponse(serializer.data, safe=False)
Now if you go to this url mapping it displays in browser a perfect JSON example. I don't see why there should be a difference between
myteam = JsonResponse(serializer.data, safe=False)
and
return JsonResponse(serializer.data, safe=False)
I've been stuck on this on and off for literally days now so any help would be hugely appreciated.
I believe you may need to do this:
from rest_framework.renderers import JSONRenderer
profile = Profile.objects.filter(user=request.user)
serializer = ProfileSerializer(profile, many=True)
content = JSONRenderer().render(serializer.data)
return JsonResponse(content)
Or this:
from rest_framework.renderers import JSONRenderer
profile = Profile.objects.filter(user=request.user)
serializer = ProfileSerializer(profile, many=True)
return JsonResponse(serializer.data)
Rather than creating a JsonResponse object and passing it back through context.
There is another alternative, however, to the above as you'll want your endpoint to be extremely flexible.
Such API querying functionality is available from a 3rd-party package. I use this a lot, and find it works really well:
pip install djangorestframework-queryfields
Declare your serializer like this:
from rest_framework.serializers import ModelSerializer
from drf_queryfields import QueryFieldsMixin
class MyModelSerializer(QueryFieldsMixin, ModelSerializer):
...
Then the fields can now be specified (client-side) by using query arguments:
GET /identities/?fields=id,data
Exclusion filtering is also possible, e.g. to return every field except id:
GET /identities/?fields!=id
There are a few things going on here.
Firstly, you have taken the rendered output of a response and passed the content back into a template. This isn't the right thing to do; you should skip the JsonResponse altogether and pass serializer.data into a renderer before sending it to the template.
serializer = ProfileSerializer(profile, many=True)
data = JSONRenderer().render(serializer.data)
context = {'myteam': data}
return render(request, 'main/myteam.html', context)
Secondly, the encoding is due to Django templates' automatic HTML escaping. You should mark your JSON as safe.
console.log({{ myteam|safe }});

Only lists and tuples may be used in a list field Validation Error

Hi I am implementing test cases for my models.
I am using Mongoengine0.9.0 + Django 1.8
My models.py
class Project(Document):
# commented waiting for org-group to get finalize
project_name = StringField()
org_group = ListField(ReferenceField(OrganizationGroup, required=False))
My Serializers.py
class ProjectSerializer(DocumentSerializer):
class Meta:
model = Project
depth = 1
test.py file
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#jacob.com', password='top_secret')
def test_post_put_project(self):
"""
Ensure we can create new clients in mongo database.
"""
org_group = str((test_utility.create_organization_group(self)).id)
url = '/project-management/project/'
data = {
"project_name": "googer",
"org_group": [org_group],
}
##import pdb; pdb.set_trace()
factory = APIRequestFactory()
user = User.objects.get(username='jacob')
view = views.ProjectList.as_view()
# Make an authenticated request to the view...
request = factory.post(url, data=data,)
force_authenticate(request, user=user)
response = view(request)
self.assertEqual(response.status_code, 200)
When I am running test cases I am getting this error
(Only lists and tuples may be used in a list field: ['org_group'])
The complete Stack Trace is
ValidationError: Got a ValidationError when calling Project.objects.create().
This may be because request data satisfies serializer validations but not Mongoengine`s.
You may need to check consistency between Project and ProjectSerializer.
If that is not the case, please open a ticket regarding this issue on https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues
Original exception was: ValidationError (Project:None) (Only lists and tuples may be used in a list field: ['org_group'])
Not getting why we cant pass object like this.
Same thing when I am posting as an request to same method It is working for me but test cases it is failing
The tests should be running using multipart/form-data, which means that they don't support lists or nested data.
You can override this with the format argument, which I'm guessing you probably want to set to json. Most likely your front-end is using JSON, or a parser which supports lists, which explains why you are not seeing this.

django-activity-stream actions not displaying

I've just set django-activity-stream up but can't get it to display my actions when I goto the built in template mysite.com/activity/. Yet if I check the admin site I can see the actions have been saved as expected. I am using django-allauth for authentication/authorization
myapp/Settings.py
ACTSTREAM_SETTINGS = {
'MODELS': ('auth.user', 'auth.group'),
'MANAGER': 'actstream.managers.ActionManager',
'FETCH_RELATIONS': True,
'USE_PREFETCH': True,
'USE_JSONFIELD': True,
'GFK_FETCH_DEPTH': 0,
}
myapp/receivers.py
from actstream import action
#receiver(user_logged_in)
def handle_user_logged_in(sender, **kwargs):
request = kwargs.get("request")
user = kwargs['user']
action.send(user, verb='logged in')
In the django-activity-stream views.py it seems models.user_stream(request.user) is returning empty. But I have no idea why.
actstream/views.py
#login_required
def stream(request):
"""
Index page for authenticated user's activity stream. (Eg: Your feed at
github.com)
"""
return render_to_response(('actstream/actor.html', 'activity/actor.html'), {
'ctype': ContentType.objects.get_for_model(User),
'actor': request.user, 'action_list': models.user_stream(request.user)
}, context_instance=RequestContext(request))
Debugging from models.userstream(request.user) it seems I've found where it's returning no results:
actstream/managers.py
#stream
def user(self, object, **kwargs):
"""
Stream of most recent actions by objects that the passed User object is
following.
"""
q = Q()
qs = self.filter(public=True)
actors_by_content_type = defaultdict(lambda: [])
others_by_content_type = defaultdict(lambda: [])
follow_gfks = get_model('actstream', 'follow').objects.filter(
user=object).values_list('content_type_id',
'object_id', 'actor_only')
if not follow_gfks:
return qs.none()
When I check the value at q = self.filter I can actually see all the correct "logged in" activities for the user I passed, however when it gets to follow_gfks = get_model because the user in question isn't following anyone else follow_gfks ends up being None and the query set qs gets deleted on the last line.
Why this works this way when im just trying to view my own users activity feed I have no idea.
Here's what a row from my actstream_action table looks like:
id 1
actor_content_type_id [fk]3
actor_object_id 2
verb logged in
description NULL
target_content_type_id NULL
target_object_id NULL
action_object_content_type_id NULL
action_object_object_id NULL
timestamp 2013-09-28 12:58:41.499694+00
public TRUE
data NULL
I've managed to get the action/activity list of the current logged in user by passing user to actor_stream() instead of user_stream(). But I have no idea why user_stream doesn't work as intended
If it's your user that you want to show the actions for, you need to pass with_user_activity=True to user_stream; if it's for another user, you need to follow them first.

Convert POST to PUT with Tastypie

Full Disclosure: Cross posted to Tastypie Google Group
I have a situation where I have limited control over what is being sent to my api. Essentially there are two webservices that I need to be able to accept POST data from. Both use plain POST actions with urlencoded data (basic form submission essentially).
Thinking about it in "curl" terms it's like:
curl --data "id=1&foo=2" http://path/to/api
My problem is that I can't update records using POST. So I need to adjust the model resource (I believe) such that if an ID is specified, the POST acts as a PUT instead of a POST.
api.py
class urlencodeSerializer(Serializer):
formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist', 'urlencoded']
content_types = {
'json': 'application/json',
'jsonp': 'text/javascript',
'xml': 'application/xml',
'yaml': 'text/yaml',
'html': 'text/html',
'plist': 'application/x-plist',
'urlencoded': 'application/x-www-form-urlencoded',
}
# cheating
def to_urlencoded(self,content):
pass
# this comes from an old patch on github, it was never implemented
def from_urlencoded(self, data,options=None):
""" handles basic formencoded url posts """
qs = dict((k, v if len(v)>1 else v[0] )
for k, v in urlparse.parse_qs(data).iteritems())
return qs
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
resource_name = 'foo'
authorization = Authorization() # only temporary, I know.
serializer = urlencodeSerializer()
urls.py
foo_resource = FooResource
...
url(r'^api/',include(foo_resource.urls)),
)
In #tastypie on Freenode, Ghost[], suggested that I overwrite post_list() by creating a function in the model resource like so, however, I have not been successful in using this as yet.
def post_list(self, request, **kwargs):
if request.POST.get('id'):
return self.put_detail(request,**kwargs)
else:
return super(YourResource, self).post_list(request,**kwargs)
Unfortunately this method isn't working for me. I'm hoping the larger community could provide some guidance or a solution for this problem.
Note: I cannot overwrite the headers that come from the client (as per: http://django-tastypie.readthedocs.org/en/latest/resources.html#using-put-delete-patch-in-unsupported-places)
I had a similar problem on user creation where I wasn't able to check if the record already existed. I ended up creating a custom validation method which validated if the user didn't exist in which case post would work fine. If the user did exist I updated the record from the validation method. The api still returns a 400 response but the record is updated. It feels a bit hacky but...
from tastypie.validation import Validation
class MyValidation(Validation):
def is_valid(self, bundle, request=None):
errors = {}
#if this dict is empty validation passes.
my_foo = foo.objects.filter(id=1)
if not len(my_foo) == 0: #if object exists
foo[0].foo = 'bar' #so existing object updated
errors['status'] = 'object updated' #this will be returned in the api response
return errors
#so errors is empty if object does not exist and validation passes. Otherwise object
#updated and response notifies you of this
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
validation = MyValidation()
With Cathal's recommendation I was able to utilize a validation function to update the records I needed. While this does not return a valid code... it works.
from tastypie.validation import Validation
import string # wrapping in int() doesn't work
class Validator(Validation):
def __init__(self,**kwargs):
pass
def is_valid(self,bundle,request=None):
if string.atoi(bundle.data['id']) in Foo.objects.values_list('id',flat=True):
# ... update code here
else:
return {}
Make sure you specify the validation = Validator() in the ModelResource meta.