Django Rest Framework Displaying Serialized data through Views.py - serialization

class International(object):
""" International Class that stores versions and lists
countries
"""
def __init__(self, version, countrylist):
self.version = version
self.country_list = countrylist
class InternationalSerializer(serializers.Serializer):
""" Serializer for International page
Lists International countries and current version
"""
version = serializers.IntegerField(read_only=True)
country_list = CountrySerializer(many=True, read_only=True)
I have a serializer set up this way, and I wish to display serialized.data (which will be a dictionary like this: { "version": xx, and "country_list": [ ] } ) using views.py
I have my views.py setup this way:
class CountryListView(generics.ListAPIView):
""" Endpoint : somedomain/international/
"""
## want to display a dictionary like the one below
{
"version": 5
"country_list" : [ { xxx } , { xxx } , { xxx } ]
}
What do I code in this CountryListView to render a dictionary like the one above? I'm really unsure.

Try this
class CountryListView(generics.ListAPIView):
""" Endpoint : somedomain/international/
"""
def get(self,request):
#get your version and country_list data and
#init your object
international_object = International(version,country_list)
serializer = InternationalSerializer(instance=international_object)
your_data = serializer.data
return your_data

You can build on the idea from here:
http://www.django-rest-framework.org/api-guide/pagination/#example
Suppose we want to replace the default pagination output style with a modified format that includes the next and previous links under in a nested 'links' key. We could specify a custom pagination class like so:
class CustomPagination(pagination.PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'results': data
})
As long as you don't need the pagination, you can setup a custom pagination class which would pack your response in whichever layout you may need:
class CountryListPagination(BasePagination):
def get_paginated_response(self, data):
return {
'version': 5,
'country_list': data
}
Then all you need to do is to specify this pagination to your class based view:
class CountryListView(generics.ListAPIView):
# Endpoint : somedomain/international/
pagination_class = CountryListPagination
Let me know how is this working for you.

Related

django rest pagination on api view decorator

im trying to do pagination on my django rest code, but i get the same code when i change the number of the page, this is what im doing to get that page: http://localhost:8000/movies?page=3
When i change the page number i get the same response, idk if i have to send the number of the page or something but i do the same of this stackoverflow thread
I put the entire view code:
#api_view(['GET', 'POST', 'DELETE', 'PUT'])
def movies(request):
if request.method == 'GET':
if request.query_params.get('id'):
try:
id = request.query_params.get('id')
movie = Movie.objects.get(id=id)
serializer = MovieSerializer(movie, many=False)
return Response(serializer.data)
except Movie.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
movies = Movie.objects.all().order_by('release_date')
serializer = MovieSerializer(movies , many=True, context={'request':request})
if request.query_params.get('page'):
paginator = LimitOffsetPagination()
result_page = paginator.paginate_queryset(movies, request)
serializer = MovieSerializer(result_page, many=True, context={'request':request})
return Response(serializer.data)
if request.query_params.get('Genre'):
genreparam = request.query_params.get('Genre')
genre = Genre.objects.get(name=genreparam)
queryset = Movie.objects.filter(genre_relation=genre.id).values().order_by('release_date')
return Response(queryset)
return Response(serializer.data)
this is my settings.py
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 2,
}
this is what i get whatever number i send via request params
[
{
"id": 1,
"title": "Guardians of the galaxy",
"tagline": "this is a tagline",
"overview": "this is an overview, starlord in the begins...",
"release_date": "1971-07-13T03:00:00Z",
"poster_url": "http\"//posterurl",
"backdrop_url": "http\"//backdropurl",
"imdb_id": "idk what is a imdb",
"genre_relation": []
},
{
"id": 2,
"title": "Avengers endgame",
"tagline": "this is a tagline",
"overview": "tony stark dies, theres no more happy days, only days",
"release_date": "2019-07-13T03:00:00Z",
"poster_url": "http//posterurl",
"backdrop_url": "http//backdropurl",
"imdb_id": "idk what is a imdb",
"genre_relation": [
1
]
}
]
You are not using the pagination properly. You need to instantiate the paginator with the request, and then call paginate_queryset. You are merely instantiating a paginator, and then completely ignoring it.
paginator = LimitOffsetPagination()
result_page = paginator.paginate_queryset(movies, request)
You thus should rewrite this to:
paginator = LimitOffsetPagination()
result_page = paginator.paginate_queryset(movies, request, view=self)
Note that we here pass view=self, since the LimitOffsetPagination uses self.request, self.response, etc.
Furthermore you should not construct a new serializer, but reuse the existing one, and pass result_page as the queryset:
serializer = MovieSerializer(result_page, many=True, context={'request': request})
Finally you should return the paginated results with:
return paginator.get_paginated_response(serializer.data)
This will add pagination metadata to the response.
So a full example:
#api_view(['GET', 'POST', 'DELETE', 'PUT'])
def movies(request):
# ...
if request.query_params.get('page'):
paginator = LimitOffsetPagination()
result_page = paginator.paginate_queryset(movies, request, view=self)
serializer = MovieSerializer(result_page, many=True, context={'request':request})
return paginator.get_paginated_response(serializer.data)
# ...
Note that using the #api_view decorator is often discouraged. You might want to consider using the #api_view decorator.

Django rest framework: Is there a way to clean data before validating it with a serializer?

I've got an API endpoint POST /data.
The received data is formatted in a certain way which is different from the way I store it in the db.
I'll use geometry type from postgis as an example.
class MyPostgisModel(models.Model):
...
position = models.PointField(null=True)
my_charfield = models.CharField(max_length=10)
...
errors = JSONField() # Used to save the cleaning and validation errors
class MyPostgisSerializer(serializers.ModelSerializer):
class Meta:
model = MyPostgisModel
fields = [
...
"position",
...
"my_charfield",
"errors",
]
def to_internal_value(self, data):
...
# Here the data is coming in the field geometry but in the db, it's called
# position. Moreover I need to apply the `GEOSGeometry(json.dumps(...))`
# method as well.
data["position"] = GEOSGeometry(json.dumps(data["geometry"]))
return data
The problem is that there is not only one field like position but many. And I would like (maybe wrongly) to do like the validate_*field_name* scheme but for cleaning (clean_*field_name*).
There is another problem. In this scheme, I would like to still save the rest of the data in the database even if some fields have raised ValidationError (eg: a CharField that is too long) but are not part of the primary_key/a unique_together constraint. And save the related errors into a JSONField like this:
{
"cleaning_errors": {
...
"position": 'Invalid format: {
"type": "NotAValidType", # Should be "Point"
"coordinates": [
4.22,
50.67
]
}'
...
},
"validating_errors": {
...
"my_charfield": "data was too long: 'this data is way too long for 10 characters'",
...
}
}
For the first problem, I thought of doing something like this:
class BaseSerializerCleanerMixin:
"""Abstract Mixin that clean fields."""
def __init__(self, *args, **kwargs):
"""Initialize the cleaner strategy."""
# This is the error_dict to be filled by the `clean_*field_name*`
self.cleaning_error_dict = {}
super().__init__(*args, **kwargs)
def clean_fields(self, data):
"""Clean the fields listed in self.fields_to_clean before validating them."""
cleaned_data = {}
for field_name in getattr(self.Meta, "fields", []):
cleaned_field = (
getattr(self, "clean_" + field_name)(data)
if hasattr(self, "clean_" + field_name)
else data.get(field_name)
)
if cleaned_field is not None:
cleaned_data[field_name] = cleaned_field
return cleaned_data
def to_internal_value(self, data):
"""Reformat data to put it in the database."""
cleaned_data = self.clean_fields(data)
return super().to_internal_value(cleaned_data)
I'm not sure that's a good idea and maybe there is an easy way to deal with such things.
For the second problem ; catching the errors of the validation without specifying with is_valid() returning True when no primary_key being wrongly formatted, I'm not sure how to proceed.

Adding new key-value pair into json using karate

My payload looks like this :
{
"override_source": "DS",
"property_code": "0078099",
"stay_date": "2018-11-26T00:00:00.000000",
"sku_prices": [
],
"persistent_override": false
}
There is an array dblist ["2","3"] , it would consists of numbers from 1 to 4. Based on the elements present in the list, I want to add key-values {"sku_price":"1500","sku_code":"2"} to my payload. I am using the following code :
* eval if(contains("3",dblist)) karate.set('pushRatesFromDS.sku_prices[]','{ "sku_price": "1500","sku_code":"3" }')
When I execute my feature file, I do not get any errors but, key-values are not added to my payload. However if I move this code to a new feature file and call it, key-value pairs get added to my payload. The code in my new feature file looks like : * set pushRatesFromDS.sku_prices[] = { "sku_price": "1500","sku_code":"2" }
Try this:
* def foo =
"""
{
"override_source": "DS",
"property_code": "0078099",
"stay_date": "2018-11-26T00:00:00.000000",
"sku_prices": [
],
"persistent_override": false
}
"""
* eval karate.set('foo', '$.sku_prices[]', { foo: 'bar' })

Elasticsearch bulk/batch indexing with python requests module

I have a smallish (~50,00) array of json dictionaries that I want to store/index in ES. My preference is to use python, since the data I want to index is coming from a csv file, loaded and converted to json via python. Alternatively, I would like to skip the step of converting to json, and simply use the array of python dictionaries I have. Anyway, a quick search revealed the bulk indexing functionality of ES. I want to do something like this:
post_url = 'http://localhost:9202/_bulk'
request.post(post_url, data = acc ) # acc a python array of dictionaries
or
post_url = 'http://localhost:9202/_bulk'
request.post(post_url, params = acc ) # acc a python array of dictionaries
both request give a [HTTP 500 error]
My understanding is that you have to have one "command" per line (index, create, delete...) and then some of them (like index) takes a row of data on the next line like so
{'index': ''}\n
{'your': 'data'}\n
{'index': ''}\n
{'other': 'data'}\n
NB the new-lines, even on the last row.
Empty index objects like above works if you POST to ../index/type/_bulk or else you need to specify index and type I think, have not tried that.
You the following function will do it:
def post_request(self, endpoint, data):
endpoint = 'localhost:9200/_bulk'
response = requests.post(endpoint, data=data, headers={'content-type':'application/json', 'charset':'UTF-8'})
return response
As data you need to pass a String such:
{ "index" : { "_index" : "test-index", "_type" : "_doc", "_id" : "1681", "routing" : 0 }}
{ "field1" : ... , ..., "fieldN" : ... }
{ "index" : { "_index" : "test-index", "_type" : "_doc", "_id" : "1684", "routing" : 1 }}
{ "field1" : ... , ..., "fieldN" : ... }
Make sure you add a "\n" at the end of each line.
I don't know much about Python, but did you look at Pyes?
Bulk is supported in Pyes.

How to include class name when calling to_json in rails3?

I'm trying to implement autocomplete that lets user to pick from list of 2 different kind of models.
This is how my controller looks:
def ac
arr = []
arr << Foo.all
arr << Bar.all
render json: arr.to_json
end
Which renders:
[[{"id":1, "name":"foo name"}], [{"id":1, "name":"bar name"}]]
How to include class name and get something like this:
[
[{"id":1, "name":"foo name", "class_name":"Foo"}],
[{"id":1, "name":"bar name", "class_name":"Bar"}]
]
?
If you don't mind doing a bit of extra work you can do smth like that with :methods option of as_json method (and to_json as well):
class Foo
def class_name
self.class.name
end
end
arr = Foo.all.map { |foo| foo.as_json(:methods => [:class_name]) }
puts arr.to_json
#=> [{ "id": 1, "name": "foo name", "class_name": "Foo" }]
If you have ActiveRecord::Base.include_root_in_json set to true (that is default afaik) then you'll get hashes like
{ "foo": { "id": 1, "name": "foo name" } }
If you want it to be exactly the class name you can pass :root option:
foo = Foo.last
puts foo.to_json(:root => foo.class.name)
#=> { "Foo": { "id": 1, "name": "foo name" } }
Note that both these solutions do not allow you simply to call to_json on an array of records. To overcome that and make class_name included by default you can override serializable_hash method in your model like that:
def serializable_hash(*)
super.merge('class_name' => self.class.name)
end
If you wrap it into a module you can include it in any model you want and get class_name included into the result of as_json or to_json without passing any extra options to these methods. You can modify the implementation a bit to respect :except option if you want to exclude class_name in some cases.
ended up adding 1 additional step:
arr << Foo.all
arr << Bar.all
arr.flatten!
arr=arr.collect{|itm| {"id":"#{itm.class.to_s}:#{itm.id}", "value":itm.name}}
then I just spit it out:
render json: arr.to_json()
as a result I get:
[{"id":"Foo:1", "value":"Foo #1"},{"id":"Bar:1", "value":"Bar #1"}]