안드로이드는 sp와 dip(화면 표시 단위)을 권장하고 있습니다.
이에 대해서 포스팅하여 글을 적어 봅니다.

프로젝트 생성 시 폴더로 LDPI(저해상도), MDPI(중해상도), HDPI(고해상도)로 생성이 됩니다.

Density(밀도)값은 LDPI->120, MDPI->160, HDPI->240으로 각각 인치당 필셀수를 의미합니다.
참조 링크 :http://developer.android.com/guide/practices/screens_support.html

LDPI는 120/240 = 1/2,
MDPI는 160/240 = 2/3로 길이당 픽셀수가 감소한다.

레이아웃용 xml 파일에 기술되어야 할 dip값은 다음과 같이 계산 할 수 있다.
dip = px * (160/density)

Density값은 HDPI, MDPI, LDPI의 값이다.
결국 MDPI일때 dip값과 px값은 동일하다.

출처 : http://ezcocoa.com/?p=507

출처 : http://www.androidpub.com/57847


안녕하세요.  

대부분의 프로그램이 설정 관련 화면을 만들게 되는데요.  

안드로이드에서 이 부분도 거의 정형화가 되어 있어서 기본적으로다  지원하더군요. 

"알짜만 골라 배우는 안드로이드 프로그래밍" 책은  이 부분을 잘 설명하고 있는데요. 
"프로페셔널  안드로이드 개발"은   그냥 보통의  activity를 만들어서 전부 코딩하는 것으로 알려주더군요. 

두 방법다 장점이 있기는 하지만, 
일관된  UI 와  작업을 효율성을 위해서는  "알짜..." 의 방법이  좋을 듯합니다. 


아래 내용은  설정 화면을  만드는 쉬운 방법입니다. 


1.  환경설정 xml 파일 만들기  

  이클립스의  File  -  New  - Other  에 가면  아래와 같은 창이 나옵니다. 
set00.png 

Android XML File을 선택합니다. 

set01.png 

빨간색 사각형 한 곳만   알맞은 값으로  넣어 놓고  [Finish] 버튼을 누릅니다. 

그럼,  setting.xml 파일이    /res/xml 아래에 생성이 됩니다. 


2.  화면 구성

/res/xml/setting.xml 파일을 선택하면, 

PreferenceScreen 이라는 것이 보이는 데요.  이것을 선택하고,   [Add] 버튼을 누르면   아래처럼 추가 가능한 것들이 나옵니다.
set02.png 

이것들이 설정화면에  사용가능한 것들인데요. 

이름 그대로   CheckBox,  List  , Edit 등을  추가할 수 있구요. 
Ringtone은   벨소리 종류 선택을 추가 할 수 있답니다. 

RreferenceCategory 는    설정의 종류를 그룹 지을 때 사용하구요. 

PreferenceScreen은   서브 화면으로  전환해서 사용하는 경우에 사용한답니다. 

set03.png 

위 그림처럼  CheckBoxPreference 를 추가하면  오른쪽에  속성값을  넣을 수가 있는데요. 

이중에 중요한 것이 

Key  인데요,   설정에 저장된 값을  읽어 올때 사용한답니다.
Title 과  Summary 는  화면에 출력되는 값이구요. 

set04.png 

ListPreference는  Key, Title, Summary  말고도, 
Entries 와  Entry values 를  넣어주어야 하는데요. 

리스트이기 때문에 출력할 내용을  배열로  넣어 주어야 합니다. 

set05.png 

strings.xml 에   출력할  내용을 가지고 있는 배열을  만듭니다. 

1개는  화면 출력용으로,  1개는  실제값을 가진 배열로 만듭니다. 

set06.png 


그런 후에   setting.xml 에서  Entries 의 옆의 [Browse...] 버튼을 누르면 ,  위 그림처럼 선택할 수 있는 것이 나온답니다. 

set07.png 

위  그림처럼  선택해 주시면 됩니다. 

이런식으로  화면 작업을  다 하신 후에  java class를 만들어 주시면 됩니다. 


3. 환경설정 java class 만들기 

이클립스의   File - New - class 를 선택합니다. 
  set08.png 

SuperClass를    옆의 [Browse...]버튼을  눌러서  선택해 줍니다. 
set09.png 

그리고  [Finish]를 눌러서  소스를 생성합니다. 


--------------------  소스 -----------------------------

public class Setting extends PreferenceActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        addPreferencesFromResource(R.xml.setting);
    }

}
------------------------------------------------------

빨간색 부분만 추가해 주시면  된답니다. 




4.   AndroidManifast.xml 에서 activity  추가 

환경설정화면도   activity 이기 때문에  매니패스트 파일에 추가해 주어야 합니다. 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.bolero.texttest"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".TextTest"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    <activity android:name="Setting" android:label="@string/app_name"></activity>
</application>
    <uses-sdk android:minSdkVersion="4" />

</manifest> 



5.  호출 부분 작업 

이제  메인 activity 에서  환경설정 activity를 호출해 주어야 하는데요. 


     private void setting()
    {
        Intent i = new Intent(this, Setting.class);
        
        startActivity(i);
    }

위와 같이  Intent를 만들어서 호출하는 함수하나 만들어서,  원하는 곳 아무곳에서나 호출하면 된답니다. 

이렇게 해서 호출하면   아래와 같이 나온답니다. 

cap01.png 

cap02.png 



6.  환결 설정화면에서  선택한  값 가져오기 

위와 같이 작업한  환경설정값을   안드로이드가  알아서  저장하고 불러오고 한답니다.  
그래서 코드의 어디에서도   저장하거나  로드하는 코드는 없구요. 

이렇게  만들어준 환경설정 값은  getDefaultSharedPreferences 를   통해서 읽어 올 수 있답니다. 

환경 설정도 하나의 activity  이기 때문에   메인에서 환경설정으로 가면,   메인의 화면을 가리게 되어서  OnPause 가 호출되고, 

환경설정에서  돌아오면 때 (환경설정이 닫히면 ),   메인의  OnResume 이 호출 된답니다. 

그러므로,  OnResume에서  환경설정에서 지정한 값을 읽어 오면 된답니다. 


@Override
    protected void onResume() {
        super.onResume();
        
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
        
        boolean check_value =  pref.getBoolean("keycheck", false);
        String list_value = pref.getString("keylist", "");

        
        m_vt2.setText("List = " + list_value + ", check = " + check_value);
    }

위의 파란 글씨 부분이  환경 설정값을 가져오는 부분이랍니다. 


이렇게  구성한 환경 설정을   프로그램을  종료했다가 다시 실행 하여도  유지가 된답니다. 


수고하세요 ^^

여기에 블루투스 관련글 참조

http://docs.androidside.com/docs/guide/topics/wireless/bluetooth.html

출처한글 : http://techblog.textcube.com/153

안드로이드는 블루투스 프로토콜 스택을 포함하고 있기 때문에 블루투스 디바이스들과 무선으로 데이터를 교환할 수 있다. 어플리케이션 프레임웍은 안드로이드 블루투스 API를 사용해 블루투스에 억세스 할 수 있다. 블루투스 API를 사용하면 다음과 같은 작업을 할 수 있다.

  • 다른 블루투스 디바이스 검색
  • 페어링 된 블루투스 디바이스를 위한 로컬 블루투스 아답터 퀘리
  • RFCOMM 채널 설정
  • SDP(Service Discovery Protocol)을 통한 다른 디바이스와의 커넥션
  • 양방향 데이터 전송
  • 복수 커넥션 관리


- 기초

이 문서는 블루투스를 사용해 통신하는데 필요한 4가지 주요 태스크(블루투스 셋업, 페어링 되어 있거나 주변에 있는 기기 검색, 디바이스와 연결, 디바이스간 데이터 전송)를 수행하기 위해 안드로이드 블루투스 API를 어떻게 사용하는가를 설명한다. 
모든 블루투스 API는 android.bluetooth 패키지에 들어있다.  다음은 블루투스 연결을 만드는데 필요한 클래스들의 요약이다.

  • BluetoothAdapter - 로컬 블루투스 아답터 하드웨어를 나타낸다. BluetoothAdapter는 모든 블루투스를 통한 상호작용의 엔트리포인트이다. 이 객체를 사용해서 다른 블루투스 디바이스 찾기, 페어링 된 디바이스 퀘리, 알려진 MAC address를 사용해 BluetoothDevice 인스턴스 얻기, 다른 디바이스에서 부터의 통신 요구를 기다리기 위한 BluetoothServerSocket 만들기를 할 수 있다.
  • BluetoothDevice - 상대방의 블루투스 디바이스를 나타낸다. 이 객체를 사용하면 BluetoothSocket을 통해 상대방 디바이스와 커넥션을 요구하거나 이름, 주소, 클래스, 페어링 상태등의 정보를 퀘리할 수 있다.
  • BluetoothSocket - 블루투스 소켓을 위한 인터페이스를 나타낸다. 어플리케이션이 InputStream과OutputStream을 사용해서 다른 블루투스 디바이스와 데이터 교환을 할 수 있는 연결 포인트이다.
  • BluetoothServerSocket – Incoming 리퀘스트를 위해 listen하고 있는 오픈된 서버소켓(TCPServerSocket과 유사)을 나타낸다. 두대의 안드로이드 디바이스를 연결하기 위해 한쪽의 디바이스는 이 클래스를 사용해서 서버소켓을 오픈해야만 한다. 원격 블루투스 디바이스가 디바이스에 커넥션 리퀘스트를  때 BluetoothServerSocket은 커넥션이 연결되면 연결된 BluetoothSocket을 리턴해준다.
  • BluetoothClass - 블루투스 디바이스의 일반적 특성과 기능을 나타낸다. 이 클래스는 디바이스의 디바이스 클래스와 서비스를 정의하는 읽기 전용 속성의 집합이다.


블루투스 퍼미션

어플리케이션에서 블루투스 기능을 사용하려면 최소한 BLUETOOTH와 BLUETOOTH_ADMIN 둘중에 하나의 블루투스 퍼미션을 선언해줘야 한다. 커넥션 요구, 커넥션 accept, 데이터 전송등의 블루투스 통신을 하기 위해서는 BLUETOOTH 퍼미션이 필요하다.
디바이스 discovery를 시작하거나 블루투스 설정을 조작하려면 BLUETOOTH_ADMIN 퍼미션이 필요하다. 
BLUETOOTH_ADMIN 퍼미션을 사용하려면 BLUETOOTH 퍼미션도 꼭 있어야만 한다. 매니페스트 파일에 블루투스 퍼미션을 선언해준다. 

<uses-permission android:name="android.permission.BLUETOOTH" /> 
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 




- 블루투스 셋업

어플리케이션이 블루투스로 통신을 하기 전에 디바이스가 블루투스를 지원하는지 확인할 필요가 있다. 그리고 블루투스를 지원한다면 활성화 되었는지도 확인해줘야 한다. 만일 블루투스를 지원하지 않으면 블루투스 기능을 비활성화 시켜야 한다. 블루투스를 지원하지만 활성화 되어 있지 않으면 사용자가 어플리케이션을떠나지 않고 블루투스를 활성화하도록 요구할 수 있다. 이 작업은 BluetoothAdapter를 사용해서  단계로 수행할 수 있다. 

1.BluetoothAdapter 를 얻는다.
모든 블루투스 액티비티를 위해 BluetoothAdapter가 요구된다. BluetoothAdapter를 얻기 위해서는 스태틱 메소드인 getDefaultAdapter()를 호출하면 된다. 그러면 디바이스의 블루투스 아답터를 나타내는 BluetoothAdapter 인스턴스를 리턴한다. 

BluetoothAdapter mBTAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBTAdapter == null) {
    // device does not support Bluetooth
}


2.블루투스 활성화
블루투스가 활성화 되어있는지 확인해야 한다. isEnabled()를 호출해서 블루투스가 현재 활성화되어 있는지 확인한다. 메소드가 false를 리턴하면 블루투스가 비활성화되어 있는 것이다. 블루투스를 활성화 시키려면 ACTION_REQUEST_ENABLE 인텐트로 startActivityForResult()를 호출하면 된다. 

If (!mBTAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT) ;
}


그림 1과 같이 블루투스를 활성화하기 위한 퍼미션을 요구하는 대화창이 나타난다. 사용자가 “Yes”를 선택하면 시스템은 블루투스를 활성화시키고 그 과정이 끝나면 어플리케이션으로 포커스가 돌아오게 된다.
블루투스 활성화가 성공하면 액티비티는 onActivityResult() 콜백에서 RESULT_OK를 리턴받게 된다. 블루투스가 에러로 인해 (또는 사용자가 “No”를 선택해서) 활성화되지 못하면 RESULT_CANCELED가 리턴된다. 옵션으로 블루투스 상태가 변경될  마다 시스템이 브로드캐스하는 ACTION_STATE_CHANGED 인텐트를 listen하도록 할 수도 있다. 

- 디바이스 검색

BluetoothAdapter를 사용하면 디바이스 discovery 또는 페어링 된 디바이스 목록을 퀘리해서 원격 블루투스 디바이스를 찾을 수 있다.
디바이스 discovery는 주변의 활성화 된 블루투스 디바이스를 찾고 각각에 대한 정보를 요구하는 검색 단계이다. 하지만 통신가능 범위에 들어있는 블루투스 디바이스라 해도 현재 discoverable 하도록 활성화 되어 있어야만 discovery 요구에 응답한다. 디바이스가 discoverable 상태인 경우 discovery 요구에 디바이스 이름, 클래스, MAC 주소같은 정보를 공유함으로서 응답한다. 이 정보를 사용해서 discovery를 수행한 디바이스는 발견된 디바이스에 커넥션을 시작하도록 선택할 수 있다.
일단 원격 디바이스와 처음으로 연결이 이루어지면 자동으로 사용자에게 페어링을 할 것인가 물어보게 된다.  디바이스 페어링이 이루어지면 상대 디바이스에 대한 기본 정보(디아비스 이름, 클래스, MAC 주소 등)가 저장되고 그 내용은 블루투스 API를 통해 읽을 수 있게 된다. 이미 알고 있는 원격디바이스의 MAC 주소를 사용하면 아무때나 (물론 해당 디바이스가 통신 가능범위에 있다는 가정 하에) discovery를 수행할 필요 없이 바로 커넥션 과정을 시작할 수 있다. 
페어링과 연결된것의 차이점은 잘 알고 있어야 한다. 페어링은 두 디바이스가 각자 상대방의 존재를 알고 있고 인증과정에 사용할 link-key를 공유하고 있어 서로간에 암호화 된 연결을 설정할 수 있다는걸 의미한다. 연결된것은 디바이스가 현재 RFCOMM 채널을 공유하고 있어 서로 데이터를 전송할 수 있는 상태를 의미한다.
현재 안드로이드 블루투스 API는 RFCOMM 커넥션을 설정하기 전에 디바이스가 페어링 되어야만 한다. (블루투스 API에서 암호화된 커넥션을 시작하려고 할 때 페어링이 자동을 이루어진다.)
다음의 섹션은 페어링 된 디바이스를 찾거나, 디바이스 discovery를 사용해 새 디바이스를 찾는 방법을 설명한다. 
주: 안드로이드 디바이스는 기본적으로 not discoverable 상태이다. 시스템 설정을 통해 짧은 시간동안 디바이스를 discoverable 상태로 만들거나 어플리케이션에서 직접 discoverable 상태로 만들어 줄 수 있다.

- 페어링 된 디바이스 퀘리

디바이스 discovery를 수행하기 전에 원하는 디바이스가 이미 페어링 되어 있는가 확인해 볼 필요가 있다.  확인하기 위해서 getBondedDevices()를 호출하면 된다. 그러면 페어링 된 디바이스들의 집합인 BluetoothDevices 를 돌려준다. 예를 들어 페어링 된 모든 디바이스를 퀘리한 다음 ArrayAdapter를 사용해 페어링 된  디바이스의 이름을 보여줄 수 있다.

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() <> 0) {
    for (BluetoothDevice device : pairedDevices) {
        mArrayAdapter.add(device.getName() + “\n” + device.getAddress());
    } 
}


BluetoothDevice 객체에서 연결을 시작하기 위해 필요한 정보는 MAC address만 있으면 된다. 위의 예제에서 이 정보는 사용자에게 보여지는 ArrayAdapter의 일부분에 저장되어 있다. MAC 주소는 나중에 연결을 시작하기 위해 추출할수도 있다. 

- 디바이스 discovery

디바이스 discovery를 시작하려면 startDiscovery()를 호출하면 된다. 이 과정은 비동기식이라 메소드를 호출하면 discovery가 성공적으로 시작되었나 결과를 알려주는 boolean값을 곧바로 돌려준다. Discovery과정은 보통 12초간의 inquiry scan후 발견된 각 디바이스에 대해 이름을 가져오기 위한 page scan으로 이루어진다.
어플리케이션은 각 발견된 디바이스에 대한 정보를 받기 위해  ACTION_FOUND 인텐트를 위한 BroadcastReceiver를 등록해야만 한다. 각 디바이스마다 시스템이 ACTION_FOUND 인텐트를 브로드캐스트한다. 이 인텐트는 각각 BluetoothDevice와  BluetoothClass가 들어있는 EXTRA_DEVICE와 EXTRA_CLASS 필드를 전달한다. 예제로 디바이스가 발견되었을 때 브로드캐스트를 처리하는 핸들러를 등록하는 방법이다.

Final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            mArrayAdapter.add(device.getName() + “\n” + device.getAddress());
        }
    }
};

BroadcastReceiverIntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND;
registerReceiver(mReceiver, filter);


커넥션을 시작하기 위해 BluetoothDevice 객체에서 필요한 정보는 MAC 주소뿐이다. 이 예에서는 사용자에게 보여지는 ArrayAdapter의 일부분에 저장되어 있다. 

주의: 디바이스  discovery를 수행하는건 블루투스 아답터에게 매우 부담이 큰 작업으로 매우 많은 리소스를 요구한다. 커넥션 할 디바이스를 찾았다면 커넥션을 시작하려고 시도하기 전에  cancelDiscovery()를 호출해서 discovery를 멈춰야 한다. 또한 이미 다른 디바이스와 커넥션 되어 있으면   discovery과정동안  대역폭이 활 떨어질수도 있기 때문에 커넥션 된  상태에서는 discovery를 하지 않아야 한다.                                                           
- Discoverable 활성화

다른 디바이스가 자신의 디바이스를 검색할 수 있도록 해 주려면 startActivityForResult(Intent, int)에 ACTION_REQUEST_DISCOVERABLE 액션 인텐트를 넣어 호출해주면 된다. 이 메소드를 호출하면 어플리케이션을 멈추지 않고 시스템 설정을 통해 discoverable 모드를 활성화 하도록 요청한다. 기본적으로 디바이스는 120초동안 discoverable 모드로 있게 된다. EXTRA_DISCOVERABLE_DURATION 인텐트 extra를 추가해서 시간을 바꿔줄 수 있다. (최대 300초)

Intent discoverableIntent = new Intent(BluetoothAdpater.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);




그림 2와 같은 다이얼로그가 떠서 사용자에게 디바이스를 discoverable 상태로 만들도록 허가할 것인지 묻는다. “Yes”를 선택하면 디바이스는 정해진 시간동안 discoverable상태가 된다. 액티비티는 result code에 디바이스가 discoverable되는 시간값이 들어가서 onActivityResult() 콜백을 호출받게 된다. 사용자가 “No”를 선택하거나 에러가 발생하면 result code는 Activity.RESULT_CANCELLED가 된다.
디바이스는 discoverable 시간동안 아무 반응이 없이 조용히 있는다. 만일 discoverable모드가 변경될 때 통보를 받고 싶으면 ACTION_SCAN_MODE_CHANGED 인텐트에 대한 BroadcastReceiver를 등록할 수 있다. 이 인텐트에는 각각 이전 스캔모드와 변경된 새 스캔모드가 들어있는 EXTRA_PREVIOUS_SCAN_MODE와 EXTRA_SCAN_MODE라는 extra 필드를 가지고 있다. 각 필드에 들어갈  있는 값은 SCAN_MODE_CONNECTABLE_DISCOVERABLE,  SCAN_MODE_CONNECTABLE,  SCAN_MODE_NONE으로 각각 discoverable 모드, discoverable은 아니지만 커넥션을 받아들일 수는 있는 모드, discoverable도 아니고 커넥션도 받아들일 수 없는 모드를 나타낸다.
원격 디바이스와 커넥션을 시작하고 싶은 경우는 자신의 디바이스를 discoverable모드로 만들 필요는 없다. 원격 디바이스가 커넥션을 시작하기 전에 디바이스를 발견해야만 하기 때문에 내 디바이스의 discoverable 모드를 활성화 시키는건 어플리케이션이 서버소켓을 사용해서 incoming 연결을 accept할 때만 필요하다.

- 디바이스 커넥션

두 디바이스에서 실행되는 어플리케이션간에 커넥션을 만들기 위해서는 서버쪽과 클라이언트쪽 메카니즘을 모두 구현해 줘야만 한다. 한 디바이스는 서버소켓을 열어줘야 하고 다른 디바이스가 서버 디바이스의 MAC 주소를 사용해서 커넥션을 시작해야만 하기 때문이다. 서버와 클라이언트는 같은 RFCOMM 채널에 각각 커넥션 된 BluetoothSocket을 가지고 있을 때 서로 커넥트 된 것으로 간주된다. 이 지점에서 각 디바이스는 입, 출력 스트림을 얻어 데이터 전송을 시작할 수 있다. 이 섹션에서는 두 디바이스간에 커넥션을 시작하는 방법에 대해서 설명한다.
서버 디바이스와 클라이언트 디바이스는 서로 다른 방법으로 필요한 BluetoothSocket을 얻는다. 서버는 incoming 연결이 accept될 때 소켓을 받게 된다. 클라이언트는 서버로의 RFCOMM 채널을 열 때 소켓을 받게 된다.



한가지 구현 테크닉은 두 디바이스를 모두 서버로 동작하도록 하기 위해 서버소켓을 열고 커넥션을 기다리는 것이다. 그러면 어느 디바이스건 클라이언트로서 상대 디바이스로 커넥션을 시작할 수 있다. 다른 방법으로는 한 디바이스는 명시적으로 서버로 지정해 서버소켓을 열고 커넥션을 기다리고 다른 디바이스는 단순히 클라이언트로 커넥션을 시작할 수 있다.
주) 두 디바이스가 미리 페어링 되어 있지 않으면 안드로이드 프레임웍은 그림 3과 같이 자동으로 페어링을 요구하는 다이얼로그를 띄워준다. 그러므로 디바이스를 커넥트 하려고 할 때 어플리케이션은 디바이스가 미리 페어링 되어 있는지 여부를 걱정할 필요가 없다. RFCOMM 커넥션 시도는 사용자가 성공적으로 페어링을 마치거나 페어링을 거부하거나 또는 어떤 이유로건 페어링이 실패할 때 까지 블럭된다.

서버로 동작
두 디바이스를 커넥트하려고 할 때 하나의 디바이스는 BluetoothServerSocket을 열어 서버로 동작해야만 한다. 서버소켓의 목적은 incoming 커넥션 요구를 기다리다 accept되면 커넥션 된 BluetoothSocket을 제공해 주는 것이다. BluetoothServerSocket에서 BluetoothSocket이 얻어지고 더 이상의 커넥션을 accept할 필요가 없으면  BluetoothServerSocket은 제거해도 된다. 

UUID란... 
Universally Unique IDentifier(UUID)는 유일하게 정보를 식별하는데 사용하기 위한 128비트 포맷의 표준화 된 문자열 ID이다. UUID의 포인트는 이 숫자가 충분히 크기 때문에 랜덤하게 아무 숫자나 골라도 다른 UUID들과 겹치지 않는다는 것이다. 여기서는 어플리케이션의 블루투스 서비스를 식별하는데 사용된다. 어플리케이션에 사용할 UUID를 얻기 위해서 인터넷상의 여러가지 랜덤 UUID 생성기중에 하나를 사용할 수 있고 fromString(String)으로 UUID를 초기화 하면 된다.
서버소켓을 셋업하고 연결을 accept하는 기본적인 절차이다.

1.listenUsingRfcommWithServiceRecord(String, UUID)를 호출해서 BluetoothServerSocket을 얻어온다.
스트링은 서비스에 대한 식별할 수 있는 이름으로 시스템이 디바이스의 새 SDP 데이터베이스 엔트리에 자동으로 그 이름을 기록한다. UUID 또한 SDP엔트리에 포함되어 클라이언트와 커넥션 agreement를 위한 기초가 된다. 즉 클라이언트가 디바이스와 커넥션하려고 시도할 때 커넥션하길 원하는 서비스를 유일하게 식별하는 UUID를 제공한다. 커넥션이 이뤄지기 위해서는 이 UUID가 일치해야만 한다.
 
2.accept()를 호출해서 커넥션 요구를 listen하기 시작한다.
이 메소드는 블럭킹 호출이다. 커넥션이 accept되거나 익셉션이 발생해야만 리턴된다. 리모트 디바이스가 listen하고 있는 서버소켓에 등록한 UUID와 일치하는 커넥션 요구에만 연결이 만들어진다. 성공하면 accept()는 커넥션 된 BluetoothSocket을 리턴한다.
 

3.더 이상의 추가 커넥션이 필요하지 않으면 close()를 호출한다.
이 메소드를 호출하면 서버소켓과 관련된 리소스를 release한다. 하지만 accept()가 리턴한 커넥션 된 BluetoothSocket은 닫지 않는다. TCP/IP와 달리 RFCOMM은 클라이언트에서 한번에 하나의 커넥션만 허용하기 때문에 대부분의 경우에 커넥션이 만들어지면 곧바로 BluetoothServerSocket을 close()하는게 합리적이다.

accept()는 블럭킹 메소드라 어플리케이션의 다른 동작을 막기 때문에 메인 액티비티 UI 스레드에서 호출하면 안된다. 일반적으로 새로운 스레드에서 BluetoothSocket이나 BluetoothServerSocket에 관련된 모든 작업을 처리하는게 합리적이다. 다른 스레드에서 BluetoothServerSocket의 accept() 같이 블럭킹  것을 취소하고 바로 리턴하도록 하려면 close()를 호출하면 된다. 그리고 BluetoothServerSocket 또는 BluetoothSocket의 모든 메소드는 스레드-세이프하다.

예제) incoming 연결을 accept하는 서버 컴포넌트를 위한 간단한 스레드
private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;
    
    public AcceptThread() {
        // Use a temporary object that is later assigned to mmServerSocket,
        // because mmServerSocket is final
        BluetoothServerSocket tmp = null;
        try {
            // MY_UUID is the app's UUID string, also usedby the client code
            tmp =mAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);
        } catch (IOException e) { }
        mmServerSocket = tmp;
    }
    public void run() {
        BluetoothSocket socket = null;
        // Keep listening until exception occurs or a socket is returned 
        while (true) {
            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) { 
                break;
            }
            // If a connection was accepted
            if (socket != null) {
                // Do work to manage the connection (in a separate thread)
                manageConnectedSocket(socket);
                mmServerSocket.close();
                break; 
            }
        }
    }
    /** Will cancel the listening socket, and cause the thread to finish */
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) { }
    }
}

이 예제에서 한개의 incoming 커넥션만 필요하기 때문에 커넥션이 accept되고 BluetoothSocket이 얻어지자 마자 어플리케이션은 얻은 BluetoothSocket을 별도의 스레드로 보낸 다음 BluetoothServerSocket을 닫고 루프를 빠져나온다. 
accept()가 BluetoothSocket을 리턴할 때 소켓은 이미 커넥션 되어 있기 때문에 따로 connect()를 호출할 필요는 없다. manageConnectedSocket()은 어플리케이션에서 데이터 전송을 위한 스레드를 시작하는 fictional 메소드이다. 
일반적으로 incoming 커넥션을 listen하는게 끝나면 곧바로 BluetoothServerSocket을 닫아준다. 이 예제에서도 BluetoothSocket이 얻어지자 마자 close()를 호출했다. 또한 listen하고 있는 서버소켓을 멈출 필요가 있을 때 private BluetoothSocket을 닫을  있는 public 메소드를 스레드에서 제공하기도 한다. 

클라이언트로 동작 
원격 디바이스와 커넥션을 시작하려면 우선 원격 디바이스를 나타내는 BluetoothDevice 객체를 얻어야만 한다. 그리고 나면 BluetoothDevice를 사용해서 BluetoothSocket을 얻어 커넥션을 시작한다.

기본적인 절차이다.

1.BluetoothDevice를 사용해서 createRfcommSocketToServiceRecord(UUID)를 호출해서 BluetoothSocket을 얻는다.
이 호출은 BluetoothDevice에 연결하는 BluetoothSocket을 초기화한다. 여기서 건네지는 UUID는 서버 디바이스가 자신의 BluetoothServerSocket(listenUsingRfcommWithServiceRecord(String, UUID)를 사용해서)을 열었을 때 사용한 UUID와 일치해야만 한다. 동일한 UUID를 사용하는건 UUID스트링을 어플리케이션 코드에 하드코딩하고 서버와 클라이언트 양쪽 코드에서 그걸 참조하면 되는 간단한 문제이다.

2.connect()를 호출해서 연결을 시작한다.
시스템은 UUID를 매치하기 위해 원격 디바이스 SDP lookup을 수행한다. Lookup이 성공하고 원격 디바이스가 커넥션을 accept하면 연결동안 사용할 RFCOMM채널을 공유하고 connect()가 리턴한다.  메소드는 블럭킹 호출이다. 어떤 이유로건 커넥션이 실패하거나 connect() 메소드가 time out (약 12초)이 되면 exception을 발생한다.
connect()는 블럭킹 호출이기 때문에 이 커넥션 절차는 언제나 메인 액티비티 스레드와 독립된 별개의 스레드에서 수행되어야만 한다.
주: connect()를 호출할 때 디바이스는 언제나 디바이스 discovery를 수행하고 있지 않는지 확인해야만 한다. Discovery가 진행중이면 커넥션 시도는 확연히 느려져서 실패할 가능성이 커진다.

예제) Bluetooth 커넥션을 시작하는 스레드

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;
        // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code 
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }

    public void run() {
        // Cancel discovery because it will slow down the connection 
        mAdapter.cancelDiscovery();
        try {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and get out 
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }
        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }
    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

cancelDiscovery()는 커넥션이 만들어지기 전에 호출되는걸 볼 수 있다. 커넥션이 되기 전에라도 언제나 호출할 수 있고 실제적으로 실행 여부를 확인하지 않고 호출해도 안전하다. (하지만 그래도 상태를 확인하고 싶으면 isDiscovering()을 사용하면 된다.) manageConnectedSocket()은 데이터 전송을 위한 스레드를 시작하는 어플리케이션에 있는 fictional 메소드이다. 
BluetoothSocket이 끝나면 clean up을 위해 언제나 close()를 호출해줘야 한다. 이 메소드를 호출해 줌으로서 곧바로 커넥션 된 소켓을 닫고 내부 리소스를 clean up 하게 된다. 

- 연결 관리

두 디바이스를 성공적으로 커넥션하게 되면 각 디바이스는 커넥션 된 BluetoothSocket을 가지게 된다. 이 소켓을 통해 디바이스간에 데이터를 교환할 수 있게 된다. BluetoothSocket을 사용해서 임의의 데이터를 전송하기 위한 일반적 절차는 매우 간단하다. 

1.각각 getInputStream()과 getOutputStream()을 사용해 소켓을 통한 전송을 처리할 InputStream과 OutputStream을 얻는다.
2.read(byte[])와 write(byte[])를 사용해서 데이터를 읽고 쓴다.

물론 implementation을 위해 고려해야  세부사항들이 있다. 먼저 무엇보다 모든 읽고 쓰기를 위한 별도의 스레드를 사용해야 한다. 이건 read(byte[])와 write(byte[])는 모두 블럭킹 호출이기 때문에 매우 중요하다.read(byte[])는 스트림에서 무언가 읽을게 있을때까지 블럭되어 있는다. write(byte[])는 일반적으로는 블럭되지 않지만 원격 디바이스가 충분히 빠르게 read(byte[])를 호출하지 않아 버퍼가 꽉 차는 경우 플로우 컨트롤을 위해 블럭될수도 있다. 그러므로 스레드의 메인 루프는 InputStream으로부터 읽기 전용으로 사용되어야 한다. 스레드의 분리된 public 메소드가 OutputStream으로 쓰기를 시작하도록 사용될  있다. 

예제) 
private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream; 
    private final OutputStream mmOutStream;

    public ConnectedThread(BluetoothSocket socket) { 
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream(); 
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }
        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    public void run() {
        byte[] buffer = new byte[1024];    // buffer store for the stream
        int bytes; // bytes returned from read()
        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try { 
                // Read from the InputStream
                bytes = mmInStream.read(buffer); 
                // Send the obtained bytes to the UI Activity
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }
    /* Call this from the main Activity to send data to the remote device */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }

    /* Call this from the main Activity to shutdown the connection */
    public void cancel() { 
        try {
            mmSocket.close(); 
        } catch (IOException e) { }
    } 
}

컨스트럭터가 필요한 스트림을 얻고 한번 실행되면 스레드는 InputStream을 통해 들어오는 데이터를 기다린다. read(byte[])가 스트림에서의 데이터를 리턴하면 그 데이터는 부모 클래스의 Handler 멤버를 사용해 메인 액티비티로 보내진다. 그리고 다시 스트림에서 데이터를 읽기 위해 기다리기 위해 돌아간다. Outgoing 데이터를 보내는건 단순히 메인 액티비티에서 스레드의 write() 메소드를 호출해 전송할 데이터를 전달해주면 된다.

스레드의 cancel() 메소드는 아무때나 BluetoothSocket을 닫아 connection을 멈출 수 있기 때문에 중요하다. 메소드는 블루투스 connection 사용이 끝나면 언제나 호출되어야 한다.

-------------------------------------------------------------------------------------

핸트폰과 PC 블루투스 연결

핸드폰과 PC 사이에 파일을 전송하기 위해서는 먼저 PC에 핸드폰을 블루투스 장치로 추가 해 주어야 합니다. 이를 위해,

  • 알림 영역에 있는 블루투스 아이콘을 클릭한 후 장치 추가를 클릭, 
     
  • 아래와 같은 장치 추가 창이 열리고 자동으로 핸드폰 장치를 찾아 줍니다. (핸드폰에서 블루투스 전원 설정을 ON 으로 해 두어야 PC에서 자동으로 찾음) 핸드폰 아이콘을 클릭한 후 다음 버튼 클릭, 

  • 아래와 같은 번호가 표시 되면, 핸드폰에 연결할 것인지를 묻는 메시지가 뜹니다. 핸드폰에서 확인에 해당하는 버튼을 클릭하면 번호를 입력하는 상자가 뜹니다. 번호를 입력한 후 핸드폰에서 확인에 해당하는 버튼을 클릭해 줍니다. 

  • 잠시 기다리면 아래와 같은 메시지가 뜨면서 핸드폰 장치 추가가 마무리 됩니다. 

핸드폰에서 PC로 전화 번호부 보내기

  • 컴퓨터 알림 영역에 있는 블루투스 아이콘을 클릭한 후 '파일 받기'를 클릭하면 아래와 같이 파일을 받을 수 있도록 연결 대기 화면이 뜹니다. 

  • 핸드폰에서 블루투스-데이터전송-전화번호부 전송을 차례로 클릭한 후 전화번호부 전체 선택 (또는 필요한 전화번호만 선택)을 하고 나면 전송 장치 선택 화면에 PC 이름이 뜹니다. 연결에 해당하는 버튼을 클릭하면, 컴퓨터 화면에 아래처럼 파일 수신 화면이 자동으로 표시됩니다. 

  • 파일을 다 받고 나면 아래 화면처럼 파일을 저장할 곳을 지정하는 화면이 뜹니다. 적당한 위치를 지정한 후 마침 버튼을 클릭하면 지정한 위치에 전화번호부가 저장됩니다.
     

만약, 휴대폰과 PC 연결이 잘 되지 않는다면, 제어판에서 블루투스 설정을 확인해 보시기 바랍니다.

제어판을 연후 제어판의 검색상자에 bluetooth 를 입력한 후 'Bluetooth 설정변경' 을 클릭하여 Bluetootht 설정 창을 연 후, 옵션탭에서  'Bluetooth 장치가 이 컴퓨터에 연결하도록 허용'에 체크가 되어 있는 지 확인해 보시기 바랍니다. 이를 체크 했음에도 연결이 잘 되지 않는다면, 'Bluetooth 장치가 이 컴퓨터를 찾을 수 있도록 허용'에도 체크를 한 후 연결해 보시기 바랍니다. 단,  'Bluetooth 장치가 이 컴퓨터를 찾을 수 있도록 허용'에 체크가 된 경우 보안에 문제가 생길 수도 있기 때문에 필요한 파일 전송을 마친 후 에는 체크를 해제하는 것이 안전합니다.

블루투스 설정변경

Note:

  • 블루투스 기능을 이용하여 전화번호부를 전송한 경우 그룹 설정등은 제대로 전송되지 않는 경향이 있습니다.
  • 사진 크기가 큰 경우 제대로 전송이 안 되는 경우도 있습니다.
  • 여러 핸드폰을 이용해 보았지만, 블루투스를 이용한 파일 전송이 품질이 아주 만족 스럽지는 않았습니다. 핸드폰 제조사에서 제공하는 매니저 프로그램을 다운 받아 이용하는 것이 가장 확실하긴 합니다.
  • 맥이나 리눅스용 드라이버나 매니저 프로그램을 제공해 주지 않는 국내 핸드폰 제조사의 특성상, 블루투스가 설치된 맥이나 리눅스 이용자는 블루투스 기능을 이용하여 기본적인 전화번호부, 파일 전송을 할 수 있습니다.

-------------------------------------------------------------------------------------

블루투스 장비와 연동해 데이터를 받아와야 한다.

통신 시간이 중요하다. 느리면 쓸 이유가 없다.

시리얼 통신 처럼 사용하고 싶다.

 

정보

지원가능 프로파일

시리얼

실제 블루투스 사용이 잘 안되는 경우가 많이 있단다. (블루투스 키보드를 이용하면..., 2010.04.17)

블루트스 에뮬에서는 안된다. (출처 : 안드로이드 블루투스 관련, 2010.03.02)

블루투스를 이용한 테더링 잘 된다 VS 안된다. (출처 : PDANet 휴대폰을 모뎀으로 2탄 Bluetooth 연결편, 2010.04.04, 안드로이드 모토로이 USB 테더링 후기, 2010.02.24)

 

개념 탑제 필요

  • 블루투스 버전간 차이
  • 각종 프로파일 지원 여부에 따른 영향
  • 페어링

---------------------------------------------------------------------------------------

안드로이드의 BlueTooth는 1.5, 1.6버젼에서는 A2DP, AVRCP(AV Remote Control), HSP(Headset), HFP(Hands Free) 정도만 지원했다. 안드로이드 2.0 버젼부터 OOP(Push Profile), PBAP(Phone Book Access Profile) 등을 지원한다고 한다.

 

안드로이드에서 BlueTooth를 사용하려면 Vendor에서 정의하는 BoardConfig.mk에서 BOARD_HAVE_BLUETOOTH를 true로 설정해야 한다.

 

기본 구조에 대하여 참고할만한 곳은 다음과 같다.

 

http://sites.google.com/a/android.com/opensource/projects/bluetooth-faq

 

기본 구조

 

 

 

system server 와  BlueZ 와의 인터페이스를 담당하는 D-Bus는 다음을 참고한다.

 

http://www.freedesktop.org/wiki/Software/dbus

http://daeji.tistory.com/entry/D-BUS

http://bebop.emstone.com/research/tech/dbus/dbus_overview

 

아직 확실하지는 않지만

BT Power on/off 를 담당하는 소스는 다음 파일 같다.

system/bluetooth/bluedroid/bluetooth.c

 

실제 하드웨어를 컨트롤하는 부분은 보이지 않으며 함수 Call에 따라 내부 설정 등을 변경해 놓는다.

하드웨어 컨트롤 관련된 부부은 2.01에서는 별도로 추가된 것으로 보이며 이는 1.6 버젼까지는 이 쪽에 대충 떄려 넣어야 할 것 같다.

 

Make파일을 보면 공유 라이브러리 libbluedroid.so 파일로 만든다.

이 라이브러리는 Java에서 사용 가능하도록 다음에서 라이브러리를 정의한다.

frameworks/base/core/jni/Android.mk

 

이를 보면 결론적으로 BlueTooth와 관련된 JNI 관련 파일은 다음 세 파일이다.

android_server_BlueToothA2dpService.cpp

android_server_BlueToothDeviceService.cpp

android_server_BlueToothEventLoop.cpp

 

그 외 파일들은

 android_bluetooth_Database.cpp
 android_bluetooth_HeadsetBase.cpp
 android_bluetooth_common.cpp

dbus 관련 함수


 android_bluetooth_BluetoothAudioGateway.cpp
 * android_bluetooth_RfcommSocket.cpp

 * android_bluetooth_BluetoothSocket.cpp 

socket 통신 관련 (write, read, ...)


 android_bluetooth_ScoSocket.cpp

Sco socket 통신 관련 (init, connect, accept, close ...)

 

당연한 얘기지만 BlueTooth의 A2DP, HSP, HFP 등을 사용하면 오디오 출력에 영향을 미치므로

framework/base/libs/audioflinger 쪽도 영향을 받는다.

 

BlueZ에 대한 소스는 external/bluez 에 있다. 

 

참고할만한 기사

http://ko.broadcom.com/press/release.php?id=s363853

출처 : http://goldenking.tistory.com/10



Bluetooth Permissions


In order to use Bluetooth features in your application, you need to declare at least one of two Bluetooth permissions: BLUETOOTH and BLUETOOTH_ADMIN.

You must request the BLUETOOTH permission in order to perform any Bluetooth communication, such as requesting a connection, accepting a connection, and transferring data.

You must request the BLUETOOTH_ADMIN permission in order to initiate device discovery or manipulate Bluetooth settings. Most applications need this permission solely for the ability to discover local Bluetooth devices. The other abilities granted by this permission should not be used, unless the application is a "power manager" that will modify Bluetooth settings upon user request. Note: If you use BLUETOOTH_ADMIN permission, then must also have the BLUETOOTH permission.

Declare the Bluetooth permission(s) in your application manifest file. For example:

 
<manifest ... >
 
<uses-permission android:name="android.permission.BLUETOOTH" />
  ...
</manifest>

See the <uses-permission> reference for more information about declaring application permissions.

Setting Up Bluetooth


Figure 1: The enabling Bluetooth dialog.

Before your application can communicate over Bluetooth, you need to verify that Bluetooth is supported on the device, and if so, ensure that it is enabled.

If Bluetooth is not supported, then you should gracefully disable any Bluetooth features. If Bluetooth is supported, but disabled, then you can request that the user enable Bluetooth without leaving your application. This setup is accomplished in two steps, using the BluetoothAdapter.

  1. Get the BluetoothAdapter

    The BluetoothAdapter is required for any and all Bluetooth activity. To get the BluetoothAdapter, call the static getDefaultAdapter() method. This returns a BluetoothAdapter that represents the device's own Bluetooth adapter (the Bluetooth radio). There's one Bluetooth adapter for the entire system, and your application can interact with it using this object. If getDefaultAdapter() returns null, then the device does not support Bluetooth and your story ends here. For example:

     
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
       
    // Device does not support Bluetooth
    }
  2. Enable Bluetooth

    Next, you need to ensure that Bluetooth is enabled. Call isEnabled() to check whether Bluetooth is currently enable. If this method returns false, then Bluetooth is disabled. To request that Bluetooth be enabled, callstartActivityForResult() with the ACTION_REQUEST_ENABLE action Intent. This will issue a request to enable Bluetooth through the system settings (without stopping your application). For example:

     
    if (!mBluetoothAdapter.isEnabled()) {
       
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult
    (enableBtIntent, REQUEST_ENABLE_BT);
    }

    A dialog will appear requesting user permission to enable Bluetooth, as shown in Figure 1. If the user responds "Yes," the system will begin to enable Bluetooth and focus will return to your application once the process completes (or fails).

    The REQUEST_ENABLE_BT constant passed to startActivityForResult() is a locally defined integer (which must be greater than 0), that the system passes back to you in your onActivityResult() implementation as the requestCode parameter.

    If enabling Bluetooth succeeds, your activity receives the RESULT_OK result code in the onActivityResult()callback. If Bluetooth was not enabled due to an error (or the user responded "No") then the result code isRESULT_CANCELED.

Optionally, your application can also listen for the ACTION_STATE_CHANGED broadcast Intent, which the system will broadcast whenever the Bluetooth state has changed. This broadcast contains the extra fields EXTRA_STATEand EXTRA_PREVIOUS_STATE, containing the new and old Bluetooth states, respectively. Possible values for these extra fields are STATE_TURNING_ONSTATE_ONSTATE_TURNING_OFF, and STATE_OFF. Listening for this broadcast can be useful to detect changes made to the Bluetooth state while your app is running.

Tip: Enabling discoverability will automatically enable Bluetooth. If you plan to consistently enable device discoverability before performing Bluetooth activity, you can skip step 2 above. Read about enabling discoverability, below.


이럴 때 nohup을 사용하면 사용자가 터미널을 종료해도 프로그램이 계속 살아있게 된다.

1.  Nohup
* 정의 : 리눅스, 유닉스에서 쉘스크립트파일(*.sh)을 데몬형태로 실행시키는 프로그램
* Nohup은 리눅스에서 쉘스크립트파일을 데몬형태로 실행시키는 명령어이다.

 - nohup으로 실행을 시키려면 실행파일 권한이 755이상으로 되어있어야 함
 - 명령어 뒤에 '&'를 추가하면 백그라운드로 실행됨 
 - nohup 을 통해 프로그램을 실행시키면 nohup.log 라는 로그 파일 생성
$nohup [실행파일]
$nohup [실행파일] &     // 백그라운드 실행
 

2.  로그 안남기기

$nohup [실행파일] 1>/dev/null 2>&1 &
 
 1. /dev/null  이 표현은 1의 결과를 /dev/null 이라는 파일 속에 넣는다.
    /dev/null로 보내버리면 모든 출력을 없애버린다.
 
 2. &1 이 표현은 2번 파일디스크립터를 1번에 지정된 형식과 동일하게 /dev/null로 지정한다.
     & 은 프로그램을 백그라운드에서 실행하도록 하는 표현이다.
 

3. nohup 종료하기

1. "ps -ef | grep 쉘스크립트파일명"  // 명령으로 데몬형식으로 실행
2. "kill -9 PID번호" // 명령으로 해당 프로세스 종료
 

 



안드로이드 프로그래밍을 하다보면 설정 액티비티를 만들어야 할때가 있습니다.
설정 액티비티들은 리스트로 구성되어있죠.
리스트를 컨버팅하면서 만든다고 해도, 
텍스트만 나오는 row가 있고, 체크박스고 혼재된 row, 입력창이 들어가야하는  row가 있을 수 있습니다.
이럴때 편하게 사용할 수 있는 방법이 있습니다.

바로 PreferenceActivity 를 상속받아서 만드는것이지요.





보통은 아래의 화면처럼 나옵니다.
분류별 항목별로 묶어 분류바를 기준으로 여러 설정들이 보여지죠.
기본적으로는 텍스트, 큰텍스트와 그 밑의 작은 텍스트, 거기에 더해진 체크박스 입니다.

 
 
 
 


사실 직접 이런 화면을 구성해도 되지만, 
특별히 월등한 UI를 제공하고자 하는 목적이 아니라면 쓰라고 있는 기능을 쓰는것이 빠르고 편리합니다.


PreferenceActivity를 사용하기 위해서는 두가지 단계를 거쳐야합니다. 
 1. 레이아웃 작성
 2. 액티비티 작성

사실 일반적인 액티비티들도 모두 레이아웃을 작성하고 액티비티를 작성하기 때문에 그렇게 어렵게 느껴지진 않습니다.
다만, 화면에 어떻게 보여질지, 어떻게 배치할지에 대한 부분을 배제하고 xml레이아웃을 작성할 수 있기때문에 이 방법은 직접 설정 xml레이아웃을 작성하는것보다 빠르고 월등합니다.


 1. 레이아웃 작성

PreferenceActivity에 사용되는 Layout xml은 일반 Activity에 사용하는 xml 과 작성법이 조금 다릅니다.
일반 액티비티에 사용하는 default layout이 LinearLayout 이라면, 
Preference 액티비티에 사용하는 default layout은 PreferenceScreen 입니다.

PreferenceScreen을 기본으로 하는 Layout XML 파일의 작성은 아래와 같이 할 수 있습니다.


PreferenceActivity에 사용할 xml 파일 생성
 
 
 
 


파일을 생성했으면 이제 작성할 차례입니다.

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
     
</PreferenceScreen>

위와같은 코드가 작성되어있습니다.
이제 여기에 필요한 기능들을 추가로 작성합니다.

PreferenceScreen 태그 안에는,
복수의 PreferenceCategory가 들어갈 수 있습니다.
또 PreferenceCategory 안에는 복수의 Preference들이 들어갈 수 있습니다.




이 Preference의 태그의 속성들은 간단하게는, 
"키(KEY)"와 "타이틀(TITLE)" / "키(KEY)"와 "타이틀(TITLE)", 그리고 "값(VALUE)"로 이루어져있습니다.
다른 속성들이 많이 있지만, 이것들만 사용해도 어렵지 않게 설정의 기능들을 구현해 낼 수 있습니다.

저는 간단하게 텍스트로 이루어진 카테고리와, 체크박스로 이루어진 카테고리를 추가해 설정 화면을 만들어보려고 합니다.
결과적으로는 아래와 같은 화면을 만들 수 있습니다.

 
 

안드로이드에서 제공해주는 것만으로도 이렇게 그럴듯한 설정화면을 만들었습니다.

이 설정 화면의 XML 코드는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    android:key="setting_activity_top_title"
    android:title="설정  - http://croute.me/340">
     
    <!-- 설정를 구성하는 Layout XML -->
    <!-- @author croute -->
    <!-- @since 2011.02.25 -->
     
    <PreferenceCategory
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:key="setting_activity_account"
        android:title="어플리케이션 정보">
        <Preference
            android:key="setting_activity_id"
            android:title="어플케이션 이름"
            android:selectable="true" />
        <Preference
            android:key="setting_activity_app_version"
            android:title="어플리케이션 버전"      
            android:selectable="true" />
    </PreferenceCategory>
     
    <PreferenceCategory
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:key="setting_activity_pushalarm"
        android:title="알림 설정">
        <CheckBoxPreference
            android:key="setting_activity_autoalarm"
            android:title="자동알림"
            android:summary="어플리케이션 알림이 종료된 경우 자동으로 재실행"
            android:defaultValue="true"/>
        <CheckBoxPreference
            android:key="setting_activity_alarm_reiceive"
            android:title="알림설정" android:defaultValue="true"/>
    </PreferenceCategory>
     
</PreferenceScreen>

눈여겨 볼점은 Preference를 두가지 사용했다는 것.
키와 타이틀을 지정했다는 것입니다.

여기서는 간단한 화면을 구성하기 위해 Preference와 CheckBoxPreference만을 사용했지만, 
(실제로는 EdittextPreference, ListPreference 등 많은 Preference가 있습니다.)





 2. 액티비티 작성

이제 액티비티로 가봅니다.

PreferenceActivity를 처음작성할 때 달라진 부분은 하나뿐입니다.

보통의 Activity에서 레이아웃을 설정할 때 setContentView(R.layout.파일이름)으로 설정한다면,
PreferenceActivity는 레이아웃을 설정할 때 addPreferencesFromResource(R.layout.파일이름)과 같이 합니다.

setContentView -> addPreferenceFromResource

이렇게 바뀐 것 빼고는 액티비티의 기본 설정에서 크게 달라진것은 없습니다.

화면만 보여주기 위해서 액티비티는 아래와 같이 작성하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package me.croute.preferenceactivity;
 
import android.os.Bundle;
import android.preference.PreferenceActivity;
 
 
public class PreferenceActivityExample extends PreferenceActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.layout.preference_activity_example);
    }
}

하지만 화면만 보여주는 어플리케이션은 없기때문에 이제부터 체크박스 체크/해제에 따른 이벤트 처리,
어플리케이션 이름등을 클릭했을때의 이벤트 처리에 대해서 알아보도록 하겠습니다.


일반적인 Activity라면 클릭이벤트를 OnClickListener로 받습니다.
하지만 PreferenceActivity는 클릭이벤트를 OnPreferenceClickListener로 받아야 합니다.
일반적인 클릭이 아닌, Preference에 대한 클릭이기 때문입니다.


클릭이벤트는 아래와 같이 구현할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package me.croute.preferenceactivity;
 
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
 
public class PreferenceActivityExample extends PreferenceActivity implements OnPreferenceClickListener
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.layout.preference_activity_example);
         
        Preference pAppName = (Preference)findPreference("setting_activity_id");
        Preference pAppVersion = (Preference)findPreference("setting_activity_app_version");
        CheckBoxPreference cbpAutoAlarm = (CheckBoxPreference)findPreference("setting_activity_autoalarm");
        CheckBoxPreference cbpAlarmReceive = (CheckBoxPreference)findPreference("setting_activity_alarm_reiceive");
         
        pAppName.setOnPreferenceClickListener(this);
        pAppVersion.setOnPreferenceClickListener(this);
        cbpAutoAlarm.setOnPreferenceClickListener(this);
        cbpAlarmReceive.setOnPreferenceClickListener(this);
    }
 
    @Override
    public boolean onPreferenceClick(Preference preference)
    {
        // 어플리케이션 이름
        if(preference.getKey().equals("setting_activity_id"))
        {
        }
        // 어플리케이션 버전
        else if(preference.getKey().equals("setting_activity_app_version"))
        {
        }
        // 자동알림
        else if(preference.getKey().equals("setting_activity_autoalarm"))
        {
        }
        // 알림 받기
        else if(preference.getKey().equals("setting_activity_alarm_reiceive"))
        {
        }
        return false;
    }
}

여기서 한가지 주의할점은 Preference의 CheckBoxPreference는 자동으로 체크됨을 잡아내서 자신의 상태를 변경합니다.
(기존의 Activity에서 체크박스를 사용하듯이 하면, 체크 되있던걸 돌리고, 체크 안되야 하는걸 체크하는 사태가 일어납니다.)

구현은 여기까지면 충분히 기본적인 설정의 기능들을 사용할 수 있습니다.

이제 클릭이벤트에 각자에 맞는 기능들을 추가해주면됩니다.



An Introduction to Python’s Flask Framework

\Rating:

An Introduction to Python’s Flask Framework

Tutorial Details
  • Difficulty: Beginner
  • Completion Time: 30 Minutes
  • Reference : Link


Flask is a small and powerful web framework for Python. It’s easy to learn and simple to use, enabling you to build your web app in a short amount of time.

In this article, I’ll show you how to build a simple website, containing two static pages with a small amount of dynamic content. While Flask can be used for building complex, database-driven websites, starting with mostly static pages will be useful to introduce a workflow, which we can then generalize to make more complex pages in the future. Upon completion, you’ll be able to use this sequence of steps to jumpstart your next Flask app.


Flask 설치

Before getting started, we need to install Flask. Because systems vary, things can sporadically go wrong during these steps. If they do, like we all do, just Google the error message or leave a comment describing the problem.

virtualenv 설치

Virtualenv is a useful tool that creates isolated Python development environments where you can do all your development work.

We’ll use virtualenv to install Flask. Virtualenv is a useful tool that creates isolated Python development environments where you can do all your development work. Suppose you come across a new Python library that you’d like to try. If you install it system-wide, there is the risk of messing up other libraries that you might have installed. Instead, use virtualenv to create a sandbox, where you can install and use the library without affecting the rest of your system. You can keep using this sandbox for ongoing development work, or you can simply delete it once you’ve finished using it. Either way, your system remains organized and clutter-free.

It’s possible that your system already has virtualenv. Refer to the command line, and try running:

1
$ virtualenv --version

If you see a version number, you’re good to go and you can skip to this “Install Flask” section. If the command was not found, use easy_install or pip to install virtualenv. If running Linux or Mac OS X, one of the following should work for you:

1
$ sudo easy_install virtualenv

or:

1
$ sudo pip install virtualenv

or:

1
$ sudo apt-get install python-virtualenv

If you don’t have either of these commands installed, there are several tutorials online, which will show you how to install it on your system. If you’re running Windows, follow the “Installation Instructions” on this pageto get easy_install up and running on your computer.

Install Flask

After installing virtualenv, you can create a new isolated development environment, like so:

1
$ virtualenv flaskapp

Here, virtualenv creates a folder, flaskapp/, and sets up a clean copy of Python inside for you to use. It also installs the handy package manager, pip.

Enter your newly created development environment and activate it so you can begin working within it.

1
2
$ cd flaskapp
$ . bin/activate

Now, you can safely install Flask:

1
$ pip install Flask

Setting up the Project Structure

Let’s create a couple of folders and files within flaskapp/ to keep our web app organized.

1
2
3
4
5
6
7
8
9
10
.
.
├── app
│   ├── static
│   │   ├── css
│   │   ├── img
│   │   └── js
│   ├── templates
│   ├── routes.py
│   └── README.md

Within flaskapp/, create a folder, app/, to contain all your files. Inside app/, create a folder static/; this is where we’ll put our web app’s images, CSS, and JavaScript files, so create folders for each of those, as demonstrated above. Additionally, create another folder, templates/, to store the app’s web templates. Create an empty Python file routes.py for the application logic, such as URL routing.

And no project is complete without a helpful description, so create a README.md file as well.

Now, we know where to put our project’s assets, but how does everything connect together? Let’s take a look at “Fig. 1″ below to see the big picture:

Fig. 1

  1. 사용자는 홈페이지에 접속하기 위해 도메인의 Root URL에 요청을 보낸다.
  2. routes.py 가 Python 함수에 URL을 요청한다.
  3. Python 함수가 '/templates' 폴더에서 Web Template을 찾는다.
  4. A web template은 '/static' 폴더에서 이미지, CSS, Javascript와 같은 HTML을 렌더링하는데 필요한 파일을 찾는다.
  5. 렌더링 된 HTML이 routes.py 로 전송된다.
  6. routes.py 가 HTML을 브라우저로 보낸다.

We start with a request issued from a web browser. A user types a URL into the address bar. The request hits routes.py, which has code that maps the URL to a function. The function finds a template in thetemplates/ folder, renders it to HTML, and sends it back to the browser. The function can optionally fetch records from a database and then pass that information on to a web template, but since we’re dealing with mostly static pages in this article, we’ll skip interacting with a database for now.

Now that we know our way around the project structure we set up, let’s get started with making a home page for our web app.


Creating a Home Page

When you write a web app with a couple of pages, it quickly becomes annoying to write the same HTML boilerplate over and over again for each page. Furthermore, what if you need to add a new element to your app, such as a new CSS file? You would have to go into every single page and add it in. This is time consuming and error prone. Wouldn’t it be nice if, instead of repeatedly writing the same HTML boilerplate, you could define your page layout just once, and then use that layout to make new pages with their own content? This is exactly what web templates do!

Web templates are simply text files that contain variables and control flow statements (if..elsefor, etc), and end with an.html or .xml extension.

The variables are replaced with your content, when the web template is evaluated. Web templates remove repetition, separate content from design, and make your application easier to maintain. In other, simpler words, web templates are awesome and you should use them! Flask uses the Jinja2 template engine; let’s see how to use it.

As a first step, we’ll define our page layout in a skeleton HTML document layout.html and put it inside thetemplates/ folder:

app/templates/layout.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
  <head>
    <title>Flask App</title>   
  </head>
  <body>
   
    <header>
      <div class="container">
        <h1 class="logo">Flask App</h1>
      </div>
    </header>
     
    <div class="container">
      {% block content %}
      {% endblock %}
    </div>
     
  </body>
</html>

This is simply a regular HTML file…but what’s going on with the {% block content %}{% endblock %}part? To answer this, let’s create another file home.html:

app/templates/home.html

1
2
3
4
5
6
7
{% extends "layout.html" %}
{% block content %}
  <div class="jumbo">
    <h2>Welcome to the Flask app<h2>
    <h3>This is the home page for the Flask app<h3>
  </div>
{% endblock %}

The file layout.html defines an empty block, named content, that a child template can fill in. The filehome.html is a child template that inherits the markup from layout.html and fills in the “content” block with its own text. In other words, layout.html defines all of the common elements of your site, while each child template customizes it with its own content.

This all sounds cool, but how do we actually see this page? How can we type a URL in the browser and “visit” home.html? Let’s refer back to Fig. 1. We just created the template home.html and placed it in thetemplates/ folder. Now, we need to map a URL to it so we can view it in the browser. Let’s open uproutes.py and do this:

app/routes.py

1
2
3
4
5
6
7
8
9
10
from flask import Flask, render_template
 
app = Flask(__name__)     
 
@app.route('/')
def home():
  return render_template('home.html')
 
if __name__ == '__main__':
  app.run(host='0.0.0.0')

That’s it for routes.py. What did we do?

  1. First. we imported the Flask class and a function render_template.
  2. Next, we created a new instance of the Flask class.
  3. We then mapped the URL / to the function home(). Now, when someone visits this URL, the functionhome() will execute.
  4. The function home() uses the Flask function render_template() to render the home.htmltemplate we just created from the templates/ folder to the browser.
  5. Finally, we use run() to run our app on a local server. We’ll set the debug flag to true, so we can view any applicable error messages if something goes wrong, and so that the local server automatically reloads after we’ve made changes to the code.

We’re finally ready to see the fruits of our labor. Return to the command line, and type:

1
$ python routes.py

Visit http://localhost:5000/ in your favorite web browser.

When we visited http://localhost:5000/routes.py had code in it, which mapped the URL / to the Python function home()home() found the web template home.html in the templates/ folder, rendered it to HTML, and sent it back to the browser, giving us the screen above.

Pretty neat, but this home page is a bit boring, isn’t it? Let’s make it look better by adding some CSS. Create a file, main.css, within static/css/, and add these rules:

static/css/main.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
body {
  margin: 0;
  padding: 0;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  color: #444;
}
 
/*
 * Create dark grey header with a white logo
 */
  
header {
  background-color: #2B2B2B;
  height: 35px;
  width: 100%;
  opacity: .9;
  margin-bottom: 10px;
}
 
header h1.logo {
  margin: 0;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  float: left;
}
 
header h1.logo:hover {
  color: #fff;
  text-decoration: none;
}
 
/*
 * Center the body content
 */
  
.container {
  width: 940px;
  margin: 0 auto;
}
 
div.jumbo {
  padding: 10px 0 30px 0;
  background-color: #eeeeee;
  -webkit-border-radius: 6px;
     -moz-border-radius: 6px;
          border-radius: 6px;
}
 
h2 {
  font-size: 3em;
  margin-top: 40px;
  text-align: center;
  letter-spacing: -2px;
}
 
h3 {
  font-size: 1.7em;
  font-weight: 100;
  margin-top: 30px;
  text-align: center;
  letter-spacing: -1px;
  color: #999;
}

Add this stylesheet to the skeleton file layout.html so that the styling applies to all of its child templates by adding this line to its <head> element:

1
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">;

We’re using the Flask function, url_for, to generate a URL path for main.css from the static folder. After adding this line in, layout.html should now look like:

app/templates/layout.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
  <head>
    <title>Flask</title>   
    <strong><link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}"></strong>
  </head>
  <body>
    <header>
      <div class="container">
        <h1 class="logo">Flask App</h1>
      </div>
     </header>
    
    <div class="container">
      {% block content %}
      {% endblock %}
    </div>
  </body>
</html>

Let’s switch back to the browser and refresh the page to view the result of the CSS.

That’s more like it! Now, when we visit http://localhost:5000/routes.py still maps the URL / to the Python function home(), and home() still finds the web template home.html in the templates/ folder. But, since we added the CSS file main.css, the web template home.html looks in static/ to find this asset, before rendering to HTML and being sent back to the browser.

We’ve achieved a lot so far. We started with Fig. 1 by understanding how Flask works, and now we’ve seen how it all plays out, by creating a home page for our web app. Let’s move on and create an About page.


Creating an About Page

In the previous section, we created a web template home.html by extending the skeleton file layout.html. We then mapped the URL / to home.html in routes.py so we could visit it in the browser. We finished things up by adding some styling to make it look pretty. Let’s repeat that process again to create an about page for our web app.

We’ll begin by creating a web template, about.html, and putting it inside the templates/ folder.

app/templates/about.html

1
2
3
4
5
6
{% extends "layout.html" %}
  
{% block content %}
  <h2>About</h2>
  <p>This is an About page for the Intro to Flask article. Don't I look good? Oh stop, you're making me blush.</p>
{% endblock %}

Just like before with home.html, we extend from layout.html, and then fill the content block with our custom content.

In order to visit this page in the browser, we need to map a URL to it. Open up routes.py and add another mapping:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask, render_template
  
app = Flask(__name__)
  
@app.route('/')
def home():
  return render_template('home.html')
  
@app.route('/about')
def about():
  return render_template('about.html')
  
if __name__ == '__main__':
  app.run(host='0.0.0.0')

We mapped the URL /about to the function about(). Now we can open up the browser and go tohttp://localhost:5000/about and check out our newly created page.


Adding Navigation

Most websites have links to their main pages within the header or footer of the document. These links are usually visible across all pages of a website. Let’s open up the skeleton file, layout.html. and add these links so they show up in all of the child templates. Specifically, let’s add a <nav> element inside the <header> element:

app/templates/layout.html

1
2
3
4
5
6
7
8
9
10
11
12
13
...
<header>
  <div class="container">
    <h1 class="logo">Flask App</h1>
    <strong><nav>
      <ul class="menu">
        <li><a href="{{ url_for('home') }}">Home</a></li>
        <li><a href="{{ url_for('about') }}">About</a></li>
      </ul>
    </nav></strong>
  </div>
</header>
...

Once again, we use the Flask function url_for to generate URLs.

Next, add some more style rules to main.css to make these new navigation elements look good:

app/static/css/main.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
 
/*
 * Display navigation links inline
 */
 
.menu {
  float: right;
  margin-top: 8px;
}
 
.menu li {
  display: inline;
}
 
.menu li + li {
  margin-left: 35px;
}
 
.menu li a {
  color: #999;
  text-decoration: none;
}

Finally, open up the browser and refresh http://localhost:5000/ to see our newly added navigation links.


Conclusion

Over the course of this article, we built a simple web app with two, mostly static, pages. In doing so, we learned a workflow that can be used to create more complex websites with dynamic content. Flask is a simple, but powerful framework that enables you to efficiently build web apps. Go ahead – check it out!


Intro to Flask: Adding a Contact Page

\Rating:

Intro to Flask: Adding a Contact Page

Tutorial Details
  • Difficulty: Beginner
  • Completion Time: 1 Hour
  • Reference : Link

In the previous article in this mini-series, we leveraged Flask to build a simple website that contains “Home” and “About” pages using a generalized workflow that we can apply to other Flask-based web apps. In this lesson, I’ll demonstrate how to add a “Contact” page that allow users to send you messages.

The code used in this article can be found on GitHub. Captions, such as Checkpoint: 05_contact_form, mean that you can switch to the branch named “05_contact_form” and review the code at that point in the article.


Flask Extensions

You can find a full list of extensions in the Flask Extension Registry.

Flask doesn’t come with many features off the shelf, making it easy to pick up and learn. There is no object-relational mapper for database interaction or admin interfaces to add and update content. It only offers a small set of functions, two of which we’ve already used — url_for() andrender_template().

Instead of shipping with extra functionality, Flask’s extension model allows you to add functionality as needed. A Flask extension is a package that adds specific functionality to your app. For example, Flask-SQLAlchemy adds database support to your app, whereas Flask-Login adds login/logout support. You can find a full list of extensions in the Flask Extension Registry.

To create a Contact page, we’ll use Flask-WTF to handle and validate form data and Flask-Mail to email the form data to you.


Flask-WTF

Flask-WTF is an exension that handles and validates form data. What does that mean? Look at the following figure:

Fig. 1

  1. A user issues a GET request for a web page that contains a form.
  2. The user fills in the form.
  3. The user clicks the “Send” button, submitting it to the server via a POST request.
  4. The server validates the information.
  5. If one or more fields do not validate, the web page containing the form loads again with a helpful error message, prompting the user to try again.
  6. If all fields validate, the form information is used in the next step in the pipeline.

A contact page will have fields for the user’s name, email, subject, and message. In Flask, we’ll POST the form to a function inside routes.py. This function is called the form handler. We’ll run a few validation checks, and if any of the input does not pass muster, we’ll refresh the page to display a message that describes the error. Once all validation checks pass, we’ll use the form data for the next step: emailing the message to you, the website owner.

Flask extensions are simple, powerful tools that extend the functionality of your Flask-based app.

That’s how form handling and validation works. Now where do we actually define the form? We could write HTML using the<form> element and set its action attribute to a Python script. The Python script would mirror the form in order to capture each form field and validate the form field data. If we use this strategy, however, we’d essentially define the form twice — once for the front-end and once for the back-end.

It would be great to define the form only once: in the Python script. This is exactly what Flask-WTF allows us to do. We’ll define the form just once in a Python script, and then we’ll let Flask-WTF generate the form’s HTML for us. The point of all of this is to separate presentation from content.

Enough chatter. Let’s code.

Creating a Form

As a first step, let’s get back into the isolated development environment we created last time.

1
2
$ cd flaskapp
$ . bin/activate

Now that we’ve entered and activated our development environment, we can safely install Flask-WTF:

1
$ pip install flask-wtf

Let’s now define the form in a Python script. We already have routes.py, which maps URLs to functions. Let’s not clutter it with unrelated code. Instead, create a new file called forms.py, and place it inside theapp/ folder.

app/forms.py

1
2
3
4
5
6
7
8
from flask.ext.wtf import Form, TextField, TextAreaField, SubmitField
 
class ContactForm(Form):
  name = TextField("Name")
  email = TextField("Email")
  subject = TextField("Subject")
  message = TextAreaField("Message")
  submit = SubmitField("Send")

We just created a form. What did we do? First, we imported a few useful classes from Flask-WTF — the base Form class, a text field, a textarea field for multi-line text input, and a submit button. Next, we created a new class named ContactForm, inheriting from the base Form class. Then we created each field that we want to see in the contact form. Instead of writing <input type="text">Name</input> in an HTML file, you write name = TextField("Name").

Using the Form

Now let’s use our form. We want it to appear when a user visits the contact page. In Flask terms, we want the form to show up in a web template and map a URL to that web template so we can visit it in the browser. This means we need to create a new web template and a new URL mapping. Let’s start by creating a new URL mapping.

This is an action-packed section, and it may be a little confusing. But stick with me and we’ll get through it.

As a first step, open routes.py and import our newly created form by adding from forms import ContactForm at the beginning of the script.

app/routes.py

1
2
from flask import Flask, render_template
from forms import ContactForm

You can prevent a CSRF attack by making sure that the form submission originates from your web app.

Next, configure Flask-WTF to handle a security exploit known as cross-site request forgery (CSRF). In a perfect world, your server would only process forms that belong to your web app. In other words, your server would only handle and validate the forms that you created. However, it is possible for an attacker to create a form on his own website, fill it in with malicious information, and submit it to your server. If your server accepts this malicious information, all sorts of bad things can happen next.

You can prevent a CSRF attack by making sure that the form submission originates from your web app. One way to do this is to keep a unique token hidden inside your HTML <form>tag that cannot be guessed by attackers. When the form POSTs to your server, the token is checked first. If the token does not match, your server rejects the form submission and does not touch the form data. If the token matches, the server proceeds with form handling and validation.

Flask-WTF does all of this with an easy one-liner. Just configure Flask-WTF with a secret key, and Flask-WTF takes care of generating and managing unique tokens for your forms.

app/routes.py

1
2
3
4
5
6
from flask import Flask, render_template, request, flash
from forms import ContactForm
 
app = Flask(__name__)
 
app.secret_key = 'development key'

Here in line six, I set the secret key to ‘development key’. Feel free to make yours more complex, longer, and alphanumeric.

Now that we’ve imported and configured our contact form, we can use it in a URL mapping in routes.py. Let’s go ahead and create that URL mapping.

app/routes.py

1
2
3
4
@app.route('/contact')
def contact():
  form = ContactForm()
  return render_template('contact.html', form=form)

Now when someone visits the URL /contact, the function contact() will execute. Inside contact(), we first create a new instance of our contact form in line three and sent it to a web template namedcontact.html in line four. We will create this web template shortly.

We still have some work to do here though. Figure 1 showed that if a GET request is sent to the server, the web page containing the form should be retrieved and loaded in browser. If the server receives a POST request, a function should capture the form field data and check if it’s valid. In Python terms, this logic can be expressed in an if...else statement. There is a Flask class for distinguishing between GET and POST requests, so let’s start by importing that class at the beginning of routes.py and add the if...else logic to the contact() function.

app/routes.py

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask, render_template, request
.
.
.
@app.route('/contact', methods=['GET', 'POST'])
def contact():
  form = ContactForm()
 
  if request.method == 'POST':
    return 'Form posted.'
 
  elif request.method == 'GET':
    return render_template('contact.html', form=form)

We already imported the Flask class and render_template() in the previous article, so here we import one more Flask class named requestrequest determines whether the current HTTP method is a GET or a POST. Next is the if...else logic to the contact() function (lines 9-13).

In the case of a POST request, a string indicating that the form has been posted will be returned.

This string is a temporary placeholder, and we’ll replace it with real code in the final step of this article. Otherwise, if the request uses GET, we return the web template contact.html that contains the form.

The next step is to create the web template contact.html and put it inside the templates/ folder.

app/templates/contact.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{% extends "layout.html" %}
 
{% block content %}
  <h2>Contact</h2>
  <form action="{{ url_for('contact') }}" method=post>
    {{ form.hidden_tag() }}
 
    {{ form.name.label }}
    {{ form.name }}
 
    {{ form.email.label }}
    {{ form.email }}
 
    {{ form.subject.label }}
    {{ form.subject }}
 
    {{ form.message.label }}
    {{ form.message }}
 
    {{ form.submit }}
  </form>
{% endblock %}

As with home.html and about.html, the contact.html template extends layout.html and fills the ‘content’ block with its own text. We first specify where to send the form data on submission by setting the<form> element’s action attribute to the contact() function we created in routes.py (line five). Next, we let the Jinja2 template engine generate the bulk of the form for us (lines 6-20). We start by inserting a hidden tag in line six to protect against CSRF exploits. Lastly, we add each label and field of the form.

We are now ready to see the result of all our work. Just type the following:

1
$ python routes.py

Then go to http://localhost:5000/contact in your favorite web browser.

The contact page containing the form has loaded. Fill in the form fields and click the "Send" button. You’ll see a page that looks like this:

Awesome! Form submission is working.

Let’s quickly review everything we did in this section:

  • We type in the URL http://localhost:5000/contact into the browser’s address bar.
  • The GET request hits routes.py, where the URL /contact is mapped to the function contact().
  • The function contact() executes, where a variable named form containing a usable instance of theContactForm class is sent to the web template contact.html.
  • contact.html generates the contact form’s HTML.
  • Rendered HTML is sent back to routes.py.
  • routes.py sends the HTML back to the browser and we see the contact page containing the form.
  • We fill in the contact form and submit it by clicking the “Send” button.
  • The POST request hits routes.py, where the URL /contact is mapped to the function contact().
  • The function contact() executes once more, this time following the if...else control flow for the HTTP POST request.
  • The string 'Form posted.' is sent back to the browser, giving us the screen above.

— Checkpoint: 05_contact_form —

This is cool, but the contact form looks ugly. Let’s make it look better by adding some CSS. Open upmain.css and add these rules:

static/css/main.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/* Contact form */
form label {
  font-size: 1.2em;
  font-weight: bold;
  display: block;
  padding: 10px 0;
}
 
form input#name,
form input#email,
form input#subject {
  width: 400px;
  background-color: #fafafa;
  -webkit-border-radius: 3px;
     -moz-border-radius: 3px;
          border-radius: 3px;
  border: 1px solid #cccccc;
  padding: 5px;
  font-size: 1.1em;
}
 
form textarea#message {
  width: 500px;
  height: 100px;
  background-color: #fafafa;
  -webkit-border-radius: 3px;
     -moz-border-radius: 3px;
          border-radius: 3px;
  border: 1px solid #cccccc;
  margin-bottom: 10px;
  padding: 5px;
  font-size: 1.1em;
}
 
form input#submit {
  display: block;
  -webkit-border-radius: 3px;
     -moz-border-radius: 3px;
          border-radius: 3px;
  border:1px solid #d8d8d8;
  padding: 10px;
  font-weight:bold;
  text-align: center;
  color: #000000;
  background-color: #f4f4f4;
  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f4f4f4), color-stop(100%, #e5e5e5));
  background-image: -webkit-linear-gradient(top, #f4f4f4, #e5e5e5);
  background-image: -moz-linear-gradient(top, #f4f4f4, #e5e5e5);
  background-image: -ms-linear-gradient(top, #f4f4f4, #e5e5e5);
  background-image: -o-linear-gradient(top, #f4f4f4, #e5e5e5);
  background-image: linear-gradient(top, #f4f4f4, #e5e5e5);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=#f4f4f4, endColorstr=#e5e5e5);
}
 
form input#submit:hover{
  cursor: pointer;
  border:1px solid #c1c1c1;
  background-color: #dbdbdb;
  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#dbdbdb), color-stop(100%, #cccccc));
  background-image: -webkit-linear-gradient(top, #dbdbdb, #cccccc);
  background-image: -moz-linear-gradient(top, #dbdbdb, #cccccc);
  background-image: -ms-linear-gradient(top, #dbdbdb, #cccccc);
  background-image: -o-linear-gradient(top, #dbdbdb, #cccccc);
  background-image: linear-gradient(top, #dbdbdb, #cccccc);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=#dbdbdb, endColorstr=#cccccc);
}

Switch back to the browser and refresh http://localhost:5000/contact to see the result of the CSS.

This looks much better. Let’s move on to form validation.

— Checkpoint: 06_contact_styling —

Validating Form Data

A user can now visit the URL /contact and fill in the form. But what happens if the user does not properly fill out the form? We need to validate the user input so that it won’t cause problems in later steps.

Form validation is performed by using form validators. Fortunately, Flask-WTF comes with many useful, built-in validators that we can use right away. We’ll put these validators in the ContactForm class definition in forms.py.

The most basic validator is presence, which simply ensures that all form fields are filled in, so let’s start here.

app/forms.py

1
2
3
4
5
6
7
8
from flask.ext.wtf import Form, TextField, TextAreaField, SubmitField, validators, ValidationError
 
class ContactForm(Form):
  name = TextField("Name",  [validators.Required()])
  email = TextField("Email",  [validators.Required()])
  subject = TextField("Subject",  [validators.Required()])
  message = TextAreaField("Message",  [validators.Required()])
  submit = SubmitField("Send")

We start by importing validators and ValidationError from Flask-WTF. This gives us access to Flask-WTF’s built-in validators. Next we add [validators.Required()] to each form field in order to validate its presence. Notice that this validator is inside a Python list, meaning that we can easily add more validators to this list.

Next, let’s require email addresses to match the pattern user@example.com by adding the Email validator to the email field.

app/forms.py

1
2
3
4
5
6
7
8
from flask.ext.wtf import Form, TextField, TextAreaField, SubmitField, validators, ValidationError
 
class ContactForm(Form):
  name = TextField("Name",  [validators.Required()])
  email = TextField("Email",  [validators.Required(), validators.Email()])
  subject = TextField("Subject",  [validators.Required()])
  message = TextAreaField("Message",  [validators.Required()])
  submit = SubmitField("Send")

That does it for our form validations.

— Checkpoint: 07_form_validations —

Looking back at Figure 1, if any validation check fails, the contact page should reload with an error message so that the user can fix the mistake and try again. This error message must only appear when validation fails and disappear when the mistake has been fixed.

Our next step is to send this sort of temporary error message to the user when validation fails. Flask makes this really easy by using its flash() function. Let’s start by opening routes.py and importing Flask’sflash() function at the beginning of the script.

app/routes.py

1
from flask import Flask, render_template, request, flash

After the contact form POSTs to the server, any validation failure should reload the form with a helpful error message. Otherwise, the input data can be used for future processing. Once again, this logic can be expressed in an if...else statement. Let’s add this if...else logic to the contact() function inside theif request.method == 'POST': block.

app/routes.py

1
2
3
4
5
6
7
8
9
10
11
12
13
@app.route('/contact', methods=['GET', 'POST'])
def contact():
  form = ContactForm()
 
  if request.method == 'POST':
    if form.validate() == False:
      flash('All fields are required.')
      return render_template('contact.html', form=form)
    else:
      return 'Form posted.'
 
  elif request.method == 'GET':
    return render_template('contact.html', form=form)

If any validation check fails, form.validate() will be False. The error message All fields are required will be sent to contact.html. Otherwise, we’ll see the temporary placeholder string Form posted, indicating the form has been successfully submitted.

Next, let’s modify contact.html so that it can receive and display these temporary error messages. See the following block:

1
2
3
{% for message in get_flashed_messages() %}
  <div class="flash">{{ message }}</div>
{% endfor %}

The function get_flashed_messages() pulls all flashed messages and returns them. We then simply display each flashed message by using a Jinja2 for loop. Add this code block to contact.html after<h2>Contact</h2> and before the <form> tag.

app/templates/contact.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{% extends "layout.html" %}
 
{% block content %}
  <h2>Contact</h2>
 
  {% for message in get_flashed_messages() %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
   
  <form action="{{ url_for('contact') }}" method=post>
    {{ form.hidden_tag() }}
 
    {{ form.name.label }}
    {{ form.name }}
 
    {{ form.email.label }}
    {{ form.email }}
 
    {{ form.subject.label }}
    {{ form.subject }}
 
    {{ form.message.label }}
    {{ form.message }}
 
    {{ form.submit }}
  </form>
{% endblock %}

Lastly, let’s add a CSS rule in main.css so that flashed error messages look pretty.

main.css

1
2
3
4
5
6
/* Message flashing */
.flash {
  background-color: #FBB0B0;
  padding: 10px;
  width: 400px;
}

Open your browser and visit http://localhost:5000/contact. Leave all the fields blank and click “Send” to test whether form validation and error message flashing work.

This is sweet! We have successfully sent an error message to our contact form if a validation check fails.

— Checkpoint: 08_error_message_flashing —

But we’re not done; we can actually do a little better. Instead of having one generic error message for all failed validation checks, it would be better to have a specific error message for each failed validation check. For example, if the user forgets to fill in the subject field, a specific error message that says Please enter a subject would be flashed. Likewise, if the user forgets to fill in their name, we’d flash a specific error message that says Please enter your name. We can accomplish this pretty easily, so let’s start by writing our specific error messages inside each validator in forms.py.

app/forms.py

1
2
3
4
5
6
7
8
from flask.ext.wtf import Form, TextField, TextAreaField, SubmitField, validators, ValidationError
 
class ContactForm(Form):
  name = TextField("Name",  [validators.Required("Please enter your name.")])
  email = TextField("Email",  [validators.Required("Please enter your email address."), validators.Email("Please enter your email address.")])
  subject = TextField("Subject",  [validators.Required("Please enter a subject.")])
  message = TextAreaField("Message",  [validators.Required("Please enter a message.")])
  submit = SubmitField("Send")

We simply write specific error messages inside each validator. Next, let’s modify contact.html to receive and display these specific error messages. Earlier, we relied on the function get_flashed_messages() to pull flashed error messages, and looped over them to display them. Let’s replace that block with this one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% for message in form.name.errors %}
  <div class="flash">{{ message }}</div>
{% endfor %}
 
{% for message in form.email.errors %}
  <div class="flash">{{ message }}</div>
{% endfor %}
 
{% for message in form.subject.errors %}
  <div class="flash">{{ message }}</div>
{% endfor %}
 
{% for message in form.message.errors %}
  <div class="flash">{{ message }}</div>
{% endfor %}

Here we use the errors attribute for each form field to pull the specific error messages and loop over them using the Jinja2 for loop to display them.

Putting it all together, contact.html now look like this:

app/templates/contact.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{% extends "layout.html" %}
 
{% block content %}
  <h2>Contact</h2>
 
  {% for message in form.name.errors %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
 
  {% for message in form.email.errors %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
 
  {% for message in form.subject.errors %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
 
  {% for message in form.message.errors %}
    <div class="flash">{{ message }}</div>
  {% endfor %}
   
  <form action="{{ url_for('contact') }}" method=post>
    {{ form.hidden_tag() }}
 
    {{ form.name.label }}
    {{ form.name }}
 
    {{ form.email.label }}
    {{ form.email }}
 
    {{ form.subject.label }}
    {{ form.subject }}
 
    {{ form.message.label }}
    {{ form.message }}
 
    {{ form.submit }}
  </form>
{% endblock %}

Switch back to the browser, go to http://localhost:5000/contact, and click “Send”. Be sure to leave all form fields blank.

Perfect! The user now has helpful error messages if he makes a mistake.

— Checkpoint: 09_specific_message_flashing —

We accomplished a lot in this section. We created a contact form from scratch, learned how to protect against CSRF attacks, distinguished between GET and POST requests, enforced form validations, and flashed specific error messages if necessary. We now need to email the message.


Flask-Mail

Flask-Mail is a Flask exension that enables you to send emails from your Flask app. The steps below are similar to those we took to use Flask-WTF.

Let’s start by installing Flask-Mail.

1
$ pip install flask-mail

Configuring Flask-Mail

Next, lets import Flask-Mail into routes.py and configure it so that we can start using it.

app/routes.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, render_template, request, flash
from forms import ContactForm
from flask.ext.mail import Message, Mail
 
mail = Mail()
 
app = Flask(__name__)
 
app.secret_key = 'development key'
 
app.config["MAIL_SERVER"] = "smtp.gmail.com"
app.config["MAIL_PORT"] = 465
app.config["MAIL_USE_SSL"] = True
app.config["MAIL_USERNAME"] = 'contact@example.com'
app.config["MAIL_PASSWORD"] = 'your-password'
 
mail.init_app(app)

First, we import the Message and Mail classes from Flask-Mail (line three). We’ll use the Message class to compose a new email and the Mail class to send the email. Next, we create the mail variable that contain a usable instance of the Mail class (line five).

We then configure Flask-Mail with few SMTP server settings (lines 11-15). I used Gmail’s SMTP server settings here, but you can easily use your favorite email provider. Just search for its SMTP settings and you’ll be set.

For example, if you want to use Yahoo! Mail, just search for “yahoo mail smtp server settings” and update the configuration.

Make sure to enter a real email and password in app.config["MAIL_USERNAME"] andapp.config["MAIL_PASSWORD"], respectively. This will be the account from which you’ll send email.

Finally, we attach mail to our Flask app so that we can start using it (line 17).

You’ve probably seen groups use contact email addresses like contact@example.com orsupport@example.com. If you own your own domain and can create a new contact email address, go ahead and put that email address in app.config["MAIL_USERNAME"]. Otherwise, you can use your personal email address just to see how this works.

Sending an Email

Now that the configuration is complete, let’s compose a new email containing the contact form data and send it. We should only send an email if the form has been submitted and all validation checks pass. This means we need to work inside the if request.method == 'POST': block again. We’ve already added logic inside the if form.validate() == False: block to handle validation failures. If all validation checks pass, form.validate() will be True and the program will enter the else block. Therefore, let’s go ahead and add logic inside the else: block.


app/routes.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route('/contact', methods=['GET', 'POST'])
def contact():
  form = ContactForm()
 
  if request.method == 'POST':
    if form.validate() == False:
      flash('All fields are required.')
      return render_template('contact.html', form=form)
    else:
      msg = Message(form.subject.data, sender='contact@example.com', recipients=['your_email@example.com'])
      msg.body = """
      From: %s <%s>
      %s
      """ % (form.name.data, form.email.data, form.message.data)
      mail.send(msg)
 
      return 'Form posted.'
 
  elif request.method == 'GET':
    return render_template('contact.html', form=form)

We start by composing a new message (line 10). The Message class takes a subject line, a “from” address, and a “to” address. We then collect the contact form’s subject field data with form.subject.data and set it as the new message’s subject line. The email will be sent from the account you configured inapp.config["MAIL_USERNAME"], so that’s what we used here for the from address. The email will be sent to your personal email address so that you can receive and respond to new messages.

Next, we write the email itself (lines 11-14). We include the user’s name, email and message. I use Python’s string formatting operator % to format the email. And finally, we use mail.send(msg) to send the email (line 15).

Let’s see if everything works. Visit http://localhost:5000/contact, fill out each field, and click “Send.” If all goes well, you’ll receive a new email from your Flask app.

— Checkpoint: 10_send_email —

Tidying Up

Our penultimate step is to remove the temporary placeholder string 'Form posted.' with a message thanking the user for his feedback. This message should only appear if our application sends the email. Once again, this logic can be expressed in an if...else statement.

When the contact form has been successfully submitted, we’ll send a success flag from routes.py to contact.html.

We’ll place the if...else logic inside contact.html. If the success flag is set to True, we’ll display the thank you message. Otherwise, we’ll display the contact form.

Let’s start in routes.py inside the contact() function. Replace the temporary placeholder line return 'Form posted.' with return render_template('contact.html', success=True) in order to send a success flag to contact.html. The contact() function now looks like this:

app/routes.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@app.route('/contact', methods=['GET', 'POST'])
def contact():
  form = ContactForm()
 
  if request.method == 'POST':
    if form.validate() == False:
      flash('All fields are required.')
      return render_template('contact.html', form=form)
    else:
      msg = Message(form.subject.data, sender='contact@example.com', recipients=['your_email@example.com'])
      msg.body = """
      From: %s &lt;%s&gt;
      %s
      """ % (form.name.data, form.email.data, form.message.data)
      mail.send(msg)
 
      return render_template('contact.html', success=True)
 
  elif request.method == 'GET':
    return render_template('contact.html', form=form)

Next open contact.html and add the if...else logic. We’ll use Jinja2′s if...else syntax to make this happen.

app/templates/contact.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{% extends "layout.html" %}
 
{% block content %}
  <h2>Contact</h2>
 
  {% if success %}
    <p>Thank you for your message. We'll get back to you shortly.</p>
 
  {% else %}
 
    {% for message in form.name.errors %}
      <div class="flash">{{ message }}</div>
    {% endfor %}
 
    {% for message in form.email.errors %}
      <div class="flash">{{ message }}</div>
    {% endfor %}
 
    {% for message in form.subject.errors %}
      <div class="flash">{{ message }}</div>
    {% endfor %}
 
    {% for message in form.message.errors %}
      <div class="flash">{{ message }}</div>
    {% endfor %}
 
    <form action="{{ url_for('contact') }}" method=post>
      {{ form.hidden_tag() }}
 
      {{ form.name.label }}
      {{ form.name }}
 
      {{ form.email.label }}
      {{ form.email }}
 
      {{ form.subject.label }}
      {{ form.subject }}
 
      {{ form.message.label }}
      {{ form.message }}
 
      {{ form.submit }}
    </form>
 
  {% endif %}
{% endblock %}

Starting in line six, {% if success %} means that if the success flag we sent from routes.py is set toTrue, then display <p>Thank you for your message. We'll get back to you shortly.</p>. Otherwise, follow the {% else %} branch and display the contact form. Jinja2 syntax asks that we close theif...else statement with {% endif %}, so we include that at the end (line 45).

— Checkpoint: 11_success_message —

Finally, let’s visit http://localhost:5000/contact one more time. Fill in each field and click “Send”.

Our last step is to add a navigation link to the contact page. In the previous article, we added these links tolayout.html inside the <header> element. Let’s also do that for the contact page (line eight).

app/templates/layout.html

1
2
3
4
5
6
7
8
9
10
11
12
<header>
  <div class="container">
    <h1 class="logo">Flask App</h1>
    <nav>
      <ul class="menu">
        <li><a href="{{ url_for('home') }}">Home</a></li>
        <li><a href="{{ url_for('about') }}">About</a></li>
        <li><a href="{{ url_for('contact') }}">Contact</a></li>
      </ul>
    </nav>
  </div>
</header>

— Checkpoint: 12_contact_nav_link —

Open up the browser and refresh http://localhost:5000/ to see the newly added navigation link.


Conclusion

In article, we added a contact page that contains a form to our Flask app. Forms appear in several places in web applications, most notably during sign up and login. This workflow can be adapted to meet those needs. In creating a contact page, we learned how to use Flask extensions.

Flask extensions are simple, powerful tools that extend the functionality of your Flask-based app.

Check out the Flask Extension Registry to explore many more extensions that you can integrate into your app.


$ python hello.py 


에서 이미 사용중이라고 에러가 나오면


$ who

$ skill -kill pts/0 




리눅스에 useradd 로 password와 계정들을만든후
vi /etc/passwd 들어가보시면 제일 밑에
님이 만드신 아이디가 잇으면 보일거에요
그중에 root 가 있어요
없을 수도 있지만 상관 없어여
만약에 user 라고 계정을 만들었으면
vi /etc/passwd   에   제 일 밑에  :9999 눌러서 가 보면
user :X:500:500::/home/user:/bin/bash
이런식으로 되있을거에요
다른 건 상관 없구
X : 500:500
에서  앞에 500 이라고 되어있는걸
0 으로 바꿔주시면  root 권한이 주어집니다
user :X:0:500::/home/user:/bin/bash
이렇게 바꿔주시면됩니다  채택 해주세요
이런식으로 하게되면!! root권한을 줄수이싿..

+ Recent posts