728x90

Django REST Framework는 라우터, 인증/권한, 데이터 규격화 (시리얼라이저), 필터/페이지네이션, 캐시, 쓰로틀, 렌더러, 테스트 등의 기능을 제공하며, 대체로 django 에서 제공하는 기능을 감싼 wrapper 형태로 되어있다.

 

https://www.django-rest-framework.org/

 

Home - Django REST framework

 

www.django-rest-framework.org

Home 에서 예시로 나온 코드를 복사하여 붙여넣기하면 기본적인 장고 REST 프레임웍이 동작되는 걸 확인할 수 있다.

 

다음 단계로 python manage.py startapp api 를 하고 나서

아래 그림처럼 파일을 각각 분리하여 serializers.py, views.py, urls.py 로 분리하여 넣는다.

 

프로젝트 수준의 urls.py

from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('api.urls')),
    # path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
 

 

api/serializers.py

Serializer 는 queryset 과 model instance 를 쉽게 JSON 또는 XML 의 데이터 형태로 렌더링 할 수 있게 해준다.

 
# Serializers define the API representation.
from django.contrib.auth.models import User
from rest_framework import serializers
 
 
class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ['url''username''email''is_staff']
 

 

api/views.py

# ViewSets define the view behavior.
from django.contrib.auth.models import User
from rest_framework import viewsets
 
from api.serializers import UserSerializer
 
 
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
 

 

api/urls.py

# Routers provide an easy way of automatically determining the URL conf.
from django.urls import path, include
from rest_framework import routers
 
from api.views import UserViewSet
 
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
 
urlpatterns = [
    path('', include(router.urls)),
]

 

 

 

 

블로그 이미지

Link2Me

,
728x90

장고(Django) 사용자 인증 테스트 하면서 제대로 이해 못해서 삽질하다 기능 구현에 성공했다.

RSA 암호화/복호화 처리하는 코드는 배제하고 URL Redirection 처리 구현 목적으로 테스트했다.

장고에서 기본 제공하는 Custom User Model은 https://docs.djangoproject.com/en/dev/topics/auth/customizing/#a-full-example 을 참고하면 된다.

 

로그인이 안되어 있으면 로그인 화면으로 이동하도록 하는 옵션을 설정한다.

login_url 과 redirect_field_name 을 추가 해준다.

#--- ListView
class PostLV(LoginRequiredMixin,ListView):
    model = Post
    template_name = 'blog/post_all.html'
    context_object_name = 'posts'
    paginate_by = 2
    login_url = '/login/'
    redirect_field_name = 'redirect_to'
 

Mixin 클래스 : 자신의 인스턴스를 만드는 용도보다는 다른 클래스에게 부가 기능을 제공하기 위한 용도로 사용되는 클래스를 의미한다.

 

forms.py

class UserLoginForm(forms.Form):
    username = forms.CharField(
        max_length=32, label='userID',
        widget=forms.TextInput(
            attrs={
                "placeholder""Username",
                "class""form-control"
            }
        ))
    password = forms.CharField(
        label='비밀번호',
        widget=forms.PasswordInput(
            attrs={
                "placeholder""Password",
                "class""form-control"
            }
        ))
 
    def clean(self):
        if self.is_valid():
            username = self.cleaned_data.get('username')
            password = self.cleaned_data.get('password')
            if not authenticate(username=username, password=password):
                raise forms.ValidationError("입력 정보를 확인하세요.")
 

 

 

https://swarf00.github.io/2018/12/10/login.html 를 자세히 읽어보지 않고

대충 복사/붙여넣기 했더니 원하는데로 동작되지 않는다.

# accounts/views.py
from django.conf import settings
from django.contrib.auth import authenticate, login, logout, REDIRECT_FIELD_NAME
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
from django.contrib.sites.shortcuts import get_current_site
from is_safe_url import is_safe_url
from django.urls import reverse_lazy, reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.debug import sensitive_post_parameters
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView, FormView
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect
from django.shortcuts import render, redirect, resolve_url
from .forms import SignUpForm, LoginForm, UserLoginForm
from .RSACipher import *
from .models import User
# from django.contrib.auth.models import User
 
class UserLoginView(SuccessURLAllowedHostsMixin,FormView):
    template_name = 'accounts/userlogin.html'
    form_class = UserLoginForm
    redirect_field_name = REDIRECT_FIELD_NAME
    redirect_authenticated_user = False
    authentication_form = None
    extra_context = None
    # success_url = reverse_lazy("home")
 
    @method_decorator(sensitive_post_parameters())
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        if self.redirect_authenticated_user and self.request.user.is_authenticated:
            redirect_to = self.get_success_url()
            if redirect_to == self.request.path:
                raise ValueError(
                    "Redirection loop for authenticated user detected. Check that "
                    "your LOGIN_REDIRECT_URL doesn't point to a login page."
                )
            return HttpResponseRedirect(redirect_to)
        return super().dispatch(request, *args, **kwargs)
 
    def get_success_url(self):
        url = self.get_redirect_url()
        print("get_success_url : {}".format(url))
        return url or resolve_url(settings.LOGIN_REDIRECT_URL)
 
    def get_redirect_url(self):
        redirect_to = self.request.POST.get(
            self.redirect_field_name, # 1. 폼의 필드 중 next 이름을 가진 필드 값
            self.request.GET.get(self.redirect_field_name, '')
            # 2. query parameter 중 next 이름을 가진 값
        )
        url_is_safe = is_safe_url(
            url=redirect_to,
            allowed_hosts=self.get_success_url_allowed_hosts(),
            require_https=self.request.is_secure(),
        )
        return redirect_to if url_is_safe else ''
 
    def form_valid(self, form):
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password')
        user = authenticate(self.request, username=username, password=password)
        if user is not None and user.is_active:
            login(self.request,user)
        return super().form_valid(form)
 
 

 

 

로그인하면 Home 화면으로 무조건 이동하는 것은 아래와 같이 구현하면 된다.

# accounts/views.py
from django.contrib.auth import authenticate, login, logout, REDIRECT_FIELD_NAME
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
from django.urls import reverse_lazy, reverse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView, FormView
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect, resolve_url
from .forms import SignUpForm, LoginForm, UserLoginForm
from .RSACipher import *
from .models import User
# from django.contrib.auth.models import User
 
 
class UserLoginView(SuccessURLAllowedHostsMixin,FormView):
    template_name = 'accounts/userlogin.html'
    form_class = UserLoginForm
    success_url = reverse_lazy("home")
 
    def form_valid(self, form):
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password')
        user = authenticate(self.request, username=username, password=password)
        if user is not None and user.is_active:
            login(self.request,user)
        return super().form_valid(form)
 

 

게시글 List를 클릭할 때 인증되어 있지 않으면 인증 URL 로 이동하고, 인증 완료후 원래 게시글 List 화면으로 이동하는 기능을 구현하려고 엄청 삽질을 좀 했다.

세션 처리를 하면 금방 해결될 사항이었는데 말이다.

# accounts/views.py
from django.conf import settings
from django.contrib.auth import authenticate, login, logout, REDIRECT_FIELD_NAME
from django.urls import reverse_lazy, reverse
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView, FormView
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect, resolve_url
from .forms import SignUpForm, LoginForm, UserLoginForm
from .models import User
# from django.contrib.auth.models import User
 
 
class UserLoginView(FormView):
    template_name = 'accounts/userlogin.html'
    form_class = UserLoginForm
    # success_url = reverse_lazy("home")
 
    def form_valid(self, form):
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password')
        user = authenticate(self.request, username=username, password=password)
        if user is not None and user.is_active:
            login(self.request,user)
        return super().form_valid(form)
 
    def get_context_data(self**kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.GET.get("redirect_to"):
            redirect_to = self.request.GET.get("redirect_to")
            self.request.session['redirect_to'= redirect_to
        return context
 
    def get_success_url(self):
        url = self.request.session.get('redirect_to')
        return url or resolve_url(settings.LOGIN_REDIRECT_URL)
 

ㅇ View : 모든 클래스형 뷰의 기본이 되는 최상위 뷰. 모든 클래스형 뷰는 이 View 클래스를 상속받는다.

ㅇ TemplateView : 단순하게 화면에 보여줄 템플릿 파일을 처리하는 정도의 간단한 뷰

ㅇ FormView : 폼을 보여주기 위한 뷰. form_class, template_name, success_url 속성 필요하다.

   만약 검색 결과를 같은 페이지에서 보여주고자 한다면, success_url 속성 지정은 생략한다.

   form_class 는 FormView, CreateView, UpdateView 에서 사용한다.

ㅇ CreateView : 새로운 레코드를 생성해서 테이블에 저장해주는 뷰. FormView의 기능을 포함하고 있다.

    폼을 만들 때 사용할 필드를 fields 속성으로 정의한다.

    fields 는 CreateView, UpdateView 에서 사용한다.

ㅇ get_context_data(**kwargs) :  모든 제네릭 뷰에서 사용하는 메소드이다.

    템플릿에서 사용할 context 데이터를 반환한다.

    뷰에서 템플릿 파일에 넘겨주는 context 데이터를 추가하거나 변경할 수 있다.

ㅇ 모델 폼(ModelForm) : 모델에 정의한 필드를 참조해서 모델 폼을 만드는 역할을 한다.

 

 

블로그 이미지

Link2Me

,
728x90

RSA 공개키, 개인키 생성 방법

# 리눅스 SSH 에서 RSA 공개키, 개인키 생성방법
mkdir -/home/rsa/key/
cd /home/rsa/key/
 
# Private Key 생성
openssl genrsa -out rsa_pri.pem 1024
 
# Public Key 생성
openssl rsa -pubout -in rsa_pri.pem -out rsa_pub.pem
 

 

rsa_key.php

Javascript 에서 RSA 암호화를 하고, PHP에서 복호화 시 아래 주석처리한 $key 부분을 주석처리 안해도 잘 동작된다.

그런데 Python 에서는 동작이 잘 안되더라. Python 에서도 동작되도록 Key 변수를 약간 수정했다.

<?php
function get_publickey() {
    // 경로 : 절대경로로 설정 필요
    $rsakeyfile = '/home/rsa/key/rsa_pub.pem';
    
    $fp = fopen($rsakeyfile,"r");
    $key = "";
    while(!feof($fp)) {
        $key .= fgets($fp,4096);
    } 
    fclose($fp);
 
    $key = preg_replace('/\r\n|\r|\n/','\n',$key);
    //$key = str_replace('-----BEGIN PUBLIC KEY-----','',$key);
    //$key = str_replace('-----END PUBLIC KEY-----','',$key);
    return $key;
 
function get_privatekey() {
    // 경로 : 절대경로로 설정 필요
    $rsakeyfile = '/home/rsa/key/rsa_pri.pem';
    
    $fp = fopen($rsakeyfile,"r");
    $key = "";
    while(!feof($fp)) {
        $key .= fgets($fp,4096);
    } 
    fclose($fp);
 
    $key = preg_replace('/\r\n|\r|\n/','\n',$key);
    //$key = str_replace('-----BEGIN RSA PRIVATE KEY-----','',$key);
    //$key = str_replace('-----END RSA PRIVATE KEY-----','',$key);
    return $key;
 
?>

 

login.html

pubkey는 예시로 일부만 발췌한 것이라 이 코드를 그대로 복사하면 동작되는게 아니다.

위에서 PHP코드로 발췌한 공개키를 복사하여 붙여녛기 해야 동작한다.

{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
 
{% block content %}
    <form method="POST" id="post-form">
        {% csrf_token %}
        {% bootstrap_form form %}
        <button type="submit" class="btn btn-primary">로그인</button>
        <a href="{% url 'home' %}">
            <button type="button" class="btn btn-warning">취소</button>
        </a>
    </form>
    <script type="text/javascript" src="{% static 'js/jsencrypt.min.js' %}"></script>
    <script>
        $(document).on('submit''#post-form'function (e) {
            e.preventDefault();
            var plainpw = $("input[name=password]").val();
            var pubkey = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCQAB\n-----END PUBLIC KEY-----";
            var crypt = new JSEncrypt();
            crypt.setPrivateKey(pubkey);
            var password = crypt.encrypt(plainpw);
 
            $.ajax({
                type: 'POST',
                url: '{% url 'login' %}',
                data: {
                    username: $("input[name=username]").val(),
                    password: password,
                    csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                    action: 'post'
                },
                success: function (json) {
                    console.log(json);
                    if (json.data == 1) {
                        window.location.href = '/';
                        alert(json.message);
                    } else if (json.data == 0) {
                        alert(json.message);
                    }
                },
                error: function (xhr, errmsg, err) {
                    alert('에러가 발생했습니다.');
                    console.log(xhr.status + ": " + xhr.responseText);
                }
            });
        });
    </script>
{% endblock content %}
 

 

 

Javascript 에서 암호화한 비밀번호를 views.py 파일에서 복호화한다.

# accounts/views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
from .forms import SignUpForm, LoginForm
from .RSACipher import *
 
def login_view(request):
    context = {}
 
    if request.method == "POST":
        form = LoginForm(request.POST)
        if form.is_valid():
            # 유효성 검사, 내부적으로 form.clean()을 실행한다.
            username = form.cleaned_data.get("username")
            enc_password = form.cleaned_data.get("password")
            password = RSACipher().decrypt(enc_password)
 
            try:
                user = User.objects.get(username=username)
                if check_password(password, user.password):
                    access = authenticate(username=username, password=password)
                    login(request, access)
                    context['data'= '1'
                    context['message'= '로그인 되었습니다.'
                else:
                    context['data'= '0'
                    context['message'= '입력 정보를 확인하세요.'
                    # context['message'] = '비밀번호가 다릅니다.'
                return JsonResponse(context)
            except User.DoesNotExist:  # 대문자 User 임에 주의
                # 가입된 회원인지 미가입된 회원인지 알 수 없는 메시지 출력이 중요
                context['data'= '0'
                context['message'= '입력 정보를 확인하세요.'
                return JsonResponse(context)
        else:
            context['form'= form
    else:
        form = LoginForm()
        context['form'= form
 
    return render(request, "accounts/login.html", context)
 
 
def logout_view(request):
    logout(request)
    return redirect("home")
 

 

 

라이브러리 설치

pip install pycryptodomex (for window)

pip install pycryptodome (for linux)

 

 

RSACipher 클래스는 아래 게시글을 참조하여 직접 구현하면 될 것이다.

https://stackoverflow.com/questions/30056762/rsa-encryption-and-decryption-in-python

 

RSA encryption and decryption in Python

I need help using RSA encryption and decryption in Python. I am creating a private/public key pair, encrypting a message with keys and writing message to a file. Then I am reading ciphertext from...

stackoverflow.com

https://pycryptodome.readthedocs.io/en/latest/src/cipher/pkcs1_v1_5.html

 

PKCS#1 v1.5 encryption (RSA) — PyCryptodome 3.14.0 documentation

A cipher object PKCS115_Cipher.

pycryptodome.readthedocs.io

 

 

Custom User Model 기반으로 jQuery ajax 처리하는 전체 코드는 https://github.com/jsk005/PythonDjango/tree/main/customUserModel2 를 참조하면 도움될 것이다.

이 코드에서 RSACipher 클래스의 함수 코드 일부를 삭제했으나, 공부해서 충분히 해결할 수 있을 것으로 본다.

블로그 이미지

Link2Me

,
728x90

장고에서 기본 제공하는 회원 로그인 기능과 회원가입 Form 기반 코드를 테스트하고 적어둔다.

기본 제공하는 것이라서 필요할까 싶어서 적어두지 않을까 하다가 언젠가는 필요할 수도 있을지 몰라 몇가지 기본적인 것만 적어둔다.

 

전체 소스코드 : https://github.com/jsk005/PythonDjango/tree/main/formProject

 

GitHub - jsk005/PythonDjango

Contribute to jsk005/PythonDjango development by creating an account on GitHub.

github.com

 

 

import os
from pathlib import Path
 
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 3rd party app
    'bootstrap4',  # add, pip install django-bootstrap4
    # local app
    'accounts.apps.AccountConfig',  # add
]
 
 
LANGUAGE_CODE = 'en-us'
 
TIME_ZONE = 'Asia/Seoul'
 
USE_I18N = True
 
USE_TZ = False
 
 
STATIC_URL = 'static/'
MEDIA_URL = 'media/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
    os.path.join(BASE_DIR, 'media'),
]
 
 
# 로그인 성공후 이동하는 URL
LOGIN_REDIRECT_URL = '/'
 

 

 

urls.py

 
# project urls.py
from django.contrib import admin
from django.urls import path
from accounts import views
from .views import homescreen_view
from django.contrib.auth import views as auth_views
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', homescreen_view, name='home'),
    path('signup/', views.register, name='signup'),
    path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'),
    path('logout/', views.logout_view, name='logout'),
]
 

 

views.py

# project views.py
from django.shortcuts import render
 
def homescreen_view(request):
    context = {}
    return render(request, "home.html", context)
 

 

 

forms.py

회원 칼럼을 마음껏 추가하는 것은 안된다.

회원 칼럼을 원하는대로 추가하려면 Custom User Model를 사용해야 한다.

# accounts/forms.py
from django import forms
from django.contrib.auth.models import User
 
class UserForm(forms.ModelForm):
    class Meta:
        model = User
        # 장고 기본 로그인 사용으로 fields 가 제한적이다.
        # re-password 위젯을 추가해봤으나 동작하지 않더라.
        fields = ['username''email''password',]
        widgets = {
            'username': forms.TextInput(attrs={
                'class'"form-control",
                'style''max-width: 300px;',
                'placeholder''userID'
            }),
            'email': forms.EmailInput(attrs={
                'class'"form-control",
                'style''max-width: 300px;',
                'placeholder''Email'
            }),
            'password': forms.PasswordInput(attrs={
                'class'"form-control",
                'style''max-width: 300px;',
                'placeholder''비밀번호'
            }),
 
        }
        labels = {
            'username''userID',
            'email''이메일',
            'password''비밀번호',
        }
 
    # 글자수 제한
    def __init__(self*args, **kwargs):
        super(UserForm, self).__init__(*args, **kwargs)
        self.fields['username'].widget.attrs['minlength'= 6
        self.fields['username'].widget.attrs['maxlength'= 15
 

 

 

views.py

회원가입 처리 부분이다. 회원 가입 도중에 중복된 ID가 있다고 모든 입력 폼을 다시 입력해야 하는 불편함을 없애고자 jQuery Ajax 처리를 했다.

회원 가입 완료되면, return render(request, 'home.html', context) 으로 처리해도 된다.

패스워드 입력체크는 jQuery 에서 하도록 했다.

# accounts/views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt
from .forms import UserForm
 
@csrf_exempt
def register(request, *args, **kwargs):
    user = request.user
    context = {}
 
    if user.is_authenticated:
        # return HttpResponse("You are already authenticated as " + str(user.email))
        return render(request,'home.html',context)
 
    if request.POST:
        form = UserForm(request.POST)
        if form.is_valid():
            # form.save()
            account = User.objects.create_user(**form.cleaned_data)
            login(request, account)
            # return render(request, 'home.html', context)
            context['data'= '1'
            context['message'= '회원 가입 완료'
            return JsonResponse(context)
        else:
            context['data'= '0'  # 기존 가입된 회원
            context['message'= '이미 가입된 회원입니다'
            return JsonResponse(context)
    else:
        form = UserForm()
        context['form'= form
 
    return render(request, 'accounts/register.html', context)
 
 
def logout_view(request):
    logout(request)
    return redirect("home")
 

 

register.html

{% bootstrap_form form %} 과 views.py 의 register 함수의 context['form'] = form 은 굵은 색깔 부분이 같아야 한다.

비밀번호 확인은 forms.py 에서 추가가 안되어서 별도로 추가했다.

url: '{% url 'signup' %}'  과 urls.py 의 path('signup/', views.register, name='signup'), 은 굵은 색깔 부분이 같아야 한다.

jQuery ajax 는 원래 파이썬이 처리하는 기능을 가로채서 처리하므로 csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val() 와 action: 'post' 을 별도 추가해야 동작한다.

{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
 
{% block content %}
    <form method="POST" id="post-form">
        {% csrf_token %}
        {% bootstrap_form form %}
        <div class="form-group">
            <label for="id_repasswd">비밀번호 확인</label>
            <input type="password" name="repasswd" class="form-control" style="max-width: 300px;" required="" id="id_repasswd">
        </div>
        <button type="submit" class="btn btn-primary">가입</button>
        <a href="{% url 'home' %}">
            <button type="button" class="btn btn-warning">취소</button>
        </a>
    </form>
    <script type="text/javascript" src="{% static 'js/user/passwd_checker.js' %}"></script>
    <script>
        $(document).on('submit''#post-form'function (e) {
            e.preventDefault();
            const pwd = $("input[name=password]");
            const repwd = $("input[name=repasswd]");
            if (PasswordChk(pwd, repwd) == falsereturn false;
            $.ajax({
                type: 'POST',
                url: '{% url 'signup' %}',
                data: {
                    username: $("input[name=username]").val(),
                    email: $("input[name=email]").val(),
                    password: $("input[name=password]").val(),
                    csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                    action: 'post'
                },
                success: function (json) {
                    if (json.data == 1) {
                        window.location.href = '/';
                        alert(json.message);
                    } else if (json.data == 0) {
                        alert(json.message);
                    }
                },
                error: function (xhr, errmsg, err) {
                    alert('에러가 발생했습니다.');
                    console.log(xhr.status + ": " + xhr.responseText);
                }
            });
        });
    </script>
{% endblock content %}
 

 

 

로그인 처리는 장고가 기본 제공하는 기능을 이용하였기 때문에 jQuery ajax 코드를 추가하면 에러가 발생한다.

jQuery ajax 처리하려면, 별도로 views.py 에서 login_view 와 같은 함수를 구현하고 login.html 에서 ajax 코드를 추가해야 한다.

 

 

 

 

블로그 이미지

Link2Me

,
728x90

장고(django)가 기본 제공하는 유저 모델(User Model)이 아닌 커스텀 유저 모델(Custom User Model)을 활용 방법이다.

 

제대로 된 방법 찾으려고 엄청 삽질을 한 끝에 이해하고 테스트한 결과를 적어둔다.

구글링을 해도 제대로 이해가 잘 안되는 것은 설명이 부족해서 인지 이해가 부족해서인지 본인 스스로 판단해야 한다.

 

https://link2me.tistory.com/2109 에 게시한 내용은 장고 기본 User Model 과는 별도의 User Model를 생성해서 별개로 로그인하는 방법이다.

 

이번 주제는 장고 기본 User Model 대신에 원하는 칼럼을 마음껏 추가할 수 있는 Custom User Model 이다.

구글링을 하면 장고 기본 User Model을 약간 수정하는 정도를 다루는 방법이 소개되어 있기도 하다.

 

Custom User Model을 구현하기 위해서는 BaseUserManagerAbstractBaseUser 클래스를 상속받아 새롭게 구현해야 한다. 여기서 BaseUserManager는 유저를 생성하는 역할을 하는 헬퍼 클래스이고, AbstractBaseUser는 실제 모델이 상속받아 생성하는 클래스이다.

 

기본 설치사항

터미날에서 아래 사항을 실행한다.

python manage.py startapp account
python manage.py startapp blog
mkdir static
mkdir media
pip install django-bootstrap4
pip install pillow

 

settings.py 수정사항

import os
from pathlib import Path
 
ALLOWED_HOSTS = ['*']
 
if DEBUG:
    EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # development only
 
AUTH_USER_MODEL = 'account.Account'
AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.AllowAllUsersModelBackend',
    'account.backends.CaseInsensitiveModelBackend',
    )
 
# Application definition
 
INSTALLED_APPS = [
    # built in django app
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 3rd party app
    'bootstrap4',  # add, pip install django-bootstrap4
    # local app
    'account.apps.AccountConfig',  # add
    'blog.apps.BlogConfig'# add
 
]
 
DATABASES = {
    'default': {
        'ENGINE''django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
        # 'ENGINE': 'django.db.backends.mysql', # mysqlclient librarly 설치
        # 'NAME': 'pythondb',
        # 'USER': 'root',
        # 'PASSWORD': 'autoset', # mariaDB 설치 시 입력한 root 비밀번호 입력
        # 'HOST': 'localhost',
        # 'PORT': ''
    }
}
 
 
LANGUAGE_CODE = 'en-us'
# 수정
TIME_ZONE = 'Asia/Seoul'
 
USE_I18N = True
# 수정
USE_TZ = False
 
 
STATIC_URL = 'static/'
MEDIA_URL = 'media/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
    os.path.join(BASE_DIR, 'media'),
]
 
BASE_URL = "http://127.0.0.1:8000"
 

 

models.py

테이블 구성요소를 작성한다.

 

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
 
class MyAccountManager(BaseUserManager):
    # 일반 user 생성, username 이 userID를 의미함
    def create_user(self, email, username, name, password=None):
        if not email:
            raise ValueError("Users must have an email address.")
        if not username:
            raise ValueError("Users must have an userID.")
        if not name:
            raise ValueError("Users must have an name.")
        user = self.model(
            email = self.normalize_email(email),
            username = username,
            name = name
        )
        user.set_password(password)
        user.save(using=self._db)
        return user
 
    # 관리자 User 생성
    def create_superuser(self, email, username, name, password):
        user = self.create_user(
            email = self.normalize_email(email),
            username = username,
            name = name,
            password=password
        )
        user.is_admin = True
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user
 
class Account(AbstractBaseUser):
    email       = models.EmailField(verbose_name='email', max_length=60, unique=True)
    username    = models.CharField(max_length=30, unique=True)
    name        = models.CharField(max_length=40null=False, blank=False)
    create_at = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
    last_login  = models.DateTimeField(verbose_name='last login', auto_now=True)
    is_admin    = models.BooleanField(default=False)
    is_active   = models.BooleanField(default=True)
    is_staff    = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
 
    object = MyAccountManager()  # 헬퍼 클래스 사용
 
    USERNAME_FIELD = 'email'  # 로그인 ID로 사용할 필드
    REQUIRED_FIELDS = ['username''name'# 필수 작성 필드
 
    def __str__(self):
        return self.username
 
    def has_perm(self, perm, obj=None):
        return self.is_admin
 
    def has_module_perms(self, app_lable):
        return True
 

 

여기까지 하고 나서 아래 두줄을 터미널 창에서 실행한다.

python manage.py makemigrations
python manage.py migrate 

 

admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from account.models import Account
 
class AccountAdmin(UserAdmin):
    # 관리자 화면에 보여질 칼럼 지정
    list_display = ('email','username','name','create_at','last_login','is_admin','is_staff')
    search_fields = ('email''username''name')
    readonly_fields = ('id''create_at''last_login')
 
    filter_horizontal = ()
    list_filter = ()
    fieldsets = ()
 
admin.site.register(Account, AccountAdmin)
 

 

Templates

Layout 구성을 위해서는 Bootstrap Template를 받아서 활용하는 것이 좋다.

https://github.com/mdbootstrap/Bootstrap-4-templates 에서 템플릿을 받아서 static 폴더에 저장한다.

https://startbootstrap.com/previews/sb-admin-2 에서 무료 템플릿을 받아서 static 폴더에 저장한다.

둘 중 선택하거나 그 외 다른 템플릿을 선택해도 된다.

 

 

base.html

{% load static %}
 
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.11.2/css/all.css">
    <!-- Bootstrap core CSS -->
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
    <!-- Material Design Bootstrap -->
    <link rel="stylesheet" href="{% static 'css/mdb.min.css' %}">
    <!-- Your custom styles (optional) -->
    <link rel="stylesheet" href="{% static 'css/style.min.css' %}">
 
    <!-- SCRIPTS -->
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'js/popper.min.js' %}"></script>
    <script src="{% static 'js/jquery.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/bootstrap.min.js' %}"></script>
    <!-- MDB core JavaScript -->
    <script type="text/javascript" src="{% static 'js/mdb.min.js' %}"></script>
    <script type="text/javascript">
        // Animations initialization
        new WOW().init();
    </script>
 
    {% block head %}
    {% endblock %}
</head>
<body>
<header>
    {% include 'header.html' %}
</header>
<!--Main layout-->
<main class="mt-5 pt-5">
    <div class="container">
        {% block content %}
        {% endblock %}
    </div>
</main>
 
<!-- SCRIPTS -->
</body>
</html>

 

header.html

수정해서 사용할 영역이며, 완벽한 예시는 아니다.

 
{% load static %}
 
<!-- Navbar -->
<nav class="navbar fixed-top navbar-expand-lg navbar-light white scrolling-navbar">
    <div class="container">
 
        <!-- Brand -->
        <a class="navbar-brand waves-effect" href="#">
            <strong class="blue-text">Home</strong>
        </a>
 
        <!-- Collapse -->
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
 
        <!-- Links -->
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
 
            <!-- Left -->
            <ul class="navbar-nav mr-auto">
                <li class="nav-item">
                    <a class="nav-link waves-effect" href="#" target="_blank">About MDB</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link waves-effect" href="#" target="_blank">Free
                        tutorials</a>
                </li>
            </ul>
 
            <form class="search-bar justify-content-start" onsubmit="return executeQuery();">
                <input type="text" class="form-control" name="q" id="id_q_large" placeholder="Search...">
            </form>
 
            <!-- Right -->
            <ul class="navbar-nav nav-flex-icons">
                {% if request.user.is_authenticated %}
                    <li class="nav-item">
                        <a href="{% url 'logout' %}" class="nav-link waves-effect" title="로그아웃">
                            <i class="fas fa-sign-out-alt"></i>
                        </a>
                    </li>
                {% else %}
                    <li class="nav-item">
                        <a href="{% url 'login' %}" class="nav-link waves-effect" title="로그인">
                            <i class="fas fa-sign-in-alt"></i>
                        </a>
                    </li>
                    <li class="nav-item">
                        <a href="{% url 'register' %}" class="nav-link waves-effect" title="회원가입">
                            <i class="far fa-registered"></i>
                        </a>
                    </li>
                {% endif %}
            </ul>
 
        </div>
 
    </div>
</nav>
<!-- Navbar -->
<script type="text/javascript">
    function executeQuery() {
        var query = ""
        query = document.getElementById('id_q_small').value;
        if (query == ""){
            query = document.getElementById('id_q_large').value;
        }
        {#window.location.replace("{% url 'search' %}?q=" + query)#}
        return false
    }
</script>
 

 

 

home.html

{% extends "base.html" %}
{% load static %}
{% block title %}Home{% endblock %}
 
{% block content %}
 
    <div>Welcome to python django world!</div>
    {% if request.user.is_authenticated %}
    <div>{{ user.name }}</div>
    <div>{{ user }}</div>
    {% endif %}
 
{% endblock %}

 

register.html

 
{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
{% block title %}회원 가입{% endblock %}
 
{% block content %}
    <div class="row justify-content-center">
        <form method="post" class="text-center border border-light p-5">
            {% csrf_token %}
            <class="h4 mb-4">회원 가입</p>
            <div class="form-group">
                <input type="email" name="email" id="inputEmail" class="form-control"
                       placeholder="Email address" required autofocus>
            </div>
            <div class="form-group">
                <input type="text" name="username" id="inputUsername" class="form-control"
                       placeholder="UserID(아이디)" required>
            </div>
            <div class="form-group">
                <input type="text" name="name" id="inputName" class="form-control"
                       placeholder="UserNM(성명)" required>
            </div>
            <div class="form-group">
                <input type="password" name="password1" id="inputPassword1" class="form-control"
                       placeholder="Password" required>
            </div>
            <div class="form-group">
                <input type="password" name="password2" id="inputPassword2" class="form-control"
                       placeholder="Confirm password" required>
            </div>
            {% for field in registration_form %}
                <p>
                    {% for error in field.errors %}
                        <p style="color: red">{{ error }}</p>
                    {% endfor %}
                </p>
            {% endfor %}
            {% if registration_form.non_field_errors %}
                <div style="color: red">
                    <p>{{ registration_form.non_field_errors }}</p>
                </div>
 
            {% endif %}
 
            <button class="btn btn-primary btn-block" type="submit">Register</button>
 
        </form>
 
    </div>
 
{% endblock content %}

 

login.html

{% extends 'base.html' %}
{% load static %}
{% load bootstrap4 %}
{% block title %}로그인{% endblock %}
 
{% block content %}
    <div class="row justify-content-center">
        <form method="post" class="text-center border border-light p-5">
            {% csrf_token %}
            <class="h4 mb-4 text-center">로그인</p>
            <div class="form-group">
                <input type="email" name="email" id="inputEmail" class="form-control mb-4" placeholder="Email address"
                       required="required">
            </div>
            <div class="form-group">
                <input type="password" name="password" id="inputPassword" class="form-control mb-4"
                       placeholder="비밀번호 입력하세요" required="required">
            </div>
 
            {% for field in login_form %}
                <p>
                    {% for error in field.errors %}
                        <p style="color: red">{{ error }}</p>
                    {% endfor %}
                </p>
            {% endfor %}
            {% if login_form.non_field_errors %}
                <div style="color: red">
                    <p>{{ login_form.non_field_errors }}</p>
                </div>
 
            {% endif %}
            {% buttons %}
                <button class="btn btn-info btn-block my-4" type="submit">로그인</button>
            {% endbuttons %}
        </form>
 
        <div class="d-flex flex-column my-4">
            {#    <a class="m-auto" href="{% url 'password_reset' %}">Reset password</a>#}
        </div>
    </div>
 
{% endblock content %}
 

 

 

views.py

 
# account/views.py
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.contrib.auth import login, authenticate, logout
from account.forms import RegistrationForm, AccountAuthForm
 
 
def register_view(request, *args, **kwargs):
    user = request.user
    if user.is_authenticated:
        return HttpResponse("You are already authenticated as " + str(user.email))
 
    context = {}
    if request.POST:
        form = RegistrationForm(request.POST)
        if form.is_valid():
            form.save()
            email = form.cleaned_data.get('email').lower()
            raw_password = form.cleaned_data.get('password1')
            account = authenticate(email=email, password=raw_password)
            login(request, account)
            # destination = kwargs.get("next")
            destination = get_redirect_if_exists(request)
            if destination: # if destination != None
                return redirect(destination)
            return redirect('home')
        else:
            context['registration_form'= form
    else:
        form = RegistrationForm()
        context['registration_form'= form
 
    return render(request, 'account/register.html', context)
 
 
def logout_view(request):
    logout(request)
    return redirect("home")
 
 
def login_view(request, *args, **kwargs):
    context = {}
 
    user = request.user
    if user.is_authenticated:
        return redirect("home")
 
    destination = get_redirect_if_exists(request)
    if request.POST:
        form = AccountAuthForm(request.POST)
        if form.is_valid():
            email = request.POST.get('email')
            password = request.POST.get('password')
            user = authenticate(email=email, password=password)
            if user:
                login(request, user)
                if destination:
                    return redirect(destination)
                return redirect("home")
    else:
        form = AccountAuthForm()
 
    context['login_form'= form
 
    return render(request, "account/login.html", context)
 
 
def get_redirect_if_exists(request):
    redirect = None
    if request.GET:
        if request.GET.get("next"):
            redirect = str(request.GET.get("next"))
    return redirect

 

forms.py

# account/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate
from account.models import Account
 
# 회원 가입 폼
class RegistrationForm(UserCreationForm):
    email = forms.EmailField(max_length=254, help_text='Required. Add a valid email address.')
 
    class Meta:
        model = Account
        fields = ('email''username''name''password1''password2', )
 
    def clean_email(self):
        email = self.cleaned_data['email'].lower()
        try:
            account = Account.objects.get(email=email)
        except Exception as e:
            return email
        raise forms.ValidationError(f"Email {email} is already in use.")
 
    def clean_username(self):
        username = self.cleaned_data['username']
        try:
            account = Account.objects.get(username=username)
        except Exception as e:
            return username
        raise forms.ValidationError(f"UserID {username} is already in use.")
 
 
 
# 로그인 인증 폼
class AccountAuthForm(forms.ModelForm):
    password = forms.CharField(label='Password', widget=forms.PasswordInput)
 
    class Meta:
        model = Account
        fields = ('email''password')
 
    def clean(self):
        if self.is_valid():
            email = self.cleaned_data['email']
            password = self.cleaned_data['password']
            if not authenticate(email=email, password=password):
                raise forms.ValidationError("Invalid login")
 

 

backends.py

# account/backends.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
 
class CaseInsensitiveModelBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None**kwargs):
        UserModel = get_user_model()
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        try:
            case_insensitive_username_field = '{}__iexact'.format(UserModel.USERNAME_FIELD)
            user = UserModel._default_manager.get(**{case_insensitive_username_field: username})
        except UserModel.DoesNotExist:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            UserModel().set_password(password)
        else:
            if user.check_password(password) and self.user_can_authenticate(user):
                return user
 

 

urls.py

# project urls.py
from django.conf import settings
from django.contrib import admin
from django.conf.urls.static import static
from django.urls import path, include
from .views import home_screen_view
from account.views import register_view, login_view, logout_view
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', home_screen_view, name='home'),
    path('register/', register_view, name='register' ),
    path('login/', login_view, name='login'),
    path('logout/', logout_view, name='logout'),
]
 
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 

 

현재까지 테스트한 전체 소스 코드는 https://github.com/jsk005/PythonDjango 에 올려두었다.

 

참고자료

https://codingwithmitch.com/courses/real-time-chat-messenger/

 

CodingWithMitch.com

If you restart this course, all your progress will be reset

codingwithmitch.com

 

블로그 이미지

Link2Me

,
728x90

실제 운용할 서버가 아닌 개발 단계에서 makemigrations 를 자주 해서 여러번의 변경 이력이 있다면 합쳐서 적용하는 것이 좋다.

혹시라도 동작이 안되면 python manage.py squashmigrations accounts 006 과 같이 마지막으로 변경된 숫자를 확인해서 직접 적어주면 된다.

 

블로그 이미지

Link2Me

,
728x90

장고 회원가입과 로그인 기능을 함수형 view 에서 class 형 view 로 변경하는 연습을 하고 코드를 적어둔다.

 

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% block title %}{% endblock %}</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    {% block head %}
    {% endblock %}
</head>
<body>
<div class="container">
    {% block contents %}
    {% endblock %}
</div>
</body>
</html>

 

 

index.html

{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block contents %}
    <div>Hello world!</div>
    <div>{{ user.username }}</div>
    <div>{{ user }}</div>
{% endblock %}
 

 

urls.py

from django.contrib import admin
from django.urls import path,include
from . import views
 
urlpatterns = [
    path('admin/', admin.site.urls),
 
    path('', views.HomeView.as_view(), name='index'),
    path('user/', include('accounts.urls')),
]
 

 

HomeView

세션처리가 일반적인 케이스가 아니라서 여기서 개 삽질을 좀 했다.

로그인 세션 정보가 없으면 로그인 화면으로 자동 이동된다.

데이터베이스의 값을 템플릿에서 사용하려면 뷰에서 값을 전달해야 한다.

그때 context라는 형태로 템플릿에 값을 전달한다.

from django.views.generic import TemplateView
from django.utils.decorators import method_decorator
from accounts.decorators import login_required
import logging
from accounts.models import User
 
@method_decorator(login_required, name='dispatch')
class HomeView(TemplateView):
    template_name = 'index.html'
 
    def get_context_data(self**kwargs):
        user_id = self.request.session.get('user')
        if user_id:
            user = User.objects.get(pk=user_id)
            logging.warning(user.username)
            context = super(HomeView, self).get_context_data(**kwargs)
            context['user'= user
            return context
 

 

decorators.py

from django.shortcuts import redirect
from .models import User
 
def login_required(function):
    def wrap(request, *args, **kwargs):
        user = request.session.get('user')
        if user is None or not user:
            return redirect('/user/login')
        return function(request, *args, **kwargs)
 
    return wrap
 
 
def admin_required(function):
    def wrap(request, *args, **kwargs):
        user = request.session.get('user')
        if user is None or not user:
            return redirect('/login')
        
        user = User.objects.get(useremail=user)
        if user.level != 'admin':
            return redirect('/')
 
        return function(request, *args, **kwargs)
 
    return wrap
 

 

models.py and app urls.py, admin.py

함수형 뷰를 사용해도 되고, 클래스형 뷰를 사용해도 된다.

클래스형 뷰를 사용하기 위해서는 가장 먼저 urls.py 에서 클래스형 뷰를 사용한다는 걸 표시해줘야 한다.

# models.py
from django.db import models
 
class User(models.Model):
    username = models.CharField(max_length=32, verbose_name='사용자명')
    useremail = models.EmailField(max_length=128, verbose_name='emailID', unique=True)
    password = models.CharField(max_length=128,verbose_name='비밀번호')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='등록시간')
    updated_at = models.DateTimeField(auto_now=True)
 
    def __str__(self):
        return self.useremail
 
    class Meta:
        db_table = 'accounts'
        verbose_name = '사용자'
        verbose_name_plural = '사용자'
 
 
# app urls.py
from django.urls import path
from . import views
 
app_name = 'accounts'
urlpatterns = [
    # path('register/', views.register, name='ajax_register'), # ajax_register
    path('register/', views.UserRegisterView.as_view(), name='ajax_register'), # ajax_register
 
    # path('login/', views.login, name='ajax_login'),
    path('login/', views.UserLoginView.as_view(), name='ajax_login'),
 
    # path('logout/',views.logout),
    path('logout/',views.UserLogoutView.as_view()),
]
 
 
# admin.py
from django.contrib import admin
from .models import User
 
# Register your models here.
class UserAdmin(admin.ModelAdmin):
    list_display = ('username','useremail','password','created_at','updated_at')
 
admin.site.register(User,UserAdmin)
 

 

 

app views.py

from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
from django.views.decorators.csrf import csrf_exempt
 
from .models import User
 
# Create your views here.
def index(reqest):
    user_id = reqest.session.get('user')
 
    if user_id:
        user = User.objects.get(pk=user_id)
        return HttpResponse(user.useremail)
 
    return redirect('/')
 
 
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    elif request.method == 'POST':
        useremail = request.POST.get('useremail'None)
        password = request.POST.get('password'None)
 
        res_data = {}
        try:
            user = User.objects.get(useremail=useremail)
            if check_password(password, user.password):
                # 비밀번호가 일치하면 세션 생성
                request.session['user'= user.id
                res_data['data'= '1'
                res_data['message'= '로그인 되었습니다.'
            else:
                res_data['data'= '0'
                res_data['message'= '입력 정보를 확인하세요.'
            return JsonResponse(res_data)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            res_data['status'= '0'
            res_data['message'= '입력 정보를 확인하세요.'
            return JsonResponse(res_data)
 
        return render(request, 'login.html', res_data)
 
 
 
def register(request):
    if request.method == 'GET':
        return render(request, 'register.html')
    elif request.method == 'POST':
        username = request.POST.get('username',None)
        useremail = request.POST.get('useremail',None)
        password = request.POST.get('password',None)
 
        res_data ={}
        try:
            user = User.objects.get(useremail=useremail)
            if user:
                res_data['status'= '0' # 기존 가입된 회원
                return JsonResponse(res_data)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            user = User(
                username = username,
                useremail = useremail,
                password = make_password(password)
            )
            user.save()
            # session 생성
            user = User.objects.get(useremail=useremail)
            request.session['user'= user.id
            res_data['data'= '1' # 회원 가입 완료
            res_data['message'= '회원 가입 완료'
            return JsonResponse(res_data)
 
        return render(request,'register.html',res_data)
 
 
def logout(request):
    if request.session.get('user'):
        del(request.session['user'])
 
    return redirect('/user/login')
 
@method_decorator(csrf_exempt, name='dispatch')
class BaseView(TemplateView):
    @staticmethod
    def response(data={}, message='', status=200):
        result = {
            'data': data,
            'message': message,
            'status': status,
        }
        return JsonResponse(result)
 
class UserLogoutView(BaseView):
    def get(self, request):
        if self.request.session.get('user'):
            del (self.request.session['user'])
        return redirect('/')
 
class UserLoginView(BaseView):
    template_name = 'login.html'
    def post(self,request):
        useremail = request.POST.get('useremail'None)
        password = request.POST.get('password'None)
 
        try:
            user = User.objects.get(useremail=useremail)
            if check_password(password, user.password):  # 비밀번호가 일치하면 세션 생성
                self.request.session['user'= user.id
                data = '1'
                message = '로그인 되었습니다.'
            else:
                data = '0'
                message = '입력 정보를 확인해주세요.'
            return self.response(data, message, status='')
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            data = '0'
            # message = '입력 정보를 확인해주세요.'
            message = '등록된 회원 정보가 없습니다.'
            return self.response(data, message, status='')
        return self.response()
 
 
class UserRegisterView(BaseView):
    template_name = 'register.html'
    def post(self,request):
        username = request.POST.get('username'None)
        useremail = request.POST.get('useremail'None)
        password = request.POST.get('password'None)
 
        if not username:
            return self.response(data='0', message='성명을 입력해주세요.', status=400)
        if not password:
            return self.response(data='0', message='패스워드를 입력해주세요.', status=400)
        try:
            validate_email(useremail)
        except ValidationError:
            return self.response(data='0', message='올바른 이메일을 입력해주세요.', status=400)
 
        try:
            user = User.objects.get(useremail=useremail)
            if user:
                return self.response(data='0', message='이미 가입된 아이디입니다.', status=400)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            user = User(
                username=username,
                useremail=useremail,
                password=make_password(password)
            )
            user.save()
            # session 생성
            user = User.objects.get(useremail=useremail)
            self.request.session['user'= user.id
            return self.response(data='1', message='회원 가입 완료.', status=200)
 
        return self.response({'user.id': user.id})
 
 

 

User.objects.get(useremail=useremail) SELECT * FROM accounts WHERE useremail=useremail;

User.objects.all()  → SELECT * FROM accounts;

User.objects.all().order_by('-id')[:20] → SELECT * FROM accounts ORDER BY id DESC LIMIT 20;

 

 

login.html

POST 방식의 <form> 을 사용하는 탬플릿에서는 CSRF(Cross Site Request Forgery)  공격을 방어하기 위해 {% csrf_token %} 태그를 사용해야 한다.

 
{% extends "base.html" %}
{% block title %}로그인{% endblock %}
{% block contents %}
<div class="login-form pt-5">
    <form method="post" id="post-form">
        {% csrf_token %}
        <h2 class="text-center">로그인</h2>
        <div class="form-group">
            <input type="text" id="useremail" class="form-control" placeholder="user email 입력" required="required">
        </div>
        <div class="form-group">
            <input type="password" id="password" class="form-control" placeholder="비밀번호 입력" required="required">
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-primary btn-block">로그인</button>
        </div>
    </form>
    <class="text-center"><a href="{% url 'accounts:ajax_register' %}" id="register">회원 가입</a></p>
</div>
<script>
$(document).on('submit''#post-form', function (e) {
    e.preventDefault();
 
    $.ajax({
        type: 'POST',
        url: '{% url "accounts:ajax_login" %}',
        data: {
            useremail: $('#useremail').val(),
            password: $('#password').val(),
            csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
            action: 'post'
        },
        success: function (json) {
            if(json.data == 1){
                document.getElementById("post-form").reset();
                alert(json.message);
                window.location.href = '/';
                {#window.location.href = {% url 'index' %};#}
            } else if(json.data == 0){
                alert(json.message);
            }
        },
        error: function (xhr, errmsg, err) {
            alert('에러가 발생했습니다.');
            console.log(xhr.status + ": " + xhr.responseText); 
        }
    });
});
</script>
{% endblock %}
 

 

register.html

{% extends "base.html" %}
{% block title %}회원 가입{% endblock %}
{% block contents %}
    <div class="signup-form pt-5">
        <form method="post" id="post-form" action=".">
            {% csrf_token %}
            <h2 class="text-center">회원가입</h2>
            <div class="form-group">
                <div class="row">
                    <div class="col">
                        <input type="text" class="form-control" id="username" placeholder="사용자 이름" required="required" />
                    </div>
                </div>
            </div>
            <div class="form-group">
                <input type="email" class="form-control" id="useremail" placeholder="Email" required="required"/>
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="password" placeholder="비밀번호" required="required">
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="repasswd" placeholder="비밀번호 확인"
                       required="required">
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-success btn-lg btn-block">회원 등록</button>
            </div>
        </form>
        <div class="text-center"><a href="{% url 'accounts:ajax_login' %}">로그인</a></div>
    </div>
    <script>
        $(document).on('submit''#post-form', function (e) {
            e.preventDefault();
            var pwd = $("#password");
            var repwd = $("#repasswd");
            if (PasswordChk(pwd, repwd) == falsereturn false;
 
            $.ajax({
                type: 'POST',
                url: '{% url "accounts:ajax_register" %}',
                data: {
                    username: $('#username').val(),
                    useremail: $('#useremail').val(),
                    password: $('#password').val(),
                    csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                    action: 'post'
                },
                success: function (json) {
                    if (json.data == 1) {
                        document.getElementById("post-form").reset();
                        window.location.href = '/';
                        alert(json.message);
                    } else if (json.data == 0) {
                        alert(json.message);
                    }
                },
                error: function (xhr, errmsg, err) {
                    alert('에러가 발생했네요.');
                    console.log(xhr.status + ": " + xhr.responseText);
                }
            });
        });
 
        function PasswordChk(pwd, repwd) {
            if (pwd.val() == '') {
                alert('비밀번호를 입력하세요');
                pwd.focus();
                return false;
            }
            if (pwd.val().indexOf(' '> -1) {
                alert("공백은 입력할 수 없습니다.");
                return false;
            }
 
            var check1 = /^(?=.*[a-zA-Z])(?=.*[0-9]).{10,15}$/.test(pwd.val()); //영문,숫자
            var check2 = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9]).{8,15}$/.test(pwd.val()); //영문,숫자,특수문자
            var check3 = /^(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{10,15}$/.test(pwd.val()); //영문,특수문자
            var check4 = /^(?=.*[^a-zA-Z0-9])(?=.*[0-9]).{10,15}$/.test(pwd.val()); //특수문자, 숫자
            if (!(check1 || check2)) {
                alert("영문/숫자 혼용시에는 10~15자리를 사용해야 합니다.\n영문/숫자/특수문자 혼용시에는 8~15자리를 사용해야 합니다.");
                return false;
            }
 
            // 동일한 문자/숫자 4자 이상
            if (/(\w)\1\1\1/.test(pwd.val())) { // /(\w)\1\1/.test(pwd)
                alert("같은 문자를 4번 이상 사용할 수 없습니다.");
                pwd.focus();
                return false;
            }
            if (isContinuedValue(pwd.val())) {
                alert("비밀번호에 4자 이상의 연속 문자 또는 숫자를 사용하실 수 없습니다.");
                pwd.focus();
                return false;
            }
 
            if (repwd.val() == '') {
                alert('비밀번호를 다시 한번 더 입력하세요');
                repwd.focus();
                return false;
            }
            if (pwd.val() !== repwd.val()) {
                alert('비밀번호를 둘다 동일하게 입력하세요');
                return false;
            }
            return true;
        }
 
        function isContinuedValue(value) {
            console.log("value = " + value);
            var intCnt1 = 0;
            var intCnt2 = 0;
            var temp0 = "";
            var temp1 = "";
            var temp2 = "";
            var temp3 = "";
 
            for (var i = 0; i < value.length - 3; i++) {
                temp0 = value.charAt(i);
                temp1 = value.charAt(i + 1);
                temp2 = value.charAt(i + 2);
                temp3 = value.charAt(i + 3);
 
                if (temp0.charCodeAt(0- temp1.charCodeAt(0== 1
                    && temp1.charCodeAt(0- temp2.charCodeAt(0== 1
                    && temp2.charCodeAt(0- temp3.charCodeAt(0== 1) {
                    intCnt1 = intCnt1 + 1;
                }
 
                if (temp0.charCodeAt(0- temp1.charCodeAt(0== -1
                    && temp1.charCodeAt(0- temp2.charCodeAt(0== -1
                    && temp2.charCodeAt(0- temp3.charCodeAt(0== -1) {
                    intCnt2 = intCnt2 + 1;
                }
            }
            return (intCnt1 > 0 || intCnt2 > 0);
        }
    </script>
{% endblock %}
 

'파이썬 > Django' 카테고리의 다른 글

Django Custom User Model - 회원가입, 로그인  (2) 2022.01.29
Django, squash migrations  (0) 2022.01.26
Python Django 회원가입 with jQuery(ajax)  (0) 2022.01.22
Python Django Login with jQuery(ajax)  (0) 2022.01.17
Django Login 예제  (0) 2022.01.16
블로그 이미지

Link2Me

,
728x90

Bootstrap UI에 회원가입시 에러메시지 처리 기능 대신에 jQuery Ajax 처리를 통해서 회원가입 방법을 테스트하고 적어둔다.

 

기존 views.py

from django.core.mail.backends import console
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from .models import User
 
# Create your views here.
def register(request):
    if request.method == 'GET':
        return render(request, 'register.html')
    elif request.method == 'POST':
        username = request.POST.get('username',None)
        useremail = request.POST.get('useremail',None)
        password = request.POST.get('password',None)
        repasswd = request.POST.get('repasswd',None)
 
        res_data ={}
        if not (username and useremail and password and repasswd):
            res_data['error'= '모든 값을 입력해야 합니다.'
        elif password != repasswd:
            res_data['error'='비밀번호가 다릅니다.'
        else:
            user = User(
                username = username,
                useremail = useremail,
                password = make_password(password)
            )
            user.save()
 
        return render(request,'register.html',res_data)

 

회원 가입 UI 처리를 위해서 base.html 파일과 register.html 파일로 분리했다.

base.html

 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<div class="container">
    {% block contents %}
    {% endblock %}
</div>
</body>
</html>
 

 

 

register.html

장고 테이블에 회원정보를 저장하기 위해서는 name="username" 이라고 적어야 하지만, jQuery 로 처리하려고 id="usernmae" 으로 변경했다.

하단 <script></script> 사이에 Javascript 코드를 구현해야 한다. js 코드만 별도로 분리하는 것은 static 폴더에 넣으면 된다.

{% extends "base.html" %}
{% block contents %}
    <div class="signup-form pt-5">
        <form method="post" id="post-form" action=".">
            {% csrf_token %}
            <h2>회원가입</h2>
            <div class="form-group">
                <div class="row">
                    <div class="col">
                        <input type="text" class="form-control" id="username" placeholder="사용자 이름" required="required" />
                    </div>
                </div>
            </div>
            <div class="form-group">
                <input type="email" class="form-control" id="useremail" placeholder="Email" required="required"/>
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="password" placeholder="비밀번호" required="required">
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="repasswd" placeholder="비밀번호 확인"
                       required="required">
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-success btn-lg btn-block">회원 등록</button>
            </div>
        </form>
        <div class="text-center"><a href="#">로그인</a></div>
    </div>
    <script>
        $(document).on('submit''#post-form'function (e) {
            e.preventDefault();
            if ($("#username").val() == '') {
                alert('이름을 입력하세요');
                $("#username").focus();
                return false;
            }
            if ($('#useremail').val() == '') {
                alert('email을 입력하세요');
                $('#useremail').focus();
                return false;
            }
 
            var pwd = $("#password");
            var repwd = $("#repasswd");
            if (PasswordChk(pwd, repwd) == falsereturn false;
 
            $.ajax({
                type: 'POST',
                url: '{% url "accounts:ajax_register" %}',
                data: {
                    username: $('#username').val(),
                    useremail: $('#useremail').val(),
                    password: $('#password').val(),
                    csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                    action: 'post'
                },
                success: function (json) {
                    if (json.status == 1) {
                        document.getElementById("post-form").reset();
                        window.location.href = 'http://127.0.0.1:8000/';
                        alert('등록되었습니다.');
                    } else if (json.status == 0) {
                        alert('이미 등록된 회원입니다.');
                    }
                },
                error: function (xhr, errmsg, err) {
                    alert('에러가 발생했네요.' + errmsg);
                    console.log(xhr.status + ": " + xhr.responseText);
                }
            });
        });
 
        function PasswordChk(pwd, repwd) {
            if (pwd.val() == '') {
                alert('비밀번호를 입력하세요');
                pwd.focus();
                return false;
            }
            if (pwd.val().indexOf(' '> -1) {
                alert("공백은 입력할 수 없습니다.");
                return false;
            }
 
            var check1 = /^(?=.*[a-zA-Z])(?=.*[0-9]).{10,15}$/.test(pwd.val()); //영문,숫자
            var check2 = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9]).{8,15}$/.test(pwd.val()); //영문,숫자,특수문자
            var check3 = /^(?=.*[a-zA-Z])(?=.*[^a-zA-Z0-9]).{10,15}$/.test(pwd.val()); //영문,특수문자
            var check4 = /^(?=.*[^a-zA-Z0-9])(?=.*[0-9]).{10,15}$/.test(pwd.val()); //특수문자, 숫자
            if (!(check1 || check2)) {
                alert("영문/숫자 혼용시에는 10~15자리를 사용해야 합니다.\n영문/숫자/특수문자 혼용시에는 8~15자리를 사용해야 합니다.");
                return false;
            }
 
            // 동일한 문자/숫자 4자 이상
            if (/(\w)\1\1\1/.test(pwd.val())) { // /(\w)\1\1/.test(pwd)
                alert("같은 문자를 4번 이상 사용할 수 없습니다.");
                pwd.focus();
                return false;
            }
            if (isContinuedValue(pwd.val())) {
                alert("비밀번호에 4자 이상의 연속 문자 또는 숫자를 사용하실 수 없습니다.");
                pwd.focus();
                return false;
            }
 
            if (repwd.val() == '') {
                alert('비밀번호를 다시 한번 더 입력하세요');
                repwd.focus();
                return false;
            }
            if (pwd.val() !== repwd.val()) {
                alert('비밀번호를 둘다 동일하게 입력하세요');
                return false;
            }
            return true;
        }
 
        function isContinuedValue(value) {
            console.log("value = " + value);
            var intCnt1 = 0;
            var intCnt2 = 0;
            var temp0 = "";
            var temp1 = "";
            var temp2 = "";
            var temp3 = "";
 
            for (var i = 0; i < value.length - 3; i++) {
                temp0 = value.charAt(i);
                temp1 = value.charAt(i + 1);
                temp2 = value.charAt(i + 2);
                temp3 = value.charAt(i + 3);
 
                if (temp0.charCodeAt(0- temp1.charCodeAt(0== 1
                    && temp1.charCodeAt(0- temp2.charCodeAt(0== 1
                    && temp2.charCodeAt(0- temp3.charCodeAt(0== 1) {
                    intCnt1 = intCnt1 + 1;
                }
 
                if (temp0.charCodeAt(0- temp1.charCodeAt(0== -1
                    && temp1.charCodeAt(0- temp2.charCodeAt(0== -1
                    && temp2.charCodeAt(0- temp3.charCodeAt(0== -1) {
                    intCnt2 = intCnt2 + 1;
                }
            }
            return (intCnt1 > 0 || intCnt2 > 0);
        }
    </script>
{% endblock %}
 

 

 

models.py

models.py 파일은 데이터베이스 테이블을 만들고 그 안의 필드들을 생성 및 수정할 수 있는 역할을 한다.
혹시라도 칼럼을 추가할 경우에는 DB 를 삭제하고 다시 하는 것이 속 편하다. 그러므로 처음부터 칼럼 설계를 잘하자.
# models.py
from django.db import models
 
# Create your models here.
class User(models.Model):
    username = models.CharField(max_length=32, verbose_name='사용자명',default='')
    useremail = models.EmailField(max_length=128, verbose_name='emailID',default='')
    password = models.CharField(max_length=128,verbose_name='비밀번호')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='등록시간')
    updated_at = models.DateTimeField(auto_now=True)
 
    def __str__(self):
        return self.email
 
    class Meta:
        db_table = 'accounts'
        verbose_name = '사용자'
        verbose_name_plural = '사용자'
 

 

수정 views.py

from django.core.mail.backends import console
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from .models import User
 
# Create your views here.
def home(reqest):
    user_id = reqest.session.get('user')
 
    if user_id:
        user = User.objects.get(pk=user_id)
        return HttpResponse(user.useremail)
 
    return HttpResponse('Home')
 
def register(request):
    if request.method == 'GET':
        return render(request, 'register.html')
    elif request.method == 'POST':
        username = request.POST.get('username',None)
        useremail = request.POST.get('useremail',None)
        password = request.POST.get('password',None)
 
        res_data ={}
        try:
            user = User.objects.get(useremail=useremail)
            if user:
                res_data['status'= '0' # 기존 가입된 회원
                return JsonResponse(res_data)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            user = User(
                username = username,
                useremail = useremail,
                password = make_password(password)
            )
            user.save()
            # session 생성
            user = User.objects.get(useremail=useremail)
            request.session['user'= user.id
            res_data['status'= '1' # 회원 가입 완료
            return JsonResponse(res_data)
 
        return render(request,'register.html',res_data)
 
 
def logout(request):
    if request.session.get('user'):
        del(request.session['user'])
 
    return redirect('/user/login')
 
 

 

 

app urls.py

# app urls.py
from django.urls import path
from . import views
 
app_name = 'accounts'
urlpatterns = [
    path('register/', views.register, name='ajax_register'), # name = that we will use in ajax url
    path('login/', views.login, name='ajax_login'),
    path('logout/',views.logout),
]
 
 

 

project urls.py

# project urls.py
from django.contrib import admin
from django.urls import path,include
from accounts.views import home
 
urlpatterns = [
    path('admin/', admin.site.urls),
 
    # path('', views.HomeView.as_view(), name='home'),
    path('user/', include('accounts.urls')),
    path('', home)
]
 

 

admin.py

# admin.py
from django.contrib import admin
from .models import User
 
# Register your models here.
class UserAdmin(admin.ModelAdmin):
    list_display = ('username','useremail','password','created_at','updated_at')
 
admin.site.register(User,UserAdmin)
블로그 이미지

Link2Me

,
728x90

장고 온라인 강좌 로그인 처리가 매끄럽지 못한 거 같아서 jQuery ajax 로그인 처리 기능을 테스트하고 적어둔다.

 

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>로그인</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<div class="container pt-5">
    <div class="login-form">
        <div class="row">
            <div class="col-12">
                {{ error }}
            </div>
        </div>
        <form method="post" id="post-form">
            {% csrf_token %}
            <h2 class="text-center">로그인</h2>
            <div class="form-group">
                <input type="text" id="useremail" class="form-control" placeholder="user email 입력">
            </div>
            <div class="form-group">
                <input type="password" id="password" class="form-control" placeholder="비밀번호 입력">
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-primary btn-block">로그인</button>
            </div>
        </form>
        <p class="text-center"><a href="#" id="register">회원 가입</a></p>
    </div>
</div>
</body>
<script>
    $(document).on('submit''#post-form'function (e) {
        e.preventDefault();
        if($('#useremail').val() == ''){
            alert('email을 입력하세요');
            $('#useremail').focus();
            return false;
        }
 
        if($('#password').val() == ''){
            alert('비밀번호를 입력하세요');
            $('#password').focus();
            return false;
        }
 
        $.ajax({
            type: 'POST',
            url: '{% url "user:ajax_login" %}',
            data: {
                useremail: $('#useremail').val(),
                password: $('#password').val(),
                csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
                action: 'post'
            },
            success: function (json) {
                if(json.status == 1){
                    document.getElementById("post-form").reset();
                    window.location.href = 'http://127.0.0.1:8000/';
                    alert('로그인되었습니다.');
                } else if(json.status == 0){
                    alert('로그인 정보를 확인하세요.');
                }
            },
            error: function (xhr, errmsg, err) {
                alert('에러가 발생했네요.' + errmsg);
                console.log(xhr.status + ": " + xhr.responseText); 
            }
        });
    });
</script>
</html>
 

 

 

views.py

완벽 동작하도록 로직을 수정했지만, Secure Coding까지 고려하지는 않은 상태다.

로그인에서 비밀번호가 일치하지 않거나, 아이디가 없거나 할 경우에 동일한 메시지를 출력해야 한다.

 

 
from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from .models import User
 
# Create your views here.
def home(reqest):
    user_id = reqest.session.get('user')
 
    if user_id:
        user = User.objects.get(pk=user_id)
        return HttpResponse(user.useremail)
 
    return HttpResponse('Home')
 
 
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    elif request.method == 'POST':
        useremail = request.POST.get('useremail'None)
        password = request.POST.get('password'None)
 
        res_data = {}
        try:
            user = User.objects.get(useremail=useremail)
            if check_password(password, user.password):
                # 비밀번호가 일치하면 세션 생성
                request.session['user'= user.id
                res_data['status'= '1'
            else:
                res_data['status'= '0'
            return JsonResponse(res_data)
        except User.DoesNotExist:
            # 대문자 User 임에 주의
            res_data['status'= '0'
            return JsonResponse(res_data)
 
        return render(request, 'login.html', res_data)
 
 
def logout(request):
    if request.session.get('user'):
        del(request.session['user'])
 
    return redirect('/user/login')
 
 

 

 

urls.py

from django.urls import path
from . import views
 
app_name = 'user'
urlpatterns = [
    path('register/', views.register, name='ajax_register'),
    path('login/', views.login, name='ajax_login'),
    path('logout/', views.logout),
]
 
 

 

참고자료

https://dev.to/thepylot/how-to-send-django-form-with-ajax-4bpo

 

How to send Django form with AJAX

What's up DEV Network? In this quick tutorial I am going to show you how to POST Django form without...

dev.to

 

블로그 이미지

Link2Me

,
728x90

장고 로그인 기능 기본적인 예제이다.

 

# models.py
from django.db import models
 
class User(models.Model):
    username = models.CharField(max_length=32, verbose_name='사용자명',null=True,default='')
    useremail = models.EmailField(max_length=128, verbose_name='emailID',default='')
    password = models.CharField(max_length=128,verbose_name='비밀번호')
    registered_dttm = models.DateTimeField(auto_now_add=True, verbose_name='등록시간')
 
    def __str__(self):
        return self.email
 
    class Meta:
        db_table = 'django_user'
        verbose_name = '사용자'
        verbose_name_plural = '사용자'
 
# admin.py
from django.contrib import admin
from .models import User
 
class UserAdmin(admin.ModelAdmin):
    list_display = ('username','useremail','password','registered_dttm')
 
admin.site.register(User,UserAdmin)
 
 
# views.py
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from .models import User
 
def home(reqest):
    user_id = reqest.session.get('user')
 
    if user_id:
        user = User.objects.get(pk=user_id)
        return HttpResponse(user.useremail)
 
    return HttpResponse('Home')
 
 
def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    elif request.method == 'POST':
        useremail = request.POST.get('useremail'None)
        password = request.POST.get('password'None)
 
        res_data = {}
        if not (useremail and password):
            res_data['error'= '모든 값을 입력해야 합니다.'
        else:
            user = User.objects.get(useremail=useremail)
            if check_password(password,user.password):
                request.session['user'= user.id
                return redirect('/')
            else:
                res_data['error'= '로그인 정보를 확인하세요'
 
        return render(request, 'login.html', res_data)
 
 
def register(request):
    if request.method == 'GET':
        return render(request, 'register.html')
    elif request.method == 'POST':
        username = request.POST.get('username',None)
        useremail = request.POST.get('useremail',None)
        password = request.POST.get('password',None)
        repasswd = request.POST.get('repasswd',None)
 
        res_data ={}
        if not (username and useremail and password and repasswd):
            res_data['error'= '모든 값을 입력해야 합니다.'
        elif password != repasswd:
            res_data['error'='비밀번호가 다릅니다.'
        else:
            user = User(
                username = username,
                useremail = useremail,
                password = make_password(password)
            )
            user.save()
 
        return render(request,'register.html',res_data)
 
 
def logout(request):
    if request.session.get('user'):
        del(request.session['user'])
 
    return redirect('/')
 
 
 
# urls.py
from django.urls import path
from . import views
 
app_name = 'accounts'
urlpatterns = [
    path('register/', views.register, name='ajax_register'), 
    path('login/', views.login),
    path('logout/',views.logout),
]
 
 

 

 

templates 디렉토리 안에 있는 login.html 파일에서

form 태그 안에 {% csrf_token %} 부분이 백엔드 장고와 통신하는 명령어이다.

jQuery ajax 처리하는 걸 시도했으나 아직 성공하지 못했다.

 
<!DOCTYPE html>
<html lang="en">
<head>
    <title>로그인</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <style>
        .login-form {
            width: 340px;
            margin: 50px auto;
            font-size: 15px;
        }
 
        .login-form form {
            margin-bottom: 15px;
            background: #f7f7f7;
            box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
            padding: 30px;
        }
 
        .login-form h2 {
            margin: 0 0 15px;
        }
 
        .form-control, .btn {
            min-height: 38px;
            border-radius: 2px;
        }
 
        .btn {
            font-size: 15px;
            font-weight: bold;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="login-form">
        <div class="row">
            <div class="col-12">
                {{ error }}
            </div>
        </div>
        <form method="post" action=".">
            {% csrf_token %}
            <h2 class="text-center">로그인</h2>
            <div class="form-group">
                <input type="text" name="useremail" class="form-control" placeholder="user email"
                       required="required">
            </div>
            <div class="form-group">
                <input type="password" name="password" class="form-control" placeholder="Password"
                       required="required">
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-primary btn-block">로그인</button>
            </div>
        </form>
        <p class="text-center"><a href="#" id="register">회원 가입</a></p>
    </div>
</div>
</body>
</html>

 

 

templates 디렉토리 안에 있는 register.html 파일이다.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>회원가입</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <style>
        body {
            color: #fff;
            background: #63738a;
            font-family: 'Roboto', sans-serif;
        }
 
        .form-control {
            height: 40px;
            box-shadow: none;
            color: #969fa4;
        }
 
        .form-control:focus {
            border-color: #5cb85c;
        }
 
        .form-control, .btn {
            border-radius: 3px;
        }
 
        .signup-form {
            width: 450px;
            margin: 0 auto;
            padding: 30px 0;
            font-size: 15px;
        }
 
        .signup-form h2 {
            color: #636363;
            margin: 0 0 15px;
            position: relative;
            text-align: center;
        }
 
        .signup-form h2:before, .signup-form h2:after {
            content: "";
            height: 2px;
            width: 30%;
            background: #d4d4d4;
            position: absolute;
            top: 50%;
            z-index: 2;
        }
 
        .signup-form h2:before {
            left: 0;
        }
 
        .signup-form h2:after {
            right: 0;
        }
 
        .signup-form .hint-text {
            color: #999;
            margin-bottom: 30px;
            text-align: center;
        }
 
        .signup-form form {
            color: #999;
            border-radius: 3px;
            margin-bottom: 15px;
            background: #f2f3f7;
            box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
            padding: 30px;
        }
 
        .signup-form .form-group {
            margin-bottom: 20px;
        }
 
        .signup-form input[type="checkbox"] {
            margin-top: 3px;
        }
 
        .signup-form .btn {
            font-size: 16px;
            font-weight: bold;
            min-width: 140px;
            outline: none !important;
        }
 
        .signup-form .row div:first-child {
            padding-right: 10px;
        }
 
        .signup-form .row div:last-child {
            padding-left: 10px;
        }
 
        .signup-form a {
            color: #fff;
            text-decoration: underline;
        }
 
        .signup-form a:hover {
            text-decoration: none;
        }
 
        .signup-form form a {
            color: #5cb85c;
            text-decoration: none;
        }
 
        .signup-form form a:hover {
            text-decoration: underline;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="signup-form">
        <div class="row">
            <div class="col-12">
                {{ error }}
            </div>
        </div>
        <form id="form" method="post" action=".">
            {% csrf_token %}
            <h2>회원가입</h2>
            <div class="form-group">
                <div class="row">
                    <div class="col">
                        <input type="text" class="form-control" name="username" placeholder="사용자 이름"
                               required="required"/>
                    </div>
                </div>
            </div>
            <div class="form-group">
                <input type="email" class="form-control" name="useremail" placeholder="Email" required="required"/>
            </div>
            <div class="form-group">
                <input type="password" class="form-control" name="password" placeholder="비밀번호" required="required">
            </div>
            <div class="form-group">
                <input type="password" class="form-control" name="repasswd" placeholder="비밀번호 확인"
                       required="required">
            </div>
            <div class="form-group">
                <button type="submit" class="btn btn-success btn-lg btn-block">회원 등록</button>
            </div>
        </form>
        <div class="text-center"><a href="#">로그인</a></div>
    </div>
</div>
<script>
 
</script>
</body>
</html>
 

 

 

 

블로그 이미지

Link2Me

,
728x90

파이참(PyCharm)을 이용해서 장고 프로젝트를 생성하는 과정을 적어둔다.

 

 

디렉토리를 정해두면 프로젝트명은 자동으로 생성되도록 나온다. 변경을 하던지 그대로 하면 된다.

Virtual 환경 설정 폴더는 별도로 지정하였다.

 

커맨드 창에서

python manage.py startproject djangoProject1

이라고 생성하는 것과 동일하게 자동으로 아래와 같이 프로젝트가 생성된 걸 확인할 수 있다.

 

애플리케이션 생성

startapp 을 생성하는 과정이다.

python manage.py startapp blog

python manage.py startapp accounts

웹 사이트에 대한 전체 프로그램을 프로젝트라고 하고, 모듈화된 단위 프로그램을 애플리케이션이라고 부른다.

 

그러면 장고(Django)가 accounts 디렉토리와 그 하위에 필요한 파일들을 생성해 준다.

blog 디렉토리와 그 하위에 필요한 파일들을 생성해 준다.

 

settings.py 파일 INSTALLED_APPS 의 아래에 두줄을 등록한다.

DEBUG = True
 
ALLOWED_HOSTS = ['*']
 
# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
 
    'accounts.apps.AccountsConfig',
    'blog.apps.BlogConfig',
]

 

DB는 나중에 Maria DB나 MySQL DB를 사용할 경우를 대비하여 아래와 같이 몇줄 주석으로 처리해두었다.

# Database
 
DATABASES = {
    'default': {
        'ENGINE''django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
        # 'ENGINE': 'django.db.backends.mysql', # mysqlclient librarly 설치
        # 'NAME': 'pythondb',
        # 'USER': 'root',
        # 'PASSWORD': 'autoset', # mariaDB 설치 시 입력한 root 비밀번호 입력
        # 'HOST': 'localhost',
        # 'PORT': ''
    }
}

 

static 디렉토리를 추가하는 과정이다.

 

 

settings.py 하단 수정사항

LANGUAGE_CODE = 'en-us'
# 변경
TIME_ZONE = 'Asia/Seoul'
 
USE_I18N = True
# 변경
USE_TZ = False
 
# Static files (CSS, JavaScript, Images)
 
STATIC_URL = 'static/'
# static 디렉토리 추가
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
 

 

기본 테이블 생성

터미널 창을 열어

python manage.py migrate

를 해준다.

migration이란 테이블 및 필드의 생성, 삭제, 변경 등과 같이 데이터베이스에 대한 변경 사항을 알려주는 정보이다.

 

Superuser 생성

admin 사이트에 로그인하기 위한 관리자(superuser)를 생성한다.

  • manage.py : 각종 장고 명령을 수행
  • __init__.py : 모든 Python 패키지에 __init__.py를 둔다. 패키지를 임포트 할 때의 임포트 대상
  • urls.py : 최상위 URL 설정. 앱 내에 별도 urls.py 를 추가하여 사용
  • wsgi.py : 실 서비스에서의 웹 서비스 진입점

여기까지가 프로젝트 생성의 1단계 작업이다.

 

장고에서 Model은 데이터베이스에 액세스하는 컴포넌트이다.

  - 장고의 ORM(Object Relation Mapping)은 DB와 모델을 연결시키는 다리 같은 역할을 한다.

  - 다양한 DB를 지원하고, SQL 문장을 사용하지 않고도 테이블을 조작할 수 있다.

View는 데이터를 가져오고 변형하는 컴포넌트이다.

Template은 데이터를 사용자에게 보여주는 컴포넌트이다.

흔히 장고를 MVT 프레임워크라고 부른다.

 

장고 설치 디렉토리 확인 방법

python -c "import django; print(django.__path__)"

장고 가상환경 설정 디렉토리 하단에 설치된 것을 확인할 수 있다.

 

model 코딩

- models.py 에 테이블을 정의한다.

- admins.py 에 정의된 테이블이 Admin 화면에 보이게 한다.

- python manage.py makemigrations : DB에 변경이 필요한 사항을 추출한다.

- python manage.py migrate : DB에 변경사항을 반영한다.

- python manage.py runserver : 개발용 Web서버로 현재까지 개발한 사항을 확인한다.

 

 

models.py 파일 코드

# pip install django-taggit
# pip install django-widget-tweaks
# pip install pillow
# pip install pytz
# pip install -U pip setuptools wheel
# pip install pyopenssl ndg-httpsclient pyasn1
 
from django.db import models
from taggit.managers import TaggableManager
 
# 모델 : 테이블 정의
class Post(models.Model):
    # PK(Primary Key)는 클래스에 지정해주지 않아도, 장고는 id라는 칼럼을 자동으로 만들어 준다.
    title = models.CharField(verbose_name='TITLE', max_length=50)
    description = models.CharField('DESCRIPTION', max_length=100, blank=True, help_text='simple description text')
    content = models.TextField('CONTENT')
    create_dt = models.DateTimeField('CREATE DATE', auto_now_add=True)
    modify_dt = models.DateTimeField('MODIFY DATE', auto_now=True)
    tags = TaggableManager(blank=True)
 
    # def __str__(self) 메소드는 객체를 문자열로 표현할 때 사용하는 함수이다.    
    def __str__(self):
        return self.title
 
    def get_absolute_url(self):
        return reversed('blog:post_detail', args=(self.id,))
 
    def get_prev(self):
        return self.get_previous_by_modify_dt()
 
    def get_next(self):
        return self.get_next_by_modify_dt()
 
 

 

admin.py 파일 코드

from django.contrib import admin
from blog.models import Post
 
# models.py 파일을 작성하고 난 이후에 이 코드를 작성한다.
class PostAdmin(admin.ModelAdmin):
    list_display = ("id""title""modify_dt""tag_list")
 
    def get_queryset(self, request):
        return super().get_queryset(request).prefetch_related('tags')
 
    def tag_list(self, obj):
        return u", ".join(o.name for o in obj.tags.all())
 
admin.site.register(Post,PostAdmin)
 
# 이후에 할 일 ==> 터미널 창에서 아래 두줄을 실행한다. 코드가 변경되면 다시 두줄을 실행한다.
# python manage.py makemigrations
# python manage.py migrate
 
# migration이란 테이블 및 필드의 생성, 삭제, 변경 등과 같이
# 데이터베이스에 대한 변경사항을 알려주는 정보
 

 

장고는 웹 요청에 있는 URL을 분석하고, 그 결과로 해당 URL에 매핑된 View를 호출한다.

from django.views.generic import ListView, DetailView
 
from blog.models import Post
 
# View : 애플리케이션의 제어 흐름 및 처리로직을 정의
# 클래스형 뷰를 코딩할 때 가장 먼저 고려할 것은 어떤 제네릭뷰를 사용할 것인가 이다.
# 테이블에서 여러개의 레코드를 가져오는 로직이 필요하면 ListView
# 테이블에서 한개의 레코드를 가져오는 로직이 필요하면 DetailView
class PostLV(ListView):
    model = Post
    # template_name = 'blog/post_list.html'
 
class PostDV(DetailView):
    model = Post
 
 

 

URLconf

클라이언트로부터 요청을 받으면 장고는 가장 먼저 요청에 들어있는 URL을 분석한다.

보통 프로젝트 전체에 URL을 정의하는 프로젝트 URL과 앱마다 정의하는 앱URL 계층으로 나눠서 코딩하는 것이 일반적이다.

 
# 프로젝트 urls.py
from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
    path('admin/', admin.site.urls),
 
    path('blog/', include('blog.urls')),
]
 
 
# 앱(blog) urls.py
# 파일을 별도 생성하고 아래와 같이 코드를 작성한다.
from django.urls import path
from blog import views
 
app_name = 'blog'
urlpatterns = [
    path('post/list/', views.PostLV.as_view(), name='post_list'),
    path('post/<int:pk>/', views.PostDV.as_view(), name='post_detail'),
]
 
 

 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

오픈소스 테마를 적용하기 前 관리자 화면

라이브러리 설치

python -m pip install django-baton

settings.py 수정

수정 후 화면

최상단과 최하단에 추가

urls.py 파일 수정

 

migrate 해주기

python manage.py makemigrations

python manage.py migrate

 

여기까지하면 테마가 적용된다.

화면 깨짐 현상이 발생하면 CTRL + R 을 눌러서 캐시를 삭제하면 된다.

테마 세부 세팅은 추가로 하면 된다.

블로그 이미지

Link2Me

,
728x90

Django 프레임웍에서 MariaDB 10.4 사용을 위한 연결을 하고 SQLite3 DB에서 MariaDB 로 변경하면서 해줘야 할 사항이다.

 

settings.py 수정사항

DATABASES = {
    'default': {
        #'ENGINE': 'django.db.backends.sqlite3',
        #'NAME': BASE_DIR / 'db.sqlite3',
        'ENGINE''django.db.backends.mysql'# mysqlclient librarly 설치
        'NAME''pythondb',
        'USER''root',
        'PASSWORD''autoset'# mariaDB 설치 시 입력한 root 비밀번호 입력
        'HOST''localhost',
        'PORT'''
    }
}

 

 

pythondb 에 테이블이 생성된 것을 phpMyAdmin 을 이용하여 확인해봤다.

테이블명은 애플리케이션명과 모델 클래스명을 밑줄(_)로 연결하고, 모두 소문자로 표시한다.

14개 테이블이 생성된 것을 확인할 수 있다.

 

블로그 이미지

Link2Me

,
728x90

결론부터 말하면 Python3.9 소스 설치하는 방법으로 설치는 문제없이 잘되었다.

문제는 SQLite3 DB 인식이 문제가 되어 온갖 삽질을 해도 해결이 잘 안되어서 포기하고.....

CentOS 7.X 버전에 python3 를 yum 으로 설치하는 방법으로 해서 SQLite3 DB 인식이 가능한 걸 확인했다.

 

yum -y install python3
python3 -V

 

파이썬 장고에서 DB 인식할 때 SQLite 3.9.0 이상으로 설치를 하라고 나온다.

기본 설치된 버전은 3.7.X 버전이다.

구글링을 하면 3.8.11 로 업데이트하는 방법이 나온다.

cd /root
mkdir sqlite3
cd sqlite3

wget https://kojipkgs.fedoraproject.org//packages/sqlite/3.10.2/1.fc22/x86_64/sqlite-3.10.2-1.fc22.x86_64.rpm
wget https://kojipkgs.fedoraproject.org//packages/sqlite/3.10.2/1.fc22/x86_64/sqlite-devel-3.10.2-1.fc22.x86_64.rpm
sudo yum -y install sqlite-3.10.2-1.fc22.x86_64.rpm sqlite-devel-3.10.2-1.fc22.x86_64.rpm
sqlite3 –version

 

https://kojipkgs.fedoraproject.org//packages/sqlite/ 에서 더 높은 버전을 받을 수 있으나, 더 상위버전으로 했을 경우 에러가 발생할 수 있다.

 

파이썬 3.6.8 버전 설치는 끝났다.

이제 가상환경 설정을 하는 방법이다. 설치할 디렉토리는 아래와 같이 /home/httpd/python/ 으로 정했다.

mkdir -p /home/httpd/python/
cd /home/httpd/python/
python3 -m pip install virtualenv


python3 -m pip install --upgrade pip

 

virtualenv django


source /home/httpd/python/django/bin/activate

 

장고 설치

pip3 install Django

 

장고 REST Framework 설치

pip3 install djangorestframework

여기까지 하면 프로젝트 생성 이전까지 준비는 다 되었다.

직접 프로젝트를 생성해도 되고, 윈도우 Vistual Studio Code 환경에서 만든 코드를 Upload하여 수정해도 된다.

 

그럼 여기에서는 윈도우10 환경에서 개발 연습한 코드를 리눅스(CentOS 7.X)에 import 하는 방법을 적는다.

프로젝트 직접 생성시에는

cd /home/httpd/python/
django-admin startproject fc_django
cd fc_django
django-admin startapp fcuser
django-admin startapp product
django-admin startapp order

순서로 명령어를 입력하면 프로젝트 및 앱이 생성된다.

 

파일 샘플은 https://link2me.tistory.com/2014 에서 받으면 된다.

파일 Upload를 한 다음에 unzip 으로 압축을 푼다.

윈도우에는 실행권한이 있는 파일 구분이 없기 때문에 manage.py 파일이 644 권한이다.

권한을 755로 변경하여 실행할 수 있는 권한을 부여한다.

cd /home/httpd/python/fc_django
chmod 755 manage.py

 

python manage.py makemigrations

python manage.py migrate

 

python manage.py createsuperuser

관리자 ID는 원하는 아이디로 지정하면 된다.

 

이제 서버를 구동시켜 보자.

먼저 CentOS7 에서 구동될 방화벽을 먼저 설정해야 한다.

8000 번 포트로 개발서버를 구동시킬 것이기 때문에 포트를 등록해야 한다.

# 방화벽 설정
yum -y install firewalld

# 방화벽 데몬 시작
systemctl start firewalld

# 서버 부팅 시 firewalld 데몬 자동 시작 설정
systemctl enable firewalld

firewall-cmd --permanent --add-service=http 
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-service=mysql
firewall-cmd --permanent --zone=public --add-port=3306/tcp

firewall-cmd --permanent --zone=public --add-port=8000/tcp
firewall-cmd --reload
firewall-cmd --list-all

 

서버 구동
python manage.py runserver 0.0.0.0:8000

 

방화벽이 오픈되어 있는데 아래와 같은 메시지 나온다면....

 

settings.py 파일을 수정해야 한다.

 

 

 

 

 

Control + C 로 빠져나와 다시 개발 서버를 구동시킨다.

 

Web 브라우저 에서 확인하면 아래 그럼처럼 정상적으로 Web 이 구동되는 걸 알 수 있다.

 

이상으로 CentOS 7 에서 파이썬 개발서버를 구동하여 프로젝트를 옮겨서 동작되는 과정을 살펴봤다.

GitHub 등에서 구한 소스를 이런 방법으로 구동시켜 볼 수 있다고 보면 될 것이다.

 

공인(Public) IP에서도 접속 가능하게 IPTIME 공유기 포트포워딩을 설정해줬더니 공인 IP에서도 잘 접속된다.





 

 

 

 

 

블로그 이미지

Link2Me

,
728x90

패스트 캠퍼스 장고 강좌를 듣고 약간 Django 플랫폼에 대해 이해를 한 거 같다.

패스크 캠퍼스 장고 강좌는 무조건 강좌를 따라서 실행해야만 하는 어려움이 있다.

단계별로 설명하기 때문에 도움이 되는 건 분명하다.

 

배우는 초보 입장에서는 동작해서 실행되는 결과를 어떻게 만드는지부터 알고 싶다는 것이다.

대략 구조 분석에 대한 사항은 유투브 동영상을 참조하다보면 이해가 될 것이다.

 

1. 가상환경 및 프로젝트 생성

https://link2me.tistory.com/2011

 

파이썬 Windows 기반 장고 설치

먼저 파이썬이 설치되어 있어야 한다. 그 다음에 pip install virtualenv 를 해서 가상환경 만들 준비를 한다. 사용법 예시 virtualenv PythonDjango cd Scripts activate.bat pip install Django python -m pip..

link2me.tistory.com

를 참조하면 프로젝트 생성은 할 수 있다.

 

2. 앱 생성 및 SQLite DB 생성

- 프로젝트 생성

  django-admin startproject fc_django

- 생성된 프로젝트 폴더로 이동하여 앱을 생성한다.

  cd fc_django

  django-admin startapp fcuser

  django-admin startapp product

  django-admin startapp order

  이 단계까지 생성한 파일을 첨부한다.

fc_django_01.zip
0.01MB

- 앱을 생성하고 나서, 해야 할 사항은 앱 폴더내의 models.py 에 SQLite DB에 연결할 Class를 정의한다.

  생성한 앱 모두 models.py 에 Class 를 정의해야 한다.

- 프로젝트 생성시 만들어진 폴더에 settings.py 폴더에 추가 생성한 앱을 등록해야 한다.

- 그 다음에 manage.py 파일이 있는 폴더에서 SQLite DB 생성하는 명령어를 수행한다.

   python manage.py makemigrations

   python manage.py migrate

   settings.py 에 앱을 추가 등록하지 않으면 위 명령어가 동작되지 않는다.

   첨부 파일은 이 단계까지 진행한 파일을 압축한 것이다.

   몇차례 시행착오를 거치면서 해당 진행단계까지 진행한 파일을 압축해두는 것이 좋을 듯해서다.

fc_django_02.zip
0.03MB

- admin.py 에 코드를 추가하고 나서 python manage.py createsuperuser 를 해서 Username, Email, Password 를 등록한다.

이제 관리자 화면을 실행해서 보자.

http://127.0.0.1:8000/admin/

 

 

회원 가입, 로그인처리까지 작성된 파일이다.

http://127.0.0.1:8000/register/
http://127.0.0.1:8000/login/

fc_django_04.zip
0.04MB

superuser 아이디 생성을 한 부분 즉, 패스워드를 알 수가 없기 때문에 이 파일에서 참조할 사항은 fcuser 폴더의 forms.py, views.py 와 fc_django 폴더의 urls.py 파일이다.

templates 폴더는 그대로 활용하면 된다.

 

개발 서버를 구동한 상태에서 파일을 수정하면 에러가 발생하면 바로 바로 화면에 표시가 되므로 코드 구현시 도움이 많이 된다.

 

REST Framework 기능 이전까지의 코드가 제대로 동작되는 걸 확인할 수 있다.

fc_django_06_fin.zip
0.05MB

 

REST Framework 를 이용하기 위해서는

https://link2me.tistory.com/2015

를 참조하시라.

 

 

블로그 이미지

Link2Me

,
728x90

터미널(CMD) 창에서 아래 명령어를 입력한다.

pip install djangorestframework

 

개발 서버를 구동시키는데 동작이 안되고 아래와 같은 에러 메시지가 발생한다.

 

VSCode 상에서 다시 실행을 했더니 동작된다.

python -m pip install djangorestframework

 

개발 서버가 잘 구동되고 있다는 걸 알 수 있다.

블로그 이미지

Link2Me

,
728x90

먼저 파이썬이 설치되어 있어야 한다.

그 다음에 pip install virtualenv 를 해서 가상환경 만들 준비를 한다.

 

사용법 예시

virtualenv PythonDjango

cd Scripts

activate.bat

pip install Django

python -m pip install --upgrade pip

가상환경에서 장고 설치를 했다.

장고 모듈이 제대로 설치되었는지 여부는 py -m django --version

 

프로젝트 만들기

앱은 특정한 기능을 수행하는 웹 어플리케이션을 말한다.

프로젝트는 이런 특정 웹 사이트를 위한 앱들과 각 설정들을 같이 묶어놓은 것이다.

board 폴더에서 templates 폴더를 생성한다.

 

아래그림에서 장고 프레임웍의 기본구조를 확인할 수 있다.

장고 라이프사이클

Web 서버는 실제 운용시 Apache 또는 Nginx 서버

 

개발 서버

Django 프로젝트가 제대로 동작하는지 확인하는 방법

py manage.py runserver

 

이제 본인의 웹브라우저에서 http://127.0.0.1:8000 으로 검색하면 아래와 같이 나온다.

 

이제 간단하게 앱(기능)을 하나 만들어서 테스트를 해보자

https://docs.djangoproject.com/

 

Django documentation | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

를 입력하고 아래 그림의 순서대로 따라하면 된다.

영문을 한글로 바꾸는 것은 en 부분을 ko 로 변경하자.

https://docs.djangoproject.com/ko/3.2/intro/

 

시작하기 | Django 문서 | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

 

블로그 이미지

Link2Me

,