2014-06-16 6 views
12

मैं फ्लास्क में एक साधारण सरल वेब ऐप बना रहा हूं जो किसी वेबसाइट के एपीआई के माध्यम से कार्य करता है। मेरे उपयोगकर्ता अपने खाता यूआरएल और एपीआई टोकन के साथ एक फॉर्म भरते हैं; जब वे फॉर्म जमा करते हैं तो मेरे पास एक पायथन स्क्रिप्ट है जो एपीआई के माध्यम से अपने खाते से पीडीएफ निर्यात करती है। इस फ़ंक्शन में काफी समय लग सकता है, इसलिए मैं फॉर्म पेज पर एक बूटस्ट्रैप प्रगति पट्टी प्रदर्शित करना चाहता हूं जो दर्शाता है कि स्क्रिप्ट की प्रक्रिया में कितनी दूर है। मेरा सवाल यह है कि फ़ंक्शन चल रहा है जैसे मैं प्रगति पट्टी को कैसे अपडेट करूं? मैं किस बारे में बात कर रहा हूं इसका सरलीकृत संस्करण यहां दिया गया है।फ्लास्क ऐप: फ़ंक्शन रन करते समय प्रगति पट्टी अपडेट करें

views.py:

@app.route ('/export_pdf', methods = ['GET', 'POST']) 
def export_pdf(): 
    form = ExportPDF() 
    if form.validate_on_submit(): 
     try: 
     export_pdfs.main_program(form.account_url.data, 
      form.api_token.data) 
     flash ('PDFs exported') 
     return redirect(url_for('export_pdf')) 
     except TransportException as e: 
     s = e.content 
     result = re.search('<error>(.*)</error>', s) 
     flash('There was an authentication error: ' + result.group(1)) 
     except FailedRequest as e: 
     flash('There was an error: ' + e.error) 
    return render_template('export_pdf.html', title = 'Export PDFs', form = form) 

export_pdf.html:

{% extends "base.html" %} 

{% block content %} 
{% include 'flash.html' %} 
<div class="well well-sm"> 
    <h3>Export PDFs</h3> 
    <form class="navbar-form navbar-left" action="" method ="post" name="receipt"> 
    {{form.hidden_tag()}} 
    <br> 
    <div class="control-group{% if form.errors.account_url %} error{% endif %}"> 
     <label class"control-label" for="account_url">Enter Account URL:</label> 
     <div class="controls"> 
     {{ form.account_url(size = 50, class = "span4")}} 
     {% for error in form.errors.account_url %} 
      <span class="help-inline">[{{error}}]</span><br> 
     {% endfor %} 
     </div> 
    </div> 
    <br> 
    <div class="control-group{% if form.errors.api_token %} error{% endif %}"> 
     <label class"control-label" for="api_token">Enter API Token:</label> 
     <div class="controls"> 
     {{ form.api_token(size = 50, class = "span4")}} 
     {% for error in form.errors.api_token %} 
      <span class="help-inline">[{{error}}]</span><br> 
     {% endfor %} 
     </div> 
    </div> 
    <br> 
    <button type="submit" class="btn btn-primary btn-lg">Submit</button> 
    <br> 
    <br> 
    <div class="progress progress-striped active"> 
    <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"> 
    <span class="sr-only"></span> 
    </div> 
</form> 
</div> 
</div> 
{% endblock %} 

और export_pdfs.py:

def main_program(url, token): 
    api_caller = api.TokenClient(url, token) 
    path = os.path.expanduser('~/Desktop/'+url+'_pdfs/') 
    pdfs = list_all(api_caller.pdf.list, 'pdf') 
    total = 0 
    count = 1 
    for pdf in pdfs: 
     total = total + 1 
    for pdf in pdfs: 
     header, body = api_caller.getPDF(pdf_id=int(pdf.pdf_id)) 
     with open('%s.pdf' % (pdf.number), 'wb') as f: 
      f.write(body) 
     count = count + 1 
     if count % 50 == 0: 
      time.sleep(1) 

कि पिछले समारोह में मैं कुल पीडीएफ़ की संख्या है मैं निर्यात करेगा, और यह प्रसंस्करण के दौरान एक सतत गिनती होगी। प्रगति पट्टी के 'शैली =' टैग के भीतर फिट करने के लिए मैं अपनी .html फ़ाइल में वर्तमान प्रगति कैसे भेज सकता हूं? अधिमानतः इस तरह से कि मैं अन्य पृष्ठों पर प्रगति सलाखों के लिए एक ही उपकरण का पुन: उपयोग कर सकता हूं। अगर मुझे पर्याप्त जानकारी प्रदान नहीं की गई है तो मुझे बताएं।

+4

मैं कोडित उत्तर देना नहीं चाहता हूं लेकिन मुझे आपको समाधान के लिए इंगित करने देता हूं। पीडीएफ निर्यात करने के लिए धागा शुरू करना एक पारंपरिक विचार है। थ्रेड डेटाबेस तालिका में प्रगति की रिपोर्ट करता है। डेटाबेस से प्रगति मूल्य प्राप्त करने के लिए आपका ब्राउज़र फ्रंट एंड AJAX मतदान करता है। AJAX मतदान के विकल्प, आप अपने ब्राउज़र पर प्रगति मूल्य को धक्का देने के लिए फ्लास्क-सॉकेटियो को देखना चाह सकते हैं। इस विकल्प के लिए अधिक इंजीनियरिंग प्रयास की आवश्यकता हो सकती है। – chfw

+1

@chfw का विचार यह है कि आपको इसे कैसे देखना चाहिए। लेकिन धागे की बजाय यह एक अतिरिक्त प्रक्रिया होनी चाहिए जो नौकरियों की प्रतीक्षा कर रही हो। और डेटाबेस के बजाय मैं रेडिस की तरह कुछ उपयोग करता हूं और संदेश कतारों के माध्यम से संवाद करता हूं। और अंततः AJAX या WebSockets का उपयोग करने के बजाय मैं [एसएसई] (https://developer.mozilla.org/en-US/docs/Server-sent_events) की अनुशंसा करता हूं जो सेटअप करना आसान है। – dAnjou

उत्तर

0

मैं स्थानीयहोस्ट पर यह सरल लेकिन शैक्षणिक फ्लास्क एसएसई कार्यान्वयन चलाता हूं। GAE में 3 पार्टी (उपयोगकर्ता द्वारा अपलोड) पुस्तकालय संभाल करने के लिए:

  1. अपने रूट पथ में lib नाम के एक निर्देशिका बनाएं।
  2. gevent लाइब्रेरी निर्देशिका lib निर्देशिका पर कॉपी करें।

    import sys 
    sys.path.insert(0,'lib') 
    
  3. यही सब:

  4. अपने main.py ये पंक्तियां जोड़ें। आप एक बच्चे फ़ोल्डर से lib निर्देशिका का उपयोग करते हैं, तो सापेक्ष संदर्भ का उपयोग करें: sys.path.insert(0, ../../blablabla/lib')

से http://flask.pocoo.org/snippets/116/

# author: [email protected] 
# 
# Make sure your gevent version is >= 1.0 
import gevent 
from gevent.wsgi import WSGIServer 
from gevent.queue import Queue 

from flask import Flask, Response 

import time 


# SSE "protocol" is described here: http://mzl.la/UPFyxY 
class ServerSentEvent(object): 

    def __init__(self, data): 
     self.data = data 
     self.event = None 
     self.id = None 
     self.desc_map = { 
      self.data : "data", 
      self.event : "event", 
      self.id : "id" 
     } 

    def encode(self): 
     if not self.data: 
      return "" 
     lines = ["%s: %s" % (v, k) 
       for k, v in self.desc_map.iteritems() if k] 

     return "%s\n\n" % "\n".join(lines) 

app = Flask(__name__) 
subscriptions = [] 

# Client code consumes like this. 
@app.route("/") 
def index(): 
    debug_template = """ 
    <html> 
     <head> 
     </head> 
     <body> 
     <h1>Server sent events</h1> 
     <div id="event"></div> 
     <script type="text/javascript"> 

     var eventOutputContainer = document.getElementById("event"); 
     var evtSrc = new EventSource("/subscribe"); 

     evtSrc.onmessage = function(e) { 
      console.log(e.data); 
      eventOutputContainer.innerHTML = e.data; 
     }; 

     </script> 
     </body> 
    </html> 
    """ 
    return(debug_template) 

@app.route("/debug") 
def debug(): 
    return "Currently %d subscriptions" % len(subscriptions) 

@app.route("/publish") 
def publish(): 
    #Dummy data - pick up from request for real data 
    def notify(): 
     msg = str(time.time()) 
     for sub in subscriptions[:]: 
      sub.put(msg) 

    gevent.spawn(notify) 

    return "OK" 

@app.route("/subscribe") 
def subscribe(): 
    def gen(): 
     q = Queue() 
     subscriptions.append(q) 
     try: 
      while True: 
       result = q.get() 
       ev = ServerSentEvent(str(result)) 
       yield ev.encode() 
     except GeneratorExit: # Or maybe use flask signals 
      subscriptions.remove(q) 

    return Response(gen(), mimetype="text/event-stream") 

if __name__ == "__main__": 
    app.debug = True 
    server = WSGIServer(("", 5000), app) 
    server.serve_forever() 
    # Then visit http://localhost:5000 to subscribe 
    # and send messages by visiting http://localhost:5000/publish 
5

के रूप में कुछ अन्य लोगों टिप्पणी में सुझाव दिया, सबसे आसान समाधान किसी अन्य रूप में अपने निर्यात समारोह को चलाने के लिए है थ्रेड, और अपने क्लाइंट को किसी अन्य अनुरोध के साथ प्रगति जानकारी खींचने दें। इस विशेष कार्य को संभालने के लिए कई दृष्टिकोण हैं। आपकी आवश्यकताओं के आधार पर, आप एक कम या ज्यादा परिष्कृत विकल्प चुन सकते हैं।

यहाँ एक बहुत ही (बहुत) कम से कम कैसे धागे के साथ यह करने के लिए पर उदाहरण है:

import random 
import threading 
import time 

from flask import Flask 


class ExportingThread(threading.Thread): 
    def __init__(self): 
     self.progress = 0 
     super().__init__() 

    def run(self): 
     # Your exporting stuff goes here ... 
     for _ in range(10): 
      time.sleep(1) 
      self.progress += 10 


exporting_threads = {} 
app = Flask(__name__) 
app.debug = True 


@app.route('/') 
def index(): 
    global exporting_threads 

    thread_id = random.randint(0, 10000) 
    exporting_threads[thread_id] = ExportingThread() 
    exporting_threads[thread_id].start() 

    return 'task id: #%s' % thread_id 


@app.route('/progress/<int:thread_id>') 
def progress(thread_id): 
    global exporting_threads 

    return str(exporting_threads[thread_id].progress) 


if __name__ == '__main__': 
    app.run() 

सूचकांक मार्ग (/) हम एक निर्यात इस काम के लिए एक धागा अंडे में, और हम उस के लिए एक आईडी वापसी कार्य ताकि क्लाइंट इसे प्रगति मार्ग (/ प्रगति/[exporting_thread]) के साथ बाद में पुनर्प्राप्त कर सके। निर्यात थ्रेड हर बार जब यह सोचता है कि यह उचित है तो इसकी प्रगति मूल्य अपडेट करता है।

ग्राहक पक्ष पर, आप कुछ इस तरह मिलेगा (इस उदाहरण jQuery का उपयोग करती):

function check_progress(task_id, progress_bar) { 
    function worker() { 
     $.get('progress/' + task_id, function(data) { 
      if (progress < 100) { 
       progress_bar.set_progress(progress) 
       setTimeout(worker, 1000) 
      } 
     }) 
    } 
} 

के रूप में कहा, इस उदाहरण बहुत minimalistic है और आप शायद एक थोड़ा और अधिक परिष्कृत दृष्टिकोण के लिए जाना चाहिए। आमतौर पर, हम डेटाबेस में किसी विशेष थ्रेड की प्रगति को स्टोर करते हैं या किसी प्रकार के कैश को स्टोर करते हैं, ताकि हम साझा संरचना पर भरोसा न करें, इसलिए मेरे उदाहरण में अधिकांश मेमोरी और कॉन्सुरेंसी मुद्दों से परहेज करना है।

रेडिस (https://redis.io) एक इन-मेमोरी डेटाबेस स्टोर है जो आम तौर पर इस तरह के कार्यों के लिए उपयुक्त है। यह पाइथन (https://pypi.python.org/pypi/redis) के साथ अच्छी तरह से ver एकीकृत करता है।

+0

कमाल। धन्यवाद। –

संबंधित मुद्दे