☆☆ 新着記事 ☆☆

2019年7月26日金曜日

初めてPythonのクラスやメソッドを理解する為の説明

Pythonの基本を理解して、そろそろ小さなプログラムから脱皮したくなった人のための、Python 「クラス」、「インスタンス」や「メッソド」、また、これらの実際の「活用方法」の説明です。



◇ クラスの定義


1) クラスとは
Pythonではクラスの定義にclass文を使用します。class文を使った定義の仕方は以下のようになります。

class クラス名:

class ClassName():
 
class後にそのクラスの名前となるクラス名をつけて(大文字で始めるのが慣行)、最後に「:」を付けます。そのあとに一段落インデントを入れてクラス定義をしていきます。

class Test:
    pass  #何もしないクラスの場合はpassと記入する

2) クラス変数
クラスは、クラス内で定義されたメソッドに共有できる変数を持つことができます。
(詳細は、インスタンスと変数の後で説明)

「インスタンスを生成する(インスタンス化する)」、とは

1) インスタンスとは
Instanceとは、Classで記述した関数(メッソドという)などを実行するために生成するもの。

class Employee():
    pass
emp_1 = Employee()  #EmployeeというClassのemp_1 というインスタンス生成
emp_2 = Employee() #EmployeeというClassのemp_2 というインスタンス生成

print(emp_1)
> <__main__.Employee object at 0x0000000001E228D0>
print(emp_2)
> <__main__.Employee object at 0x0000000002856518>
Employee クラスの独立したオブジェクトがインスタンスとして、それぞれ生成された。

2) インスタンスと変数
インスタンスは、変数を属性としてもつことが出来る。 ここでは、ひとつずつ、マニュアルで属性値を与えた例を示しますが、コンストラクタを使うことで操作がもっと簡単になります。(後述)

emp_1 = Employee()
emp_1.first = 'Bruno'
emp_1.second = 'Mars'
emp_1.email = 'Bruno.Mars@test.com'
emp_1.pay = 4000

emp_2 = Employee()
emp_2.first = 'Charlie'
emp_2.second = 'Puth'
emp_2.email = 'Charlie.Puth@test.com'
emp_2.pay = 3000

print(emp_1.email)
>Bruno.Mars@test.com
print(emp_2.pay)
>3000
print('{} {}'.format(emp_1.first,emp_1.second))
>Bruno Mars


◇メソッドとは

メソッドとは、クラス内に定義された関数のことです。メソッドは定義されたクラスからしか呼び出すことが出来ないのも特徴。

class クラス名:
    def メソッド名(self):
        #メソッドの定義

メソッドは必ず1つ以上の引数を持ちます。また、引数のうち最初のものはselfという名前にすることになっています。メソッドを呼び出すには以下のようにします。

インスタンス.メソッド名()

class Test:
    def test_print(self):
        print('This is test')

test = Test()      #インスタンス生成
test.test_print()  #メソッド呼び出し
> This is test

◇ コンストラクタとデストラクタ (特殊関数)

メソッドの中でも、インスタンスが生成されるときに自動的に呼び出されるメソッドのことをコンストラクタと言います。デストラクタは、インスタンス消滅時に自動的に呼び出されるメソッドです。ただし、デストラクタは使わないことが、Python公式ドキュメントでも推奨されていないので、深堀りしない。

コンストラクタを定義するには「init」という名前のメソッドを作成します。

例)
class Test:
    def __init__(self, 引数1, 引数2, ….):
        #コンストラクタの定義

  例)
  Class Test():
         def __init__(self):
             print('Constructor is called!')
         def func(self):
              print('Function is called!')

  で、
  test_instance = Test()

  とインスタンスを生成すると、その時点で 
  print('Constructor is called!') が実行される。


例2)
インスタンスの例で、ここではコンストラクタを使用して、各インスタンスの変数に値を入れて見ます。

クラス Employee で、__init__ メソッドを使って、selfというインスタンスに、first, last,pay という引数を指定する記述が以下になります。


class Employee():
    def __init__(self, first,last,pay):
        self.first = first
        self.second = second
        self.pay = pay
        self.email = first + '.' + 'test.com'

    #Full Nameをprint outする関数を作成
    def  fullname(self):
        print('{} {}'.format(self.first, self.second))

メンバー変数を指定してみます。メンバー変数はインスタンス間をまたぎません。
emp_1 = Employee('Bruno','Mars',4000) 
emp_2 = Employee('Charlie','Puth',3000)
 

print(emp_1.email)
>Bruno.Mars@test.com
print(emp_2.pay)
>3000

♯Instanceを使って関数を呼び出し。fullname( )メソッドにemp2の属性を再記述する必要なし。

print(emp_2.fullname() #()を付けることで、属性ではなく関数を呼びだし。
>Charlie Puth

#Class名を使って関数を呼び出し。fullname( )メソッドにemp2の属性を再記述する必要あり。

print( Employee.fullname(emp_2) )
>Charlie Puth


(挙動の解説)
*コンストラクタである__init__() で、このように第一属性として self を定義すると、emp_1が読まれた際に、Employee()にパスされて、selfとして理解されて、各属性が self(emp_1) = first ,self(emp_1) = secondと解釈される。 したがって、

self.first = firstは、emp_1.first = と記述していることと同じになる。


*emp_1 や emp_2 は、Employeeというクラスで定義されているので、このEmployee()クラスで定義されているメソッド fullname()を利用することができる。

 *   def  fullname(self): に引数 selfの記述をしないで、以下のprint outを実行した場合、
print(emp_2.fullname())
TypeError: fullname() takes 0 positional arguments but 1 was given
という、「引数が1つしか与えられていない」といいうエラーになる。

それは、emp_2.fullname() で、fullname()に引数を与えていないように見えるが、実際には、emp_2というインスタンスが自動的に引数としてfullname()に渡されているので、selfで受け取る必要があるから。



クラス変数
(クラスとは、ちょっと書きましたが)クラス変数は、クラス内で記述されたメソッドで共有できる変数です。インスタンス間で同じ値を参照します。

class Employee():

    raise_amount = 1.04  # クラス変数の定義

    def __init__(self, first,last,pay):
        self.first = first
        self.second = second
        self.pay = pay
        self.email = first + '.' + 'test.com'

    def  fullname(self):
        print('{} {}'.format(self.first, self.second))

    def  apply_raise(self):
        self.pay = int (self.pay * self.raise_amount)

   #クラス変数を各メソッド内で参照する場合にも、self. や、クラス名そのもの Employee.変数
    を明記して、Employee.raise_amount と記述する必要がある。これらの記述を明示しない
    と、「raise_amountは定義されていない」というエラーになる。


emp_1 = Employee('Bruno','Mars',4000)
emp_2 = Employee('Charlie','Puth',3000)


print(emp_1.pay)
>4000
emp_1.apply_raise()
print(emp_1)
>4160

*(解説)
クラス変数を使用する際にインスタンス名、又はクラス名を明示しなければならない理由。
print(Employee.raise_amount)
>1.04
print(emp_1.raise_amount)
>1.04
print(emp_2.raise_amount)、
>1.04

クラス変数は、最初、インスタンスに引数が定義されていないかチェック。なければクラスで定義されいないかチェックする挙動となります。

この例では、
最初、emp_1 の属性を参照
print(emp_1.__dict__)
{'first': 'Bruno', 'second': 'Mars', 'pay': 4000, 'email': 'Bruno.test.com'}

raise_amountが定義されていないので、Classの属性を参照。
print(Employee.__dict__)

{'__module__': '__main__', 'raise_amount': 1.04, '__init__': <function Employee.__init__ at 0x0000000002F026A8>, 'fullname': <function Employee.fullname at 0x0000000002F02730>, 'apply_raise': <function Employee.apply_raise at 0x0000000002F027B8>, '__dict__': <attribute '__dict__' of 'Employee' objects>, '__weakref__': <attribute '__weakref__' of 'Employee' objects>, '__doc__': None}

で、変数の値を参照することができる。


コンストラクタとは逆に、インスタンスが不要になり削除される時に呼ばれるメソッドをデストラクタと言います。デストラクタは「del」という名前のメソッドで定義されます。

class Test:
    def __init__(self):
        print('コンストラクタが呼ばれました')
    def __del__(self):
        print('デストラクタが呼ばれました')

test = Test()  #インスタンスを生成
del test      #インスタンスを削除

実行結果

コンストラクタが呼ばれました
 デストラクタが呼ばれました

◇ 継承
Pythonのクラスも他のクラスを継承し、拡張することができます。クラスを継承する場合はクラス文に親となるクラスを指定します。親のクラスのメソッドを呼び出す場合はsuper()を使います。

class Test( ):
    def __init__(self, num):
        self.num = num;
    def print_num(self):
        print('引数で渡された数字は{}です。'.format(self.num))

class Test2(Test):  #Testクラスを継承
    def print_test2_info(self):
        print('このクラスはTestクラスを継承しています。')
        super().print_num()  #親クラスのprint_num()を呼び出す

test = Test2(10)
test.print_test2_info()

実行結果

このクラスはTestクラスを継承しています。
 引数で渡された数字は10です。

参考)
https://www.youtube.com/watch?v=ZDa-Z5JzLYM

2019年7月21日日曜日

Flaskで、Login ページを作ってDBを操作する(1)

このポストの範囲:

◇ 全体の流れを考える
・ ログインできるユーザを管理する。(html)
  1) ユーザ登録ページの作成
  2)  ログインページ(ログイン認証要求ページ)を作成


・必要となるFunction
    1) app.pyで、これらhtmlのルーティングを管理
  2) forms.pyで、htmlから送られてくる値のセキュリティー・チェック
  3) 送信されてきたユーザデータをDBに登録。 ログイン認証時に参照できるようにする。



作業の流れとしては、

Step1 :
 forms.pyで、各htmlに設置する「入力フィールドの定義」と送信されてくる値の「validation」の方法を定義する。 => FLASK-WTFを利用。

Step2:
各htmlに入力フィールドを記述

Step3:
DBを作成する。

◇出来上がりのCode

<forms.py> 

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo

class RegistrationForm(FlaskForm):
    username = StringField('Username',
                           validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('Email',
                        validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password',
                                     validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Sign Up')

class LoginForm(FlaskForm):
    email = StringField('Email',
                        validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember = BooleanField('Remember Me')
    submit = SubmitField('Login')


</templates/register.html>
  < form method="POST" action="">
            {{ form.hidden_tag() }}

    <legend>Join Today</legend>

    {% with messages = get_flashed_messages(with_categories=true)%}
      {% if messages %}
         {% for category, message in messages %}
        <div class="alert alert-{{ category }}">
        {{ message }}
        </div>
         {% endfor %} 
      {% endif %}
    {% endwith %}
           
                    {{ form.username.label }}
                    {% if form.username.errors %}
                        {{ form.username }}
                     
                            {% for error in form.username.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                     
                    {% else %}
                        {{ form.username }}
                    {% endif %}
                </div>
                <div class="form-group">
                    {{ form.email.label }}
                    {% if form.email.errors %}
                        {{ form.email }}
                        <div class="invalid-feedback">
                            {% for error in form.email.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.email }}
                    {% endif %}
                </div>
                <div class="form-group">
                    {{ form.password.label }}
                    {% if form.password.errors %}
                        {{ form.password }}
                        <div class="invalid-feedback">
                            {% for error in form.password.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.password }}
                    {% endif %}
                </div>
                <div class="form-group">
                    {{ form.confirm_password.label(class="form-control-label") }}
                    {% if form.confirm_password.errors %}
                        {{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
                        <div class="invalid-feedback">
                            {% for error in form.confirm_password.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.confirm_password }}
                    {% endif %}
                </div>
            </fieldset>
            <div class="form-group">
                {{ form.submit }}
            </div>
        </form>
    </div>
    <div class="border-top pt-3">
        <small class="text-muted">
            Already Have An Account? <a  href="{{ url_for('login') }}">Sign In</a>
        </small>
    </div>
           

</templates/login.html>

                 <legend>Log In</legend>


    {% with messages = get_flashed_messages(with_categories=true)%}
      {% if messages %}
         {% for category, message in messages %}
        <div class="alert alert-{{ category }}">
        {{ message }}
        </div>
         {% endfor %} 
      {% endif %}


    {% endwith %}
       

    <!-- Input email -->
                    {{ form.email.label}}
                    {% if form.email.errors %}
                        {{ form.email }}
                        <div class="invalid-feedback">
                            {% for error in form.email.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.email }}
                    {% endif %}
                </div>
               <!-- Input password -->
                    {{ form.password.label }}
                    {% if form.password.errors %}
                        {{ form.password(class="form-control form-control-lg is-invalid") }}
                        <div class="invalid-feedback">
                            {% for error in form.password.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.password }}
                    {% endif %}
                </div>
     <!-- Remember me -->
                <div class="form-check">
                    {{ form.remember }}
                    {{ form.remember.label}}
                </div>
            <div class="form-group">
                {{ form.submit }}
            </div>
            <small class="text-muted ml-2">
                <a href="#">Forgot Password?</a>
            </small>
        </form>
      </div>
      <div class="border-top pt-3">
            <small class="text-muted">
              Need An Account? <a class="ml-2" href="{{ url_for('register') }}">Sign Up Now</a>
            </small>
   </div>
  
  

<app.py>


from flask import Flask, render_template, url_for, flash, redirect
from forms import RegistrationForm, LoginForm
app = Flask(__name__)
app.config['SECRET_KEY'] = '5791628bb0b13ce0c676dfde280ba245'


@app.route("/")
@app.route("/home")
def home():
    return render_template('home.html')

@app.route("/register", methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit(): 

        flash(f'Account created for {form.username.data}!', 'success')
        return redirect(url_for('home')) ♯ユーザが正しく作成できた場合home にリダイレクト

    return render_template('register.html', title='Register', form=form)

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():

        if form.email.data == 'admin@blog.com' and form.password.data == 'password':
     ♯データベースを作る前なので仮にテスト用

            flash('You have been logged in!', 'success')
            return redirect(url_for('home')) ♯ユーザが正しく作成できた場合home にリダイレクト
        else:
            flash('Login Unsuccessful. Please check username and password', 'danger')

    return render_template('login.html', title='Login', form=form)
    #form を htmlにpass することで, htmlからLoginFormにアクセス可能になる。

if __name__ == '__main__':
    app.run(debug=True)
 

* flash(f'Account created for {form.username.data}!', 'success')
  flashは、2つめの引数にBootstrapのカテゴリーを指定することが可能。指定しなくても良い。
 指定した場合は、HTML側に

{% with messages = get_flashed_messages() %}に、引数として以下のように指定する。
{% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
         {% for category, message in messages %}
        <div class="alert alert-{{ category }}">
        {{ message }}
        </div>
         {% endfor %} 
      {% endif %}
などと記入する。


<app.py>

2019年7月14日日曜日

FLASK - SQLAlchemy 1対1 1対多 リレーション・データベース

【(最も参照中) FLASK SQLAlchemyを使ってみる】

のスピンオフの投稿。
投稿のページが、書きすぎて重くなったので分割して書きます。




 Simple Relationships

SQLAlchemy は、relational databases の連携を強力にサポートする(らしい)のでやってみる。

上記のapp.pyに以下のDBを追加する。

 from datetime import datetime
 class Post(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     title = db.Column(db.String(80), nullable=False)
     body = db.Column(db.Text, nullable=False)
     pub_date = db.Column(db.DateTime, nullable=False,
         default=datetime.utcnow)
     category_id = db.Column(db.Integer, db.ForeignKey('category.id'),
         nullable=False)
     category = db.relationship('Category',
         backref=db.backref('posts', lazy=True))
     def __repr__(self):
         return '<Post %r>' % self.title

 class Category(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(50), nullable=False)
     def __repr__(self):
         return '<Category %r>' % self.name

 First let’s create some objects:
>>> from app import Post
>>> from app import Category

 >>> py = Category(name='Python')
 >>> Post(title='Hello Python!', body='Python is pretty cool', category=py)
<Post 'Hello Python!'>  <-- 何故かprintされる。
 >>> p = Post(title='Snakes', body='Ssssssss')
 >>> py.posts.append(p)
 >>> db.session.add(py)


*最初にやった、やり方は

 (*インスタンスの生成)
 >>> admin = User(username='admin', email='admin@example.com')
 >>> db.session.add(admin)
 >>> db.session.commit()


As you can see, there is no need to add the Post objects to the session. Since the Category is part of the session all objects associated with it through relationships will be added too. It does not matter whether db.session.add() is called before or after creating these objects. The association can also be done on either side of the relationship - so a post can be created with a category or it can be added to the list of posts of the category.

 Let’s look at the posts. Accessing them will load them from the database since the relationship is lazy-loaded, but you will probably not notice the difference - loading a list is quite fast:

 >>> py.posts
 [<Post 'Hello Python!'>, <Post 'Snakes'>]

While lazy-loading a relationship is fast, it can easily become a major bottleneck when you end up triggering extra queries in a loop for more than a few objects. For this case, SQLAlchemy lets you override the loading strategy on the query level. If you wanted a single query to load all categories and their posts, you could do it like this:

 >>> from sqlalchemy.orm import joinedload
 >>> query = Category.query.options(joinedload('posts'))
 >>> for category in query:
 ...     print category, category.posts
 <Category u'Python'> [<Post u'Hello Python!'>, <Post u'Snakes'>]

 If you want to get a query object for that relationship, you can do so using with_parent(). Let’s exclude that post about Snakes
 for example:

 >>> Post.query.with_parent(py).filter(Post.title != 'Snakes').all()
 [<Post 'Hello Python!'>]


 One-to-Many Relationships

 The most common relationships are one-to-many relationships. Because relationships are declared before they are established you can use strings to refer to classes that are not created yet (for instance if Person defines a relationship to Address which is declared later in the file).

 Relationships are expressed with the relationship() function. However the foreign key has to be separately declared with the ForeignKey class:

 class Person(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(50), nullable=False)
     addresses = db.relationship('Address', backref='person', lazy=True)
 class Address(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     email = db.Column(db.String(120), nullable=False)
     person_id = db.Column(db.Integer, db.ForeignKey('person.id'),
         nullable=False)

 What does db.relationship() do? That function returns a new property that can do multiple things. In this case we told it to point to the Address class and load multiple of those. How does it know that this will return more than one address? Because SQLAlchemy guesses a useful default from your declaration. If you would want to have a one-to-one relationship you can pass uselist=False to
 relationship().

 Since a person with no name or an email address with no address associated makes no sense, nullable=False tells SQLAlchemy to create the column as NOT NULL. This is implied for primary key columns, but it’s a good idea to specify it for all other columns to make it clear to other people working on your code that you did actually want a nullable column and did not just forget to add it.

So what do backref and lazy mean? backref is a simple way to also declare a new property on the Address class. You can then also use my_address.person to get to the person at that address. lazy defines when SQLAlchemy will load the data from the database:
 *'select' / True (which is the default, but explicit is better than implicit) means that SQLAlchemy will load the data as necessary in one go using a standard select statement.

 *'joined' / False tells SQLAlchemy to load the relationship in the same query as the parent using a JOIN statement.
 *'subquery' works like 'joined' but instead SQLAlchemy will use a subquery.
 *'dynamic' is special and can be useful if you have many items and always want to apply additional SQL filters to them. Instead of loading the items SQLAlchemy will return another query object which you can further refine before loading the items. Note that this cannot be turned into a different loading strategy when querying so it’s often a good idea to avoid using this in favor of lazy=True. A query object equivalent to a dynamic user.addresses relationship can be created using Address.query.with_parent(user)
 while still being able to use lazy or eager loading on the relationship itself as necessary.
 How do you define the lazy status for backrefs? By using the backref() function:

 class User(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(50), nullable=False)
     addresses = db.relationship('Address', lazy='select',
         backref=db.backref('person', lazy='joined'))

 Many-to-Many Relationships
 If you want to use many-to-many relationships you will need to define a helper table that is used for the relationship. For this
 helper table it is strongly recommended to not use a model but an actual table:

 tags = db.Table('tags',
     db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True),
     db.Column('page_id', db.Integer, db.ForeignKey('page.id'), primary_key=True)
 )
 class Page(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     tags = db.relationship('Tag', secondary=tags, lazy='subquery',
         backref=db.backref('pages', lazy=True))
 class Tag(db.Model):
     id = db.Column(db.Integer, primary_key=True)

 Here we configured Page.tags to be loaded immediately after loading a Page, but using a separate query. This always results in two queries when retrieving a Page, but when querying for multiple pages you will not get additional queries.
 The list of pages for a tag on the other hand is something that’s rarely needed. For example, you won’t need that list when retrieving the tags for a specific page. Therefore, the backref is set to be lazy-loaded so that accessing it for the first time will trigger a query to get the list of pages for that tag. If you need to apply further query options on that list, you could either switch to the 'dynamic' strategy - with the drawbacks mentioned above - or get a query object using Page.query.with_parent(some_tag) and then use it exactly as you would with the query object from a dynamic relationship.




Step1 : Creating Models
A Model is a Python class which represents the database table and its attributes map to the column of the table. A model class
inherits from db.Model and defines columns as an instance of db.Column class.
プロジェクト・ディレクトリ/main2.py

#...
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
#...
class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    def __repr__(self):
        return "<{}:{}>".format(id, self.name)
class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer(), primary_key=True)
    title = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text(), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)
    def __repr__(self):
        return "<{}:{}>".format(self.id, self.title[:10])

__tablename__ は、テーブル名を指定するクラス変数。 指定しなければ、SQLAlchemyは、modelの名前を自動的に使うが、複数系の名前はつけられない。

・db.Columnの代表的なConstraints.

nullable  When set to False makes the column required. Its default value is True.
index      A boolean attribute. If set to True creates an indexed column.
primary_key  A boolean attribute. If set to True marks the column as the primary key of the table.
unique  A boolean attribute. If set to True each value in the column must be unique.

You might have noticed that we have set the default value of created_on and updated_on to a method name ( datetime.utcnow ) instead of calling the method ( datetime.utcnow() ). This is because we don’t want to call datetime.utcnow() method when the code is executed. Instead, we want it to call it when an actual record is added or updated.
Defining Relationship
In the previous section, we have created a Post model with a couple of fields. In the real world, however, model classes seldom
exist on their own. Most of the time they are connected with other models through various relationships like one-to-one, one-to-many
and many-to-many.
Let’s expand on the analogy of a blog site. Generally, a blog post belongs to a category and one or more tags. In other words,


there is a one-to-many relationship between a category and a post and a many-to-many relationship between a post and a tag. The following figure demonstrates this relationship.

flask_app/models.py
#...
class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    posts = db.relationship('Post', backref='category')
    def __repr__(self):
        return "<{}:{}>".format(id, self.name)

class Post(db.Model):
__tablename__ = 'posts'
    id = db.Column(db.Integer(), primary_key=True)
    title = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text(), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)
category_id = db.Column(db.Integer(), db.ForeignKey('categories.id'))

class Tag(db.Model):
    __tablename__ = 'tags'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    def __repr__(self):
        return "<{}:{}>".format(id, self.name)
#...


One to Many Relationship (between a category and a post)


*db.relationship():2つのテーブルが関連していることを明示。model classと関連するobjectsを指定。

*db.ForeignKey() : one to manyのmany側に記述する。参照するテーブルのcolumn名を指定する。db.relationship()は、one側、many側のどちらに記述しても良い。
db.relationship()がある方を親クラス、db.ForeignKey()があるクラスを子クラスと呼ぶことがある。

*backref='category'
Now if we have a Category object (say c) then we can access all posts under it as c.posts. What if you want to access the data from the other side of the relationship i.e get category from a post object? This is where backref comes into play. So the code:
 posts = db.relationship('Post', backref='category')
adds a category attribute to the Post object. That means if we have a Post object (say p) then we can access its category as
p.category.
The category and posts attributes on Post and Category object only exist for your convenience they are not actual columns in the
table.

Many to Many Relationship

many-to-many relationship を作る際には、もう一つ、 association table と呼ばれるテーブルを作る。
以下の例では、post.id と tag.id を含む、post_tagsという名前のアソシエーション・テーブルを作る。

A many-to-many relationship between the post and tags is implemented as a two one-to-many relationship. The first one-to-one relationship is between
①posts and post_tags table and the second is between
②tags and post_tags table.

flask_app/main2.py
# ...
class Category(db.Model):
    # ...
① post_tags = db.Table('post_tags',
    db.Column('post_id', db.Integer, db.ForeignKey('posts.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
)
class Post(db.Model):
__tablename__ = 'posts'
    id = db.Column(db.Integer(), primary_key=True)
    # ...
class Tag(db.Model):
    # ...
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    posts = db.relationship('Post', ②secondary=post_tags, backref='tags')
#...
① db.Table(): post_tagsという名前のアソシエーション・テーブルをdb.Table()のオブジェクトとして定義する。(Syntaxは、SQLAlchemyのもの
で、通常のclassの定義とは異なる。) db.Table()の最初の引数はテーブル名。次の引数は、参照されるテーブル Columnのインスタンス。.
②secondary=post_tags
モデル・classが使用するアソシエーション・テーブルの指定する。
Let’s say we have a Post object p, then we can access all its tags as p.tags. Similarly, Given a Tag object t, we can access all
the posts under it as t.posts.



One to One relationship


One to Manyモデルとの唯一の違いは、以下のように、argument uselist=False をdb.relationship() をディレクティブの中に指定する。
(従業員と免許書IDを関連づけたモデルの例)

class Employee(db.Model):
    __tablename__ = 'employees'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    designation = db.Column(db.String(255), nullable=False)
    doj = db.Column(db.Date(), nullable=False)
    dl = db.relationship('DriverLicense', backref='employee', uselist=False)

class DriverLicense(db.Model):
    __tablename__ = 'driverlicense'
    id = db.Column(db.Integer(), primary_key=True)
    license_number = db.Column(db.String(255), nullable=False)
    renewed_on = db.Column(db.Date(), nullable=False)
    expiry_date = db.Column(db.Date(), nullable=False)
    employee_id = db.Column(db.Integer(), db.ForeignKey('employees.id'))

Now if we have an Employee object e then e.dl would return a DriverLicense object. If we hadn’t passed uselist=False to the db.relationship() directive then the relationship between Employee and DriverLicense would be one-to-many and e.dl would return a list of DriverLicense objects instead of a single object. The uselist=False argument doesn’t have any effect on the employee attribute of the DriverLicense object. As usual, it will return a single object.
Now its time to create our database and tables inside it.

2019年7月11日木曜日

Numpyの数値タイプは Sqlite3 に入れられない件

numpy(Pandas)の数値タイプは、例えば、以下のようになる。

dtype: object
<class 'numpy.int64'>
1149266929565261824


これをsqlite3に格納しようとするとエラー(文字化け)する。

<b'\x00`\xd7\xf0.\x04\xf3\x0f'>,

この対処方法は、
import numpy as np
import sqlite3
sqlite3.register_adapter(np.int64, lambda val: int(val))
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE foo (id INTEGER NOT NULL, primary key (id))")
conn.execute("insert into foo values(?)", (np.int64(100),))

というsql文で対処するようだ。

https://stackoverflow.com/questions/38753737/inserting-numpy-integer-types-into-sqlite-with-python3

sql文は、まだ得意じゃないので、やめておく。

2019年7月7日日曜日

Flask SQLAlchemy で作る簡単CRUDアプリ

Flask SQLAlchemy書籍管理アプリを作ります。Flask自身は、操作できるのが前提です。
作成されたDBを確認するコマンドも解説しますが、
以下のサイトでも確認できます。

== databaseのオンラインチェック用 url ==



1. 最終形

sqlalc.py

import os

from flask import Flask
from flask import redirect
from flask import render_template
from flask import request

from flask_sqlalchemy import SQLAlchemy

project_dir = os.path.dirname(os.path.abspath(__file__))
database_file = "sqlite:///{}".format(os.path.join(project_dir, "bookdatabase.db"))

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = database_file

db = SQLAlchemy(app)

class Book(db.Model):
    title = db.Column(db.String(80), unique=True, nullable=False, 
               primary_key=True)

    def __repr__(self):
        return "<Title: {}>".format(self.title)

@app.route('/', methods=["GET", "POST"])
def home():
    books = None
    if request.form:
        try:
            book = Book(title=request.form.get("title"))
            db.session.add(book)
            db.session.commit()
        except Exception as e:
            print("Failed to add book")
            print(e)
    books = Book.query.all()
    return render_template("home.html", books=books)

@app.route("/update", methods=["POST"])
def update():
    try:
        newtitle = request.form.get("newtitle")
        oldtitle = request.form.get("oldtitle")
        book = Book.query.filter_by(title=oldtitle).first()
        book.title = newtitle
        db.session.commit()
    except Exception as e:
        print("Couldn't update book title")
        print(e)
    return redirect("/")

@app.route("/delete", methods=["POST"])
def delete():
    title = request.form.get("title")
    book = Book.query.filter_by(title=title).first()
    db.session.delete(book)
    db.session.commit()
    return redirect("/")


if __name__ == "__main__":
    app.run(debug=True)



templates/home.html


 
<html>
  <body>
    <h1>Add book</h1>
    <form method="POST" action="/">
      <input type="text" name="title">
      <input type="submit" value="Add">
    </form>

    <h1>Books</h1>
    <table>
      {% for book in books %}
        <tr>
          <td>
            {{book.title}} 
          </td>
          <td>
            <form method="POST" action="./update" style="display: inline">
              <input type="hidden" value="{{book.title}}" name="oldtitle">
              <input type="text" value="{{book.title}}" name="newtitle">
              <input type="submit" value="Update">
            </form>
          </td>
          <td>
            <form method="POST" action="./delete" style="display: inline">
              <input type="hidden" value="{{book.title}}" name="title">
              <input type="submit" value="Delete">
            </form>
          </td>
        </tr>
        {% endfor %}
    </table>
  </body>
</html>
 



1. Step1
dependencyのインストール

pip3 install --user flask sqlalchemy flask-sqlalchemy


2. DBのセットアップ

SQLite databaseを使います。

ここでは、シェル・コマンドから最初のDBを作成します。

pythonを起動してから、以下を実行。

>>> from sqlalc import db
>>> db.create_all()

これで、カレントディレクトリにDB(bookdatabase.db)が作成されます。




この段階で、上記のページが表示できるようになります。

2nd Screen



(登録されているレコードは、以下のようにコマンドラインから確認することが出来る。)
> flask shell
>from sqlalc import db
> from sqlalc import Book
>list = Book.query.all()
>print(list)
[<Title: beatiful soup>, <Title: art of the deal>]

又は、python からsqlite3を起動してDBに接続して確認することもできる。
>python
>>> import sqlite3
>>> connection = sqlite3.connect('bookdatabase.db')
>>> cursor = connection.cursor()
>>> cursor.execute('SELECT * FROM Book')
<sqlite3.Cursor object at 0x0000000001E67880>
>>> rows = cursor.fetchall()
>>> for row in rows:
...     print(row)
...
('beatiful soup',)
('art of the deal',)


(その他の例)
参考)
Python Flask Tutorial: Full-Featured Web App Part 4 - Database with Flask-SQLAlchemy
https://www.youtube.com/watch?v=cYWiDiIUxQc&t=1515s

×Connecting to a SQLite Database Using Flask-SQLAlchemy (Pretty Printed)
https://www.youtube.com/watch?v=KrRzZGcHjK8
(コネクトしてお終い。役に立つ情報なし。)


(参考スクリプト)

◇簡単ユーザ管理
https://stackoverflow.com/questions/43409670/delete-one-row-from-database-with-sqlalchemy-flask

class Subscriber(db.Model):
    user_id = db.Column(db.Integer, primary_key=true)
    client_id = ....
    ....

    # method to add new subscriber
    def __init__(self, user_id, client_id)
        self.user_id = user_id
        self.client_id = client_id


@app.route('/subscribe/')
def subscribe():
    # add a subscriber with user id 21, client 543
    new = Subscriber(21, 543)
    db.session.add(new)
    db.session.commit()


@app.route('/unsubscribe/')
def unsubscribe():
    # remove subscriber
    Subscriber.query.filter_by(user_id=21, client_id=543).delete()
    db.session.commit()

flask-SQLAlchemy で良く使うクエリーコマンド一覧


flask-SQLAlchemy で良く使うコマンド一覧。



 """
◇Query all user data
    User.query.all()

    How many users are queried?
    User.query.count()

*query.all()で検索した結果をprintするとレコード・オブジェクトがリスト形式で表示される。
>>> a = Tw_msg.query.all()
>>> print(a)
[<Tw_msg 1>, <Tw_msg 2>, <Tw_msg 3>, <Tw_msg 4>, <Tw_msg 5>]

・中のレコードの内容を確認したい場合、
>>> a[4].__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x00000000046B8DA0>, 'message': 'Hello, world!', 'id': 5, 'hashtag': '1', 'time': '12:20', 'url': 'https://test.com'}

・オブジェクトの構造を確認したい場合、
>>> print(dir(a[4]))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__mapper__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__table__', '__tablename__', '__weakref__', '_decl_class_registry', '_sa_class_manager', '_sa_instance_state', 'hashtag', 'id', 'message', 'metadata', 'query', 'query_class', 'time', 'url']
>>>

 
Query the first user
    User.query.first()
         User.query.get(1) # Query based on id

 

◇◇ filter method ◇◇

・合致する条件のものを抽出
Query user with id 4 [3 ways]
    User.query.get(4)
         User.query.filter_by(id=4).all() # simple query Use the form of keyword arguments to set the field name
         User.query.filter(User.id == 4).all() # Complex Query Use other forms such as identities to set conditions

・特定の文字列で(始まる、終わる)、文字列を含む の検索

Query all users whose name ends with g [start/include]
    User.query.filter(User.name.endswith("g")).all()
    User.query.filter(User.name.startswith("w")).all()
    User.query.filter(User.name.contains("n")).all()
         User.query.filter(User.name.like("%n%g")).all() Fuzzy Query

 Query all users whose names and mailboxes start with li [2 ways]
    User.query.filter(User.name.startswith("li"), User.email.startswith("li")).all()


・複数の条件で検索
 AND
from sqlalchemy import and_
    User.query.filter(and_(User.name.startswith("li"), User.email.startswith("li"))).all()

import and_ を書かない場合(普通、書かない) dbモデルからのメソッドの引用であることを明示。
User.query.filter(db.and_(User.name.startswith("li"), User.email.startswith("li"))).all()


*単に検索条件を , (カンマ)で区切る記述でも可
  User.query.filter(User.name.startswith("li"), User.email.startswith("li")).all()


Queryで条件を徐々に積み上げて、検索結果を絞り込んでいくことで複数条件で、検索することも可能。(AND検索と効果は同じ)

(例)
User.query.filter(User.name=='Anthony').filter(User.email == 'test.@test.com').first()
というように検索条件を連ねていくことで、検索結果を絞りこんでいくことができる。以下のような書き方も可能。

q1 = User.query
q2 = q1.filster(User.name=='Anthony')

>> q1.all()
#
>>q2.all()
#


OR

Query age is 25 or `email` all users ending with `itheima.com`
    from sqlalchemy import or_
    User.query.filter(or_(User.age == 25, User.email.endswith("itheima.com"))).all()
    *db.or_ が普通(andと同じ)

 
Query all users whose name is not equal to wang [2 ways]
    from sqlalchemy import not_
    User.query.filter(not_(User.name == "wang")).all()
    User.query.filter(User.name != "wang").all()

         Query users with id [1, 3, 5, 7, 9]
    User.query.filter(User.id.in_([1, 3, 5, 7, 9])).all()

         All users are sorted by age from small to large, then by id from big to small, take the first 5
    User.query.order_by(User.age, User.id.desc()).limit(5).all()

         Paging query, 3 per page, query data on page 2
    pn = User.query.paginate(2, 3)
         Pn.items Get the data of this page pn.page Get the current page number pn.pages Get the total number of pages


・Not Equal
    User.query.filter(User.id !=  4).all()

・Like
  User.query.filter(User.name.like('%eh%')).all()
      *nameというカラムの中に 'eh' という文字列を含むrow(レコード)を取得

・In と Not In
 (In)
   User.query.filter(User.name.in_(['Anthony', 'Zach'])).all()
      *nameがAnthonyとZachに合致するrow(レコード)を、それぞれ取得
   in_([ リスト形式で記述] )

   (Not In): Inと同じ書き方だが、クラス名の前にチルダをつける。
    User.query.filter(~User.name.in_(['Anthony', 'Zach'])).all()

・Null と Not Null
  (Null) 値が入力されていない(null)のレコードの取得
   User.query.filter(User.name == None)).all()

   (Not Null)
   User.query.filter(User.name != None)).all()

・Limit
  条件にマッチするレコードの抽出する数を指定
 User.query.limit(2).all() や
   User.query.order_by(User.name).limit(2).all()
   (

・Offset
  検索する際にスキップする行数を指定。
 User.query.offset(2).all()
  (*最初の2行を検索対象から除外する。)

・Count
 クエリーに合致するレコード(row)が幾つあるか数値で返す
 User.query.filter(User.name.like('%eh%')).count()  
   >> 21
  とか。

・Inequality (不等式)
   User.query.filter(User.id >= 4).all()

   文字列比較もできる
   User.query.filter(User.name>= 't').all()
   名前がt 以降で始まるレコードが取得できる。


 ◇◇ Order_by ◇◇

 User.query.order_by(User.name).all()
*nameがアルファベット順に昇順にソートされたリストが返される。

(降順)
SQLAlchemy文で
from sqlalchemy import desc
User.query.order_by(desc(User.id)).limit(3).all()

又は
User.query.order_by(User.id.desc()).limit(3).all()



 *.scalar()
     Return the first element of the first result or None if no rows present.
  If multiple rows are returned,
     raises MultipleResultsFound
     >>> session.query(Item.id).filter(Item.id < 0).scalar()
     None
     '''
   (良く使う記述は)
  if Tw_msg.query.filter_by(time=timeStr).scalar() is not None:
   #もし、noneでなければ(何が該当があれば)、以下の処理
           people = Tw_msg.query.filter_by(time=timeStr).all()
           print(people)
  else:
          pass

 
*queryの2つの記述方法
① class名.query は、(db.model)に基づいたquery.

② session.queryで、sessionを利用したqueryも可能
書き方は、上記のclass名の代替としてsession. と記述する。
session.query.all()
又は
db.session と記述する。


How can we order by a count? I.e. for example "give me the ten most common names in descending order of frequency"?

If you are using Table.query property:

from sqlalchemy import func
Table.query.with_entities(Table.column, func.count(Table.column)).group_by(Table.column).all()

If you are using session.query() method

from sqlalchemy import func
session.query(Table.column, func.count(Table.column)).group_by(Table.column).all()

2019年7月5日金曜日

Bootstrapで使う小技


よく使う小技をまとめておきます。


◇ 文字の大きさをかえる。



Font Awsomeを使って、絵文字を入れる。

1) </body>の直前に以下を記述。

  <!-- Font Awsome -->
 <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">

2)挿入したい場所に、以下のようにclassで指定する。

<h3><p><span class="text-primary"><i class="fab fa-twitter"></i> </span></p></h3>
等。

Textに色をつけてセンターに

<p class ='text-primary'style="text-align:center">これで青色の文字が真ん中に</p>