GRDB를 사용하다가 생긴 문제점 공유

GRDB를 사용하다보니 JOIN시에 해당 구조체를 가져오는게 쉽지 않다는 문제점을 발견했다.
이를 해결하기 위해, 검색을 하다보니 GRDB를 더 깊게 이해해야 한다는 문제점에 도달했다.

Sqlite3으로 모든 작업을 돌리기로 결정.
이유는 Sqlite3은 내가 맘대로 만들고, 제어도 더 쉽다고 생각했다.
Join과 cascade등의 처리를 위해서는 Sqlite3을 직접 사용하는게 더 좋은것 같다.

일단, Sqltie3으로 개발을 완료하고 나서 추후 nosql을 사용해보겠다.

오랫만에 개인 프로젝트를 하나 해보고 있는데, 트랜드를 다시 익히려다 보니 온갖 삽질을 다 하고 있다..

GRDB를 사용해서 대부분의 것들도 사용이 가능한거 같긴한데, 사전 지식이 꽤 많이 필요한거 같다.

RxSwift 쉽지 않구나.

Swift를 지나고 Reactive를 지나서 가는 여정이 참으로 험난하다.
뭔가 깨달은것 같으면 다시 막힘에 들어서는 반복중이다.

간단한걸 RxSwift MVVM으로 만드는게 이렇게 고통스러울줄이야…
좀 자료를 정리해서 글을 남겨야 할것 같다.

어떻게 글을 정리하고 글을 써야 할지 고민을 좀 해봐야 겠다.
정리가 안된다.. ㅠ

Sqlite3 JOIN

앱 개발자는 DB를 사용할 일이 그렇게 많지 않다.
이참에 대강 알고 있던 JOIN을 정리하려고 한다.

SQLite3는 세 가지 조인 방식을 지원하고 있는데, INNER JOIN, LEFT JOIN, CROSS JOIN 이 있다.
문법은 다음과 같다.

SELECT {col1, alias1.col2...} FROM {tab1} AS {alias1}
{INNER/LEFT/CROSS} JOIN {tab2} as {alias2} ON {결합조건}
-- ON 대신 USING을 쓸 수 있다.
{INNER/LEFT/CROSS} JOIN USING {결합칼럼}

JOIN 샘플코드

select * from TB_BK_ST as BS join TB_BK as BK ON BK.id = BS.id ;
select * from TB_BK_ST as BS INNER JOIN TB_BK as BK ON BK.id = BS.id;
select * from TB_BK_ST as BS LEFT JOIN TB_BK as BK ON BK.id = BS.id;
select * from TB_BK_ST as BS CROSS JOIN TB_BK as BK ON BK.id = BS.id;
select * from TB_BK_ST as BS INNER JOIN TB_BK as BK ON BK.id = BS.id INNER JOIN TB_BK_MM AS MM ON BS.id = MM.id;
select * from TB_BK_ST as BS INNER JOIN TB_BK as BK ON BK.id = BS.id LEFT JOIN TB_BK_MM AS MM ON BS.id = MM.id;
select * from TB_BK_ST as BS INNER JOIN TB_BK as BK ON BK.id = BS.id CROSS JOIN TB_BK_MM AS MM ON BS.id = MM.id;

INNER JOIN

가장 많이 사용되는 JOIN
조건에 맞는 행이 없는 경우는 모두 제욍시킨다.
교집합과 같다. 해당 값이 없는 테이블이 있다면, 이런 데이터들은 결과에서 제외된다.

LEFT JOIN

왼쪽 테이블을 기준으로 알수 없는 값들을 NULL로 채워서 만든다.

CROSS JOIN

양쪽 테이블로 JOIN을 실행한다.
용도는 아직 잘 모르겠다.

RxTableView

RxSwift를 학습은 했는데, 쓸 때마다 기억이 안난다.
아직 익숙해지지 않아서일까.. 그래서 정리해본다.

어떻게 정리해봐야 할까 고민을 해보아야 할것 같다.
일단 방식만 나열하는 걸로…

총 네가지 방식의 사용법이 존재한다.

1. tableView.rx.items 사용하기

    func bindingTableViewItems01() {
        let cities = ["01", "L", "K"]
        let citiesOb: Observable<[String]> = Observable.of(cities)
        citiesOb.bind(to: tableView.rx.items) { (tableView: UITableView, index: Int, element: String) -> UITableViewCell in
            guard let cell = tableView.dequeueReusableCell(withIdentifier: TitleCell.identifier) as? TitleCell else {
                return UITableViewCell()
            }
            cell.title?.text = element
            
            // CellType 변경
            // element 타입을 기준으로 셀을 리턴 가능

            return cell
        }
        .disposed(by: disposeBag)
    }

2. tableView.rx.items(cellIdentifier:String) 사용하기


    func bindingTableViewItems02() {
        let cities = ["02", "L", "K"]
        let citiesOb: Observable<[String]> = Observable.of(cities)
        citiesOb.bind(to: tableView.rx.items(cellIdentifier: TitleCell.identifier))
        { (index: Int, element: String, cell: UITableViewCell) in
            if let cell = cell as? TitleCell  {
                cell.title.text = element
            }
        }
        .disposed(by: disposeBag)
    }
   

3. tableView.rx.items(cellIdendifier:String,cellType:Cell.Type) 사용하기


    func bindingTableViewItems03() {
        let cities = ["03", "L", "K"]
        let citiesOb: Observable<[String]> = Observable.of(cities)
        citiesOb.bind(to: tableView.rx.items(cellIdentifier: TitleCell.identifier, cellType: TitleCell.self))
        { (index: Int, element: String, cell: TitleCell) in
            cell.title.text = element
            cell.textLabel?.text = element + "     " + element
        }
        .disposed(by: disposeBag)
    }

4. tableView.rx.items(dataSource:protocol<RxTableViewDataSourceType, UITabelViewDataSource>) 사용하기


    // tableView를 어떻게 표현할지 미리 지정한 datasource를 사용한 방법
    // pod으로 RxDataSource를 설치
    // TODO: 참고 자료 https://github.com/RxSwiftCommunity/RxDataSources
    func bindingTableViewItems04() {
        // RxDataSource에서는 SectionModelType을 따르는 SectionModel을 이미 구현해 놓았는데, 이것을 사용하면 된다.
        typealias CitySectionModel = SectionModel<String, String>
        typealias CityDataSource = RxTableViewSectionedReloadDataSource<CitySectionModel>
        
        let cities = ["03", "L", "K", "L", "K", "L", "K", "L", "K", "L", "K"]
        let sections = [
            CitySectionModel(model: "first section", items: cities),
            CitySectionModel(model: "second section", items: cities)
        ]
        
        let configureCell: (TableViewSectionedDataSource<CitySectionModel>, UITableView, IndexPath, String) -> UITableViewCell = {
            (datasource, tableView, indexPath, element) in
            guard let cell = tableView.dequeueReusableCell(withIdentifier: TitleCell.identifier, for: indexPath) as? TitleCell else {
                return UITableViewCell()
            }
            cell.title.text = element
            return cell
        }
        let datasource = CityDataSource.init(configureCell: configureCell)

        datasource.titleForHeaderInSection = { datasource, index in
            return datasource.sectionModels[index].model
        }
                
        Observable.just(sections)
            .bind(to: tableView.rx.items(dataSource: datasource))
            .disposed(by: disposeBag)
    }

[Django 07] API 연습

대화식 Python 쉘을 통해 Django API를 사용해볼 시간

$ python manage.py shell

Python 3.8.3 (default, May 18 2020, 11:25:01) 
[GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from polls.models import Choice, Question
>>> Question.objects.all()
<QuerySet []>
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
>>> q
<Question: Question object (None)>
>>> q.save()
>>> q.id
1
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2021, 4, 13, 12, 48, 15, 294472, tzinfo=<UTC>)
>>> q.question_text = "What's up?"
>>> q.save()
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
>>> 

Question object (1)은 객체 표현에 도움이 되지 않는다.
polls/models.py 파일의 Question 모델을 수정하여, __str__() 메소드를 Question과 Choice에 추가해보자

class Question(models.Model):    
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')
    
    def __str__(self):
        return self.question_text

__str__() 메소드를 추가하는 것은 객체의 표현을 대화식 프롬프트에서 편하게 보려는 이유 말고도, Django가 자동으로 생성하는 관리 사이트에서도 객체의 표현이 사용하기 때문이다.

>>> from polls.models import Choice, Question

# Make sure our __str__() addition worked.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>

# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
<QuerySet []>

# Create three choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# Choice objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

모델의 관계에 대한 더 많은 정보는 관련 객체에 접근하기를 참조하세요. API에서 이중 밑줄(__) 을 이용해서 어떻게 필드를 조회할 수 있는지는 필드 조회를 읽어보세요.데이터베이스 API에 대한 자세한 내용을 보려면, 데이터베이스 API 레퍼런스를 읽어보세요.

[Django 06] SQLite 모델 만들기

이제 모델을 만들겁니다.

장고 프로젝트 폴더로 이동합니다.
아래 명령어를 실행합니다.
마이그레이트를 하는건데, 기본 설정을 잡아주는것으로 알고 있습니다.

python3 manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  No migrations to apply.

모델 만들기

모델 : 부가적인 메타데이터를 가진 데이터베이스의 구조(layout)
– 데이터에 관한 단 하나의, 가장 확실한 진리의 원천.
장고는 DRY(Dont Repeat Yourself) 원칙을 따름
http://wiki.c2.com/?DontRepeatYourself

Question과 Choice 모델을 만들거고, Question은 질문과 일자를 가지고 있을 겁니다.
Choice는 두개의 필드를 가집니다.(the text of the choice and a vote tally). 각 Choice는 Question과 관련있습니다.

polls/models.py 파일을 생성합니다.

from django.db import models

# 모델의 활성화
# 앱을 위한 데이터베이스 스키마 생성(CREATE TABLE)
# Question과 Choice 객체에 접근하기 위한 Python 데이터베이스 접근 API 생성

# 현재 프로젝트에게 polls 앱이 설치 되었다는것을 알려야 한다. (앱을 꼈다 뺐다 할수 있다.)
# 앱을 현재의 프로젝트에 포함시키기 위해서는 settings.py > INSTALLED_APPS 설정에 추가해야 한다. 
# PollsConfig 클래스는 polls/apps.py 파일 내에 존재 -> polls.apps.PollsConfig




# django.db.models.Model subclass
# 데이터베이스의 각 Field는 클래스의 인스턴스로 표현
    
# 각 필드 
# CharField는 문자 필드
# DateTimeField는 날짜와 시간(datetime) 필드 등

# 각 필드의 인스턴스 이름(question_text)은 
# 기계가 읽기 좋은 형식(machine-friendly format)의 데이터베이스 필드 이름은
# 필드명을 Python 코드에서 사용할 수 있으며, 데이터베이스에서는 컬럼명으로 사용 

# 필드 클래스의 생성자에 선택적인 첫번째 위치 인수를 전달하여 사람이 읽기 좋은(human-readable)
# 이름을 지정할 수도 있다. -> Question.pub_date

# 몇몇 Field 클래스는 인수가 필요합니다. (CharField의 경우 max_length)
# Field는 다양한 선택적 인수들을 가질수 있다.
class Question(models.Model):    
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

#  ForeignKey를 사용한 관계설정
#  각각의 Choice가 하나의 Question에 관계된다는 정보.
#  question = models.ForeignKey(Question, on_delete=models.CASCADE)
class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

위와 같은 코드를 작성하고 다음 명령을 통해 변경시킨 모델을 migration으로 저장하고 싶다고 장고에게 알려준다.

python3 manage.py makemigrations polls

-> 결과
Migrations for 'polls':
  polls/migrations/0001_initial.py
    - Create model Question
    - Create model Choice

잘 보면 polls 밑에 migrations 폴더와 0001 이름의 모델이 생성되었다.

아래 코드를 실행해보면, 내부적으로 어떤 SQL 문장이 실행되는지 알수 있다.

python3 manage.py sqlmigrate polls 0001

-> 결과테테테ㅌ
BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;

테이블 이름은 앱 이름 + _ + 모델의 이름으로 자동 생성한다.
기본키 id도 자동 생성되고, 재지정이 가능하다.
외래 키 필드명에 “_id” 이름이 자동으로 추가된다. (재지정 가능)

python manage.py migrate

migrate 명령은 아직 적용되지 않은 마이그레이션을 모두 수집해 이를 실행하며(Django는 django_migrations 테이블을 두어 마이그레이션 적용 여부를 추적한다), 이 과정을 통해 모델에서의 변경 사항들과 뎅터베이스의 스키마의 동기화가 이루어진다.

마이그레이션은 매우 기능이 강력하여, 데이터베이스나 테이블을 수정하지 않고 모델의 반복적인 변경을 가능하게 해준다. 동작중인 데이터베이스를 자료 손실없이 업그레이드 하는 데 최적화 되어 있다.

모델의 변경을 만드는 세단계의 지침

1. (moles.py에서) 모델을 변경
2. python manage.py makemigrations 
 -> 변경사항에 대해 마이그레이션을 만듬
3. python manage.py migrate
 -> 변경 사항을 데이터베이스에 적용

manage.py 유틸리티로 어떤 일들을 할 수 있는지 django-admin 문서를 읽어보세요.

[Django 05] settings.py Installed_Apps

장고 settings.py와 관련된 정리 글입니다.

기본적으로는, INSTALLED_APPS는 Django와 함께 딸려오는 다음의 앱들을 포함합니다.


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

[Django 04] 데이터베이스 변경 가능 옵션

이제 장고에 디비를 붙여줄 시간이다.
Sqlite3이 기본으로 탑재되어 있으나, 다른놈들을 사용할수 있다고 한다.

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

너무 깊게 들어가기 시작한게 아닌가 싶기도 한데..
일단 학습을 시작해보자.

공식 지원은 PostgreSQL, MariaDB, MySQL, Oracle, SQLite이다. Sqlite는 다 쓸만한데 복잡한거 하고 싶으면 커스터마이징 해서 알아서 잘 하라고 하네요.
3rd-party database backend로는 CockroachDB, Firebird, Microsoft SQL Server가 있답니다.

뭐 전 마리아가 맘에 드니.. 마리아를 볼께요.

MySQL 또는 MariaDB를 쓸 경우, DB API driver를 설치해야 합니다.(mysqlclient)
관련 자료

https://www.python.org/dev/peps/pep-0249/

https://pypi.org/project/mysqlclient/

생각보다 쉬워보이지 않는군요.
결국 전 그냥 Sqlite를 그냥 사용해 볼께요.

문서 읽다가 지쳐서 나가 떨어지느니.. 그냥 사용해보다가 필요해지면, 그때 더 자세히 볼께요.

[Django 03] include()와 path()

REST API를 쓰고 싶다는 생각이였는데, 어쩌다보니 장고로 들어와버렸다.
파이썬을 공부하다보니 자연스럽게 흘러들어왔는데, 초보이지만 나름 정리를 하면서 내용을 공유하고 싶어서 글을 작성한다.

내용은 튜토리얼을 정리한 수준이다.

어느정도 시간이 지나고 나면 좋은 글이 나올수도 있겠지..

"""mysite URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    # 최상위 URL에서 polls.urls 모듈을 바라보게 설정
    # include() 함수는 다른 URLconf들을 참조할 수 있도록 도와준다.
    # - URL의 그 시점까지 일치하는 부분을 잘라내고, 
    # 남은 문자열 부분을 후속 처리를 위해 include된 URLconf로 전달

    # path()는 route와 view, kwargs, name을 사용 가능
    # - route : URL 패턴을 가진 문자열. 일치하는 패턴을 찾을 때 까지 요청된 URL을 
    #   각 패턴과 리스트의 순서대로 비교한다.
    #   패턴들은 GET이나 POST의 매개 변수들, 혹은 도메인 이름을 검색하지 않는다.
    #   https://www.example.com/myapp/이 요청될 경우, URLconf는 myapp/만 바라본다.
    #   https://www.example.com/myapp/?page=3 이 요청될 경우에도 myapp/만 신경쓴다.
    # - view 
    #   일치하는 패턴을 찾으면, HttpRequest 객체를 첫번째 인수로 하고, 경로로부터 <캡쳐된> 값을
    #   키워드 인수로 하여 특정한 view 함수를 호출
    # - kwargs
    #   view에 전달될 사전형 데이터
    # - name 
    #   URL에 이름을 지으면, 템플릿을 포함한 어디서나 명확하게 참조할 수 있다.
    #   이 강력한 기능을 통해, 단 하나의 파일만 수정해도 project내의 모든 URL 패턴을 바꿀 수 
    #   있도록 도와준다.
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]