Why doesn't my tastypie cache get called? - python

I'm looking at the tastypie caching docs and trying to set up my own simple caching thing, but the cache doesn't seem to get called. When I visit http://localhost:8000/api/poll/?format=json, I get my tastypie generated json, but I don't get the output from the cache class.
from tastypie.resources import ModelResource
from tastypie.cache import NoCache
from .models import Poll
class JSONCache(NoCache):
def _load(self):
print 'loading cache'
data_file = open(settings.TASTYPIE_JSON_CACHE, 'r')
return json.load(data_file)
def _save(self, data):
print 'saving to cache'
data_file = open(settings.TASTYPIE_JSON_CACHE, 'w')
return json.dump(data, data_file)
def get(self, key):
print 'jsoncache.get'
data = self._load()
return data.get(key, None)
def set(self, key, value, timeout=60):
print 'jsoncache.set'
data = self._load()
data[key] = value
self._save(data)
class PollResource(ModelResource):
class Meta:
queryset = Poll.objects.all()
resource_name = 'poll'
cache = JSONCache()

It seems that Tastypie doesn't automatically cache lists, tastypie.resources around line 1027:
def get_list(self, request, **kwargs):
# ...
# TODO: Uncached for now. Invalidation that works for everyone may be
# impossible.
objects = self.obj_get_list(
request=request, **self.remove_api_resource_names(kwargs))
# ...
, whereas with details (around line 1050):
def get_detail(self, request, **kwargs):
# ...
try:
obj = self.cached_obj_get(
request=request, **self.remove_api_resource_names(kwargs))
# ...
... note that in the former snippet obj_get_list is called instead of cached_obj_get_list. Perhaps overriding get_list and using cached_obj_get_list would allow you to use cache here as well?
Now you probably would get output from your class for http://localhost:8000/api/poll/<pk>/?format=json (detail view) but not for http://localhost:8000/api/poll/?format=json (list view) by default.

Related

Django syndication function error

I'm trying to create a custom Django RSS feed using django syndication (actually using django wagtail feeds). I have an error which I think I've identified as stemming from a NoneType object which is returned by the get_object() function inside syndication/views.py.
`AttributeError at /feed/basic/Chups/
'NoneType' object has no attribute 'startswith'
Exception Location: /Users/technical/.virtualenvs/wagtest4-plnzODoN/lib/python3.6/site-packages/django/contrib/syndication/views.py in add_domain, line 19`
That function is called as part of class Feed() and looks like this:
def get_object(self, request, *args, **kwargs):
return None
That function is called at line 36 but fails because get_object() returns a None object.
My customisation of django wagtail feeds extends Feed in the following way:
from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import (
SyndicationFeed, rfc3339_date, Rss201rev2Feed
)
from .models import RSSFeedsSettings, RSSFeed
class BasicFeed(Feed):
# FEED TYPE
feed_type = Rss201rev2Feed
def get_object(self, request, category):
return category
try:
feed_app_settings = RSSFeedsSettings.objects.get(feed_category_name="Flex")
print(feed_app_settings)
feed_app_label = feed_app_settings.feed_app_label
feed_model_name = feed_app_settings.feed_model_name
feed_category_name = feed_app_settings.feed_category_name
use_feed_image = feed_app_settings.feed_image_in_content
except: # pragma: no cover
feed_app_settings = None
try:
feed_model = apps.get_model(app_label=feed_app_label,
model_name=feed_model_name)
except: # pragma: no cover
feed_model = None
# The RSS information that gets shown at the top of the feed.
if feed_app_settings is not None:
title = feed_app_settings.feed_title
link = feed_app_settings.feed_link
description = feed_app_settings.feed_description
author_email = feed_app_settings.feed_author_email
author_link = feed_app_settings.feed_author_link
item_description_field = feed_app_settings.feed_item_description_field
item_content_field = feed_app_settings.feed_item_content_field
def items(self, obj):
url_category = obj
categories = ContentType(app_label="blog", model="blogcategory")
category_id = categories.get_object_for_this_type(name=url_category).id
return feed_model.objects.filter(categories=category_id).order_by('-date').live()
def item_pubdate(self, item):
return datetime.combine(item.date, time())
def item_link(self, item):
return item.full_url
def item_author_name(self, item):
pass
urls.py includes this and requests seem to be reaching the function fine.
url(r'^feed/basic/(?P<category>[0-9a-zA-Z]+)/$', BasicFeed(), name='basic_feed'),
Can anyone tell me why that might be? I'm missing something about the expected functioning of this. Thanks!

AttributeError when serializing model object on deletion

I work an a django project and want to keep objects deleted by users for databases synchronization, so I decided to store them in a json file. For this I override the delete() method of the model.
I first retreive previously deleted objects from the file, and then add the one being deleted. When serializing this new list, I get an AttributeError:
AttributeError: 'DeserializedObject' object has no attribute '_meta'
What am I doing wrong ?
Here is the delete() code :
def delete(self, *args, **kwargs):
force = kwargs.pop("force", None)
if force is None:
objects_to_delete = list()
user_dir_path = os.path.join(STATIC_ROOT, self.user.username)
if not os.path.exists(user_dir_path):
os.makedirs(user_dir_path)
path = os.path.join(user_dir_path, "obj_to_delete.json")
if os.path.exists(path):
with open(path, "r") as fp:
json_str = fp.read()
if len(json_str) > 0:
objects_to_delete = list(serializers.deserialize(
"json",
json_str,
indent=4,
use_natural_foreign_keys=True,
fields=('pk', 'user', 'slug')
))
objects_to_delete.append(self)
if objects_to_delete:
with open(path, "w") as fp:
jsonData = serializers.serialize("json",
objects_to_delete, indent=4,
use_natural_foreign_keys=True,
fields=('pk', 'user', 'slug')
)
fp.write(jsonData)
super(UserOwnedModel,self).delete(*args, **kwargs)
https://docs.djangoproject.com/en/1.11/topics/serialization/#deserializing-data
As stated in the documentation, the deserialize method from the serializers doesn't return your object directly but rather wrap it in a DeserializeObject. You need to call deserialized_object.object for instance to access your object.
You can try this instead:
objects_to_delete = list(obj.object for obj in serializers.deserialize(
"json",
json_str,
indent=4,
use_natural_foreign_keys=True,
fields=('pk', 'user', 'slug')
))

Getting type error in python

I am using a class based service in python and I get error whenever I want to use it. Unable to figure out the reason.
#!/usr/bin/python
# -*- coding: utf-8 -*-
from xml.dom import minidom
from pysimplesoap.client import SoapClient
from pysimplesoap.helpers import sort_dict
MEDIA_ROOT = '/User/sunand/documents/resumes/'
parser = ResumeParser()
names = parser.get_names(MEDIA_ROOT)
print names
class ParserClient(SoapClient):
""" Extends the soap client to encode the response with utf-8 encoding.
"""
def wsdl_call(
self,
method,
*args,
**kwargs
):
""" Override wsdl_call method to make sure unmarshall is not called.
"""
operation = self.get_operation(method)
# get i/o type declarations:
inp = operation['input']
header = operation.get('header')
if 'action' in operation:
self.action = operation['action']
# construct header and parameters
if header:
self.__call_headers = sort_dict(header, self.__headers)
(method, params) = self.wsdl_call_get_params(method, inp,
*args, **kwargs)
response = self.call(method, *params)
return response
def send(self, method, xml):
""" Overrides the send method to get the actual xml content.
"""
content = super(ParserClient, self).send(method, xml)
self.result = content
return content
class ResumeParser(object):
""" Connects to the Resume Parser's XML api to get parsed data.
"""
def __init__(self, simple=True, timeout=60):
""" Initializes the ResumeParser class.
"""
self.wsdl = \
'http://jobsite.onlineresumeparser.com/rPlusParseResume.asmx?WSDL'
self.secret = 'my-secret-key' # Enter key here
self.encoding = 'base64'
self.simple = simple
self.client = ParserClient(wsdl=self.wsdl, timeout=timeout)
self.names = []
def get_file_content(self, file_path):
""" Return the encoded content for the given file.
"""
file_obj = open(os.path.abspath(file_path), 'r')
content = file_obj.read().encode(self.encoding)
file_obj.close()
return content
def get_names(self, path):
"""
Given a path to a folder that contains resume files this method
will parse the resumes and will return the names of the candidates
as a list.
"""
opt = os.path
resumes = [opt.join(path, r) for r in os.listdir(path)
if opt.isfile(opt.join(path, r))]
# Parse information for each resume.
for resume in resumes:
try:
xml_data = self.get_xml(resume)
name = self.get_name_from_xml(xml_data)
if name:
self.names.append(name)
except Exception, err:
# print name
print 'Error parsing resume: %s' % str(err)
return list(set(self.names))
def get_name_from_xml(self, data):
""" Returns the full name from the xml data given.
"""
xmldata = minidom.parseString(data)
name = xmldata.getElementsByTagName('CANDIDATE_FULL_NAME')
name = name[0].childNodes[0].data.title()
return name
def get_xml(self, filepath):
""" Fetches and returns the xml for the given file from the api.
"""
filename = os.path.basename(filepath)
extension = os.path.splitext(filepath)[1]
base64 = self.get_file_content(filepath)
filedata = {
'B64FileZippedContent': base64,
'FileName': filename,
'InputType': extension,
'UserID': 1,
'secretKey': self.secret,
}
get = \
(self.client.GetSimpleXML if self.simple else self.client.getHRXML)
get(**filedata)
return self.process_raw_xml()
def process_raw_xml(self, data=None):
""" Processes and returns the clean XML.
"""
raw = (data if data else self.client.result)
parsed = minidom.parseString(raw)
result = parsed.getElementsByTagName('GetSimpleXMLResult')[0]
text_node = result.childNodes[0]
data = text_node.data.encode('UTF-8')
return data
Upon running the code I am getting an error
TypeError: wsdl_call_get_params() got an unexpected keyword argument 'secretKey'
What am I doing wrong?
It looks like you are incorrectly overriding wsdl_call.
Firstly, we can see that SoapClient (which you extend in ParserClient), has a __getattr__ function that fetches pseudo-attributes of the SoapClient.
def __getattr__(self, attr):
"Return a pseudo-method that can be called"
if not self.services: # not using WSDL?
return lambda self=self, *args, **kwargs: self.call(attr,*args,**kwargs)
else: # using WSDL:
return lambda *args, **kwargs: self.wsdl_call(attr,*args,**kwargs)
You can see that this function is using wsdl_call to help it map functions to unknown attributes.
The specific pseudo-method that is causing the problem is in your code (or appears to be):
filedata = {
'B64FileZippedContent': base64,
'FileName': filename,
'InputType': extension,
'UserID': 1,
'secretKey': self.secret, # <-- the secretKey key word argument
}
get = \
(self.client.GetSimpleXML if self.simple else self.client.getHRXML)
get(**filedata)
# here client is an instance of your `ParserClient` (and `SoapClient`).
This above bit took me a while to track down. With a full stack trace I would have found it much quicker. Please always post stack traces (when there is one) in future when asking for help.
How to solve this
Provide a concrete implementation of GetSimpleXML and getHRXML. This will solve the immediate problem, but not the larger problem.
Rewrite wsdl_call
The rewritten section of code should check the value of the method argument and either do what you want, or delegate to the SoapClient implementation.
eg.
def wsdl_call(self, method, *args, **kwargs):
if method == "some_method":
return self._my_wsdl_call(method, *args, **kwargs)
else:
return super(ParserClient, self).wsdl_call(method, *args, **kwargs)
def _my_wsdl_call(self, method, *args, **kwargs):
...

How to pass template text to a view programmatically without using a file?

I'm writing some tests and I need to use templates in my view without using a file (e.g. via ViewPageTemplateFile). The template must evaluate the tal expressions and access the functions of the view. Here is my current problem. I get an error on evaluating the view in the PageTemplate. I'm not sure whether my code follows best practices.
from plone.app.testing import TEST_USER_NAME, TEST_USER_PASSWORD
from plone.testing.z2 import Browser
from zope.component import getGlobalSiteManager
from zope.publisher.interfaces.browser import IBrowserView
from Products.Five.browser import BrowserView
from zope.pagetemplate.pagetemplate import PageTemplate
class MyView(BrowserView):
def __init__(self, context, request):
self.context = context
self.request = request
def render(self):
template = PageTemplate()
text = '''\
<div tal:on-error="string:view fails"><span tal:replace="view/var"/></div>
<div tal:on-error="string:python fails"><span tal:replace="python:'python works'"/></div>
'''
template.write(text)
return template()
def __call__(self):
return self.render()
def var(self):
return 'my_value'
global_site_manager = getGlobalSiteManager()
global_site_manager.registerAdapter(MyView, (None, None), IBrowserView, 'my_view')
browser=Browser(app.Plone)
browser.addHeader('Authorization',
'Basic %s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD))
browser.open(app.Plone.absolute_url() + '/##my_view')
print browser.contents
produces following output:
<div>view fails</div>
<div>
python works
</div>
My workaround until now is to create my own class MyPageTemplate(PageTemplate), passing the view's instance as argument in the constructor, and override pt_getContext to populate the namespace with view, context, and request from the instance.
I would like to know whether there is a better solution (best practices).
Here my workaround:
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import TEST_USER_PASSWORD
from plone.testing.z2 import Browser
from zope.component import getGlobalSiteManager
from zope.publisher.interfaces.browser import IBrowserView
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope.pagetemplate.pagetemplate import PageTemplate
from zope.interface import Interface
from zope.publisher.browser import IDefaultBrowserLayer
class MyPageTemplate(PageTemplate):
instance = None
def __init__(self, instance):
self.instance = instance
def pt_getContext(self, instance, request, **kw):
namespace = super(MyPageTemplate, self).pt_getContext(**kw)
namespace['view'] = self.instance
namespace['context'] = self.instance.context
namespace['request'] = self.instance.request
return namespace
class MyView(BrowserView):
def __init__(self, context, request):
self.context = context
self.request = request
def render(self):
template = MyPageTemplate(self)
text = '''\
<div tal:on-error="string:request fails">request<span tal:replace="structure request"/> works</div>
<div tal:on-error="string:context fails">context/id=<span tal:replace="context/id"/> works</div>
<div tal:on-error="string:view fails">view/my_var=<span tal:replace="view/my_var"/> works</div>
<div tal:on-error="string:python fails"><span tal:replace="python:'python works'"/></div>
'''
template.write(text)
return template()
def __call__(self):
return self.render()
def my_var(self):
return 'my_value'
global_site_manager = getGlobalSiteManager()
global_site_manager.registerAdapter(MyView, (None, None), IBrowserView, 'my_view')
browser=Browser(app.Plone)
browser.addHeader('Authorization', 'Basic %s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD))
browser.open(app.Plone.absolute_url() + '/##my_view')
print browser.contents

django rest framework - custom renderer

I'm trying to tweak the existing XMLRenderer to create a custom one -
class CustomRenderer(renderers.BaseRenderer):
"""
Renderer which serializes to CustomXML.
"""
media_type = 'application/xml'
format = 'xml'
charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Renders *obj* into serialized XML.
"""
if data is None:
return ''
stream = StringIO()
xml = SimplerXMLGenerator(stream, self.charset)
xml.startDocument()
xml.startElement("job id='string1'", {})
self._to_xml(xml, data)
xml.endElement("job")
xml.endDocument()
return stream.getvalue()
def _to_xml(self, xml, data):
if isinstance(data, (list, tuple)):
for item in data:
xml.startElement("string2", {})
self._to_xml(xml, item)
xml.endElement("string2")
elif isinstance(data, dict):
for key, value in six.iteritems(data):
xml.startElement(key, {})
self._to_xml(xml, value)
xml.endElement(key)
elif data is None:
# Don't output any value
pass
else:
xml.characters(smart_text(data))
For string1 I want it to get the value from the view that's calling it. string1 = the primary key from the GET in the API.
ie. if I'm calling http://localhost/API/2345 then string1 = 2345
For string2 I want it to return the model name similar to what they are doing in the following post -
Adding root element to json response (django-rest-framework)
Which is customizing the values returned by the Renderer so that the root value of the JSON/XML can be set as the model name.
I've tried tweaking the CustomRenderer to contain the lines but then when running my view it complains that "object() takes no parameters" on my views.py -
if request.method == 'GET':
DEV = Trgjob.objects.using('database1').filter(job_id=pk).order_by('job_order')
serializer = CustomSerializer(DEV, many=True)
return CustomRenderer(serializer.data)
It looks like you're trying to return the renderer instance from the view?
You should just be returning regular data from the view, but set the renderer classes using the renderer_classes attribute on the view class.
class MyView(APIView):
renderer_classes = [CustomRenderer]
...
def get(self, request, pk=None):
DEV = Trgjob.objects.using('database1').filter(job_id=pk).order_by('job_order')
serializer = CustomSerializer(DEV, many=True)
return Response(serializer.data)
Edit: As to getting extra context into the renderer from the view... you can do that by inspecting renderer_context['view'], so something along these lines in your render() method...
view = renderer_context['view']
job_id = view.kwargs['job_id']

Categories