I'm trying to implement an ajax upload in my DJANGO app using (django-ajax-uploader). everything seems to work fine but when i upload a file i get upload failed with a 500 error caused by a bad response from AWS S3:
S3ResponseError at /ajax-upload↵S3ResponseError: 400 Bad Request↵<Error><Code>MalformedXML</Code><Message>The XML you provided was not well-formed or did not validate...
here is my backend class :
from ajaxuploader.backends.base import AbstractUploadBackend
class S3Upload(AbstractUploadBackend):
NUM_PARALLEL_PROCESSES = 4
def upload_chunk(self, chunk, *args, **kwargs):
self._counter += 1
buffer = StringIO()
buffer.write(chunk)
self._pool.apply_async(
self._mp.upload_part_from_file(buffer, self._counter))
buffer.close()
def setup(self, filename, *args, **kwargs):
self._bucket = boto.connect_s3(
settings.AWS_ACCESS_KEY_ID,
settings.AWS_SECRET_ACCESS_KEY
).lookup(settings.AWS_BUCKET_NAME)
self._mp = self._bucket.initiate_multipart_upload(filename)
self._pool = Pool(processes=self.NUM_PARALLEL_PROCESSES)
self._counter = 0
def upload_complete(self, request, filename, *args, **kwargs):
# Tie up loose ends, and finish the upload
self._pool.close()
self._pool.join()
self._mp.complete_upload()
Template (javascript):
<script src="{% static "ajaxuploader/js/fileuploader.js" %}"></script>
<script>
$(function(){
var uploader = new qq.FileUploader({
action: "{% url "my_ajax_upload" %}",
element: $('#file-uploader')[0],
multiple: true,
onComplete: function(id, fileName, responseJSON) {
if(responseJSON.success) {
alert("success!");
} else {
alert("upload failed!");
}
},
onAllComplete: function(uploads) {
// uploads is an array of maps
// the maps look like this: {file: FileObject, response: JSONServerResponse}
alert("All complete!");
},
params: {
'csrf_token': '{{ csrf_token }}',
'csrf_name': 'csrfmiddlewaretoken',
'csrf_xname': 'X-CSRFToken',
},
});
});
</script>
I solved this problem with a custom s3 backend that override the upload function & use django-storages instead of boto to save files. try this :
from ajaxuploader.backends.base import AbstractUploadBackend
from django.core.files.storage import default_storage
class S3CustomUpload(AbstractUploadBackend):
NUM_PARALLEL_PROCESSES = 4
def upload_chunk(self, chunk):
#save file to s3
self._fd.write(chunk)
self._fd.close()
def setup(self, filename):
self._fd = default_storage.open('%s/%s' % ('uploads/materials/', str(filename)), 'wb')
def upload(self, uploaded, filename, raw_data, *args, **kwargs):
try:
if raw_data:
# File was uploaded via ajax, and is streaming in.
chunk = uploaded.read(self.BUFFER_SIZE)
while len(chunk) > 0:
self.upload_chunk(chunk, *args, **kwargs)
chunk = uploaded.read(self.BUFFER_SIZE)
else:
# File was uploaded via a POST, and is here.
for chunk in uploaded.chunks():
self.upload_chunk(chunk, *args, **kwargs)
return True
except:
# things went badly.
return False
def upload_complete(self, request, filename, *args, **kwargs):
upload = Upload()
upload.upload = settings.S3_URL + "uploads/materials/"+ filename
upload.name = filename
upload.save()
return {'pk': upload.pk}
This works for me:
def upload_chunk(self, chunk, *args, **kwargs):
self._counter += 1
buffer = BytesIO(chunk)
self._pool.apply_async(
self._mp.upload_part_from_file(buffer, self._counter))
buffer.close()
Related
I have a download button that should return a users CSV. The code for it below:
class StartCSVHandler(ThreeScaleResourceHandler):
""" Starts generating a user's CSV file """
allowed_methods = ('new',)
requires_input = False
def post(self, *args, **kwargs):
user = self.current_user()
if not user:
self.abort(401, detail=ERROR_NOT_AUTHORIZED)
task_url = '/jobs/users/%s/data/csv' % user.key()
taskqueue.add(url=task_url, queue_name='users')
return {}
def generate_data_from_query(query, user, format, handler, filename, mission=None):
batch_size = 500
cursor = None
spottings = []
count = 0
mime_type = 'text/csv' if format == CSV_FORMAT else 'text/xml'
timestamp = '%0.6f' % time.time()
gcs_filename_template = '/{bucket}/{user}/{timestamp}/{filename}'
from global_config import config
gcs_filename = gcs_filename_template.format(
bucket='project-noah-backups',
# bucket=config['cloudstorage']['export_files_bucket'],
user=str(user.key()),
timestamp=timestamp,
filename=filename
)
logging.debug(str(user.key()))
f = cloudstorage.open(gcs_filename, mode='w', content_type=mime_type)
# blobstore_file_name = files.blobstore.create(mime_type=mime_type, _blobinfo_uploaded_filename=filename.encode('utf-8'))
while True:
if format == CSV_FORMAT:
writer = utils.UnicodeWriter(f)
if count == 0:
writer.writerow(csv_display_names)
elif format == KML_FORMAT and count == 0:
f.write(template.render('spotting_export_pre.kml', {}))
if cursor:
query.with_cursor(cursor)
spottings = query.fetch(batch_size)
if format == CSV_FORMAT:
dicts = [s.as_dict() for s in spottings]
logging.debug(dicts)
for spotting_dict in dicts:
writer.writerow([spotting_dict[k] for k in csv_keys])
elif format == KML_FORMAT:
output = template.render('spotting_export_mid.kml', {'spottings' : spottings, 'server_url' : utils.server_url(handler.request)})
f.write(output.encode('utf-8'))
cursor = query.cursor()
logging.info('written spottings %d to %d' % (count, count + len(spottings)))
count += len(spottings)
if not mission:
push_to_beacon_user(user, {'format':format,'progress':count})
else:
push_to_beacon_user_mission(user, mission, {'format':format,'progress':count})
if len(spottings) < batch_size:
break
if format == KML_FORMAT:
f.write(template.render('spotting_export_post.kml', {}))
blob_key = BlobKey(blobstore.create_gs_key(u'/gs' + gcs_filename))
logging.debug(blob_key)
return blob_key
def generate_data_from_user_spottings(user, format, handler):
filename = u'My-Spottings.%s' % format
# query = user.mySpottings
query = user.mySpottings
logging.debug(query)
return generate_data_from_query(query, user, format, handler, filename)
class GenerateUserDataHandler(NoahHandler):
def post(self, user_key=None, format=None):
if not user_key:
return
user = NoahUser.get(user_key)
if not user:
return
if format not in (CSV_FORMAT, KML_FORMAT):
return
blob_key = generate_data_from_user_spottings(user, format, self)
user = NoahUser.get(user_key)
if format == CSV_FORMAT:
if user.csv:
user.csv.delete()
user.csv = blob_key
user.put()
elif format == KML_FORMAT:
if user.kml:
user.kml.delete()
user.kml = blob_key
user.put()
logging.debug(user.recent_spottings)
logging.debug(str(blob_key))
push_to_beacon_user(user, {'format': format,'url':'/data?key=%s' % str(blob_key)})
class ThreeScaleResourceHandler(ResourceHandler):
#three_scale_authenticate
def get(self, *args, **kwargs):
super(ThreeScaleResourceHandler, self).get(*args, **kwargs)
#three_scale_authenticate
def post(self, *args, **kwargs):
super(ThreeScaleResourceHandler, self).post(*args, **kwargs)
#three_scale_authenticate
def put(self, *args, **kwargs):
super(ThreeScaleResourceHandler, self).put(*args, **kwargs)
#three_scale_authenticate
def delete(self, *args, **kwargs):
super(ThreeScaleResourceHandler, self).delete(*args, **kwargs)
This should download users data in the form of an CSV. The problem i am getting is two fold; firstly, the end point that this generates is '/api/v1/users/me/data/csv' and when visiting it, i receive the following error
{"error": {"title": "Unauthorized", "status": 401, "message": "You are not authorized to perform that action. Please use the api_key parameter with your registered key."}}
Secondly, the link it provides for the user to save cannot be found:
http://localhost:8080/data?key=encoded_gs_file:cHJvamVjdC1ub2FoLWJhY2t1cHMvYWdoa1pYWi1UbTl1WlhJVkN4SUlUbTloYUZWelpYSVlnSUNBZ0lDQWdBb00vMTU4MTAxODk3My4wODgyODEvTXktU3BvdHRpbmdzLmNzdg==
I am not entirely sure what i need to correct.
firstly, the end point that this generates is '/api/v1/users/me/data/csv' and when visiting it, i receive the following error: {"error": {"title": "Unauthorized", "status": 401, "message": "You are not authorized to perform that action. Please use the api_key parameter with your registered key."}}
Which handler in your code snippet handles /api/v1/users/me/data/csv? Is it StartCSVHandler? Are you sure it isn't being thrown because of this line? self.abort(401, detail=ERROR_NOT_AUTHORIZED)
Secondly, the link it provides for the user to save cannot be found: http://localhost:8080/data?key=encoded_gs_file:cHJvamVjdC1ub2FoLWJhY2t1cHMvYWdoa1pYWi1UbTl1WlhJVkN4SUlUbTloYUZWelpYSVlnSUNBZ0lDQWdBb00vMTU4MTAxODk3My4wODgyODEvTXktU3BvdHRpbmdzLmNzdg==
In what way? like you are trying to find the file on your machine or this link is throwing a 404?
On your localhost, encoded_gs_file files can be found here: http://localhost:8000/blobstore
If it's a 404, then what does your handler for /data do? It doesnt look like it's in your code snippet
I have a Django app that lets you upload a video or image, it sends it to an OpenCV function that returns a new image/video with some annotations on it and then shows it in the template.
For images it works just fine, but for video it doesn't.
The video is shown as unavailable both in the view (as HTML5 video) and when following the /media/[path_to_file], but when I'm opening it with VLC (or some other desktop player, locally), the video exists and is working as expected.
Moreover, I tried removing the OpenCV function and left only the upload functionality. When I uploaded a normal video, everything was fine, but when I tried to upload a video that was previously processed with OpenCV, it appears as unavailable again.
My question is: is opencv changing the video properties in any way comparing to the original so that Django doesn't recognise it anymore? What might be the problem? And how can I make it that opencv processed videos are visible in my view?
Also, the processed video has the same name and extension (and type) as the original.
If you have any idea it's more than helpful. Thanks a lot!
Edit (included the code)
views.py
class IndexView(View):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.context = dict()
def get(self, request, *args, **kwargs):
""" Action for GET requests """
upload_form = UploadForm()
self.context['upload_form'] = upload_form
return render(request, 'homepage/home.html', self.context)
def post(self, request, *args, **kwargs):
""" Action for POST requests """
upload_form = UploadForm(request.POST, request.FILES)
if upload_form.is_valid:
try:
uploaded_file = upload_form.save()
file_type = upload_form.cleaned_data['file'].content_type.split('/')[0]
# task = start_annotation_process.delay(uploaded_file.file.name, file_type)
task = add.delay(4, 5)
except ValidationError:
print('FAILED VALIDATION ERROR')
self.context['upload_status'] = 'failed'
return render(request, 'homepage/home.html', self.context)
except ValueError:
print('FAILED VALUE ERROR')
self.context['upload_status'] = 'failed'
return render(request, 'homepage/home.html', self.context)
self.context['upload_form'] = upload_form
self.context['task_id'] = task.task_id
self.context['uploaded_file'] = uploaded_file
self.context['upload_status'] = 'successfull'
self.context['content_type'] = upload_form.cleaned_data['file'].content_type
return render(request, 'homepage/home.html', self.context)
models.py
class UploadModel(models.Model):
""" Model for storing uploaded files """
file = models.FileField(upload_to='uploads/%Y/%m/%d/')
upload_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return 'UploadModel({file}, {date})'.format(file=self.file, date=self.upload_date)
class Meta:
db_table = 'UploadModel'
forms.py
class UploadForm(forms.ModelForm):
file = forms.FileField(widget=forms.ClearableFileInput(attrs={'class': "custom-file-input",
'id':"input-file",
'aria-describedby':"inputGroupFileAddon01"}))
def clean_file(self):
file = self.cleaned_data.get('file')
if file != None:
file = self.cleaned_data['file'] # try to delete
file_type = file.content_type.split('/')[0]
if file_type == settings.FILE_TYPES[0]: # image
self.__validate_size(file, settings.MAX_UPLOAD_IMAGE_SIZE)
elif file_type == settings.FILE_TYPES[1]: # video
self.__validate_size(file, settings.MAX_UPLOAD_VIDEO_SIZE)
else:
print('File type ', file.content_type)
raise forms.ValidationError(
'File type not supported. It has to be an image or a video.')
return file
def __validate_size(self, file, upload_size):
if file.size > int(upload_size):
raise forms.ValidationError('The file size exceeds {upload_size}. '
'Current file size is {file_size}'.format(upload_size=filesizeformat(upload_size),
file_size=filesizeformat(file.size)))
class Meta:
model = UploadModel
exclude = ('upload_date',)
fields = ('file',)
urls.py (homepage)
urlpatterns = [
path('', homepage_views.IndexView.as_view(), name='homepage'),
path('celery-progress', include('celery_progress.urls')), # the endpoint is configurable
path('download/<str:content_type>/<path:path>', homepage_views.download_file, name='download'),
]
urls.py (root app)
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('homepage.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
tasks.py (celery)
#shared_task(bind=True)
def start_annotation_process(self, file_path, file_type):
""" Celery task for starting the annotation process """
print('start task')
media_src_path = path.join(settings.MEDIA_ROOT, file_path)
media_dst_path = path.join(settings.MEDIA_ROOT, 'results', file_path)
media_res_path = path.join(settings.MEDIA_ROOT, 'results', path.dirname(file_path))
if not path.exists(media_res_path):
makedirs(media_res_path)
if file_type == 'image':
start_process_image(media_src_path, media_dst_path)
elif file_type == 'video':
start_process_video(media_src_path, media_dst_path)
move(media_dst_path, media_src_path)
print('finished task')
Opencv function (FastObjectDetector is a class that will predict the annotations and return the processed frame with predict_img() method)
def start_process_video(source_path, dest_path, process_offline=True, rotate=False):
""" Start annotation process for video """
if not process_offline:
cfod = FastObjectDetector(score_threshold=0.5)
vstrm = VideoCameraStream(logger=cfod.logger,
process_func=cfod.predict_img,
info_func=cfod._DEBUG_INFO,
onclick_func=cfod.on_click,
hd=1,
camera=0)
if vstrm.video != None:
video_frame_shape = (vstrm.H, vstrm.W)
cfod.prepare(image_shape=video_frame_shape)
vstrm.play()
vstrm.shutdown()
if cfod.DEBUG:
cfod.show_fr_stats()
cfod.shutdown()
else:
cfod = FastObjectDetector(score_threshold=0.5)
FRAME_W, FRAME_H = 1280, 720
cfod.prepare(image_shape=(FRAME_H, FRAME_W))
video = cv2.VideoCapture(source_path)
fourcc = int(video.get(cv2.CAP_PROP_FOURCC))
fourcc = cv2.VideoWriter_fourcc(*'XVID')
fps = video.get(cv2.CAP_PROP_FPS)
frame_size = (FRAME_W, FRAME_H)
source_path, _ = os.path.splitext(source_path)
new_video = cv2.VideoWriter(dest_path, fourcc, fps, frame_size)
while (video.isOpened()):
ret, frame = video.read()
if ret:
frame = cv2.resize(frame, (FRAME_W, FRAME_H))
if rotate:
(cW, cH) = (FRAME_W // 2, FRAME_H // 2)
m = cv2.getRotationMatrix2D((cW, cH), -90, 1)
frame = cv2.warpAffine(frame, m, (FRAME_W, FRAME_H))
frame = cfod.predict_img(frame)
new_video.write(frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
else:
break
video.release()
new_video.release()
cv2.destroyAllWindows()
template.html
<video id="result-video" controls style="width:45vw; height:auto; max-height:40vh">
<p> view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video </p>
</video>
<script type="text/javascript">
{% if task_id and upload_status == "successfull" %}
var progressUrl = "{% url 'celery_progress:task_status' task_id %}";
$('#btn-upload').addClass('disabled');
$(function () {
let options = {
onProgress: function(progressBarElement, progressBarMessageElement, progress){
console.log('ON PROCESS');
$('#div-progress').show();
$('#div-results').hide();
},
onSuccess: function(progressBarElement, progressBarMessageElement){
console.log('ON SUCCESS');
var d = new Date();
{% get_content_type content_type as type %}
{% if type == 'image' %}
$('.result-img').attr('src', '{{ uploaded_file.file.url }}?' + d.getTime());
{% elif type == 'video' %}
$('#result-video').html('<source src="{{ uploaded_file.file.url }}" type="{{ content_type }}">');
$('#result-video')[0].load();
{% endif %}
$('#div-progress').hide();
$('#div-results').show();
$('#btn-upload').removeClass('disabled')
}
};
CeleryProgressBar.initProgressBar(progressUrl, options);
});
{% endif %}
</script>
For the template I used celery_progress's callback functions to add the video source after the celery task is finished. The final HTML file will have the correct source included in the video tag. But the video is unavailable following the /media/ url too.
I hope this helps. Thanks.
I solved the problem myself, so if anyone has the same problem as me, check your codecs inside cv2.VideoWriter_fourcc(*'XVID'). I was trying to upload an .mp4 file while the codec was set for .avi.
This is even more embarrassing as the previous line (fourcc = int(video.get(cv2.CAP_PROP_FOURCC))) was getting the codec from the uploaded file and was about to use it, but the next line was overwriting it.
I ended up using AVC1 codec and changed the code to make any uploaded video into a .mp4 file, just to be sure that this problem will not repeat.
I hope this helps someone.
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):
...
CherryPy keeps returning blank pages or with the values I return in the controllers. I rewrote a django and jinja2 version that did work, apparently this one doesn't which is almost identical to the previous mentioned.
I did some pprint's in the tool bit which does fill the request.body with parsed html but doesn't output it when pass is set in the controller. If I return a {'user':True} in the controller that is shown in the form of a simple "User".
with a few examples online and the code of SickBeard I came to the following:
controller:
class RootController(object):
#cherrypy.expose
#cherrypy.tools.render(template="page/home.html")
def index(self):
pass
tool:
class CheetahTool(cherrypy.Tool):
def __init__(self):
cherrypy.Tool.__init__(self, 'on_start_resource',
self._render,
priority=30)
def _render(self, template=None, debug=False):
if cherrypy.response.status > 399:
return
# retrieve the data returned by the handler
data = cherrypy.response.body or {}
template = cherrypy.engine.publish("lookup-template", template).pop()
if template and isinstance(data, dict):
for k,v in data:
template.__setattr__(k, v)
# dump the template using the dictionary
if debug:
try:
cherrypy.response.body = unicode(template).encode('utf-8', 'xmlcharrefreplace')
except Exception as e:
from pprint import pprint
pprint(e.message)
else:
cherrypy.response.body = template.respond()
plugin:
class PageTemplate(Template):
"""
Thank you SickBeard
"""
def __init__(self, base_dir, template, *args, **KWs):
KWs['file'] = os.path.join(base_dir, template)
super(PageTemplate, self).__init__(*args, **KWs)
application = cherrypy.tree.apps['']
config = application.config
self.sbRoot = base_dir
self.sbHttpPort = config['global']['server.socket_port']
self.sbHttpsPort = self.sbHttpPort
self.sbHttpsEnabled = False
if cherrypy.request.headers['Host'][0] == '[':
self.sbHost = re.match("^\[.*\]", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0)
else:
self.sbHost = re.match("^[^:]+", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0)
if "X-Forwarded-Host" in cherrypy.request.headers:
self.sbHost = cherrypy.request.headers['X-Forwarded-Host']
if "X-Forwarded-Port" in cherrypy.request.headers:
self.sbHttpPort = cherrypy.request.headers['X-Forwarded-Port']
self.sbHttpsPort = self.sbHttpPort
if "X-Forwarded-Proto" in cherrypy.request.headers:
self.sbHttpsEnabled = True if cherrypy.request.headers['X-Forwarded-Proto'] == 'https' else False
self.sbPID = str(aquapi.PID)
self.menu = [
{ 'title': 'Home', 'key': 'home' },
{ 'title': 'Users', 'key': 'users' },
{ 'title': 'Config', 'key': 'config' },
]
def render(self):
return unicode(self).encode('utf-8', 'xmlcharrefreplace')
class CheetahTemplatePlugin(plugins.SimplePlugin):
def __init__(self, bus, base_dir=None, base_cache_dir=None,
collection_size=50, encoding='utf-8'):
plugins.SimplePlugin.__init__(self, bus)
self.base_dir = base_dir
self.base_cache_dir = base_cache_dir or tempfile.gettempdir()
self.encoding = encoding
self.collection_size = collection_size
def start(self):
self.bus.log('Setting up Cheetah resources')
self.bus.subscribe("lookup-template", self.get_template)
def stop(self):
self.bus.log('Freeing up Cheetah resources')
self.bus.unsubscribe("lookup-template", self.get_template)
self.lookup = None
def get_template(self, name):
"""
Returns Cheetah's template by name.
"""
return PageTemplate(self.base_dir, name)
init:
# Template engine tool
from aquapi.web.tools.template import CheetahTool
cherrypy.tools.render = CheetahTool()
# Tool to load the logged in user or redirect
# the client to the login page
from aquapi.web.tools.user import UserTool
cherrypy.tools.user = UserTool()
from aquapi.web.controllers import RootController
webapp = RootController()
# Let's mount the application so that CherryPy can serve it
app = cherrypy.tree.mount(webapp, '/', os.path.join(self.base_dir, "app.cfg"))
# Template engine plugin
from aquapi.web.plugin.template import CheetahTemplatePlugin
engine.cheetah = CheetahTemplatePlugin(engine,
os.path.join(self.base_dir, 'aquapi/web/templates'),
os.path.join(self.base_dir, 'cache'))
engine.cheetah.subscribe()
In general, to me it's some sort of over-engineering happened in your snippets. CherryPy plugins are usually used for a system task (e.g. put PID-file on engine start, remove it on stop) or for an asynchronous task (e.g. sending email in separate thread). Template rendering happens clearly synchronously to the request handling, so I don't see the point of extracting this logic out of CherryPy tool. There's a class in CherryPy, cherrypy._cptools.HandlerWrapperTool, which demonstrate the suggested approach to wrapping handler return values.
I haven't ever used Cheetah, so my example is Jinja2-based. You will just have to change the templating engine instance (to Cheetah) and correct its calls. The rest is the same.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import types
import cherrypy
import jinja2
path = os.path.abspath(os.path.dirname(__file__))
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 4
}
}
class TemplateTool(cherrypy.Tool):
_engine = None
'''Jinja environment instance'''
def __init__(self):
viewLoader = jinja2.FileSystemLoader(os.path.join(path, 'view'))
self._engine = jinja2.Environment(loader = viewLoader)
cherrypy.Tool.__init__(self, 'before_handler', self.render)
def __call__(self, *args, **kwargs):
if args and isinstance(args[0], (types.FunctionType, types.MethodType)):
# #template
args[0].exposed = True
return cherrypy.Tool.__call__(self, **kwargs)(args[0])
else:
# #template()
def wrap(f):
f.exposed = True
return cherrypy.Tool.__call__(self, *args, **kwargs)(f)
return wrap
def render(self, name = None):
cherrypy.request.config['template'] = name
handler = cherrypy.serving.request.handler
def wrap(*args, **kwargs):
return self._render(handler, *args, **kwargs)
cherrypy.serving.request.handler = wrap
def _render(self, handler, *args, **kwargs):
template = cherrypy.request.config['template']
if not template:
parts = []
if hasattr(handler.callable, '__self__'):
parts.append(handler.callable.__self__.__class__.__name__.lower())
if hasattr(handler.callable, '__name__'):
parts.append(handler.callable.__name__.lower())
template = u'/'.join(parts)
data = handler(*args, **kwargs) or {}
renderer = self._engine.get_template(u'{0}.html'.format(template))
return renderer.render(**data)
cherrypy.tools.template = TemplateTool()
class App:
#cherrypy.expose
def index(self):
'''No renderer applied, CherryPy outputs dict keys'''
return {'user': 123}
#cherrypy.tools.template
def auto(self):
return {'user': 123}
#cherrypy.tools.template(name = 'app/auto')
def manual(self):
return {'user': 234}
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)
Along the python file, create directory view/app and put the following in file named auto.html there.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' content='text/html; charset=utf-8' />
<title>Test</title>
</head>
<body>
<p>User: <em>{{ user }}</em></p>
</body>
</html>
Some notes on the TemplateTool. First, you can use it as a decorator in two ways: not making a call, and making a call with template name argument. You can use the tool as any other CherryPy tool in the configuration (e.g. make all controller methods to render templates). Second, following convention-over-configuration principle, the tool when not provided with template name will use classname/methodname.html. Third, the decorator exposes the method, so you don't need to add #cherrypy.expose on top.
I was wondering if it is possible to create an upload function to upload picture through my own site to the gravatar site?
Yes, this is possible. See http://en.gravatar.com/site/implement/xmlrpc/ , specifically the grav.saveData or grav.SaveUrl calls.
Yes it's possible!
import base64
from xmlrpclib import ServerProxy, Fault
from hashlib import md5
class GravatarXMLRPC(object):
API_URI = 'https://secure.gravatar.com/xmlrpc?user={0}'
def __init__(self, request, password=''):
self.request = request
self.password = password
self.email = sanitize_email(request.user.email)
self.email_hash = md5_hash(self.email)
self._server = ServerProxy(
self.API_URI.format(self.email_hash))
def saveData(self, image):
""" Save binary image data as a userimage for this account """
params = { 'data': base64_encode(image.read()), 'rating': 0, }
return self._call('saveData', params)
#return self.useUserimage(image)
def _call(self, method, params={}):
""" Call a method from the API, gets 'grav.' prepended to it. """
args = { 'password': self.password, }
args.update(params)
try:
return getattr(self._server, 'grav.' + method, None)(args)
except Fault as error:
error_msg = "Server error: {1} (error code: {0})"
print error_msg.format(error.faultCode, error.faultString)
def base64_encode(obj):
return base64.b64encode(obj)
def sanitize_email(email):
return email.lower().strip()
def md5_hash(string):
return md5(string.encode('utf-8')).hexdigest()
Just call the class in your view :)