장고 - ajax를 이용한 회원가입 및 로그인 기능 적용

  • ajax를 이용한 회원가입 및 로그인 기능 적용 방법에 대해 알아볼 것입니다.

ex) extore_project

1. 회원가입, 로그인 템플릿 디자인 및 ajax 통신 코드 작성

  • 경로: extore_project > layout > base.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
...

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link href='https://fonts.googleapis.com/css?family=Satisfy' rel='stylesheet' type='text/css'>

...

<ul>
{% if user.is_authenticated %}
<li><a href="{% url 'accounts:logout' %}" class="user-logout">로그아웃</a></li>
{% else %}
<li style="float:left;"><a href="#" data-toggle="modal" data-target="#loginModal" data-whatever="@getbootstrap">로그인</a></li>
<li><a href="#" data-tog-gle="modal" data-target="#signupModal" data-whatever="@getbootstrap" >회원가입</a></li>
{% endif %}
</ul>

<!--회원가입 modal-->
<div class="modal fade" id="signupModal" tabindex="-1" role="dialog" aria-labelledby="signupModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="signupModalLabel">회원가입</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true" class="btn-close">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="user-signup" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label for="signup-user-email" class="col-form-label">이메일 주소</label>
<input type="text" placeholder="ex. adc@abc.com" class="form-control" id="signup-user-email" name="email">
</div>
<div class="form-group">
<label for="signup-user-name" class="col-form-label">이름</label>
<input type="text" placeholder="ex. 홍길동" class="form-control" id="signup-user-name" name="realName">
</div>
<div class="form-group">
<label for="signup-user-profile" class="col-form-label">프로필 사진</label><br>
<input type="file" id="signup-user-profile" name="profile">
</div>
<div class="form-group">
<label for="signup-user-phonenumber" class="col-form-label">휴대폰 번호</label>
<input type="text" placeholder="ex. 01012345678" class="form-control" id="signup-user-phonenumber" name="phoneNumber">
</div>
<div class="form-group">
<label for="signup-user-password" class="col-form-label">비밀번호</label>
<input type="password" placeholder="8자리 이상 (영어 대문자, 소문자, 숫자, 특수문자 중 3종류 조합)" class="form-control" id="signup-user-password" name="password">
</div>
<div class="form-group">
<label for="signup-user-password2" class="col-form-label">비밀번호 확인</label>
<input type="password" placeholder="비밀번호 재입력" class="form-control" id="signup-user-password2" name="password2">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn-close btn btn-secondary" data-dismiss="modal">돌아가기</button>
<a type="button" href="{% url 'accounts:signup' %}" class="btn-signup btn btn-primary">가입하기</a>
</div>
</div>
</div>
</div>


<!--로그인 modal-->
<div class="modal fade" id="loginModal" tabindex="-1" role="dialog" aria-labelledby="loginModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="loginModalLabel">로그인</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span class="btn-close" aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="user-login" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label for="login-user-email" class="col-form-label">이메일 주소</label>
<input type="text" placeholder="이메일 주소 입력" class="form-control" id="login-user-email" name="email">
</div>
<div class="form-group">
<label for="login-user-password" class="col-form-label">비밀번호</label>
<input type="password" placeholder="비밀번호 입력" class="form-control" id="login-user-password" name="password">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn-close btn btn-secondary" data-dismiss="modal">돌아가기</button>
<a type="button" href="{% url 'accounts:login' %}" class="btn-login btn btn-primary">로그인하기</a>
</div>
</div>
</div>
</div>


<script>
$(function(){
// 회원가입, 로그인 창 닫기
$('.btn-close').click(function(e){
// 회원가입, 로그인 창 닫을 시 현재 화면으로 새로고침
location.reload();
});

// 회원가입 진행
$('.btn-signup').click(function(e){
// btn-signup 클래스 부분을 클릭 시, 링크 이동 등 어떠한 행위도 동작하지 않도록 해주는 함수 -> ajax 작동 위해 필요한 선행 작업
e.preventDefault();
// btn-signup 클래스에서 'href' 속성 값을 가져와 url 변수에 저장
var url = $(this).attr('href');
// user-signup 아이디(여기선 회원가입 form 태그) 부분의 html 태그를 form 변수에 저장
var form = $('#user-signup')[0];
// form 태그 안에 있는 모든 input으로 입력한 value값을 'name'을 key로 하여, formData 변수에 저장
// --> ex. <input type="text" placeholder="이메일 주소 입력" class="form-control" id="login-user-email" name="email"> 에서 유저가 positipman@gmail.com을 입력했다면, formData에서 key는 email, value는 positipman@gmail.com 이 된다.
var formData = new FormData(form);
// formData에 image 데이터가 없다면, set 함수를 이용하여 "profile"이라는 이름을 key로 하여 입력받은 이미지 데이터를 value로 업데이트 시켜준다.
formData.set("profile", $('#signup-user-profile')[0].files[0]);
var realName = $('#signup-user-name').val();
$.ajax({
url : url,
// form에 file type 이 있는 경우 enctype: 'multipart/form-data'를 설정해야 한다.
enctype: 'multipart/form-data',
// formData를 이용하기 위해서 아래 processData, contentType을 반드시 false로 설정해줘야 한다.
processData: false,
contentType: false,
// ajax 통신 중, cache가 남아서 갱신 데이터를 받아오지 못하는 경우 사용한다.
cache: false,
type: "POST",
// data로는 formData를 request로 보낸다.
data: formData,
}).done(function(data){
// request 보낸 url에서 회원가입 정상 진행해도 무방하여 {'works':True}를 JsonResponse로 보낸 경우
if(data.works){
alert('환영합니다 '+realName+'님\n'+'회원가입이 성공적으로 완료되었습니다.');
location.reload();
// request 보낸 url에서 사용자 이메일 주소가 없다고 {'noEmail':True}를 JsonResponse로 보낸 경우
} else if(data.noEmail) {
alert('이메일 주소를 입력해주세요.');
// request 보낸 url에서 사용자 이름이 없다고 {'noRealName':True}를 JsonResponse로 보낸 경우
} else if(data.noRealName) {
alert('이름을 입력해주세요.');
// request 보낸 url에서 사용자 패스워드가 없다고 {'noPassword':True}를 JsonResponse로 보낸 경우
} else if(data.noPassword) {
alert('비밀번호를 입력해주세요.');
// request 보낸 url에서 사용자 2차 패스워드가 없다고 {'noPassword2':True}를 JsonResponse로 보낸 경우
} else if(data.noPassword2) {
alert('비밀번호를 확인해주세요.');
// request 보낸 url에서 사용자 프로필사진이 없다고 {'noProfile':True}를 JsonResponse로 보낸 경우
} else if(data.noProfile) {
alert('프로필 사진을 등록해주세요.');
// request 보낸 url에서 사용자 연락처가 없다고 {'noPhoneNumber':True}를 JsonResponse로 보낸 경우
} else if(data.noPhoneNumber) {
alert('휴대폰 번호를 입력해주세요.');
// request 보낸 url에서 사용자가 잘못된 이메일 형식을 입력했다고 {'wrongEmail':True}를 JsonResponse로 보낸 경우
} else if(data.wrongEmail) {
alert('올바른 이메일 주소 형식이 아닙니다.');
// request 보낸 url에서 사용자가 입력한 이메일이 이미 존재한다고 {'emailExists':True}를 JsonResponse로 보낸 경우
} else if(data.emailExists){
alert('입력하신 이메일이 이미 등록되어있습니다.');
// request 보낸 url에서 사용자가 입력한 연락처가 이미 존재한다고 {'noProfile':True}를 JsonResponse로 보낸 경우
} else if(data.phoneNumberExists){
alert('입력하신 휴대폰 번호가 이미 등록되어있습니다.');
// request 보낸 url에서 사용자가 입력한 연락처에 숫자가 아닌 문자가 입력되어 {'notNumber':True}를 JsonResponse로 보낸 경우
} else if(data.notNumber){
alert('휴대폰 번호로는 숫자만 입력해야 합니다.');
// request 보낸 url에서 사용자가 입력한 연락처가 허용길이를 초과한다고 {'tooLongNumber':True}를 JsonResponse로 보낸 경우
} else if(data.tooLongNumber){
alert('입력하신 휴대폰 번호는 허용 길이를 초과합니다.');
// request 보낸 url에서 사용자가 입력한 연락처가 허용길이를 만족하지 못한다고 {'tooShortNumber':True}를 JsonResponse로 보낸 경우
} else if(data.tooShortNumber){
alert('입력하신 휴대폰 번호는 허용 길이를 만족하지 못합니다.');
// request 보낸 url에서 사용자 이름에 영문자, 숫자, 특수문자가 포함되어 {'wrongName':True}를 JsonResponse로 보낸 경우
} else if(data.wrongName){
alert('이름엔 영문자, 숫자, 특수문자가 허용되지 않습니다.');
// request 보낸 url에서 사용자가 입력한 이름이 허용길이를 초과한다고 {'tooLongName':True}를 JsonResponse로 보낸 경우
} else if(data.tooLongName){
alert('입력하신 이름은 허용 길이를 초과합니다.');
// request 보낸 url에서 사용자가 입력한 비밀번호가 허용길이를 만족하지 못한다고 {'tooShortPwd':True}를 JsonResponse로 보낸 경우
} else if(data.tooShortPwd){
alert('비밀번호는 최소 8자리 이상이어야 합니다.');
// request 보낸 url에서 사용자가 입력한 비밀번호가 잘못된 형식이라고 {'wrongCombination':True}를 JsonResponse로 보낸 경우
} else if(data.wrongCombination){
alert('비밀번호는 최소 영어 소문자/대문자, 숫자, 특수문자 중,\n 3개 이상 조합으로 구성되어야 합니다.');
// request 보낸 url에서 사용자가 입력한 2차 비밀번호가 1차 비밀번호와 다르다고 {'notMatch':True}를 JsonResponse로 보낸 경우
} else if(data.notMatch){
alert('재입력한 비밀번호가 이전 비밀번호와 일치하지 않습니다.');
// 그 밖 모든 data를 JsonResponse로 보낸 경우
} else {
alert('정상 요청이 아닙니다.');
}
});
});

// 로그인 진행
$('.btn-login').click(function(e){
e.preventDefault();
var url = $(this).attr('href');
var form = $('#user-login')[0];
var formData = new FormData(form);

$.ajax({
url : url,
enctype: 'multipart/form-data',
processData: false,
contentType: false,
cache: false,
method : 'POST',
data : formData,
}).done(function(data){
if(data.works){
alert('로그인되었습니다.')
location.reload();
} else if(data.wrongInformation) {
alert('입력된 정보와 일치하는 회원 정보가 없습니다.');
$('#login-user-email').val("");
$('#login-user-password').val("");

} else if(data.noEmail) {
alert('이메일 주소를 입력해주세요.');
$('#login-user-password').val("");
} else if(data.noPassword) {
alert('비밀번호를 입력해주세요.');
} else {
alert('정상 요청이 아닙니다.');
}
});
});


// 로그아웃 진행
$('.user-logout').click(function(e){
e.preventDefault();
var url = $(this).attr('href');
var check = confirm('로그아웃 하시겠습니까?');
if(check==true){
$.ajax({
url : url,
method : "POST",
data : {
'csrfmiddlewaretoken' : '{{csrf_token}}',
},
}).done(function(data){
if(data.works){
alert('로그아웃 되었습니다.');
location.reload();
} else {
alert('정상 요청이 아닙니다.');
}
});
} else {
location.reload();
}
});
});
</script>

...

2. ajax로 request data 송신할 url 경로 설정

  • 경로 : accounts > urls.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from django.urls import path
    from .views import *

    app_name = 'accounts'

    urlpatterns = [
    path('signup/', user_signup, name='signup'),
    path('login/', user_login, name='login'),
    path('logout/', user_logout, name='logout'),
    ]

3. response data 보내줄 view 작성

  • 경로 : accounts > views.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    import re
    from django.contrib.auth import login, logout, authenticate
    from django.db.models import Q
    from django.http import JsonResponse, Http404
    from django.shortcuts import render
    from django.template.loader import render_to_string

    from .models import User

    from extore.models import Group, InviteStatus, InviteDate


    # 사용자 회원가입
    def user_signup(request):
    # request 요청이 ajax 요청인 경우 is_ajax() 함수 사용
    if request.is_ajax():
    # POST 방식으로 요청한 경우, 해당 name이 email인 값을 가져오고 싶은 경우, request.POST.get('email') 사용
    # request 보낸 email 이 빈 문자열인 경우
    if request.POST.get('email') == "":
    return JsonResponse({'noEmail':True})
    elif request.POST.get('realName') == "":
    return JsonResponse({'noRealName':True})
    # request 요청에서 file type이 있는 경우, 해당 데이터는 request.FILES.get(name)으로 가져온다.
    elif request.FILES.get('profile', None) is None:
    return JsonResponse({'noProfile':True})
    elif request.POST.get('phoneNumber') == "":
    return JsonResponse({'noPhoneNumber': True})
    elif request.POST.get('password') == "":
    return JsonResponse({'noPassword':True})
    elif request.POST.get('password2') == "":
    return JsonResponse({'noPassword2':True})



    # request로 받은 email 값이 이메일 형식이 아닌 경우
    signup_email = request.POST.get('email')
    if '@' not in signup_email or '.' not in signup_email:
    return JsonResponse({'wrongEmail':True})

    index = signup_email.index('.')
    try:
    signup_email[index + 1]
    except IndexError:
    return JsonResponse({'wrongEmail': True})

    # request로 받은 email 값이 이미 등록된 email인 경우
    if User.objects.filter(username=request.POST.get('email')).exists():
    return JsonResponse({'emailExists':True})

    # request로 받은 phoneNumber 값이 이미 등록된 phoneNumber인 경우
    if User.objects.filter(phone_number=request.POST.get('phoneNumber')).exists():
    return JsonResponse({'phoneNumberExists':True})

    # request로 받은 phoneNumber 길이가 적합하지 않은 경우
    try:
    int(request.POST.get('phoneNumber'))
    pass
    except ValueError:
    return JsonResponse({'notNumber':True})

    if len(request.POST.get('phoneNumber'))>11:
    return JsonResponse({'tooLongNumber':True})
    if len(request.POST.get('phoneNumber')) < 10:
    return JsonResponse({'tooShortNumber':True})

    # request로 받은 realName에 영문자, 숫자, 특수문자가 존재하는 경우
    wrong_str = re.compile('[a-zA-Z0-9-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]')
    if wrong_str.search(request.POST.get('realName')):
    return JsonResponse({'wrongName':True})

    # request로 받은 realName 길이가 5자리를 초과한 경우
    if len(request.POST.get('realName')) > 5:
    return JsonResponse({'tooLongName':True})

    # request로 받은 password가 비밀번호 형식에 적합하지 않은 경우 (8자리이상 & 영어 소문자/대문자/특수문자/숫자 중 3개 이상 조합)
    password = request.POST.get('password')
    if len(password) < 8:
    return JsonResponse({'shortLength':True})
    lower_case = re.compile('[a-z]')
    higher_case = re.compile('[A-Z]')
    number = re.compile('[0-9]')
    symbol = re.compile('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…》]')

    i = 0
    count = 0
    if re.search(lower_case, password):
    count += 1
    if re.search(higher_case, password):
    count += 1
    if re.search(number, password):
    count += 1
    if re.search(symbol, password):
    count += 1
    # 문자 조합이 3가지 미만일 경우
    if count < 3:
    return JsonResponse({'wrongCombination':True})

    # request로 받은 password2와 password 값이 일치하지 않는 경우
    if request.POST.get('password') != request.POST.get('password2'):
    return JsonResponse({'notMatch':True})


    # 사용자가 작성한 회원가입 내용 형식이 정상인 경우
    real_name = request.POST.get('realName')
    email = request.POST.get('email')
    phone_number = request.POST.get('phoneNumber')
    password = request.POST.get('password')
    profile = request.FILES.get('profile')

    user = User(last_name = real_name[0], first_name=real_name[1:], profile=profile, phone_number=phone_number, username=email)
    user.set_password(password)
    user.save()

    return JsonResponse({'works':True})

    return JsonResponse({'notValid':True})


    # 사용자 로그인
    def user_login(request):
    if request.is_ajax():
    # 이메일 주소를 입력하지 않은 경우
    if request.POST.get('email')=="" :
    return JsonResponse({'noEmail':True})

    # 비밀번호를 입력하지 않은 경우
    elif request.POST.get('password')=="":
    return JsonResponse({'noPassword':True})

    email = request.POST.get('email')
    password = request.POST.get('password')
    user = authenticate(username=email, password=password)

    if user is not None:
    login(request, user)
    return JsonResponse({'works':True})
    return JsonResponse({'wrongInformation':True})

    return JsonResponse({'notAjax':True})


    # 사용자 로그아웃
    def user_logout(request):
    if request.is_ajax():
    logout(request)
    return JsonResponse({'works':True})

    return JsonResponse({'notAjax':True})