0

แก้ปัญหา Elixir + Mysql

จริงๆ ก็เป็นปัญหาเดิม แต่ตอนรีบๆ มากๆ แล้วจะหาไม่เจอ. ปกติผมใช้ SQLite แล้วค่อยไปใส่ MySQL อีกที. ใน Model สำหรับ SQLite ใส่อะไรประมาณนี้

class Document(Entity):
    body = Field(Unicode)

ก็ใช้ได้แต่พอเอาใส่ mysql แล้วมันจะแปลง Unicode เป็น varchar ก็จะเจ๊งทันที แก้ง่ายๆ เป็น

class Document(Entity):
    body = Field(Unicode(10000000))

ก็ใช้ได้แล้ว :-P

1

Authkit + Elixir

ใช้ pylons 0.9.7rc3 แต่ว่าใช้ elixir 0.6.1 มาครอบ sqlalchemy อีกที แทนที่จะใช้ sqlalchemy ตรงๆ ซึ่งเป็นวิถีทางที่เป็นที่นิยมกว่า ก็ไม่ได้ลำบากยากเย็นอะไร แก้ให้ใช้ elixir ก็ทำตาม ที่เคยๆ เขียนไว้ไม่มากก็เป็นอันใช้ได้ แต่พอมาเจอ authkit 0.4.2 ก็เป็นท่ายากขึ้นไปอีกนิดเหมือนกัน เพราะว่าไม่มี driver สำหรับ elixir ติดมาด้วย. ลองเอา driver ที่ http://wiki.pylonshq.com/display/pysbook/Authentication+and+Authorization มาก็ยังใช้ไม่ได้. ก็เลยเขียนใหม่ซะโดยใช้วิธีคล้ายกับของเดิมแต่ว่าเอา sqlalchemy 0.5.x driver มาแก้ แถมด้วยแก้ c-tor ของ entity ด้วย ทำให้ใช้กับ code เก่าง่ายขึ้น นอกจากนั้นก็เอา session.add(…) ที่ไม่จำเป็นสำหรับ elixir ออกซะ (ใช่มะถ้าผมเข้าใจไม่ผิด). elixir driver for authkit ก็เขียนแบบข้างล่างครับ ยาวหน่อย

# Elixir Driver for AuthKit
# Based on the SQLAlchemy 0.5 driver
# authkit_elixir_driver.py
# modified by Vee Satayamas
#
# Copyright (c) Copyright 2005-2007 James Gardner 
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

from sqlalchemy import *
from paste.util.import_string import eval_import
from authkit.users import *

from sqlalchemy.orm import *
from elixir import *

class UsersFromDatabase(Users):
    """
    Database Version
    """
    def __init__(self, model, encrypt=None):
        print "!!! Driver"
        if encrypt is None:
            def encrypt(password):
                return password
        self.encrypt = encrypt
        if isinstance(model, (str, unicode)):
            model = eval_import(model)
        if hasattr(model, 'authkit_initialized'):
            raise AuthKitError(
                'The AuthKit database model has already been setup'
            )
        else:
            model.authkit_initialized = True

        # Update the model
        self.model = self.update_model(model)
        self.meta = self.model.meta

    def update_model(self, model):
        # following methods from a comment at
        # http://wiki.pylonshq.com/display/pysbook/Authentication+and+Authorization

        class User(Entity):
            uid = Field(Integer, primary_key=True)
            username = Field(String(255), unique=True, nullable=False)
            password = Field(String(255), nullable=False)
            group = ManyToOne("Group")
            roles = ManyToMany("Role", lazy=True) #"roles": relation(Role, lazy=True, secondary=users_roles_table),
            group = ManyToOne("Group")

            def __init__(
                self,
                username,
                uid=None,
                password=None,
                group_uid=None,
            ):
                Entity.__init__(self)
                self.uid         = uid
                self.username   = username
                self.password   = password
                self.group_uid  = group_uid

            def __repr__(self):
                return "User(%(username)s)" % self.__dict__

        class Group(Entity):
            uid = Field(Integer, primary_key=True)
            name = Field(String(255), unique=True, nullable=False)
            users = OneToMany("User")

            def __init__(self, name=None):
                Entity.__init__(self)
                self.name = name

            def __repr__(self):
                return "Group(%(name)s)" % self.__dict__

        class Role(Entity):
            uid = Field(Integer, primary_key=True)
            name = Field(String(255), unique=True, nullable=False)
            users = ManyToMany("User", lazy=True) #"users": relation(User, lazy=True, secondary=users_roles_table)

            def __init__(self, name=None):
                Entity.__init__(self)
                self.name = name

            def __repr__(self):
                return "Role(%(name)s)" % self.__dict__


        model.User = User
        model.Group = Group
        model.Role = Role
        setup_all()
        return model

    # Create Methods
    def user_create(self, username, password, group=None):
        """
        Create a new user with the username, password and group name specified.
        """
        if ' ' in username:
            raise AuthKitError("Usernames cannot contain space characters")
        if self.user_exists(username):
            raise AuthKitError("User %r already exists"%username)
        if group is None:
            new_user = self.model.User(
                username=username.lower(),
                password=self.encrypt(password)
            )
        else:
            if not self.group_exists(group):
                raise AuthKitNoSuchGroupError(
                    "There is no such group %r"%group
                )
            new_user = self.model.User(
                username=username.lower(),
                password=self.encrypt(password),
                group_uid=self.meta.Session.query(self.model.Group).\
                    filter_by(name=group.lower()).first().uid
            )
        self.meta.Session.flush()

    def role_create(self, role):
        """
        Add a new role to the system
        """
        if ' ' in role:
            raise AuthKitError("Roles cannot contain space characters")
        if self.role_exists(role):
            raise AuthKitError("Role %r already exists"%role)
        new_role = self.model.Role(role.lower())
        self.meta.Session.flush()

    def group_create(self, group):
        """
        Add a new group to the system
        """
        if ' ' in group:
            raise AuthKitError("Groups cannot contain space characters")
        if self.group_exists(group):
            raise AuthKitError("Group %r already exists"%group)
        new_group = self.model.Group(group.lower())
        self.meta.Session.flush()

    # Delete Methods
    def user_delete(self, username):
        """
        Remove the user with the specified username
        """
        user = self.meta.Session.query(self.model.User).filter_by(username=username.lower()).first()
        if user is None:
            raise AuthKitNoSuchUserError("There is no such user %r"%username)
        else:
            self.meta.Session.delete(user)
            self.meta.Session.flush()

    def role_delete(self, role):
        """
        Remove the role specified. Rasies an exception if the role is still in use.
        To delete the role and remove it from all existing users use
        ``role_delete_cascade()``
        """
        role = self.meta.Session.query(self.model.Role).filter_by(name=role.lower()).first()
        if role is None:
            raise AuthKitNoRoleUserError("There is no such role %r"%role)
        else:
            self.meta.Session.delete(role)
            self.meta.Session.flush()

    def group_delete(self, group):
        """
        Remove the group specified. Rasies an exception if the group is still in use.
        To delete the group and remove it from all existing users use ``group_delete_cascade()``
        """
        group = self.meta.Session.query(self.model.Group).filter_by(name=group.lower()).first()
        if group is None:
            raise AuthKitNoGroupUserError("There is no such group %r"%group)
        else:
            self.meta.Session.delete(group)
            self.meta.Session.flush()

    # Existence Methods
    def user_exists(self, username):
        """
        Returns ``True`` if a user exists with the given username, ``False``
        otherwise. Usernames are case insensitive.
        """
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        if user is not None:
            return True
        return False

    def role_exists(self, role):
        """
        Returns ``True`` if the role exists, ``False`` otherwise. Roles are
        case insensitive.
        """
        role = self.meta.Session.query(self.model.Role).filter_by(
            name=role.lower()).first()
        if role is not None:
            return True
        return False

    def group_exists(self, group):
        """
        Returns ``True`` if the group exists, ``False`` otherwise. Groups
        are case insensitive.
        """
        group = self.meta.Session.query(self.model.Group).filter_by(
            name=group.lower()).first()
        if group is not None:
            return True
        return False

    # List Methods
    def list_roles(self):
        """
        Returns a lowercase list of all roll names ordered alphabetically
        """
        return [r.name for r in self.meta.Session.query(
            self.model.Role).order_by(self.model.Role.name)]

    def list_users(self):
        """
        Returns a lowecase list of all usernames ordered alphabetically
        """
        return [r.username for r in self.meta.Session.query(
            self.model.User).order_by(self.model.User.username)]

    def list_groups(self):
        """
        Returns a lowercase list of all groups ordered alphabetically
        """
        return [r.name for r in self.meta.Session.query(
            self.model.Group).order_by(self.model.Group.name)]

    # User Methods
    def user(self, username):
        """
        Returns a dictionary in the following format:

        .. code-block :: Python

            {
                'username': username,
                'group':    group,
                'password': password,
                'roles':    [role1,role2,role3... etc]
            }

        The role names are ordered alphabetically
        Raises an exception if the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        roles = [r.name for r in user.roles]
        roles.sort()
        return {
            'username': user.username,
            'group':    user.group and user.group.name or None,
            'password': user.password,
            'roles':    roles
        }

    def user_roles(self, username):
        """
        Returns a list of all the role names for the given username ordered
        alphabetically. Raises an exception if the username doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        roles = [r.name for r in \
            self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first().roles]
        roles.sort()
        return roles

    def user_group(self, username):
        """
        Returns the group associated with the user or ``None`` if no group is
        associated. Raises an exception is the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        return self.meta.Session.query(self.model.User).filter_by(username =
            username.lower()).first().group.name

    def user_password(self, username):
        """
        Returns the password associated with the user or ``None`` if no
        password exists. Raises an exception is the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        return self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first().password

    def user_has_role(self, username, role):
        """
        Returns ``True`` if the user has the role specified, ``False``
        otherwise. Raises an exception if the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        if not self.role_exists(role.lower()):
            raise AuthKitNoSuchRoleError("No such role %r"%role.lower())
        for role_ in self.meta.Session.query(self.model.User).filter_by(
                username=username.lower()).first().roles:
            if role_.name == role.lower():
                return True
        return False

    def user_has_group(self, username, group):
        """
        Returns ``True`` if the user has the group specified, ``False``
        otherwise. The value for ``group`` can be ``None`` to test that
        the user doesn't belong to a group. Raises an exception if the
        user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        if group is not None and not self.group_exists(group.lower()):
            raise AuthKitNoSuchGroupError("No such group %r"%group.lower())
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        if user.group is None:
            if group == None:
                return True
        else:
            if group is not None and user.group.name == group.lower():
                return True
        return False

    def user_has_password(self, username, password):
        """
        Returns ``True`` if the user has the password specified, ``False``
        otherwise. Passwords are case sensitive. Raises an exception if the
        user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        if user.password == self.encrypt(password):
            return True
        return False

    def user_set_username(self, username, new_username):
        """
        Sets the user's username to the lowercase of new_username.
        Raises an exception if the user doesn't exist or if there is already
        a user with the username specified by ``new_username``.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        if self.user_exists(new_username.lower()):
            raise AuthKitError(
                "A user with the username %r already exists"%username.lower()
            )
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        user.username = new_username.lower()
        self.meta.Session.flush()

    def user_set_password(self, username, new_password):
        """
        Sets the user's password. Should be plain text, will be encrypted using self.encrypt
        Raises an exception if the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        user.password = self.encrypt(new_password)
        self.meta.Session.flush()

    def user_set_group(self, username, group, auto_add_group=False):
        """
        Sets the user's group to the lowercase of ``group`` or ``None``. If
        the group doesn't exist and ``add_if_necessary`` is ``True`` the
        group will also be added. Otherwise an ``AuthKitNoSuchGroupError``
        will be raised. Raises an exception if the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        if not self.group_exists(group.lower()):
            if auto_add_group:
                self.group_create(group.lower())
            else:
                raise AuthKitNoSuchGroupError("No such group %r"%group.lower())
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        user.group = self.meta.Session.query(self.model.Group).filter_by(
            name=group.lower()).first()
        self.meta.Session.flush()

    def user_add_role(self, username, role, auto_add_role=False):
        """
        Sets the user's role to the lowercase of ``role``. If the role doesn't
        exist and ``add_if_necessary`` is ``True`` the role will also be
        added. Otherwise an ``AuthKitNoSuchRoleError`` will be raised. Raises
        an exception if the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        if not self.role_exists(role.lower()):
            if auto_add_role:
                self.role_create(role.lower())
            else:
                raise AuthKitNoSuchRoleError("No such role %r"%role.lower())
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        role = self.meta.Session.query(self.model.Role).filter_by(
            name=role.lower()).first()
        user.roles.append(role)
        self.meta.Session.flush()

    def user_remove_group(self, username):
        """
        Sets the group to ``None`` for the user specified by ``username``.
        Raises an exception if the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        user.group = None
        self.meta.Session.flush()

    def user_remove_role(self, username, role):
        """
        Removes the role from the user specified by ``username``. Raises
        an exception if the user doesn't exist.
        """
        if not self.user_exists(username.lower()):
            raise AuthKitNoSuchUserError("No such user %r"%username.lower())
        if not self.role_exists(role.lower()):
            raise AuthKitNoSuchRoleError("No such role %r"%role.lower())
        user = self.meta.Session.query(self.model.User).filter_by(
            username=username.lower()).first()
        for role_ in user.roles:
            if role_.name == role.lower():
                user.roles.pop(user.roles.index(role_))
                self.meta.Session.flush()
                return
        raise AuthKitError(
            "No role %r found for user %r"%(role.lower(), username.lower())
        )

ในกรณีตัวอย่างของผม ผมทำ project ชื่อ auth7 ขึ้นมาแล้วก็เอา driver ไปไว้ใน auth7/users ใน auth7/users ก็มีไฟล์ __init__.py (ตามแบบ python module ทั่วไป) และ authkit_elixir_driver.py ตามที่เขียนไว้ข้างบน

__init__.py ก็เขียนตามนี้

import authkit_elixir_driver

ใน development.ini ก็เพิ่มแบบนี้เข้ามา

authkit.setup.enable = true
authkit.setup.method = form, cookie
authkit.form.authenticate.user.type = auth7.users.authkit_elixir_driver:UsersFromDatabase
authkit.form.authenticate.user.data = auth7.model
authkit.cookie.secret = SomeSec
authkit.cookie.signoutpath = /auth/signout

ใน websetup ก็เขียนแบบนี้

import logging

from auth7.config.environment import load_environment
from auth7.users.authkit_elixir_driver import UsersFromDatabase

log = logging.getLogger(__name__)

def setup_app(command, conf, vars):
    load_environment(conf.global_conf, conf.local_conf)
    from auth7.model import meta
    from auth7 import model
    from elixir import metadata
    users = UsersFromDatabase(model)
    metadata.create_all(checkfirst=True)
    users.user_create("admin", password="password")

ลืมเขียนอะไรอีกหรือเปล่านะ :-P … ว่าจะ post ที่ blog.vee-u.com ไปสักพักก่อน ไว้แก้ๆ อีกสักนิดค่อยเอาลง pylons66.com ละกัน

0

pylons-20080818 + tw.forms + elixir: Create Read Update Delete มั้ง

ลองใช้ pylons, tw.forms และ elixir ลองทำ application ง่ายๆ ตาม http://pylonsbook.com/alpha1/simplesite_tutorial ดูก็ดัดแปลงนิดหน่อยๆ เลยอย่างจะเขียนรวมกันไว้ที่เดียว. อาจจะดูมั่วๆ บ้างไว้มี version ต่อไปค่อย post อีกทีแล้วกัน.

pylons (20080818) นี้เลือกได้เลยว่าให้สร้าง code ที่ support mako และ sqlalchemy ได้เลย แต่จะใช้ elixir และ tw.forms ก็ต้องมาแก้นิดๆ หน่อย. เอา elixir ก่อนแล้วกัน.

แก้ lib/base.py:


from pylons.controllers import WSGIController
from pylons.templating import render_mako as render

import elixir # เพิ่มนี้เข้ามา
class BaseController(WSGIController):
    def __call__(self, environ, start_response):
        try:
            return WSGIController.__call__(self, environ, start_response)
        finally:
            elixir.session.remove() # เพิ่มนี้เข้ามา

แก้ model/meta.py:

from sqlalchemy import MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
__all__ = ['Session', 'metadata']
engine = None

แก้ model/__init__.py:

import sqlalchemy as sa
from sqlalchemy import orm
from simple1.model import meta
import elixir

sm = orm.sessionmaker(autoflush=True, autocommit=True)
elixir.session = orm.scoped_session(sm)
elixir.options_defaults.update({ 'shortnames': True })

def init_model(engine):
    elixir.metadata.bind = engine

from elixir import *
from entities import *

elixir.setup_all()

แล้วก็แก้ websetup.py:

"""Setup the simple1 application"""
import logging

from simple1.config.environment import load_environment

log = logging.getLogger(__name__)

def setup_app(command, conf, vars):
    """Place any commands to setup simple1 here"""
    load_environment(conf.global_conf, conf.local_conf)

    import elixir # ใส่ elixir ไปแทน
    elixir.metadata.create_all(checkfirst=True) # บรรทัดนี้ด้วย

ส่วนที่ใส่ไปข้างบนนี้ไม่ขึ้นกับ app นะครับ ต่อไปก็เติม entity ใน model สักหน่อย ชื่อ Person ข้างในก็มีแค่ first name กับ last name :-P

แก้ model/entities.py:

from elixir import *
class Person(Entity):
    first_name = Field(String(1000))
    last_name = Field(String(1000))

ว่าด้วยเรื่อง elixir ก็เกือบหมดแล้ว ที่เหลือก็อยู่ใน controller เลย. เพื่อที่จะสร้าง model ก็ลองสร้าง database เลยสั่ง

paster setup-app development.ini

ต่อด้วย tw.forms โดยทั่วไปแล้วพอหลังลง package ก็ลงแก้ config/middleware.py: แต่มันยาว ก็แค่เพิ่มอะไรไปนิดๆ หน่อยๆ

...
from tw.api import make_middleware  # แก้ตรงนี้ เพิ่มเข้ามา
...
    # แก้ตรงนี้
    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
    app = make_middleware(app, {
        'toscawidgets.framework' : 'pylons',
        'toscawidgets.framework.default_view' : 'mako',
        'toscawidgets.middleware.inject_resources' : True,
        })
   ...

ว่าด้วยเรื่อง view (template)

แก้ lib/helpers.py

from webhelpers import *
from webhelpers.html.tags import *
from routes import url_for
from webhelpers.html import literal
from webhelpers.pylonslib import Flash as _Flash
from webhelpers import paginate
flash = _Flash()

สร้าง widgets ก่อนจะได้เขียน template สั้นๆ สร้าง widgets/person.py

from tw import forms
from tw.api import WidgetsList

forms.FormField.engine_name = 'mako'

class PersonForm(forms.TableForm):
    class fields(WidgetsList):
        first_name = forms.TextField()
        last_name = forms.TextField()
        submit = forms.SubmitButton(attrs = {'value': 'save'})

person_form = PersonForm(id = 'person_form')

สร้าง base template ใน template/base.html: ต่างจากใน tutorial นิดๆ ที่มี flash message ด้วย

## -*- coding: utf-8 -*-





    ${self.title()}
    ${self.head()}


    ${self.header()}
    ${self.tabs()}
    ${self.menu()}
    ${self.heading()}
    ${self.breadcrumbs()}
    ${self.flash_messages()}
    ${next.body()}
    ${self.footer()}





% if messages:
<ul id="flash-messages">
    % for message in messages:
	<li>${message}</li>
% endfor</ul>
% endif

Simple1

<a name="top"></a>



<h1>${c.heading or 'No Title'}</h1>


<a href="#top">Top ^</a>

แก้ template/person/list.html:




<h1 class="main">Person List</h1>
<ul id="titles">
% for person in c.paginator:
	<li>${person.first_name} ${person.last_name}
        [${h.link_to('view', h.url_for(controller='person', action='view', id=person.id))}]
        [${h.link_to('edit', h.url_for(controller='person', action='edit', id=person.id))}]
        [${h.link_to('delete', h.url_for(controller='person', action='delete', id=person.id), onclick="return confirm('Are you sure you want to delete #" + str(person.id) + "?');")}]</li>
% endfor</ul>


<a href="${h.url_for(controller='person', action='new', id=None)}">New person</a>

${parent.footer()}

แก้ template/person/new.html:




<h1 class="main">Add person</h1>

${h.literal(person_form(action=h.url_for(controller='person', action='create')))}

แก้ template/person/new.html:




<h1 class="main">Edit person info</h1>

${h.literal(person_form(action=h.url_for(controller='person', action='save')))}



  <a href="${h.url_for(controller='person', action='list')}">All people</a>
| <a href="${h.url_for(controller='person', action='edit', id=c.person.id)}">Edit person</a>
| <a href="${h.url_for(controller='person', action='delete', id=c.person.id)}">Delete person</a>

${parent.footer()}

แก้ template/person/view.html:


View person

<h1>View person</h1>
${c.person.first_name}
${c.person.last_name}



  <a href="${h.url_for(controller='person', action='list')}">All people</a>
| <a href="${h.url_for(controller='person', action='edit', id=c.person.id)}">Edit person</a>
| <a href="${h.url_for(controller='person', action='delete', id=c.person.id)}">Delete person</a>

${parent.footer()}

สุดท้ายแล้วมั้งก็สร้าง controller แก้ controller/person.py:

import logging

from pylons import request, response, session
from pylons import tmpl_context as c
from pylons.controllers.util import abort, redirect_to

from simple1.lib.base import BaseController, render
import simple1.model as model
from simple1.lib import helpers as h

log = logging.getLogger(__name__)

from simple1.widgets.person import person_form
from tw.mods.pylonshf import validate
from webhelpers import paginate

import elixir

from formencode import htmlfill

def get_person(id):
    if id is None:
        abort(404)
    person = model.Person.query.get(int(id))
    if person is None:
        abort(404)
    return person

class PersonController(BaseController):

    def index(self):
        return redirect_to(action='list')

    def list(self):
        page_number = request.params.get('page', 1)
        people = model.Person.query.all()
        c.paginator = paginate.Page(people, page = page_number)
        return render('/person/list.html')

    def new(self):
        return render('/person/new.html')

    @validate(form=person_form, error_handler='new')
    def create(self):
        if request.method != 'POST':
            about(500)
        person = model.Person()
        for k,v in self.form_result.items():
            setattr(person, k, v)
        elixir.session.flush()
        h.flash("added person")
        return redirect_to(action='index')

    def view(self, id):
        c.person = model.Person.query.get(int(id))
        return render('/person/view.html')

    def delete(self, id=None):
        person = get_person(id)
        person.delete()
        elixir.session.flush()
        h.flash("deleted person")
        #referer = request.headers.get('REFERER', '/')
        #return redirect_to(referer)
        return redirect_to(action='list')

    def edit(self, id=None):
        person = get_person(id)
        return htmlfill.render(render('/person/edit.html'), person.to_dict())

    @validate(form=person_form, error_handler='edit')
    def save(self, id=None):
        person = get_person(id)
        for k,v in self.form_result.items():
            setattr(person, k, v)
        elixir.session.flush()
        h.flash("edited person")
        return redirect_to(action='view')

ดูอนาถๆ กลับมาอ่านทีหลังไม่รู้รู้เรื่องเปล่า

อ้างอิงเพิ่ม:
http://toscawidgets.org/documentation/tw.forms/tutorials/pylons_one.html

https://www.knowledgetap.com/hg/webhelpers/file/2eb9f423aafb/webhelpers/pylonslib.py

0

Pylons-20080818 + Elixir: Deletion action

ใน Pylonsbook มีตัวอย่างเขียน code สำหรับ delete ไว้เรียบร้อยแล้ว http://pylonsbook.com/alpha1/simplesite_tutorial แต่ว่าผมก็อยากจะดัดแปลงนิดหน่อย เพราะใช้ Elixir แล้วก็อยากได้ dialog เพื่อยืนยันก่อนลบด้วย

จากตอนเดิมที่ใช้ Pagination list item ต่างๆ ออกมาได้แล้วก็แก้ code นิดหน่อยให้คล้ายกับ CakePHP เพื่อที่จะได้ลบ item ต่อไป

แก้ template ใน person/index.html


...

% for item in c.corpora_paginator:
<tr>
<td>${item.id}</td>
<td>${item.name}</td>
<td>

    <!-- เพิ่มส่วน delete เข้ามา -->

    <a href="${h.url_for(controller='person',
                             action='delete',
                             id=item.id)}">[delete]</a></td>
</tr><tr>
% endfor

...

แล้วก็ไปเพิ่ม action ใน controller: person.py


    def delete(self, id):
        if id is None:
            about(404)
        corpus = model.Corpus.query.get(id)
        if corpus is None:
            abort(404)
        elixir.session.delete(corpus)
        elixir.session.flush()
        h.flash("corpus #%s deleted" % id)
        return redirect_to(action='index')

ป.ล. ตัวอย่างเขียนคร่าวๆ นะครับ อาจจะผิดบ้าง copy & paste เลยก็อาจจะเจ๊ง ขออภัยด้วย

0

pylons-20080818 กับ pagination

ทีแรกหลงไปใช้ webhelpers.pagination.paginate อยู่นาน ที่ไหนได้มัน deprecated ไปแล้ว  http://pylonshq.com/project/pylonshq/ticket/504 เรื่องนี้สอนให้รู้ว่า search ไปเรื่อยเปื่อยบางก็จะอ้อมได้. ถ้าคิดอะไรไม่ออกถาม pylonsbook ก่อนดีกว่า เช่น site:http://pylonsbook.com paginate เท่านี้ก็สบายขึ้นเยอะแล้ว.

มาพบหน้านี้ http://pylonsbook.com/alpha1/simplesite_tutorial ทางสว่างเลย ผมใช้ elixir ก็ต่างออกไปนิดๆ. วิธีใช้มีดังนี้

แก้ lib/helpers.py โดยเพิ่ม from webhelpers import paginate เข้าไป

หลังจากนั้นก็เขียน controller


...

    def index():

        page_number = request.params.get('page', 1) # อันนี้จริงก็ลอกตัวอย่างมา เดาว่าถ้าไม่มีค่าส่งมาทาง get ก็ให้ใส่ 1 ไปแทน

        c.paginator = h.paginate.Page(model.Person.query.all(), page_nr=page_number)

        return render('/index.mako')

...

แล้วก็แก้ template


...
<div>
<table>

% for i in c.paginator:
<tr>
<td>${i.id}</td>
<td>${i.name}</td>
</tr>
% endfor _</table>
${c.paginator.pager('~2~')}</div>
...

อาจจะมีมั่วๆ บ้าง ไม่ค่อยได้ check นะ ขออภัยทุกท่าน และขออภัยผมในอนาคตด้วย

0

ข้อควรระวัง ในการใช้ Elixir

ชีวิตคนเรามีบางทีก็มีอะไรสำคัญกว่าแก้ script-fu … จริงๆ อาจจะไม่ใช่ข้อควรระวังสำหรับใครๆ แต่ผมต้องระวัง เพราะผมงง.

Elixir มันดูเป็น declarative มากๆ แต่ทว่า อย่าลืมประกาศ Session และ elixir.session = อะไรสักอย่าง ก่อนประกาศ Entity ไม่งั้นมันจะเจ๊งแบบงงๆ

1

Pylons + Elixir

I use Pylons and Elixir from trunk (both). Paster create -t pylons … generate most of code for SQLAlchemy so in order to use Elixir, I just made small modification following http://cleverdevil.org/computing/68/ and http://elixir.ematia.de/trac/wiki/TutorialDivingIn. In the example, meta.py has not been modified but I try to modify meta.py too. Also I put a lot of thing in function “init_model”. I don’t know whether these will casue problems … but as least, it works with a small example :-P.

In my example, the project name is “mydb4″. Firstly, I modified mydb4/model/meta.py:

 engine = None 

Secondly, mydb4/model/__init__.py was modfied. In this file, I put also the class Person, which declares the table in the database.

import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import scoped_session, sessionmaker
import meta
import elixir

from unlalign.model import meta

sm = orm.sessionmaker(autoflush=True, autocommit=True)
meta.Session = orm.scoped_session(sm)
elixir.session = meta.Session
elixir.options_defaults.update({ 'shortnames': True })

def init_model(engine):
    meta.engine = engine
    elixir.metadata.bind = engine

from elixir import *

class Person(Entity):
    name = Field(String(100))

elixir.setup_all()

mydb4/websetup.py has been edited also:

"""Setup the mydb4 application"""
import logging

from mydb4.config.environment import load_environment

log = logging.getLogger(__name__)

def setup_app(command, conf, vars):
    """Place any commands to setup mydb4 here"""
    load_environment(conf.global_conf, conf.local_conf)

    # Load the models
    from elixir import metadata
    from mydb4.model import meta

    metadata.bind = meta.engine

    # Create the tables if they aren't there already
    metadata.create_all(checkfirst=True)

The controller (mydb4/controller/person.py) looks like below:

import logging

from pylons import request, response, session
from pylons import tmpl_context as c
from pylons.controllers.util import abort, redirect_to

from mydb4.lib.base import BaseController
import mydb4.model as model

log = logging.getLogger(__name__)

class PersonController(BaseController):

    def index(self):
        # Return a rendered template
        #   return render('/template.mako')
        # or, Return a response
        return ",".join([i.name for i in model.Person.query.all()])

We have to modify also mydb4/lib/base.py

"""The base Controller API

Provides the BaseController class for subclassing.
"""
from pylons.controllers import WSGIController
from pylons.templating import render_mako as render

from mydb4.model import meta
import elixir

class BaseController(WSGIController):

    def __call__(self, environ, start_response):
        """Invoke the Controller"""
        # WSGIController.__call__ dispatches to the Controller method
        # the request is routed to. This routing information is
        # available in environ['pylons.routes_dict']
        try:
            return WSGIController.__call__(self, environ, start_response)
        finally:
            elixir.session.remove()

At the shell, command “paster setup-app development.ini” was called, then it created database correctly. After database was created, data are insert into table manually by “sqlite3 development.db” … then

insert into mydb4_model_person(id,name) values (1, "titi");
insert into mydb4_model_person(id,name) values (2, "tata");

After running “paster serve –reload development.ini”, then visit the url “http://localhost:5000/person/index&#8221;, it looks like this screen capture below: