Quick Start for Developers

จาก คูนิฟ็อกซ์ วิกิ

Start here

สร้าง Table - Database

เป็นหน้าที่ทั้งหน้ามี table อย่างเดียวดังในภาพ

<image1>

  • สิ่งของที่จำเป็นในการสร้างมี
    • สร้างดาต้าเบส: จะถูกเขียนอยู่ใน Model.py
    • สร้างองค์ประกอบหน้าตาราง(ตารางต้องมีรูปร่างหน้าตาอย่างไร ต้องโชว์คอลัมน์อะไรบ้าง): จะถูกเขียนอยู่ใน Teble.py
    • สร้างหน้าHTML: จะถูกเขียนอยู่ใน HTML.py
    • สร้างท่องที่เชื่อมระหว่าง UX-UI เราจะเรียกท่อนนี้ว่า rount: จะถูกเขียนอยู่ใน rounts.py

โดยไฟล์ที่จำเป็นมีดังนี้

Model.py

Model.py จะเป็นตัวกำหนดโครงสร้างดาต้าเบส โดยชื่อคาสจะเอาไปตั้งเป็นชื่อตารางดาต้าเบส และตัวแปรแต่ละตัวเปรียบเสมือนfield ที่จะต้องกำหนดชนิดตัวแปรเข้าไป (สามารถไปดูวิธีการสร้างModelแบบละเอียดได้ที่cunei#...)

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

ออกแบบรูปร่างหน้าตาตัวตาราง เพื่อที่จะเอาข้อมูลที่สร้างไว้ในดาตาเบสมาลง ทั้งนี้สามารถกำหนดความกว้างคอลัมน์เป็น0ได้ หากไม่ต้องการให้ผู้ใช้เห็น และสามารถกำหนดคอลัมน์เพิ่มเติมจากAttributeที่มีอยู่ในดาต้าเบสได้ (Attributeที่มีอยู่ในดาต้าเบสต้องนำมาสร้างใส่คอลัมน์ให้หมด และสามารถสร้างคอลัมน์ในตารางเพิ่มนอกเหนือจากAttributeที่มีอยู่ในดาตาเบสได้)

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"))

ฟังก์ชั่นการใช้งานและตัวแปร

  • คำสั่งในการใช้งาน สามารถเข้าไปดูวิธีสร้างตารางแบบละเอียดได้ที่ CuneiTable#Define
    • 'MASTER_colsizes': ความกว้างของช่องข้อมมูล(ทุกคอลัมน์ที่อยู่ในdatabaseต้องมีช่องข้อมูลเป็นของตัวเองทั้งหมด แต่สามารถใส่ค่าเป็น 0 ได้ หากไม่อยากให้ผู้ใช้เห็น)
    • 'MASTER_colnames': หัวคอลัมน์ที่ผู้ใช้เห็น(ไม่จำเป็นต้องตรงกับที่อยู่ในdatabase)
    • 'MASTER_coltypes': ชนิดของช่องข้อมูล
    • 'MASTER_choicecols': กำหนดช่องข้อมูลที่จะทำเป็นDrop Drown
    • 'MASTER_choices': ข้อมูลที่อยู่ใน Choices
    • 'MASTER_schfill': กำหนดช่องข้อมูลที่จะใช้ค้นหาโดยการขึ้นหน้าต่างใหม่
    • 'lazy_gettext': แปรช่องนี้ให้เป็นภาษาที่มนุยษ์เข้าใจ
    • 'class SubForm': เป็นเรื่องของ Form กรุณาไปดูที่ CuneiForm#Define

rounts.py

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/endblock content': เปิด/ปิดส่วนที่แสดงเนื้อหา ในที่นี้เนื้อหาจะเป็นการแสดง Table
    • {{ CuneiTables.general_table_block(table, block_height) }}
      {{ CuneiTables.general_table_tail(table) }}
      
      เป็นวิธีสร้าง Table แบบมารตราฐาน
  • 'block jvs/endblock 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
    • publisher_tb = ...
      
      เป็นการเพิ่มTableอื่นเข้ามาใช้งานร่วมกัน
    • request.arge.get()
      
      sectionทำงานอยู่ 2 แบบคือ
      - fetch : จะทำงานทุกครั้งที่มีการreload หน้า(ทุกการreloadจะมีการดึงข้อมูลใหม่ทุกครั้ง)
      - is_submit : จะทำงานก็ต่อเมื่อมีการบึนทึกของลงdatabase
    • if request.form.get("mode")
      
      โหมดในการทำงานมีทั้งหมด 4 โหมด คือ
      - sch : โหมดการค้นหาข้อมูล กรณีกดปุ่มค้นหา สามารถreturnได้ 2 แบบ
      - return jsonify({"data":['code':xxxxx, 'name':xxxx], "qs_status":"success"}) คือ returnผลลัพธ์ที่มีตัวเดียว
      - return jsonify({"data":[['code':xxxxx, 'name':xxxx],...]}) คือ returnผลลัพธ์ที่มีหลายตัว
      - ncol : โหมดใส่ค่าเริ่มต้นตามdatabaseนั้นๆ
      - return jsonify({"data":['code':xxxxx, 'name':xxxx]}) คือ returnผลลัพธ์ที่มี
      - sel : โหมดการเลือกบรรทัด
      - return jsonify({"data":['code':xxxxx, 'name':xxxx]}) คือ returnผลลัพธ์ที่มี
      - grab : โหมดการค้นหาข้อมูล กรณีที่co-database นำstidไปหาค่า
      - return jsonify({"data":['code':xxxxx, 'name':xxxx]}) คือ returnผลลัพธ์ที่มี

วิธีลัด

@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> โดยต้องเตรียมไฟล์ดังนี้

Form.py

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"))

สร้าง filedที่ ต้องการใช้ในหน้า Form นี้ขึ้นมา พร้อมทั้งกำหนดชนิดของตัวแปร

ฟังก์ชั่นการใช้งานและชนิดช้อมูล

  • คำสั่งในการใช้งาน
    • 'lazy_gettext': แปรช่องนี้ให้เป็นภาษาที่มนุยษ์เข้าใจ
    • 'validators': สร้างเงื่อนไขให้กับช่องเช่น วันที่ที่กรอกต้อง ไม่น้อยกว่าหรือมากกว่าเท่านี้นะ
    • 'InputRequired': แจ้งErrorหากไม่ผ่านเงือนไขที่ให้
  • ตัวแปรและชนิดข้อมูล
    • 'StringField()': ตัวแปรชนิดที่เป็นตัวอักษร
    • 'TextField()': ตัวแปรชนิดที่เป็นตัวอักษรและช่องข้อความขยายได้
    • 'IntegerField()': ตัวแปรชนิดตัวเลข
    • 'FloatField()': ตัวแปรชนิดตัวเลขที่จุดทศนิยม
    • 'BoolField()': ตัวแปรชนิดTrue/False
    • 'RadioField()': ตัวแปรชนิดติ๊กถูก
      • 'choices': ตัวเลือกของ RadioField
      • 'default': ค่าเริ่มต้นของ RadioField
    • 'FileField': ตัวแปรชนิดไฟล์
    • 'SubmitField': ตัวแปรสร้างปุ่มของ Form นั้นๆ
  • ขยาย Code
    •     pict = FileField(lazy_gettext("Photo"))
          pict_isdel = BoolField("Pict: isdel", render_kw={"readonly":""})
      
      เรื่องการใส่รูปกรุณาไปดูรายละเอียดได้ที่ CuneiForm#File_&_Image_Fields
    •     submit = SubmitField("Submit")
          del_submit = SubmitField("Delete")
          is_del = BoolField("is_del")
      
      เป็นการสร้างปุ่ม Submit และ Delete ส่วน is_del เป็น check box ที่ไม่ได้เปิดให้เห็น ทุกครั้งที่กดปุ่ม Delete check box จะเปลี่ยนเป็น true โดยทันที ระบบข้างในจะทำงานกับ check boxตัวนี้แทนปุ่ม Delete เพราะว่าปุ่ม Submit และปุ่ม Delete ไม่สามารถใช้งานภายในFormเดียวกันได้ จึงต้องมีcheck boxมารับค่าแทน
    • MASTER_sptypes = [("id", "int"), ("birthday", "date"), ("saving", "amt"), 
                              ("pict", ["img", "table", "foreigner"])]
      
      แปลงชนิดตัวแปรก่อนเอาเข้า database, จาก field ที่โชว์อยู่บนหน้าจอ field พวกนี้จะเป็นตัวแปรชนิด String ทั้งหมด เพราะอย่างนั้นเวลาจะบันทึกเข้า database จึงต้องแปลงชนิดตัวแปรสะก่อน
      • ชนิดตัวแปรที่เป็นตัวเลข
        • int: เลขจำนวนเต็ม
        • amt: ตัวเลขระบุที่เกี่ยวกับยอดเงิน
        • acc: ตัวเลขที่ไว้ลงบัญชี
        • pct: จำนวนที่เป็นพวกร้อนละ/เปอร์เซ็น
        • qty: ตัวเลขที่เกี่ยวกับจำนวน/หน่วย

สามารถไปอ่านเพิ่มเติมได้ที่ CuneiForm#Available_Types

    •     def validate_spouse_first_name(...)
          def validate_saving(...)
          def validate_birthday(...)
      
      เป็น function สร้างเงื่อนไขให้กับ field เช่น วันที่ที่กรอกต้องไม่น้อยกว่าหรือมากกว่าเท่านี้นะ โดยวิธีการตั้งชื่อให้ใส่ validate ไว้ข้างหน้าแล้วตามชื่อ _ชื่อ field ที่ต้องการสร้างเงื่อนไขให้ แล้วส่ง argument selfและชื่อfieldมาด้วย

HTML.py

{% import "cunei_forms.html" as CuneiForms %}
{% import "cunei_tables.html" as CuneiTables with context %}
{% import "cunei_modals.html" as CuneiModals with context %}
{% import "cunei_scripts.html" as CuneiScripts %}

{% extends "greeter_slim.html" %}

{% block content_wgreeter %}
<div class="col-sm-11">
    <div class="card border-dark mb-3" style="max-width:100%;">
        <div class="card-body py-0">
            {{ CuneiForms.general_form_head(form, is_master=true) }}
            <div class="row align-items-start pt-3">
                <div class="col-lg-2">
                    <div class="row align-items-start">
                        <div class="col"><img id="{{ form.pict.id }}_imgbox" 
                            style="width:100%;"></div>
                    </div>
                    <div class="row align-items-start">
                        {{ CuneiForms.render_check(form, form.pict_isdel, hidden=true) }}
                        <div class="col">{{ CuneiForms.render_upload(form, form.pict) }}</div>
                    </div>
                </div>
                <div class="col-lg">
                    <div class="row align-items-start">
                        {{ CuneiForms.render_blank(form, form.id, hidden=false) }}
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.passport) }}</div>
                    </div>
                    <div class="row align-items-start">
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.first_name) }}</div>
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.middle_name) }}</div>
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.last_name) }}</div>
                    </div>
                    <div class="row align-items-start">
                        <div class="col-4">{{ CuneiForms.render_blank(form, 
                            form.birthday) }}</div>
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.saving) }}</div>
                    </div>
                    <div class="row align-items-start">
                        <div class="col-5">{{ CuneiForms.render_radio(form, form.gender, 
                            col=3, force_slim=true, label_width="105px") }}</div>
                    </div>
                    <div class="row align-items-start">
                        <div class="col-5">{{ CuneiForms.render_radio(form, 
                            form.status, col=3, force_slim=true) }}</div>
                    </div>
                    <div class="row align-items-start">
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.spouse_first_name) }}</div>
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.spouse_middle_name) }}</div>
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.spouse_last_name) }}</div>
                    </div>
                    <div class="row align-items-start">
                        <div class="col">{{ CuneiForms.render_blank(form, 
                            form.address) }}</div>
                    </div>
                    <div class="row align-items-start">
                        <div class="col-0 text-right">{{ CuneiForms.render_check
                            (form, form.is_del, hidden=true) }}</div>
                        <div class="col-0 text-right">{{ CuneiForms.render_submit
                            (form, form.del_submit, class="danger") }}</div>
                        <div class="col-0 text-right">{{ CuneiForms.render_submit
                            (form, form.submit) }}</div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

{{ CuneiForms.general_form_tail(form, true, pack_del=true) }}

{% endblock content_wgreeter %}

{% block greeter_jvs %}
{% endblock greeter_jvs %}


  • ขยายCode
    • {{ CuneiForms.general_form_head(form, is_master=true) }}
      .
      .
      .
      {{ CuneiForms.general_form_tail(form, true, pack_del=true) }}
      
      เปิด/ปิดส่วนของform ทึกข่องข้อมูลของฟอร์มต้องอยู่ระหว่าง 2 บรรทัดนี้
    • {{ CuneiForms.render_upload(form, form.pict) }}
      

Rount.py

@cunei_pos.route("/foreigner", methods=["GET", "POST"])
def foreigner():
    form = Foreigner_Form.init_form(prefix="foreigner_form", 
                populate_route=url_for("cunei_pos.foreigner_form", fetch="yes"), 
                populate_id=["passport"],
                post_route=url_for("cunei_pos.foreigner_form", issubmit="yes"))
    if request.args.get("issubmit", False) == "yes":
        msg = ""
        flashers = []
        if form.validate_on_submit():
            gender = "Woman" if new_entry.gender == "female" else "Man"
            msg = "welcome to Thailand"+ new_entry.first_name + gender
            sh = [msg, "success"]
            data = "success"
        else:
            msg = "Go to jail"+ new_entry.first_name + gender
            sh = [msg, "danger"]
            data = "failure"
        flashers.append(sh)
        return jsonify(dict(data=data,err=form.errors,flash_messages=flashers))
    form.id.data = 1
    return render_template("cunei_pos/foreigner.html, form=form")

ตัวแปร

.......

เมื่อถึงตรงนี้ Form ที่คุณทำจะสามารถ

- กรอกข้อมูลเข้าได้
- จับ Error ได้

แต่ไม่สามารถ

- บันทึกข้อมูลเข้า database ได้ เพราะไม่ได้สร้าง databaseไว้
- เรียกดูข้อมูล

สร้าง Form - Database