問い合わせ項目を扱うクラスの中身

担当者が異動してしまうからといって、作りかけで投げてしまっては意味がない。K-enq.tdの開発は続いています。

さて、アンケートフォームの定義を扱うクラスとは別に、各フォームに追加される質問の項目を管理するクラスを作っていました。といっても、基本的にはアンケートフォームのクラス、FormDefinitionにそんなに違っていせん。

FormDefinitionとFormItemsの両方で使いたいメソッドがあったので、それらをFormsというクラスに持たせておいて、継承することにしました。こういうやりかたが正しいかどうかはわからないけど、まあ便利なのでいいことにしよう。

class Forms:
    # internal methods
    def _open_db(self):
        return sqlite3.connect(self.dbpath)

    def _pickle(self, value):
        return base64.encodestring(pickle.dumps(value))

    def _unpickle(self, value):
        if value is None or value == u'None':
            return u''
        else:
            return pickle.loads(base64.decodestring(value))

    def _generate_id(self, digit=8):
        if not isinstance(digit, int):
            raise TypeError(u'need int, got %r' % \
                            type(digit))
        seed = str(random.randint(0, sys.maxint - 1))
        message = hashlib.new('sha512')
        message.update(seed)
        temp_id = message.hexdigest()
        if len(temp_id) > digit:
            return temp_id[0:digit]
        else:
            return temp_id

FormItemsでは、登録されている質問項目をソートするのに、order_numberというフィールドを使っています。ここに連番がふられていて、その番号にしたがって並べられるようにしました。なので、項目を追加する際にorder_numberをインクリメントできるように、最大値を知るメソッド_max_order_number()を作りました。最初は_count_item()を使ってたんですが、途中で抜けが発生したらいかんなと思いなおして、SQLiteの関数、max()を使ったものに置き換えました。

    def _count_item(self, cursor, iid):
        cursor.execute('select count(*) from items where iid=?;', \
                       (iid,))
        recordcount = cursor.fetchone()
        return recordcount[0]

    def _max_order_number(self):
        connection = self._open_db()
        cursor = connection.cursor()
        cursor.execute('select max(order_number) from items where fid=?', \
                       (self.fid,))
        recordcount = cursor.fetchone()
        cursor.close()
        connection.close()
        return recordcount[0]

この連番があるので、削除の際には連番を崩さないよう、詰めてやらないといけません。SQLで対象となるレコードを絞り込んで、order_number=(order_number - 1)とすることにしました。こういうのが一発でできるので、SQLは便利ですね。

    def delete_item(self, iid):
        if not isinstance(iid, basestring):
            raise TypeError(u'need str or unicode, got %r' % \
                            type(iid))
        connection = self._open_db()
        cursor = connection.cursor()
        try:
            cursor.execute('select order_number from items \
            where iid=? and fid=?;', \
                           (iid, self.fid))
            record = cursor.fetchone()
        except:
            cursor.close()
            connection.close()
            raise
        tonum = None
        if isinstance(record, tuple):
            tonum = record[0]
        if tonum is not None:
            try:
                cursor.execute('delete from items where iid=? and fid=?;', \
                               (iid, self.fid))
            except:
                cursor.close()
                connection.close()
                raise

            try:
                cursor.execute('update items \
                set order_number=(order_number - 1) \
                where fid=? and order_number>?;', \
                               (self.fid, tonum))
            except:
                cursor.close()
                connection.close()
                raise
        cursor.close()
        connection.commit()
        connection.close()

項目の並べ替えも、delete_item()の応用です。対象のIDを指定し、それをどれだけずらしたいか数値で指定します。1とするとひとつ上昇し、-2とするとふたつ下がります。この移動量が項目の数を超えてしまったときは、OutOfRangeErrorを発生させることにして、それをキャッチするなりなんなりするなりは、その都度考えて書けばいいんじゃない? ということにします。

    def reorder_item(self, iid, distance):
        if not isinstance(distance, int):
            raise TypeError(u'need int, got %r' % \
                            type(distance))
        connection = self._open_db()
        cursor = connection.cursor()
        try:
            cursor.execute('select order_number from items \
            where iid=? and fid=?;', \
                           (iid, self.fid))
        except:
            cursor.close()
            connection.close()
            raise
        order_number = cursor.fetchone()
        if isinstance(order_number, tuple):
            order_number = order_number[0]
            if isinstance(order_number, basestring):
                order_number = int(order_number)
            new_number = order_number - distance
            max_number = self._max_order_number()
            if 0 <= new_number and new_number <= max_number:
                if new_number < order_number:
                    try:
                        cursor.execute('update items \
                        set order_number=(order_number + 1) \
                        where fid=? and order_number<? and order_number>=?;', \
                                       (self.fid, order_number, new_number))
                    except:
                        cursor.close()
                        connection.close()
                        raise
                elif new_number > order_number:
                    try:
                        cursor.execute('update items \
                        set order_number=(order_number - 1) \
                        where fid=? and order_number>? and order_number<=?;', \
                                       (self.fid, order_number, new_number))
                    except:
                        cursor.close()
                        connection.close()
                        raise
                try:
                    cursor.execute('update items \
                    set order_number=? \
                    where iid=? and fid=?;', \
                                   (new_number, iid, self.fid))
                except:
                    cursor.close()
                    connection.close()
                    raise
            else:
                cursor.close()
                connection.close()
                raise OutOfRangeError(u'Distance Outs of Range')
        cursor.close()
        connection.commit()
        connection.close()

とりあえず、今日はこれくらい。疲れてしまって、もう頭がまわりません。