본문 바로가기
패스트캠프

[패스트캠퍼스] 파이썬 강의 6주차 장고 ORM

by 리딩파이 2022. 11. 22.
728x90

어느새 6주차다. 5주차부터 장고가 나를 괴롭?히는 반면에 동적 크롤링은 나를 재미나게 해 주었다. 정적 크롤링과는 달리 동적 크롤링은 변하고 있는 페이지와 새로운 페이지를 크롤링하는 것이다. 처음에 조금 헤맸지만 재미난다. 하지만 재미없는 장고가 본강의이기 때문에 장고부터 시작해 보자. 
6주차에는 장고 디비관리, admin 기능 소개,  회원 가입, 로그인-로그아웃, 폼스, 게시판의 구조, 게시판 스타일링 - bootstrap, 템플린 태그, 로그인 만들기, 로그인과 세션, 게시판 만들기, 배포를 위한 장고 설정, 배포하기 이렇게 강의가 진행된다.

■■■ORM(Object Relational Mapping), 객체-관계 매핑
Python 객체와 관계형 데이터베이스 연결
SQL 쿼리를 자동으로 생성함
데이터베이스 데이터 <- 매핑 -> Python Object
객체를 통해 간접적으로 데이터 베이스 핸들링
Django ORM, SQLAlchemy, Pony

■■ORM의 장점
직관적이다.
SQL 학습 시간보다 Business Logic에 더 집중 할 수 있다.
가독성이 올라간다. (Zen of Python)
코드 재사용 및 유지보수가 수월하다.
DBMS 종속성이 줄어든다.
다양한 SQL 환경에서도 같은 ORM으로 DB 작업을 할 수 있다.
SQL Ingection을 막아준다.

■■ORM의 단점
Raw쿼리가 필요한 구간이 반드시 있다.
프로젝트의 복잡도가 올라가면, ORM의 난이도도 올라간다.
프로시저 사용이 수월하지 않다.

■■Django ORM 문법 종류

.get()
.all()
.filter()
.save()
.delete()
.update()
.exists()
.first()
.count()
.select_related()
.prefetch_related()
.order_by()

■■■HTTP Method
GET – 정보를 요청하는 Method (서버 -> 클라이언트)
POST – 정보 입력(생성)을 위한 Method (클라이언트 -> 서버)
DELETE – 정보 삭제 요청을 위한 Method
PATCH – 정보 일부 변경을 위한 Method
PUT – 정보 전체 변경을 위한 Method

예시)
• 페이지를 불러오는 등 정보를 가지고 오는 경우 : GET
• 아이디 패스워드를 입력하여 회원가입을 하는 경우 : POST
• 게시물 삭제등 삭제 요청을 하는 경우 : DELETE
• 유저명을 바꾸는 경우 – PATCH
• 회원 정보 전체를 바꾸는 경우 – PUT

■URLs.py

from shortener.views import index, redirect_test

urlpatterns = [
    path('admin/', admin.site.urls),
    path("",index,name="index"),
    path("redirect",redirect_test)
]

■BASE.html 파일을 루트 폴더의 templates 폴더 안에 생성

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FastCampus Django</title>
</head>
<body>
    {{ welcome_msg }}
</body>
</html>

def index(request):
    user = Users.objects.filter(username="admin").first()
    email = user.email if user else "Anonymous User!"
    print(email)
    print(request.user.is_authenticated)
    if request.user.is_authenticated is False:
        email = "Anonymous User!"
    print(email)
    return render(request,"base.html",{"welcome_msg":"Hello Fastcampus!"})

def redirect_test(request):
    print("Go Redirect")
    return redirect("index")

@csrf_exempt
def get_user(request,user_id):
    print(user_id)
    if request.method == "GET":
        abc = request.GET.get("abc")
        xyz = request.GET.get("xyz")
        user = Users.objects.filter(pk=user_id).first()
        return render(request,'base.html',{'user':user,'params':[abc,xyz]})
    elif request.method == "POST":
        username = request.GET.get('username')
        if username:
            user = Users.objects.filter(pk=user_id).update(username=username)
        return JsonResponse(status=201,data = dict(msg = "You just reached with Post Method!"),safe=False)


http://127.0.0.1:8000/get_user/1?abc=123&xyz=098
?부터는 쿼리스트링 화면에 출력이 된다
admin
123
098

■■■중간 중간에 강의대로 했는데 건너 뛰는 부분이 많아서 따라 가기 바빴다. 나만 이런가?

이제 툴바를 설치하는데 아니나 다를까 오류가 난다. 뭔 강의가 챕터 하나 나갈 때마다 오류가 나냐?...킹받네

■디버깅 툴
requirements.txt에 저장

appdirs==1.4.4
asgiref==3.3.4
black==21.4b
click==7.1.2
Django==3.2
django-debug-toolbar==3.2.1
django-ninja==0.13.0
djangorestframework==3.12.4
mypy-extensions==0.4.3
pathspec==0.8.1
pydantic==1.8.2
pytz==2021.1
regex==2021.4.4
sqlparse==0.4.1
toml==0.10.2
typing-extensions==3.10.0.0

pip install -r requirements.txt

강사가 시키는 대로 다 해봤지만 오류에 오류에 오류...
디버그 툴바 설치를 구글링했다. 이건 강의를 듣는 건지 강의의 버그를 찾아 해결하는 건지...

pip install django-debug-toolbar 이것을 먼저하고 pip install -r requirements.txt를 하니 잘 작동이 된다.

나머지는 settings에서 따라하고 이 중요한 걸 빼먹네...

■urls.py에 다음을 추가

from django.conf import settings

if settings.DEBUG:
    import debug_toolbar
    urlpatterns += [
        url(r'^__debug__/', include(debug_toolbar.urls)),
    ]

include에도 빨간 줄 뜨면 import에 추가해 주자. 강의를 만들면서 해야지 만들어 놓고 강의를 하면 뭐 어쩌라는 거야. 패스트캠프에 전화를 해야 겠다. 오늘은 토요일이니 월요일에...

■관리자 등록하고 몇 개의 plan 추가해 보자
admin.py

from django.contrib import admin
from shortener.models import PayPlan

admin.site.register(PayPlan)

 

728x90

■■■회원가입
■shortener\form.py

from django import forms
from django.contrib.auth.forms import UserCreationForm
from shortener.models import Users

class RegisterForm(UserCreationForm):
    full_name = forms.CharField(max_length=30, required=False,help_text="Optional",label="이름")
    username = forms.CharField(max_length=30, required=False,help_text="Optional")
    email = forms.EmailField(max_length=254,help_text="Required. Inform a valid email address")

    class Meta:
        model = Users
        fields = (
            "username",
            "full_name",
            "email",
            "password1",
            "password2",
        )

■shirinkers\urls.py에

from shortener.views import index, get_user, register

path("register", register, name="register"),
path("__debug__/", include(debug_toolbar.urls)),

■register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FC 장고</title>
</head>
<body>
    <h2>회원가입</h2>
    <h4>{%if msg%}{{msg}}{%endif%}</h4>
    <form method="POST">
        {% csrf_token %} {{ form.as_p }}
        <button type="submit">가입하기</button>
    </form>
    <a href="{% url 'index' %}">홈으로</a>
</body>
</html>

■view.py

from django.contrib.auth import login, authenticate
from shortener.forms import RegisterForm

def register(request):
    if request.method == "POST":
        form = RegisterForm(request.POST)
        msg = "올바르지 않은 데이터 입니다"
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get("username")
            raw_password = form.cleaned_data.get("password1")
            user = authenticate(username=username,password=raw_password)
            login(request,user)
            msg = '회원가입완료'
        return render(request,'register.html',{'form':form,'msg':msg})
    else:
        form = RegisterForm()
    return render(request,'register.html',{'form':form})

■models.py에 수정

class Users(AbstractUser) :
    full_name = models.CharField(max_length=100, null=True)
    pay_plan = models.ForeignKey(PayPlan, on_delete=models.DO_NOTHING, null=True)

강사를 스킵의 제왕이라고 불러야 겠다. 웬만한 것은 스킵한다. 알아서 경로를 설정하고 살아 남으려면 강해야 한다.

■base.html에
<a href="{% url 'register' %}">회원가입</a>를 아래와 같이 추가하자

    <body>
        {{ user }} <br>
        {% for i in params %}
            {{i}}<br>
        {% endfor %}
        <a href="{% url 'register' %}">회원가입</a>
    </body>

■■■Django 로그인, 로그아웃

urls.py 경로 추가경로 추가 그리고 강사는 모듈 import에 대해서는 신경 잘 안쓰므로 import는 알아서...

    path("login", login_view, name="login"),
    path("logout", logout_view, name="logout"),

■view.py

def login_view(request):
    if request.method == "POST":
        form = AuthenticationForm(request,request.POST)
        msg = '가입되어 있지 않거나 로그인 정보가 잘못 되었습니다'
        print(form.is_valid)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            raw_password = form.cleaned_data.get('password')
            user = authenticate(username=username,password=raw_password)
            if user is not None:
                msg = '로그인 성공'
                login(request,user)
        return render(request,'base.html',{'form':form,'msg':msg})
    else:
        form = AuthenticationForm()
        return render(request,'login.html',{'form':form})
def logout_view(request):
    logout(request)
    return redirect('index')

■base.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>FC Django</title>
    </head>
    <body>
        {{ user }} <br>
        {% for i in params %}
            {{i}}<br>
        {% endfor %}{% if user.is_authenticated %}
        <p><a href="{% url 'logout' %}">로그아웃</a></p>
        {% else %}
        <p><a href="{% url 'register' %}">회원가입</a></p>
        <p><a href="{% url 'login' %}">로그인</a></p>
        <!-- <button type="button" onclick="location.href='register' ">회원가입</button> -->
        {% endif %}
    </body>
</html>

강사의 추천대로 requirements.txt 이걸로 하지 말고
■■pip install django-seed
이렇게 설치하는 걸 추천  requirements.txt 해봤자 또 오류가 나겠지?

■Settings.py 의 INSTALLED_APPS 에 아래 내용 추가
'django_seed',

■■python manage.py seed shortener --number=50

No module named 'psycopg2' 오류가 나면 pip install psycopg2 설치해주면 된다.
역시 강의에는 아무런 설명이 없다.

■■python manage.py seed shortener --number=50 다시 이렇게 하면 db에는 50개의 랜덤 유저가 추가 된다. 로그인 할게 아니고 서치만 할거니깐 상관없다.

urls.py 임포트는 알아서 하자

    path('list', list_view, name='list_view'),

■views.py 임포트 해주면 빨간 줄이 없어 질 것이다.

from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required

@login_required
def list_view(request):
    page = int(request.GET.get('p', 1))
    users = Users.objects.all().order_by("-id")
    paginator = Paginator(users, 10)
    users = paginator.get_page(page)

    return render(request, 'boards.html', {'users':users})

■boards.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>FC 장고:회원목록</title>
    </head>
    <body>
    <h2>유저 리스트</h2>
    <h4>{%if msg%}{{msg}}{%endif%}</h4>
    <table class="table table-bordered">
      <thead>
        <th>ID</th>
        <th>유저이름</th>
        <th>이메일</th>
        <th>가입일</th>
        <th>페이플랜</th>
        <th>가격</th>
      </thead>
      {% for u in users %}
      <tr class={% cycle "table-dark" "" %}>
        <td>{{u.id}}</td>
        <td>{{u.username}}</td>
        <td>{{u.email|email_ma:u.id}}</td>
        <td>{{u.date_joined}}</td>
        <td>{{u.pay_plan.name}}</td>
        <td>{{u.pay_plan.price}}</td>
      </tr>
      {% endfor %}
    </table>



    <nav aria-label="Page navigation example">
      <ul class="pagination">
        {% if users.has_previous %}
        <li class="page-item">
          <a class="page-link" href="?p={{users.previous_page_number }}">이전으로</a>
        </li>
        {% else %}
        <li class="page-item">
          <a class="page-link" href="#">이전으로</a>
        </li>
        {% endif %}
        <li class="page-item">
          <a class="page-link" href="?p={{users.number|add:1 }}">{{users.number}}/{{users.paginator.num_pages}}</a>
        </li>
        {% if users.has_next %}
        <li class="page-item">
          <a class="page-link" href="?p={{users.next_page_number }}">다음으로</a>
        </li>
        {% else %}
        <li class="page-item">
          <a class="page-link" href="#">다음으로</a>
        </li>
        {% endif %}
      </ul>
    </nav>
    <p>
      <a href="{% url 'index' %}">홈으로</a>
      <a href="{% url 'register' %}">회원가입</a>
    </p>
    </body>
</html>


■■■부트스트랩 html에 옷을 입히다.

■boards.html 수정 코드

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>FC 장고:회원목록</title>

    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
      integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
      crossorigin="anonymous"
    />
    <script
      src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
      integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
      integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
      integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
      crossorigin="anonymous"
    ></script>
  </head>
  <body>
    <h2>유저 리스트</h2>
    <h4>{% if msg %} {{ msg }} {% endif %}</h4>
    <table class="table table-striped table-dark">
      <thead>
        <th>ID</th>
        <th>유저이름</th>
        <th>이메일</th>
        <th>가입일</th>
        <th>페이플랜</th>
        <th>가격</th>        
      </thead>
      {% for u in users %}
      <tr>
        <td>{{u.id}}</td>
        <td>{{u.username}}</td>
        <td>{{u.email}}</td>
        <td>{{u.date_joined}}</td>
        <td>{{u.pay_plan.name}}</td>
        <td>{{u.pay_plan.price}}</td>
      </tr>
      {% endfor %}
    </table>
    <nav aria-label="Page navigation example">
      <ul class="pagination">
        {% if users.has_previous %}
        <li class="page-item">
          <a class="page-link" href="?p={{users.previous_page_number }}">이전으로</a>
        </li>
        {% else %}
        <li class="page-item">
          <a class="page-link" href="#">이전으로</a>
        </li class="page-item">
        {% endif %}
        <li class="page-item">
          <a class="page-link" href="?p={{users.number|add:1 }}">{{users.number}}/{{users.paginator.num_pages}}</a>
        </li>
        {% if users.has_next %}
        <li class="page-item">
          <a class="page-link" href="?p={{users.next_page_number }}">다음으로</a>
        </li>
        {% else %}
        <li class="page-item">
          <a class="page-link" href="#">다음으로</a>
        </li>
        {% endif %}
      </ul>
    </nav>
    <p>
      <p><a href="{% url 'index' %}">홈으로</a></p>
      <p><a href="{% url 'register' %}">회원가입</a></p>
    </p>
  </body>
</html>

■■■Built-in Django Template Tags 

-{% csrf_token %} 
-{% cycle "a" "b" %} 
-{% extends %} 
-{% block %} 
-{% if %} {% else %} 
-{% for i in items %} 
-{% includes %}

■■■로그인
static폴더를 생성하고 github에서 다운 받아 복사하니 로그인이 예뻐졌다.

이미 가입되었거나 없다는 문구는 보안상 사용하지 않는 것이 좋다고 한다.

6주차 강의는 게시판까지 있는데 너무 많이 헤매서 시간이 부족해서 7주차로 넘겨야 겠다.

반응형

 

728x90
반응형

댓글