Showing posts with label Python 3. Show all posts
Showing posts with label Python 3. Show all posts

2015-07-20

Abstract methods in Python3

You can use the Abstract Base Classes to implement abstract methods in Python. For example, if you have an Animal class and a Cat class like the following:
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"
You can turn Animal.speak() to an abstract method with a metaclass and a decorator:
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"
Note that although the Animal.speak() method is always shadowed by its subclass method, it can still be called explicitly by using super():
class Cat(Animal):
    def speak(self):
        super().speak()

Cat().speak() # returns "Animal speak"

2015-07-13

Add a button to export the search results in Django - Part 2

Refering to a previous post, 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.
Using pickle to serialize the query solves the problem.
# 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)
When the time comes to re-execute the query:
# 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
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.
=== settings.py ===
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'

2015-07-06

Set the width of an input field in an inline formset

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:
 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'})})
Reference: https://docs.djangoproject.com/en/1.7/topics/forms/modelforms/#specifying-widgets-to-use-in-the-inline-form

2015-06-29

Automatic version numbering with Mercurial hook - Part 2

The pre-commit hook commands in the last post 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.
Step 1 - Modify the hook to run an external script.
# .hg/hgrc

[hooks]
pre-commit.version = python:./script/hghooks.py:increment_version
Step 2 - Create a python module with the handler function.
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.
# ./script/hghooks.py

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)
Using an external script makes it not only cross-platform, but also version control friendly because it is not stored in the .hg folder!

EDITED 2015-07-09 In order to take care of multiple heads, the increment_version() is revised as follow:

# ./script/hghooks.py

import os.path
import subprocess

def increment_version(*args, **kw):
    """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.
    """
    repo = kw['repo']
    version = int(subprocess.check_output(["hg", "id", "-n"]).strip().split("+")[0]) + 1
    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)
EDITED 2015-07-15
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 Bitbucket.
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)