Quick Start for Developers
Start here
สร้าง Table - Database
เป็นหน้าที่แสดงแค่ table อย่างเดียวโดยมีรูปร่างหน้าตาดังนี้
<รูปหน้าที่มีแต่ตาราง(image1)>
โดยไฟล์ที่จำเป็นมีดังนี้
Model.py
กำหนดรูปแบบของ database
class Anime(CuneiModel):
id = AutoIncrementField()
stid = IntegerField()
code = CharField(default="")
name = CharField(default="")
type = CharField(default="")
Publisher = CharField(default="")
writer = CharField(default="", null=True)
class Meta:
database = SqliteExtDatabase(None)
ฟังก์ชั่นการใช้งานและชนิดช้อมูล
- คำสั่งในการใช้งาน
'AutoIncrementField()': กำหนดให้มีรันตัวเลขอัตโนมัติ'null': กำหนดให้databaseสามารถเว้นว่างได้'SqliteExtDatabase': เปิดdatabase
- กำหนดชนิดของข้อมูล
'IntegerField()': ตัวแปรชนิดตัวเลขที่เป็นจำนวนเต็ม'FloatField()': ตัวแปรชนิดตัวเลขที่จุดทศนิยม'CharField()': ตัวแปรชนิดที่เป็นตัวอักษร'TextField()': ตัวแปรชนิดที่เป็นตัวอักษรและช่องข้อความขยายได้'DateField()': ?
Table.py
class Anime_table(CuneiTable):
MASTER_colsizes = [1,3,10,10,0,7,7,7]
MASTER_colnames = ["stid", "code", "name", "type", "publisher_stid",
"publisher_code", "publisher_name", "writer"]
MASTER_coltypes = ["int", "str", "str", "sel", "int", "str", "str", "str"]
MASTER_choicecols = ["type"]
MASTER_choices = [[("Comedy", "Comedy"), ("Action", "Action"),
("Fiction", "Fiction"), ("Fantasy", "Fantasy"),
("Adventure", "Adventure")]]
MASTER_schfill = {"publisher_code":["ModalPublisher:code",
"publisher_stid:stid,publisher_name:name"]}
class SubForm(FlaskForm, CuneiSubForm):
stid = StringField("")
code = StringField("", validators=[InputRequired("Required!")])
name = StringField("", validators=[InputRequired("Required!")])
type = SelectField("")
publisher_stid = StringField("Publisher StID",
validators=[InputRequired(lazy_gettext("Required!"))],
render_kw={"readonly":""})
publisher_code = StringField("Publisher Code",
validators=[InputRequired(lazy_gettext("Required!"))])
publisher_name = StringField("Publisher Name", render_kw={"readonly":""})
writer = StringField("", validators=[InputRequired("Required!")])
def validate_writer(self, writer):
if len(writer.data) <= 1:
raise ValidationError(lazy_gettext("Very short"))
ฟังก์ชั่นการใช้งานและตัวแปร
- คำสั่งในการใช้งาน
'MASTER_colsizes': ความกว้างของช่องข้อมมูล(ทุกคอลัมน์ที่อยู่ในdatabaseต้องมีช่องข้อมูลเป็นของตัวเองทั้งหมด แต่สามารถใส่ค่าเป็น 0 ได้ หากไม่อยากให้ผู้ใช้เห็น)'MASTER_colnames': หัวคอลัมน์ที่ผู้ใช้เห็น(ไม่จำเป็นต้องตรงกับที่อยู่ในdatabase)'MASTER_coltypes': ชนิดของช่องข้อมูล'MASTER_choicecols': กำหนดช่องข้อมูลที่จะทำเป็นDrop Drown'MASTER_choices': ข้อมูลที่อยู่ใน Choices'MASTER_schfill': กำหนดช่องข้อมูลที่จะใช้ค้นหาโดยการขึ้นหน้าต่างใหม่'**validators': สร้างเงื่อนไขให้กับช่องเช่น วันที่ที่กรอกต้อง ไม่น้อยกว่าหรือมากกว่าเท่านี้นะ'**InputRequired': แจ้งErrorหากไม่ผ่านเงือนไขที่ให้'lazy_gettext': แปรช่องนี้ให้เป็นภาษาที่มนุยษ์เข้าใจ'class SubForm': ไปดูที่ Form
HTML.py
มีส่วนประกอบที่สำคัญดังนี้
{% import "cunei_forms.html" as CuneiForms with context %}
{% import "cunei_tables.html" as CuneiTables with context %}
{% import "cunei_modals.html" as CuneiModals with context %}
{% extends "layout.html" %}
{% block content %}
{{ CuneiTables.general_table_block(table, block_height) }}
{{ CuneiTables.general_table_tail(table) }}
{% endblock content %}
{% block jvs %}
{% endblock jvs %}
'block content': ส่วนที่แสดงเนื้อหา ในที่นี้เนื้อหาจะเป็นการแสดง Table'block jvs': เป็นส่วนที่การแสดงเนื้อหาjavascript
routes.py
วิธีตรง
@cunei_pos.route("/anime", methods=["GET", "POST"])
def anime():
table = Anime_Table(prefix="anime_table", perm_bit=4, editable=True,
populate_route=url_for("cunei_pos.test_table", fetch="yes"),
post_route=url_for("cunei_pos.test_table", is_submit="yes"))
publisher_tb = Publisher_Table(prefix="PublisherTable", searchable=True, editable=True,
populate_route=url_for("cunei_pos.publisher", fetch="yes"),
post_route=url_for("cunei_pos.publisher", is_submit="yes"),
in_modal="ModalPublisher", modal_head=lazy_gettext("Choose Publisher"),
perm_bit=4)
table.sch_tbs = [publisher_tb]
dbpath = path.join(session["company"]["datadir"], "table", "anime.db")
db = SqliteExtDatabase(dbpath, pragmas={"journal_mode":"wal"})
if request.args.get("fetch", False) == "yes":
if request.form.get("mode") == "qsch":
return jsonify({"data":['code':xxxxx, 'name':xxxx], "qs_status":"success"})
if request.form.get("mode") == "ncol":
return jsonify({"data":['code':xxxxx, 'name':xxxx]})
if request.form.get("mode") == "sel":
return jsonify({"data":['code':xxxxx, 'name':xxxx]})
if request.args.get("is_submit", False) == "yes":
dbpath = path.join(session["company"]["datadir"], "table", "publisher.db")
db = SqliteExtDatabase(dbpath, pragmas={"journal_mode":"wal"})
model_fields = Publisher._meta.fields.keys()
vals = {cn:getattr(table.form, cn).data for cn in table.cnames if cn in model_fields}
vals.pop("id", None)
isid = int(table.form.id.data)
isdel = table.form.is_del.data
with db.bind_ctx([Publisher]):
if isdel: // delete case
qry = Publisher.delete().where(Publisher.id==isid).execute()
return jsonify(dict(data="success", flash_messages=[], entry="deleted"))
elif isid == -1: // add case
new_entry = Publisher.create(**vals)
else: // edit case
try:
new_entry = Publisher.select().where(Publisher.id==isid)
except DoesNotExist:
new_entry = Publisher.create(**vals)
else:
for k,v in vals.items():
setattr(new_entry, k, v)
new_entry.save()
return_entry = [getattr(new_entry, cn, None) for cn in table.cnames]
return jsonify(dict(data="success", flash_messages=[], entry=return_entry))
else:
return jsonify(dict(err=table.form.errors, flash_messages=[]))
return render_template("cunei_pos/anime_table.html", table=table)
- ตัวแปร
'prefix': ชื่อที่ใช้ตั้งเป็นIDของTable (ต้องตั้งชื่อไฟล์HTMLเป็นชื่อนี้ด้วย)'perm_bit': สิทธิ์ในการเข้าถึงหน้านี้- - 4 สามารถ add/edit/delete
- - 3 สามารถ add/edit
- - 2 สามารถ add
- - 1 สามารถ look only
'searchable': สถานะการปล่อยให้มีการค้นหาในtableนี้'editable': สถานะการเปิดให้มีการแก้ไข้ตารางนี้'populate_route': เปิดให้มีการrefetchหน้าใหม่- - argumentี่ท1: สดงถึงหน้าที่ต้องการให้มีการrefetch
- - argumentที่2: เป็นคำสั่งยืนยันการrefetch
'post_route': เรียกใช้mode Submit บันทึกลงdatabase- - argumentี่ท1: แสดงถึงหน้าที่ต้องการให้มีการบันทึก
- - argumentี่ท2: เป็นคำสั่งยืนยันการบันทึก
'in_modal': Tableนี้ใช้โครงสร้างdatabaseตัวไหน'modal_head': ชื่อที่จะอยู่บนหัวpopup'table.form': เป็น Obj form ที่เก็บ field ทั้งหมดในหน้านี้ไว้ และใน field ก็จะเก็ยdataอีกที'getattr(x,y).data': ดึงข้อมูลจาก Obj มาใช้'setattr': วิธีบันทึกของลงในObj'table.cnames': เก็บชื่อคอลัมน์ที่มีทั้งหมดของdatabase + 'submit'(สถานะการบันทึก), 'del_submit'(เป็นปุ่มที่เมื่อกดมันจะไปบังคับติ๊๊กถูกที่is_delอีกที)และ 'is_del'(สภานะการลบ)
- ขยายcode
- เป็นการเพิ่มTableอื่นเข้ามาใช้งานร่วมกัน
publisher_tb = ...
- sectionทำงานอยู่ 2 แบบคือ
request.arge.get()
- - fetch : จะทำงานทุกครั้งที่มีการreload หน้า(ทุกการreloadจะมีการดึงข้อมูลใหม่ทุกครั้ง)
- - is_submit : จะทำงานก็ต่อเมื่อมีการบึนทึกของลงdatabase
- โหมดในการทำงานมีทั้งหมด 4 โหมด คือ
if request.form.get("mode")
- - sch : โหมดการค้นหาข้อมูล กรณีกดปุ่มค้นหา สามารถreturnได้ 2 แบบ
- -
returnjsonify({"data":['code':xxxxx, 'name':xxxx], "qs_status":"success"}) คือ returnผลลัพธ์ที่มีตัวเดียว - -
returnjsonify({"data":[['code':xxxxx, 'name':xxxx],...]}) คือ returnผลลัพธ์ที่มีหลายตัว
- -
- - ncol : โหมดใส่ค่าเริ่มต้นตามdatabaseนั้นๆ
- -
returnjsonify({"data":['code':xxxxx, 'name':xxxx]}) คือ returnผลลัพธ์ที่มี
- -
- - sel : โหมดการเลือกบรรทัด
- -
returnjsonify({"data":['code':xxxxx, 'name':xxxx]}) คือ returnผลลัพธ์ที่มี
- -
- - grab : โหมดการค้นหาข้อมูล กรณีที่co-database นำstidไปหาค่า
- -
returnjsonify({"data":['code':xxxxx, 'name':xxxx]}) คือ returnผลลัพธ์ที่มี
- -
- - sch : โหมดการค้นหาข้อมูล กรณีกดปุ่มค้นหา สามารถreturnได้ 2 แบบ
วิธีลัด
@cunei_pos.route("/anime", methods=["GET", "POST"])
def anime():
table = Anime_Table(prefix="test_table", suppress_copy=False, editable=True)
table.sch_tbs = [Publisher_Table(prefix="PublisherTable", perm_bit=4, editable=True,
populate_route=url_for("cunei_pos.publisher", fetch="yes"),
post_route=url_for("cunei_pos.publisher", is_submit="yes"),
in_modal="ModalPublisher", modal_head=lazy_gettext("Choose Publisher"))]
dbtype, tbname = "table", "anime"
model, module = Anime, "cunei_pos"
new_default = get_default_anime
fetch_all = anime_grab_all
return single_tb_page(dbtype, tbname, model, module, table, new_default, fetch_all, request, forced_perm=4, in_modal="ModalPublisher", modal_head=lazy_gettext("Choose Publisher"))]
วิธีนี้จะช่วยลดขั้นตอนต่างๆให้สั้นลง แต่ขั้นตอนไม่ได้หายไปไหนเพียงแค่ย่อมันให้สั้นลงเท่านั้น
table = Anime_Table(...)
Seach Publisher_Table จะถูกเขียนลงไปใน table.sch_tbs และเพิ่ม argument fetch="yes" และ fetch="yes"เข้าไป
new_default = get_default_anime
เป็นfunction Obj ที่จะคืนผล default กลับมา โดยปกติแล้วจะเขียนฟังชั่นก์นี้ในไฟล์ functions.py
def get_default_anime(): return dict(stid=get_next_stid("Anime", Anime), id=-1)
fetch_all = anime_grab_all
เป็นfunction Obj ที่จะคืนผลการselect โดยปกติแล้วจะเขียนฟังชั่นก์นี้ในไฟล์ functions.py
def anime_grab_all(qsch_spec={}): tb_db = get_db("table", "anime", auto_create=[Anime]) crits = get_where(Anime, qsch_spec) with tb_db.bind_ctx([Anime]): if len(crits) == 0: return Anime.select().order_by(fn.LOWER(Anime.code)) else: return Anime.select().order_by(fn.LOWER(Anime.code)).where(*crits)
สร้าง Form - non Database
เป็นหน้าที่แสดง Form อย่างเดียว โดยไม่มีการบันทึกของลงdatabase <image2> โดยต้องเตรียมไฟล์ดังนี้ class Foreigner_Form(FlaskForm, CuneiForm):
id = StringField("ID")
passport = StringField(lazy_gettext("Passport"),
validators=[Length(8,8,lazy_gettext("Wrong passport number"))])
first_name = StringField(lazy_gettext("First Name"),
validators=[InputRequired(lazy_gettext("Who the fack are you"))])
middle_name = StringField(lazy_gettext("Middle Name"))
last_name = StringField(lazy_gettext("Last Name"))
birthday = StringField(lazy_gettext("Birthday"),
validators=[InputRequired(lazy_gettext("Birthday la?"))])
saving = StringField(lazy_gettext("Saving"))
gender = RadioField(lazy_gettext("Gender"),
choices=[("male", lazy_gettext("Male")),
("female", lazy_gettext("Female")),
("other", lazy_gettext("Other"))],
default="male")
status = RadioField(lazy_gettext("status"),
choices=[("single", lazy_gettext("Single")),
("widow", lazy_gettext("Widowed")),
("married", lazy_gettext("Married"))],
default="single")
spouse_first_name = StringField(lazy_gettext("Spouse first name"))
spouse_middle_name = StringField(lazy_gettext("Spouse middle name"))
spouse_last_name = StringField(lazy_gettext("Spouse last name"))
address = TextAreaField(lazy_gettext("Address"))
pict = FileField(lazy_gettext("Photo"))
pict_isdel = BoolField("Pict: isdel", render_kw={"readonly":""})
submit = SubmitField("Submit")
is_del = BoolField("is_del")
del_submit = SubmitField("Delete")
MASTER_sptypes = [("id", "int"), ("birthday", "date"), ("saving", "amt"),
("pict", ["img", "table", "foreigner"])]
def validate_spouse_first_name(self, spouse_first_name):
if self.status.data == "married" and spouse_first_name.data == "":
raise ValidationError(lazy_gettext("What is your spouse's name"))
def validate_saving(self, saving):
try:
test_money = float(saving.data)
except (TypeError, ValueError):
test_money = 0.0
if test_money < 80000.0:
raise ValidationError(lazy_gettext("Eww! Peasant!"))
def validate_birthday(self, birthday):
bth = date_from_dbstr(birthday.data)
if bth > datetime.now():
raise ValidationError(lazy_gettext("You are embryo"))
if datetime.now().year - bth.year >150:
raise ValidationError(lazy_gettext("You're really old"))