I am using drf-extension for caching my APIs. But it is not working as expected with cache_response decorator.
It caches the response for say /api/get-cities/?country=india . But when I hit /api/get-cities/?country=usa, I get the same response.
Here is the sample code:
settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient"
},
"KEY_PREFIX": "city"
}
}
REST_FRAMEWORK_EXTENSIONS = {
'DEFAULT_USE_CACHE': 'default',
'DEFAULT_CACHE_RESPONSE_TIMEOUT': 86400,
}
views.py
class GetCities(APIView):
#cache_response()
def get(self, request):
country = request.GET.get("country", "")
return get_cities_function(country)
Please help with this.
I was able to find a solution for the problem. I created my own keys in redis with the combination of api name and parameter name(in my case country). So when API is hit with query parameters, I check if a key exists corresponding to that, if it exists then cached response is returned.
class GetCities(APIView):
def calculate_cache_key(self, view_instance, view_method, request, args, kwargs):
api = view_instance.get_view_name().replace(' ', '')
return "api:" + api + "country:" + str(request.GET.get("country", ""))
#cache_response(key_func='calculate_cache_key')
def get(self, request):
country = request.GET.get("country", "")
return get_cities_function(country)
Related
I am new to flask and I was trying to make GET request for url containing "?" symbol but it look like my program is just skipping work with it. I am working with flask-sql alchemy, flask and flask-restful. Some simplified look of my program looks like this:
fields_list = ['id']
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
class Get(Resource):
#staticmethod
def get():
users = User.query.all()
usr_list = Collection.user_to_json(users)
return {"Users": usr_list}, 200
class GetSorted(Resource):
#staticmethod
def get(field, type):
if field not in fields_list or type not in ['acs', 'desc']:
return {'Error': 'Wrong field or sort type'}, 400
users = db.session.execute(f"SELECT * FROM USER ORDER BY {field} {type}")
usr_list = Collection.user_to_json(users)
return {"Users": usr_list}, 200
api.add_resource(GetSorted, '/api/customers?sort=<field>&sort_type=<type>')
api.add_resource(Get, '/api/customers')
Output with url "http://127.0.0.1:5000/api/customers?sort=id&sort_type=desc" looks like this
{
"Users": [
{
"Id": 1
},
{
"Id": 2
},
{
"Id": 3
},
]
}
But I expect it to look like this
{
"Users": [
{
"Id": 3
},
{
"Id": 2
},
{
"Id": 1
},
]
}
Somehow if I replace "?" with "/" in url everything worked fine, but I want it to work with "?"
In order to get the information after ?, you have to use request.args. This information is Query Parameters, which are part of the Query String: a section of the URL that contains key-value parameters.
If your route is:
api.add_resource(GetSorted, '/api/customers?sort=<field>&sort_type=<type>')
Your key-values would be:
sort=<field>
sort_type=<type>
And you could get the values of the field and type keys like this:
sort = request.args.get('field', 'field_defaul_value')
sort_type = request.args.get('type', 'type_defaul_value')
More info:
1
2
With Flask you can define path variables like you did, but they must be part of the path. For example, defining a path of /api/customers/<id> can be used to get a specific customer by id, defining the function as def get(id):. Query parameters cannot be defined in such a way, and as you mentioned in your comment, you need to somehow "overload" the get function. Here is one way to do it:
from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
USERS = [
{"id": 1},
{"id": 3},
{"id": 2},
]
class Get(Resource):
#classmethod
def get(cls):
if request.args:
return cls._sorted_get()
return {"Users": USERS, "args":request.args}, 200
#classmethod
def _sorted_get(cls):
field = request.args.get("sort")
type = request.args.get("sort_type")
if field not in ("id",) or type not in ['acs', 'desc']:
return {'Error': 'Wrong field or sort type'}, 400
sorted_users = sorted(USERS, key=lambda x: x[field], reverse=type=="desc")
return {"Users": sorted_users}, 200
api.add_resource(Get, '/api/customers')
if __name__ == '__main__':
app.run(debug=True)
Here is Flask's documentation regarding accessing request data, and Flask-Restful's quickstart guide.
I want to create an API by using django-rest-framework. So far I've managed to setup one endpoint of API and managed to fetch all items. A basic response (without the BaseResponse class described later) would look like this:
[
{
"uuid": "1db6a08d-ec63-4beb-8b41-9b042c53ab83",
"created_at": "2018-03-12T19:25:07.073620Z",
"updated_at": "2018-03-12T19:25:37.904350Z",
"deleted_at": null,
"random_name": "random name"
}
]
The result I would like to achieve would be something like this:
[
"success": true
"message": "Some exception message",
"data" :{
"uuid": "1db6a08d-ec63-4beb-8b41-9b042c53ab83",
"created_at": "2018-03-12T19:25:07.073620Z",
"updated_at": "2018-03-12T19:25:37.904350Z",
"deleted_at": null,
"random_name": "random name"
}
]
I managed to achieve this by creating a BaseReponse class and in view I simply return BaseResponse.to_dict() (a method that I have created inside of class).
class BaseResponse(object):
data = None
success = False
message = None
def __init__(self, data, exception):
self.data = data
self.message = str(exception) if exception is not None else None
self.success = exception is None
def to_dict(self):
return {
'success': self.success,
'message': self.message,
'data': self.data,
}
View:
class RandomModelList(APIView):
def get(self, request, format=None):
exception = None
models = None
try:
models = RandomModel.objects.all()
except Exception as e:
exception = e
serializer = RandomModelSerializer(models, many=True)
base_response = BaseResponse(data=serializer.data, exception=exception)
return Response(base_response.to_dict())
I want to mention that with the current code everything its working as expected but I have a huge double about the code (I just feel like I reinvented the wheel). Can someone tell me if this is the optimal solution for my problem and if not what should I change/use?
You can create a Custom Renderer instead. Something like
class CustomRenderer(JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
resp = {
'data': data
}
return super(CustomRenderer, self).render(resp, accepted_media_type, renderer_context)
Then create a middleware like
class CustomResponseMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response.data.update({'success': is_client_error(response.status_code) or is_server_error(response.status_code)})
return response
I am trying to connect my Django application to the mailchimps API with help from the Django Rest Framework, if I want to create a batch operation, I need to send the following call:
{
"operations": [
{
"method": "PUT",
"path": "lists/abc123/members/817f1571000f0b843c2b8a6982813db2",
"body": "{\"email_address\":\"hall#hallandoates.com\", \"status_if_new\":\"subscribed\"}"
},...
]
}
As you can see, the body object should be a json string. To create these calls, I created a model operation:
models.py
class Operation(object):
def __init__(self, method, list, contact):
email_clean = contact.email_address.lower().encode('utf-8')
subscriber_hash = hashlib.md5(email_clean).hexdigest()
serializer = ContactSerializer(contact)
body = serializer.data
self.method = method
self.path = "lists/%s/members/%s" % (list, subscriber_hash)
self.body = body
And the following serializer:
serializer.py
class OperationSerializer(serializers.Serializer):
method = serializers.CharField()
path = serializers.CharField()
body = serializers.CharField(allow_blank=True, required=False)
When I use my serializer to generate JSON, and parse the data with JSONRenderer(), the following call is returned:
{
"operations": [
{
"method": "PUT",
"path": "lists/abc123/members/817f1571000f0b843c2b8a6982813db2",
"body": "{\'email_address\':\'hall#hallandoates.com\', \'status_if_new\':\'subscribed\'}"
},...
]
}
This call breaks because of the single quotes, can anyone help me a hand in solving this?
I have the following two classes in my app.models and i'm using the wagtail APIs to get the data as json
class AuthorMeta(Page):
author=models.OneToOneField(User)
city = models.ForeignKey('Cities', related_name='related_author')
class Cities(Page):
name = models.CharField(max_length=30)
So, when I try /api/v1/pages/?type=dashboard.AuthorMeta&fields=title,city, it returns the following data:
{
"meta": {
"total_count": 1
},
"pages": [
{
"id": 11,
"meta": {
"type": "dashboard.AuthorMeta",
"detail_url": "http://localhost:8000/api/v1/pages/11/"
},
"title": "Suneet Choudhary",
"city": {
"id": 10,
"meta": {
"type": "dashboard.Cities",
"detail_url": "http://localhost:8000/api/v1/pages/10/"
}
}
}
]
}
In the city field, it returns the id and meta of the city. How can I get the name of the city in the response here, without making an extra query? :/
I couldn't find any solution in the Documentation. Am I missing something?
Use Django model property to return through the ForeignKey:
class AuthorMeta(Page):
author=models.OneToOneField(User)
city = models.ForeignKey('Cities', related_name='related_author')
city_name = property(get_city_name)
def get_city_name(self):
return self.city.name
Check Term Property to better understand the concept
In case you have the foreign key in a Streamfield, e.g. a PageChooserBlock, you can customize the api response by overwriting the get_api_representation of a block, as described in the example as provided here:
class CustomPageChooserBlock(blocks.PageChooserBlock):
""" Customize the api response. """
def get_api_representation(self, value, context=None):
""" Return the url path instead of the id. """
return value.url_path
I want to grab some information from Foursquare , add some fields and return it via django-tastypie.
UPDATE:
def obj_get_list(self, request=None, **kwargs):
near = ''
if 'near' in request.GET and request.GET['near']:
near = request.GET['near']
if 'q' in request.GET and request.GET['q']:
q = request.GET['q']
client = foursquare.Foursquare(client_id=settings.FSQ_CLIENT_ID, client_secret=settings.FSQ_CLIENT_SECRET)
a = client.venues.search(params={'query': q, 'near' : near, 'categoryId' : '4d4b7105d754a06374d81259' })
objects = []
for venue in a['venues']:
bundle = self.build_bundle(obj=venue, request=request)
bundle = self.full_dehydrate(bundle)
objects.append(bundle)
return objects
Now I am getting:
{
"meta": {
"limit": 20,
"next": "/api/v1/venue/?q=Borek&near=Kadikoy",
"offset": 0,
"previous": null,
"total_count": 30
},
"objects": [
{
"resource_uri": ""
},
{
"resource_uri": ""
}]
}
There are 2 empty objects. What should I do in order to fill this resource?
ModelResource is only suitable when you have ORM Model behind the resource. In other cases you should use Resource.
This subject is discussed in ModelResource description, mentioning when it is suitable and when it is not: http://django-tastypie.readthedocs.org/en/latest/resources.html#why-resource-vs-modelresource
Also there is a whole chapter in the documentation, aimed at providing the details on how to implement non-ORM data sources (in this case: external API): http://django-tastypie.readthedocs.org/en/latest/non_orm_data_sources.html