The audio is in Bangla, you can switch to HD video by toggling the youtube settings.
(I do not prepare the scripts beforehand, so please apologize my clumsiness) .
The audio is in Bangla, you can switch to HD video by toggling the youtube settings.
(I do not prepare the scripts beforehand, so please apologize my clumsiness) .
Let’s assume you have a desktop application built with Python. It could be a traditional GUI app built with PyQT/wxPython/Kivy or any other GUI framework. Or it could be a web server that serves a browser based HTML GUI for the user. Either way, you have “frozen” the app using cx_freeze, py2app/py2exe or pyinstaller and now you want to add “auto update” to the app, so when there’s a new version of the application is available, the app can download and install the update, automatically. For this particular task, I found esky to be a good viable option. In this article, I am going to demonstrate how we can use esky to deliver updates to our apps.
If we want to use Esky to deliver updates, we need to freeze the app first. But this time, we will ask Esky to freeze the app for us, using our freezer of choice. For example, if we used py2app before, we will still use py2app but instead of directly using it, we will pass it to Esky and Esky will use the py2app to freeze the app for us. This step is necessary so that Esky can inject the necessary parts to handle updates/patches and install them gracefully.
For the apps to locate and download the updates, we need to serve the updates from a location on the internet/local network. Esky produces a zip archive. We can directly put it on our webserver. The apps we freeze needs to know the URL of the webserver and must have access to it.
On the other hand, inside our app, we need to write some codes which will scan the URL of the above mentioned webserver, find any newer updates and install them. Esky provides nice APIs to do these.
So now that we know the steps to follow, let’s start.
setup
file If you have frozen an app before, you probably know what a setup file is and how to write one. Here’s a sample that uses py2app to freeze an app:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import sys from esky import bdist_esky from distutils.core import setup PY2APP_OPTIONS = {"includes": ['ssl', 'sip', 'PyQt4']} DATA_FILES = ['my_pyqt_ui_file.ui'] # Using py2app setup( name="My Awesome App", version="0.1", scripts=["main.py"], data_files=DATA_FILES, options={"bdist_esky": { "freezer_module": "py2app", "freezer_options": PY2APP_OPTIONS, }} ) |
Now we can generate the frozen app using:
1 |
python setup.py bdist_esky |
This should generate a zip archive in the dist
directory.
Collect the zip file from the dist
directory and put it somewhere accessible on the internet. For local testing, you can probably use Python’s built in HTTP server to distribute it.
Now we will see the client side code that we need to write to locate and install the updates.
Here’s some codes taken from a PyQT app. The find_esky_update
method is part of a QMainWindow
class. It is called inside the onQApplicationStarted
method. So it checks the update as soon as the application starts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def find_esky_update(self): if getattr(sys, "frozen", False): updater = esky.Esky(sys.executable, "http://localhost:8000") if updater.find_update(): reply = QtGui. \ QMessageBox \ .question(self, 'Update', "New update available! Do you want to update?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: updater.auto_update(self.handle_esky_status) else: print("No new updates found!") else: print ("App is not frozen!") |
We first check if the app is frozen. If it’s not, then there’s no way we can install updates. sys.frozen
will contain information about the app if it’s frozen. Otherwise it will not be available. So we first ensure that it is indeed a frozen app.
Then we create an Esky app instance by providing it the URL of our webserver (where the updates are available). We only pass the root URL (without the zip file name). The find_update()
method on the Esky app will find newer update and return some information if a new update is available. Otherwise it will be falsy.
If an update is available, we ask our user if s/he wants to update. Here we used QMessageBox
for that. If they agree, we call the auto_update
method with a callback. We will see the callback soon. The auto_update
downloads the update and installs it. The callback we pass – it gets called every time something happens during the process. It can be a good way to display download progress using this callback.
Let’s see our example code here:
1 2 3 4 5 6 7 8 9 10 |
def handle_esky_status(self, message): progress = None if message['status'] == 'downloading': progress = int((float(message['received']) / float(message['size'])) * 100) elif message['status'] == 'ready': progress = 100 if progress is not None: print(progress) #self.progressBar.setValue(progress) |
As you can see from the code, the callback gets a dictionary which has a key status
and if it is “downloading”, we also have the amount of data we have received so far and the total size. We can use this to calculate the progress and print it. We can also display a nice progress bar if we wish.
So basically, this is all we need to find and install updates.
We have learned to use Esky, we have seen how to add auto update to our app. Now it’s time to build a new update. That is easy, we go back to the setup.py
file we defined earlier. We had version="0.1",
inside the setup()
function. We need to bump it. So let’s make it 0.2
and build it. We will get a new zip file (the file contains the version if you notice carefully). Drop it on the webserver (the URL where we put our app). Run an older copy of the app (which includes the update checking codes described above). It should ask you for an update 🙂
Please note, you need to call the find_esky_update()
method for the prompt to trigger. As I mentioned above, I run it in onQApplicationStarted
method for PyQt. You need to find the appropriate place to call it from in your application.
You can find a nice tutorial with step by step instructions and code samples here: https://github.com/cloudmatrix/esky/tree/master/tutorial
In this post, we would like to see how we can limit user accesses to our Django views.
If you have worked with Django, you probably have used the login_required
decorator already. Adding the decorator to a view limits access only to the logged in users. If the user is not logged in, s/he is redirected to the default login page. Or we can pass a custom login url to the decorator for that purpose.
Let’s see an example:
1 2 3 4 5 |
from django.contrib.auth.decorators import login_required @login_required def secret_page(request): return render_to_response("secret_page.html") |
There’s another nice decorator – permission_required
which works in a similar fashion:
1 2 3 4 5 |
from django.contrib.auth.decorators import permission_required @permission_required('entity.can_delete', login_url='/loginpage/') def my_view(request): return render_to_response("entity/delete.html") |
Awesome but let’s learn how do they work internally.
We saw the magic of the login_required
and permission_required
decorators. But we’re the men of science and we don’t like to believe in magic. So let’s unravel the mystery of these useful decorators.
Here’s the code for the login_required
decorator:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None): """ Decorator for views that checks that the user is logged in, redirecting to the log-in page if necessary. """ actual_decorator = user_passes_test( lambda u: u.is_authenticated(), login_url=login_url, redirect_field_name=redirect_field_name ) if function: return actual_decorator(function) return actual_decorator |
By reading the code, we can see that the login_required
decorator uses another decorator – user_passes_test
which takes/uses a callable to determine whether the user should have access to this view. The callable must accept an user instance and return a boolean value. user_passes_test
returns a decorator which is applied to our view.
If we see the source of permission_required
, we would see something quite similar. It also uses the same user_passes_test
decorator.
Now that we know how to limit access to a view based on whether the logged in user passes a test, it’s quite simple for us to build our own decorators for various purposes. Let’s say we want to allow access only to those users who have verified their emails.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth import REDIRECT_FIELD_NAME def check_email_verification(user): return EmailVerification.objects.all().filter(user=user, verified=True) def check_email(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None): """ Decorator for views that checks that the user is logged in, redirecting to the log-in page if necessary. """ actual_decorator = user_passes_test( check_email_verification, login_url=login_url, redirect_field_name=redirect_field_name ) if function: return actual_decorator(function) return actual_decorator |
Now we can use the decorator to a view like:
1 2 3 4 |
@login_required @check_email(login_url="/redirect/login/?reason=verify_email") def verified_users_only(request): return render_to_response("awesome/offers.html") |
Users who have verified their email addresses will be able to access this view. And if they didn’t, they will be redirected to the login view. Using the reason
query string, we can display a nice message explaining what’s happening.
Please note, we have used two decorators on the same view. We can use multiple decorators like this to make sure the user passes all the tests we require them to.