tag:blogger.com,1999:blog-145245242024-03-14T05:00:45.365-07:00Wherever you are, be there!davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.comBlogger60125tag:blogger.com,1999:blog-14524524.post-60499495200553412322018-12-08T17:56:00.001-08:002018-12-08T17:56:18.540-08:00Flutter Development in Chromebook<div style="line-height: 1.38; margin-bottom: 3pt; margin-top: 0pt;">
<span style="font-family: "arial"; white-space: pre-wrap;">In November 2018, I am excited to have my HP Chromebook set up for </span><a href="https://flutter.io/" style="font-family: Arial; white-space: pre-wrap;">Flutter</a><span style="font-family: "arial"; white-space: pre-wrap;"> development with VS Code. I am sharing here how I did it: </span><br />
<br />
<span id="docs-internal-guid-e88290bf-7fff-9c60-4792-067c5a0ef523"></span><br />
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Enable Chromebook Developer Mode</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Enable Linux in ChromeOS</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Install Flutter</span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Download tar file from Flutter website</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Move tar file to Linux partition</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Untar (no need to run any install script)</span></div>
</li>
</ol>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Download and unzip Android Studio, and install Flutter plugin</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Enable Android Developer Mode in ChromeOS</span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Chromebook settings</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Google Play Store</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Manage Android Properties</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">About</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Device</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Build (tap 7 times)</span></div>
</li>
</ol>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Enable Android ADB Debugging</span></div>
</li>
<ol style="margin-bottom: 0pt; margin-top: 0pt;">
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Chromebook settings</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Google Play Store</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Manage Android Properties</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Developer Options</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: lower-alpha; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">ADB Debugging</span></div>
</li>
</ol>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Run ~/Android/Sdk/platform-tools/adb connect 100.115.92.2:5555</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Run ~/Android/Sdk/platform-tools/adb devices</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Download VS Code .deb package, double click to install</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">Install VS Code Flutter plugin</span></div>
</li>
<li dir="ltr" style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; list-style-type: decimal; text-decoration: none; vertical-align: baseline; white-space: pre;"><div dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">
<span style="background-color: transparent; color: black; font-family: Arial; font-size: 11pt; font-style: normal; font-variant: normal; font-weight: 400; text-decoration: none; vertical-align: baseline; white-space: pre-wrap; white-space: pre;">VS Code: locate Dart-SDK at ~/flutter/bin/cache/dart-sdk</span></div>
</li>
</ol>
</div>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-25135461239655900982016-02-03T08:00:00.000-08:002016-02-03T08:00:12.802-08:00<pre>
My Filezilla client application takes forever (a.k.a several minutes) to start up if the last local directory is a remote share which is not available any more, possibly waiting for time out.
A way to fix this is to patch the Filezilla configure file <b>~/filezilla/filezilla.xml</b>.
The relevant section is:
<FileZilla3>
<Settings>
...
<b><Setting name="Last local directory">/mnt/remote/share/</Setting></b>
...
</Settings>
</FileZilla3>
I need to change the Last local directory value to some local directory such as "/". That can be done easily with <b>xmlstarlet</b>.
<b>
$ xmlstarlet edit --update '//Setting[@name="Last local directory"]' --value "/" ~/.filezilla/filezilla.xml
</b>
Basically you ask xmlstarlet to edit filezilla.xml by updating the value of a "Setting" node with the name "Last local directory" to "/".
Of course you will have to write the output back to filezilla.xml.
You can install xmlstarlet with apt-get:
<b>$ sudo apt-get install xmlstarlet</b>
I am using:
- Ubuntu 14.04
- Filezilla Client 3.7.3
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-9046699304821351232015-12-14T08:00:00.001-08:002015-12-14T08:00:08.049-08:00Auto loading user-defined models in Django shell<pre>
I would like the Django shell to auto-load all user-defined models on startup without the
use of a third-party app. Below is that I did.
[00] # FILE .djangoshellrc
[01] # alias m='PYTHONSTARTUP="path/to/.djangoshellrc" python manage.py'
[02]
[03] import sys
[04]
[05] from django.apps import apps
[06]
[07] def load_models():
[08] try:
[09] models = [m for m in apps.get_models() if not m.__module__.startswith('django')]
[10] for model in sorted(models, key=lambda x: x.__name__):
[11] name = model.__name__
[12] globals()[name] = model
[13] print("[+] imported %s" % name)
[14] except:
[15] print("[*] %s" % sys.exc_info()[0])
[16] print("[*] %s" % sys.exc_info()[1])
[17]
[18] load_models()
<b>Line 00-01</b>: To use this script, create a bash alias to start the Django shell, with
this script specified in the PYTHONSTARTUP environ variable. In other words, this
script will be run on startup as long as the alias is used to start the Django shell.
<b>Line 03-05</b>: All the necessary imports.
<b>Line 09</b>: Ask Django to provide a list of all the app models, with the django built-in
models filtered out.
<b>Line 10</b>: Iterate the models list after sorting them based on the model names.
<b>Line 11-13</b>: Add the models to the globals dict with the model name as the key,
effectively doing a "from <app>.models import <Model> in the Django shell."
Hope this script is useful to you.
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-72273671225379198602015-12-07T08:00:00.000-08:002015-12-07T08:00:08.622-08:00Comparing changes in a Django form<blockquote>
With the help of the Form.<b>changed_data</b> list, the Form.<b>initial</b> dict, and the Form.<b>cleaned_data</b> dict, Django makes it easy to compare changes in a form.
</blockquote>
<blockquote>
For example, we can override the form_valid method in an UpdateView to print the old and new values of all the changed fields as below:
</blockquote>
<pre>
def form_valid(self, form):
for fieldname in form.changed_data:
print(fieldname)
print("old=%s" % <b>form.initial</b>[fieldname])
print("new=%s" % <b>form.cleaned_data</b>[fieldname])
return super().form_valid(form)
</pre>
<edited>
References:
</edited>
<pre>
<a href="https://docs.djangoproject.com/en/1.8/ref/forms/api/#django.forms.Form.changed_data">Form.changed_data</a>
<a href="https://docs.djangoproject.com/en/1.8/ref/forms/api/#django.forms.Form.initial">Form.initial</a>
<a href="https://docs.djangoproject.com/en/1.8/ref/forms/api/#django.forms.Form.cleaned_data">Form.cleaned_data</a>
</pre>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-89334408569631784142015-11-30T08:00:00.000-08:002015-11-30T08:00:07.033-08:00ReportLab Chinese Support<blockquote>
I need to generate a PDF with Chinese characters in a Django project. Generating a PDF is straightforward as ReportLab does the job well. The hard part is to make sure the user can read the Chinese characters in the PDF.
</blockquote>
<blockquote>
At the end I settled in using the Chinese font provided by the Adobe's Asian Language Packs. That way, if no usable Chinese font is available, the user can download and install the Chinese font on demand automatically. This approach offers good performance since nothing is embedded in the PDF.
</blockquote>
<blockquote>
The following is the code snippet in registering the Chinese font for ReportLab to use.
</blockquote>
<pre>
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
font_chinese = 'STSong-Light' # from Adobe's Asian Language Packs
pdfmetrics.registerFont(UnicodeCIDFont(font_chinese))
...
canvas.setFont(font_chinese)
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-90324595499788308282015-11-22T17:19:00.002-08:002015-11-22T17:20:22.913-08:00Django Form - Confirm Before Saving (Part 2)<blockquote>
In <a href="http://amgcomputing.blogspot.ca/2015/11/django-form-confirm-before-saving.html">Part 1</a> of this post, we create a form which conditionally shows a checkbox for user confirmation. The implementation is to raise an validation error when a condition is met. That does not work well with multiple confirmations, as the checking stops when the first condition is met. We need a way to check all conditions without raising an validation error, with one checkbox to confirm them all. We can do that with Form.add_error()
</blockquote>
<blockquote>
Instead of raising an validation error like in <a href="http://amgcomputing.blogspot.ca/2015/11/django-form-confirm-before-saving.html">Part 1</a> of this post:
</blockquote>
<pre>
class InvoiceForm(forms.ModelForm):
...
def clean(self):
super().clean()
...
if all((key in self.cleaned_data for key in ['date', 'confirm'])):
if self.cleaned_data['date'].weekday() == 6 and not self.cleaned_data['confirm']:
<b>raise forms.ValidationError(MSG001)</b>
</pre>
<blockquote>
We add an validation error when a condition is met:
</blockquote>
<pre>
class InvoiceForm(forms.ModelForm):
...
def clean(self):
super().clean()
...
if 'confirm' in self.cleaned_data and not self.cleaned_data['confirm']:
if 'date' in self.cleaned_data:
d = self.cleaned_data['date']
if d.weekday() == 6:
<b>self.add_error(None, forms.ValidationError(CONFIRM_MSG[1]))</b>
if (datetime.date.today() - d).days > 14:
<b>self.add_error(None, forms.ValidationError(CONFIRM_MSG[2]))</b>
</pre>
<blockquote>
In the example above, a confirmation is required if either the invoice date falls on a Sunday or the invoice date is more than 14 days in the past. CONFIRM_MSG is a dict of confirmation messages such as the following:
</blockquote>
<pre>
CONFIRM_MSG = {
1: "The invoice date does not fall on a Sunday. Please check confirm to proceed.",
2: "The invoice date is more than 14 days in the past. Please check confirm to proceed.",
}
</pre>
<blockquote>
In the Form.__init__() method, we check for existence of any of the confirmation messages to show the checkbox. Note the use of the Form.non_field_errors() method to retrieve all non-field errors, such as those added in the Form.clean().
</blockquote>
<pre>
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
...
<b>confirm_set = set(CONFIRM_MSG.values())
if confirm_set.intersection(self.non_field_errors()):
self.fields['confirm'].widget = forms.CheckboxInput()</b>
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-58017496792107863732015-11-17T16:01:00.002-08:002015-11-22T17:26:53.670-08:00Django Form - Confirm Before Saving (Part 1)<blockquote>
How can I ask the user to confirm before saving a form if the form meets certain conditions? I tried different approaches and settled on the following.
</blockquote>
<blockquote>
This example is about saving an invoice. I would like to ask the user to confirm (by checking a checkbox) before saving an invoice if the invoice date falls on a Sunday. The checkbox will only appear if confirmation is required.
</blockquote>
<blockquote>
Step 1: Add a checkbox in the form as a hidden input.
</blockquote>
<pre>
class InvoiceForm(forms.ModelForm):
<b>confirm = forms.BooleanField(initial=False, required=False, widget=forms.HiddenInput)</b>
class Meta:
model = Invoice
fields = ('data', ..., <b>'confirm'</b>,)
</pre>
<blockquote>
Step 2: Check for the condition in the form.clean() method. The required data are retrieved from the form cleaned_data dictionary. Note that it is a straightly a read only access. If the condition is met, raise a validation error thereby invalidating the form.
</blockquote>
<blockquote>
MSG001 is a string constant used as the error message. For example something like "The invoice date does not fall on a Sunday. Please check confirm to proceed."
</blockquote>
<pre>
class InvoiceForm(forms.ModelForm):
...
def clean(self):
super().clean()
...
<b>if all((key in self.cleaned_data for key in ['date', 'confirm'])):
if self.cleaned_data['date'].weekday() == 6 and not self.cleaned_data['confirm']:
raise forms.ValidationError(MSG001)</b>
</pre>
<blockquote>
Step 3: Check for the error condition in the __init__() method of the form, and dynamically change the checkbox from a hidden input to a regular checkbox on the fly. This effectively adds a checkbox to the form and unless it is checked, the form is invalid.
</blockquote>
<pre>
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
...
<b>if any(e == MSG001 for e in self.errors.get('__all__', [])):
self.fields['confirm'].widget = forms.CheckboxInput()</b>
</pre>
<blockquote>
Step 4: In the HTML template, add conditional logic to show the checkbox either as a hidden input or as a checkbox as appropriate.
</blockquote>
<pre>
<b>{% if form.confirm in form.hidden_fields %}</b>
{{ form.confirm }}
<b>{% else %}</b>
<tr><td>{{ form.confirm.label_tag }}</td><td>{{ form.confirm }}</td></tr>
<b>{% endif %}</b>
</pre>
<blockquote>
Final thoughts:
</blockquote>
<blockquote>
This approach does not require AJAX or any third party packages. The reason is to minimize Javascript knowledge and external dependency.
</blockquote>
<blockquote>
This approach uses validation error as a signal to add a checkbox to the form.
</blockquote>
<blockquote>
This approach modifies a form field dynamically in the form.__init__() method.
</blockquote>
<blockquote>
Also, in another approach, I tried to to update the form.cleaned_data dict without success. The changes I made are lost somewhere in the call chain. It is evident that I do not have sufficient knowledge in how Django forms work.
</blockquote>
<blockquote>
Continue to <a href="http://amgcomputing.blogspot.ca/2015/11/django-form-confirm-before-saving-part-2.html">Part 2</a> of this blog for working with multiple confirmations.
</blockquote>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-81448358783435710092015-07-20T07:30:00.000-07:002015-07-20T07:30:00.543-07:00Abstract methods in Python3<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>You can use the <a href="https://docs.python.org/3/library/abc.html">Abstract Base Classes</a> to implement abstract methods in Python. For example, if you have an Animal class and a Cat class like the following:
</blockquote>
<pre>
class Animal():
def speak(self):
print("Animal speak")
class Cat(Animal):
pass
class Doc(Animal):
pass
Cat().speak() # returns "Animal speak"
Dog().speak() # returns "Animal speak"
</pre>
<blockquote>You can turn Animal.speak() to an abstract method with a metaclass and a decorator:</blockquote>
<pre>
import abc
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
def speak(self):
print("Animal speak")
class Cat(Animal):
def speak(self):
print("Cat speak")
class Dog(Animal):
pass
Cat().speak() # returns "Cat speak"
Dog().speak() # returns "TypeError: Can't instantiate abstract class Dog with abstract methods speak"
</pre>
<blockquote>Note that although the Animal.speak() method is always shadowed by its subclass method, it can still be called explicitly by using super():</blockquote>
<pre>
class Cat(Animal):
def speak(self):
super().speak()
Cat().speak() # returns "Animal speak"
</pre>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-87962972138021090182015-07-13T07:30:00.000-07:002015-07-13T07:30:00.755-07:00Add a button to export the search results in Django - Part 2<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>Refering to a <a href="http://amgcomputing.blogspot.ca/2014/09/add-button-to-export-search-results-in.html">previous post</a>, it turns out that using str() to serialize the QuerySet query is not a viable solution if one of the selection criteria is a date. The str() will not quote the date value resulting in an invalid SQL statement.</blockquote>
<blockquote>Using pickle to serialize the query solves the problem.</blockquote>
<pre>
# Replace str()
request.session['search_sql'] = str(Person.objects.filter(**kw).query)
# with pickle.dumps()
request.session['search_query'] = pickle.dumps(Person.objects.filter(**kw).query)
</pre>
<blockquote>When the time comes to re-execute the query:</blockquote>
<pre>
# Rather than executing a raw sql statement:
sql = request.session.get('search_sql')
object_list = Person.objects.raw(sql)
# deserialize the saved query and assign it to the QuerySet instead:
saved_query = request.session.get('search_query')
query = pickle.loads(saved_query)
object_list = Person.objects.all()
object_list.query = query
</pre>
<blockquote>Although less secured, in order to store the query object in the request session, have to use the pickle based session serializer instead of the JSON based counterpart in Django 1.7.</blockquote>
<pre>
=== settings.py ===
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
</pre>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-37048292460591598052015-07-06T07:30:00.000-07:002015-07-06T07:30:01.106-07:00Set the width of an input field in an inline formset<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>To set the width of an input field in an inline formset, you can specify the widget and its attributes in the inlineformset_factory call. For example:
</blockquote>
<pre>
LineFormSet = forms.models.inlineformset_factory(Document, DocumentLine,
- fields=['seqno', 'sku', 'desc', 'qty', 'unit_price'])
+ fields=['seqno', 'sku', 'desc', 'qty', 'unit_price'],
+ widgets={
+ 'seqno': forms.TextInput(attrs={'size':'10'}),
+ 'sku': forms.TextInput(attrs={'size':'10'}),
+ 'desc': forms.TextInput(attrs={'size':'50'}),
+ 'qty': forms.TextInput(attrs={'size':'10'}),
+ 'unit_price': forms.TextInput(attrs={'size':'10'})})
</pre>
<blockquote>Reference:
<a href="https://docs.djangoproject.com/en/1.7/topics/forms/modelforms/#specifying-widgets-to-use-in-the-inline-form">https://docs.djangoproject.com/en/1.7/topics/forms/modelforms/#specifying-widgets-to-use-in-the-inline-form</a>
</blockquote>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-52760888510293506002015-06-29T07:30:00.001-07:002015-07-15T19:12:29.284-07:00Automatic version numbering with Mercurial hook - Part 2<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<style>
edited { margin: 0; padding: .5em 1em; border-left: 5px solid #f6ebc1; background-color: #fce27c; } blockquote p { margin: 0; }
</style>
<blockquote>The pre-commit hook commands in the <a href="http://amgcomputing.blogspot.ca/2015/06/automatic-version-numbering-with.html">last post</a> only works in Linux or Mac. I need to run it in Windows as well. Instead of converting it to MS-DOS commands, I found it easier to write an external python script as the hook handler, as it will work in Windows, Linux and Mac.</blockquote>
<blockquote>
Step 1 - Modify the hook to run an external script.
</blockquote>
<pre>
<b># .hg/hgrc</b>
[hooks]
pre-commit.version = python:./script/hghooks.py:increment_version
</pre>
<blockquote>Step 2 - Create a python module with the handler function.</blockquote>
<blockquote>It basically uses the subprocess module to get the local revision number, strip any ending newline or plus sign, add one to it, and then write it out to the version.py file. Note that we obtain the repo root folder from the repo object passed into the hook handler, so that we don't have to hardcode the path to the version file.</blockquote>
<pre>
<b># ./script/hghooks.py</b>
import os.path
import subprocess
def increment_version(*args, **kw):
repo = kw['repo']
version = int(subprocess.check_output(["hg", "id", "-n"]).strip().strip("+")) + 1
setting = "VERSION = %s" % version
file = os.path.join(repo.root, "dms/dms/version.py")
with open(file, "w") as fp:
fp.write(setting)
print(setting)
</pre>
<blockquote>Using an external script makes it not only cross-platform, but also version control friendly because it is not stored in the .hg folder!</blockquote>
<pre>
</pre>
<edited>EDITED 2015-07-09 In order to take care of multiple heads, the increment_version() is revised as follow:</edited>
<pre>
<b># ./script/hghooks.py</b>
import os.path
import subprocess
def increment_version(*args, **kw):
<b>"""hg id -n can returns:
145 # no uncomitted changes
145+ # with uncomitted changes
146+145+ # multi-heads"
The strip() and split() will take care of all the above cases.
"""</b>
repo = kw['repo']
<b>version = int(subprocess.check_output(["hg", "id", "-n"]).strip().split("+")[0]) + 1</b>
setting = "VERSION = %s" % version
file = os.path.join(repo.root, "pms/pms/version.py")
with open(file, "w") as fp:
fp.write(setting)
print(setting)
</pre>
<edited>EDITED 2015-07-15</edited>
<blockquote>The script is further enhanced by removing the hard-coded file path to the Mercurial project config file, and add a test to make sure something has changed indeed before incrementing the version number. The final version of the script is listed below, and is also available in <a href="https://bitbucket.org/davidfung/hghook">Bitbucket</a>.</blockquote>
<pre>
import os.path
import subprocess
def increment_version(ui, repo, **kw):
"""
An in-process Mercurial hook to increment a version setting.
FILE .hg/hgrc
[hooks]
pre-commit.version = python:path/to/hghooks.py:increment_version
[hook_params]
version.file = path/to/version.py
ENDFILE
hg id -n can returns:
145 # no uncomitted changes
145+ # with uncomitted changes
146+145+ # multi-heads"
The strip() and split() will take care of all the above cases.
"""
has_changes = subprocess.check_output(["hg", "stat"])
if has_changes:
version = int(subprocess.check_output(["hg", "id", "-n"]).strip().split("+")[0]) + 1
setting = "VERSION = %s" % version
path = ui.config('hook_params', 'version.file', default='version.py', untrusted=False)
if not os.path.isabs(path):
path = os.path.join(repo.root, path)
with open(path, "w") as fp:
fp.write(setting)
print(setting)
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-66867501973144056662015-06-22T07:30:00.000-07:002015-07-06T16:12:53.606-07:00Automatic version numbering with Mercurial hook<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>Assuming you put your version number setting in a version.py file as VERSION = NNN, you can add the following to the project .hg/hgrc to automatically increment the version number upon each commit. The version number is actually the local revision number before the commit plus 1, although not exactly robust but is generally increasingly upward. Any conflict should be resolved manually.</blockquote>
<pre>
# .hg/hgrc
[hooks]
pre-commit.version = echo VERSION = $((`hg id -n | tr -d +` + 1)) > ./path/version.py
</pre>
<blockquote>We use Mercurial pre-commit hook to update the version number in the file version.py. When the hook is run, the current directory is set to the repo's root directory. We get the local revision number with "hg id -n", remove the trailing "+" if any, and then add one to it as the version number.
Please note that the hook is pre-commit not precommit. Both hooks exist, but if you use the precommit hook, you will not get what you want. See <a href="https://lildude.co.uk/mercurial-precommit-isnt-entirely-pre/">this post</a> for details.</blockquote>
<blockquote>EDITED 2015-07-06: See <a href="http://amgcomputing.blogspot.ca/2015/06/automatic-version-numbering-with_29.html">Part 2</a> for a different approach.</blockquote>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-42295669777070003162015-05-25T21:47:00.000-07:002015-05-25T21:47:56.289-07:00PyQt5 Password Dialog<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>The following is the complete source code showing how to program a password input dialog in PyQt5. The password dialog is based on QInputDialog, with the QLineEdit.Password set as the echo mode. Note that we do not need the QApplication to provide an event loop in this simple demonstration.
</blockquote>
<pre>
import sys
from PyQt5.QtWidgets import QApplication, QInputDialog, QLineEdit
def getPassword():
text, ok = QInputDialog.getText(None, "Attention", "Password?",
<b>QLineEdit.Password</b>)
if ok and text:
print("password=%s" % text)
if __name__ == '__main__':
app = QApplication(sys.argv)
getPassword()
</pre>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-62098468112337090752014-12-30T14:58:00.000-08:002014-12-30T14:58:00.297-08:00How Django shows the “It worked!” page?<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>If you do the following, you will see a “It worked!” page at http://127.0.0.1:8000
</blockquote>
<pre>
<b>
$ django-admin.py startproject myproject
$ python manage.py runserver
</b>
</pre>
<blockquote>Let's find out how it is done.
<br><br>
First search the django package to find the phrase “It worked!”. Assuming you are at the root of the django package:
</blockquote>
<pre>
<b>$ grep -ri "it worked!" --include=*.py .</b>
./views/debug.py: <h1>It worked!</h1>
</pre>
<blockquote>Look into debug.py and you will see “It worked!” is defined in the string “DEFAULT_URLCONF_TEMPLATE”.
<br><br>
“DEFAULT_URLCONF_TEMPLATE” is used in a module level function “default_urlconf()” in the same file.
<br><br>
The “default_urlconf()” function is called by another module level function “technical_404_response()” in the same file.
<br><br>
Portion of “technical_404_response() is listed below:
</blockquote>
<pre>
try:
tried = exception.args[0]['tried']
except (IndexError, TypeError, KeyError):
tried = []
else:
if (not tried # empty URLconf
or (<i><b>request.path == '/'</b></i>
and len(tried) == 1 # default URLconf
and len(tried[0]) == 1
and getattr(tried[0][0], 'app_name', '') ==
getattr(tried[0][0], 'namespace', '') == '<i><b>admin</b></i>')):
return default_urlconf(request)
</pre>
<blockquote>
You can see that it checks for the special case of hitting the root url “/” and filtering out the admin site.
<br><br>
Let's do a search on technical_404_response():
</blockquote>
<pre>
<b>$ grep -ri "technical_404_response" --include=*.py .</b>
./views/debug.py:def technical_404_response(request, exception):
./contrib/staticfiles/handlers.py: return debug.technical_404_response(request, e)
./core/handlers/base.py: response = debug.technical_404_response(request, e)
</pre>
<blockquote>The relevant file is “base.py”. In the definition of class BaseHandler(object) in base.py, if the DEBUG setting is True, then it will call technical_404_response() and eventually returns the “It worked!” page, as illustrated in the code fragment below.
</blockquote>
<pre>
except http.Http404 as e:
logger.warning('Not Found: %s', request.path,
extra={
'status_code': 404,
'request': request
})
if <i><b>settings.DEBUG</b></i>:
response = debug.<i><b>technical_404_response</b></i>(request, e)
</pre>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-26407323066116892302014-12-23T14:54:00.000-08:002014-12-23T14:54:00.431-08:00How to install a python package for one particular webapp in WebFaction<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>Both of the following examples are based on a Django 1.7 / Python 3.4 app.<br><br>
Example 1: ReportLab
</blockquote>
<pre>
$ pip3.4 install --user -U setuptools
$ cd ~/webapps/APP
$ PYTHONPATH=$PWD/lib/python3.4 pip3.4 install \
--install-option="--install-scripts=$PWD/bin" \
--install-option="--install-lib=$PWD/lib/python3.4" \
reportlab==3.1.8
$ python3.4 manage.py shell
>>> import reportlab
>>> reportlab.__file__ # to verify
</pre>
<blockquote>Example 2: django-taggit
</blockquote>
<pre>
$ pip3.4 install --user -U setuptools
$ cd ~/webapps/APP
$ PYTHONPATH=$PWD/lib/python3.4 pip3.4 install -t $PWD/lib/python3.4 \
django-taggit
$ python3.4 manage.py shell
>>> import taggit
>>> taggit.__file__ # to verify
</pre>
<blockquote>The exact method to use depends on how the package is built.
</blockquote>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-50768670115847646222014-12-16T14:48:00.002-08:002014-12-16T14:48:49.407-08:00Ever wondering what does {% load staticfiles %} mean?<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>
It means Django will loop through all INSTALLED_APPS directory to look for a “templatetags” subdirectory.<br/><br/>
If found, it will in turn look for a “staticfiles.py” file inside the templatetags subdirectory.<br><br>
If staticfiles.py is found, then it will import it.<br/><br/>
For example, if django.contrib.staticfiles is in the INSTALLED_APPS, then
</blockquote>
<pre>{% load staticfiles %}</pre>
<blockquote>
will import the following file:
</blockquote>
<pre>
django/contrib/staticfiles/templatetags/staticfiles.py
</pre>
<blockquote>
There is a module level function called “static” defined in staticfiles.py, which is the “static” in the {% static %} template tag.
</blockquote>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-25763012769153394822014-12-02T07:00:00.000-08:002014-12-02T07:00:02.548-08:00How Django deals with methods renaming<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>A RenameMethodsBase metaclass is defined in the django.utils.deprecation module. It seems that Django uses it to deal with methods renaming. Below is a simplified explanation of how it works.
<br><br>
First we subclass the RenameMethodsBase to specify the old and new method names, as well as the type of warning. Then we use it as a metaclass on two classes Field1 and Field2.
</blockquote>
<pre>
import warnings
warnings.simplefilter("always")
from django.utils.deprecation import *
class RenameFieldMethods(RenameMethodsBase):
renamed_methods = (
('_has_changed', 'has_changed', DeprecationWarning),
)
class Field1(metaclass=RenameFieldMethods):
def _has_changed(self):
print('_has_changed')
class Field2(metaclass=RenameFieldMethods):
def has_changed(self):
print('has_changed')
</pre>
<blockquote>If we instantiate Field1 and call both old and new methods, the following results:</blockquote>
<pre>
>>> f1 = Field1()
>>> f1.has_changed()
_has_changed
>>> f1._has_changed()
__main__:1: DeprecationWarning: `Field1._has_changed` is deprecated, use `has_changed` instead.
_has_changed
</pre>
<blockquote>On the other hand if we instantiate Field2 and call both old and new methods, the following results:</blockquote>
<pre>
>>> f2 = t2.Field2()
>>> f2.has_changed()
has_changed
>>> f2._has_changed()
__main__:1: DeprecationWarning: `Field2._has_changed` is deprecated, use `has_changed` instead.
has_changed
</pre>
<blockquote>In short, both methods will succeed, but if called by the name of the old method, a warning is issued.</blockquote>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-30408004670794732782014-11-09T19:00:00.000-08:002014-11-09T19:00:03.862-08:00Surprise in Django render()<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>
The doc says <b>render()</b> <a href="https://docs.djangoproject.com/en/1.7/topics/http/shortcuts/#render">(Note 1)</a> is the same as a call to <b>render_to_response()</b> with a <i>context_instance</i> argument that forces the use of a <i>RequestContext</i>.<br/>
<br/>
It happens that <b>render()</b> takes a <i>request</i> object as its first argument, and a <i>context_instance</i> and a <i>dictionary</i> as two of its optional arguments. The <i>context_instance</i> is the context instance to render the template with. By default, the template will be rendered with a <i>RequestContext</i> instance (filled with values from <i>request</i> and <i>dictionary</i>).<br/>
<br/>
As a Django beginner, I naively assume the <i>request</i> object will be automatically available in the template as I pass it as the first argument in the first place, and also because the <i>context_instance</i> argument forces the use of a <i>RequestContext</i> instance which is filled with values from the <i>request</i> object. Turns out that is not the case.<br/>
<br/>
The <i>request</i> object is for the construction of the <i>RequestContext</i> only, which by default (as of Django 1.7) does not include the <i>request</i> object itself <a href="https://docs.djangoproject.com/en/1.7/ref/templates/api/#django-core-context-processors-request">(Note2)</a>. It took me some time to figure that out.<br/>
<br/>
To have the <i>request</i> object available in the template, I will have to pass it to <b>render()</b> in the <i>dictionary</i> argument:
</blockquote>
<pre>
return render(request, template_name, {'request':request})
</pre>
<blockquote>Or enable the request context processor at the settings, such that the <i>request</i> object will be automatically added to every <i>RequestContext</i> instance:</blockquote>
<pre>
<b>=== settings.py ===</b>
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS += (
'django.core.context_processors.request',
)
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-8048550630473774692014-11-02T19:00:00.000-08:002014-11-05T22:26:14.419-08:00Appending to Django default settings<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>
You can have a very minimal <i>settings.py</i> file in Django 1.7. Have you ever wondered how Django provides all the default settings?
<br/><br/>
All the default settings are defined in the module <a href="https://github.com/django/django/blob/master/django/conf/global_settings.py">django/conf/global_settings</a>.
<br/><br/>
There is usually no need to access the <i>global_setting</i> directly <a href="https://docs.djangoproject.com/en/1.7/topics/settings/#default-settings">as explained in the doc.</a>.
<br/><br/>
The <i>settings</i> object will automatically get the default from the <i>global_setting</i> module on demand:
</blockquote>
<pre>
from django.conf import settings
if settings.DEBUG:
# Do something
</pre>
<blockquote>
Or you can override a setting in <i>settings.py</i> like below:
</blockquote>
<pre>
DEBUG = True
</pre>
<blockquote>
However, if you ever want to add something to an existing setting instead of replacing it, you will need to do something like below in settings.py:
</blockquote>
<pre>
from django.conf.<b>global_settings</b> import TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS += (
'django.core.context_processors.request',
)
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-34456048799928970502014-10-29T16:41:00.000-07:002014-10-29T16:41:35.427-07:00What is a Python decorator?<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>Suppose we have a function that returns the boiling point of water in celsius:</blockquote>
<pre>
def boiling_point():
return 100
print boiling_point() # returns 100
</pre>
<blockquote>Suppose we are now at high altitude and the boiling point is now 97 degree. We can wrap the function like this:</blockquote>
<pre>
<b>def calibrate(f):
def g():
return f() - 3
return g
</b>
def boiling_point():
return 100
print boiling_point() # returns 100
<b>boiling_point_calibrated = calibrate(boiling_point)
print boiling_point_calibrated() # returns 97</b>
</pre>
<blockquote>In fact, we can do better. We can assign the calibrated function back to the original function name. As a result, nothing is changed on the calling side:</blockquote>
<pre>
def calibrate(f):
def g():
return f() - 3
return g
def boiling_point():
return 100
print boiling_point() # return 100
<b>boiling_point</b> = calibrate(boiling_point)
<b>print boiling_point() # returns 97</b>
</pre>
<blockquote>When used in this way, the function calibrate() is called a decorator, and there is a shortcut for it:</blockquote>
<pre>
def calibrate(f):
def g():
return f() - 3
return g
<b>@calibrate</b>
def boiling_point():
return 100
print boiling_point() # returns 97
</pre>
<blockquote>Even better is for the offset to be configurable. The boiling point is dependent on the altitude after all. What we can do is to create a function which returns a function that decorates a function:</blockquote>
<pre>
<b>def calibrate_offset(offset):</b>
def calibrate(f):
def g():
return f() - offset
return g
<b>return calibrate</b>
@calibrate_offset<b>(3)</b>
def boiling_point():
return 100
print boiling_point() # returns 97
</pre>
<blockquote>Finally we can generalize the original function by adding *args and **kwargs to it:</blockquote>
<pre>
def calibrate_offset(offset):
def calibrate(f):
def g(<b>*args, **kwargs</b>):
return f(<b>*args, **kwargs</b>) - offset
return g
return calibrate
@calibrate_offset(3)
def boiling_point():
return 100
print boiling_point() # returns 97
</pre>
<blockquote>Lastly, decorator can be nested. What do you think x will be in the following case?</blockquote>
<pre>
def calibrate_offset(offset):
def calibrate(f):
def g(*args, **kwargs):
return f(*args, **kwargs) - offset
return g
return calibrate
<b>@calibrate_offset(3)
@calibrate_offset(4)</b>
def boiling_point():
return 100
x = boiling_point()
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-62427797359094352052014-10-12T17:23:00.000-07:002014-10-12T17:23:59.965-07:00Django user login & logout signals<style>
blockquote { margin: 0; padding: .5em 1em; border-left: 5px solid #fce27c; background-color: #f6ebc1; } blockquote p { margin: 0; }
</style>
<blockquote>I would like to do something when a user logs in and out from my Django 1.7 project. The best way to do it is to take advantage of the Django Authentication System Login and Logout signals.<br/>
<br/>
First I need to define the signal handlers in a signals submodule in one of Django application. (<a href="https://docs.djangoproject.com/en/1.7/topics/signals/#connecting-receiver-functions">Note 1</a>) In this case, I just write a log entry whenever a user logs in or logs out.</blockquote>
<pre>
<b>=== myapp/signals.py ===</b>
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.dispatch import receiver
from django.conf import settings
@receiver(user_logged_in)
def sig_user_logged_in(sender, user, request, **kwargs):
logger = logging.getLogger(__name__)
logger.info("user logged in: %s at %s" % (user, request.META['REMOTE_ADDR']))
@receiver(user_logged_out)
def sig_user_logged_out(sender, user, request, **kwargs):
logger = logging.getLogger(__name__)
logger.info("user logged out: %s at %s" % (user, request.META['REMOTE_ADDR']))
</pre>
<blockquote>Then I create an application config in an apps submodule and override the Ready() method to register the signals. (<a href="https://docs.djangoproject.com/en/1.7/ref/applications/#django.apps.AppConfig.ready">Note 2</a>)</blockquote>
<pre>
<b>=== myapp/apps.py ===</b>
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'MyApp'
verbose_name = "My Application"
def ready(self):
import myapp.signals # register the signals
</pre>
<blockquote>Finally I put 'myapp.apps.MyAppConfig' in the INSTALLED_APPS setting. (<a href="https://docs.djangoproject.com/en/1.7/ref/applications/#for-application-authors">Note 3</a>)</blockquote>
<pre>
<b>=== myproject/settings.py ===</b>
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
<b>'myapp.apps.MyAppConfig'</b>,
)
</pre>
<blockquote>The log will look like something similar to the following.</blockquote>
<pre>
<b>=== app.log === </b>
[2014-10-09 05:08:28,372] INFO [myapp.signals:10] user logged in: david at 127.0.0.1
[2014-10-09 05:08:29,791] INFO [myapp.signals:15] user logged out: david at 127.0.0.1
</pre>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com4tag:blogger.com,1999:blog-14524524.post-70809139030232412002014-10-06T17:49:00.000-07:002014-10-06T17:49:21.802-07:00Migrating from Django 1.6 to 1.7<blockquote>Recently I upgraded a simple Django project from Django 1.6 (running on Python 2.7) to Django 1.7 (running on Python 3.4). Below are some of the steps taken.</blockquote>
<pre>
<b>1. Switch to new syntax of relative import</b>
OLD> from local_setting import *
NEW> from .local_setting import *
<b>2. Change HttpResponse() argument from mimetype to content_type</b>
OLD> response = HttpResponse(mimetype='text/csv')
NEW> response = HttpResponse(content_type='text/csv')
<b>3. Specify binary mode in open() to avoid UnicodeDecodeError</b>
OLD> with open("binary_file") as
NEW> with open("binary_file", "rb") as
<b>4. Replace __unicode__() with __str__()</b>
OLD> def __unicode__(self):
NEW> def __str__(self):
</pre>
<blockquote>I also updated my .vimrc to exclude the __pycache__ directories from showing up in netrw.</blockquote>
<pre>
<b>=== ~/.vimrc ===</b>
let g:netrw_list_hide= '.*\.pyc$,.*\.swp$,__pycache__'
</pre>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-42938794747231704612014-09-28T15:53:00.000-07:002014-09-28T15:53:07.860-07:00Add a button to export the search results in Django<blockquote>I have a simple search page with a form at the top for the user to enter the search criteria (e.g. last name, first name, age, etc), and the search results are shown in the bottom half of the page.</blockquote>
<blockquote>What I want to do is to add a button to the form to export the search results as a CSV file. At first, I tried to have the search and export buttons post to two different URLs, but of course that did not work because an HTML form can only post to one and one only one URL.</blockquote>
<blockquote>Then I tried to have both the search button and the export button post to the same URL, and have the Django view handling the difference based on which button the user has clicked to submit the form, e.g. if the search button is clicked, then routes to the search view, else if the export button is clicked, then routes to the export view. It worked but there is a problem.</blockquote>
<blockquote>Since the user can change the search criteria before clicking the export button, and since it is a POST, the export view will execute the search based on the newly entered search criteria and export the new search results, which will be different from what the user sees in the bottom half of the search page. This is counter-intuitive and unacceptable to me.</blockquote>
<blockquote>My third attempt is to have the search and export button post to two different URLs. When a user clicks the search button, the search view will execute the search and save the search criteria (e.g. last name, first name, age, etc) in a Django session variable. If the user then clicks the export button, the export view will grab the search criteria from the session variable, execute the search, and export the results. It works, but it is not very portable because whenever I change the search form, I will need to change the views.</blockquote>
<blockquote>My next approach is to save the Django QuerySet in a session variable instead of the search criteria. It didn't work because a QuerySet is not JSON serializable. I cannot get Django to save the QuerySet in a session variable. </blockquote>
<blockquote>My final approach is to save the QuerySet query (rather than the search criteria) in a session variable. Then it works perfectly. I don't have to modify the views when the form changes. The export view grabs the QuerySet query from the session and then execute it as a raw SQL query. The performance hit of saving the QuerySet query is negligible, although the session variable itself will be wasted if the user eventually does not click the export button. But that is really just a small price to pay.</blockquote>
<pre>
<b>=== search view sample code ===</b>
last_name = form.cleaned_data['last_name'] or None
first_name = form.cleaned_data['first_name'] or None
age = form.cleaned_data['age'] or None
if last_name: kw['last_name__icontains'] = last_name
if first_name: kw['first_name__icontains'] = first_name
if age: kw['age__exact'] = age
object_list = Person.objects.filter(**kw) <i># for the search</i>
request.session['search_sql'] = str(Person.objects.filter(**kw).query) <i># for the export</i>
<b>=== export view sample code ===</b>
sql = request.session.get('search_sql')
object_list = Person.objects.raw(sql)
</pre>davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-50899253505893881772014-09-21T15:41:00.002-07:002014-09-21T15:41:41.411-07:00Reset button that works across postback to same page<blockquote>
By definition, a HTML Reset button does not work after a form is postback to the same page, because the posted back values then become the "default" values of the form.</blockquote>
<blockquote>
Although you can use Javascript to reset all the fields in the form, I usually resort to a more simple way. Just create a button with an onClick handler to visit the same page. The difference this time is that it will be a GET instead of a POST, so all fields in the form will be cleared and reset to the original defaults.</blockquote>
<blockquote>
Yes, it takes a round trip to the server, but the logic is simple and portable.</blockquote>
<pre><b>=== Sample Code ===</b>
<input type="button" onclick="window.location=''" value="Reset" />
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0tag:blogger.com,1999:blog-14524524.post-10122475556677798352014-09-15T00:06:00.001-07:002015-07-08T19:31:23.095-07:00Using a datepicker in Django model form<blockquote>
If your front end framework (e.g. Bootstrap) has a datepicker CSS class that you would like to use in a Django model form, how can you do that?</blockquote>
<blockquote>
It turns out we can add any attribute to a HTML input element in a Django model form via the formfield_callback defined in the model form. We can make use of this technique to add the datepicker class (an attribute) to a model DateField (an HTML input element).</blockquote>
<blockquote>
The following is an example of adding the datepicker class to all the DateFields in a model form. You will also have to make sure the datepicker class definition is available, probably by including the front end framework files in the base template.</blockquote>
<pre><b>=== forms.py (model datefield) ===</b>
<span style="color: blue;">
from django.forms import ModelForm
from django.db import models
from .models import MyModel
def make_custom_datefield(f):
# f is a model field
# if f is a date field, add the datepicker class to it
# return a form field
formfield = f.formfield()
if isinstance(f, models.DateField):
formfield.widget.attrs.update({'class':'datePicker'})
return formfield
class MyForm(ModelForm):
formfield_callback = make_custom_datefield
class Meta:
model = MyModel
</span>
<b>=== html rendered ===</b>
<span style="color: blue;">
<input <i>class="datePicker"</i> id="id_datefield" name="datefield" type="text" value="" />
</span></pre>
<blockquote>
How about adding the datepicker class to a form DateField (instead of a model DateField)? We can just add an attribute directly in the form DateField definition. The following is an example of it.</blockquote>
<pre><b>=== forms.py (form datefield) ===</b>
<span style="color: blue;">
class MyForm(ModelForm):
somedate = forms.DateField(
widget=forms.TextInput(attrs={'class':'datePicker'}))
class Meta:
model = MyModel
</span>
</pre>
<blockquote>EDITED 2015-07-07 If the form uses a custom widget, add a keyword argument to the formfield_callback function like below, otherwise you will have a "make_custom_datefield() got an unexpected keyword argument 'widget'" error.</blockquote>
<pre>
def make_custom_datefield(f, <b>**kwargs</b>):
formfield = f.formfield(<b>**kwargs</b>)
if isinstance(f, models.DateField):
formfield.widget.attrs.update({'class':'datePicker'})
return formfield
</pre>
davidfunghttp://www.blogger.com/profile/01406529121629008078noreply@blogger.com0