Skip to content

Commit 8e29a11

Browse files
authored
Merge pull request #102 from pirogramming/dev
Dev
2 parents 3641690 + 7720aa8 commit 8e29a11

File tree

7 files changed

+115
-33
lines changed

7 files changed

+115
-33
lines changed

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI/CD
22

33
on:
44
push:
5-
branches: [ "feature/pje" ] # 운영은 main으로 전환 권장
5+
branches: [ "main" ] # 운영은 main으로 전환 권장
66
workflow_dispatch:
77

88
concurrency:

apps/places/urls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
app_name = 'places'
55

66
urlpatterns = [
7-
path('', views.main, name='main'), # 메인 화면
7+
path('', views.place_search, name='main'), # 메인 화면
88
path('search/', views.search, name='search'),
9-
path('place-search/', views.place_search, name='place_search'), # 새로운 장소 검색 화면
9+
path('place-search/', views.main, name='place_search'), # 새로운 장소 검색 화면
1010
path('<int:pk>/', views.place_detail, name='place_detail'),
1111
path('<int:pk>/like/', views.toggle_place_like, name='place_like'),
1212
path('fragment/', views.place_list_fragment, name='place_list_fragment'),

apps/users/adapter.py

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,85 @@
1+
# apps/users/adapters.py
12
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
2-
from django.shortcuts import redirect
3-
from allauth.account.utils import user_username
4-
from django.utils.text import slugify
5-
from django.contrib.auth import get_user_model
63
from allauth.exceptions import ImmediateHttpResponse
7-
from django.contrib import messages
84
from allauth.socialaccount.models import SocialAccount
5+
from django.contrib.auth import get_user_model
6+
from django.shortcuts import redirect
7+
from django.contrib import messages
8+
from django.utils.text import slugify
9+
import uuid
910

1011
User = get_user_model()
1112

1213
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
1314
def pre_social_login(self, request, sociallogin):
15+
"""
16+
- 로그인된 상태면 통과
17+
- 동일 이메일의 기존 유저가 있는데 현재 provider가 아니면 차단(중복 가입 방지)
18+
"""
1419
if request.user.is_authenticated:
15-
return redirect('/')
20+
return
1621

17-
email = sociallogin.account.extra_data.get("email")
18-
provider = sociallogin.account.provider # ex: 'google', 'kakao', 'naver'
22+
extra = sociallogin.account.extra_data or {}
23+
email = extra.get("email") or extra.get("kakao_account", {}).get("email")
24+
provider = sociallogin.account.provider # "kakao" / "google" / "naver" ...
1925

20-
if email:
21-
try:
22-
existing_user = User.objects.get(email=email)
26+
if not email:
27+
# 이메일 없이 들어오면 폼으로 가지 않게 즉시 리다이렉트
28+
messages.error(request, "카카오 이메일 제공에 동의해야 로그인할 수 있어요.")
29+
raise ImmediateHttpResponse(redirect("/accounts/login/"))
2330

24-
# ✅ 이 이메일의 소셜 계정이 현재 provider인지 확인
25-
if not SocialAccount.objects.filter(user=existing_user, provider=provider).exists():
26-
messages.error(request, "이미 해당 이메일로 가입된 계정이 있습니다. 소셜 로그인할 수 없습니다.")
27-
raise ImmediateHttpResponse(redirect("/accounts/login/"))
31+
try:
32+
existing = User.objects.get(email__iexact=email)
33+
# 같은 이메일의 유저가 있는데, 해당 provider로 연결되지 않았다면 막기
34+
if not SocialAccount.objects.filter(user=existing, provider=provider).exists():
35+
messages.error(request, "이미 해당 이메일로 가입된 계정이 있어 소셜 로그인할 수 없습니다.")
36+
raise ImmediateHttpResponse(redirect("/accounts/login/"))
37+
except User.DoesNotExist:
38+
pass # 없으면 계속 진행
2839

29-
except User.DoesNotExist:
30-
pass # 없으면 자동가입 진행
40+
def is_auto_signup_allowed(self, request, sociallogin):
41+
"""
42+
이메일이 있을 때만 추가 폼 없이 자동가입 허용.
43+
(없으면 pre_social_login에서 이미 차단됨)
44+
"""
45+
extra = sociallogin.account.extra_data or {}
46+
email = extra.get("email") or extra.get("kakao_account", {}).get("email")
47+
return bool(email)
3148

32-
def save_user(self, request, sociallogin, form=None):
33-
user = super().save_user(request, sociallogin, form)
49+
def populate_user(self, request, sociallogin, data):
50+
"""
51+
소셜 데이터로 User 필수 필드 채우기 (email/username).
52+
"""
53+
user = super().populate_user(request, sociallogin, data)
54+
55+
extra = sociallogin.account.extra_data or {}
56+
kakao_account = extra.get("kakao_account", {})
57+
profile = kakao_account.get("profile", {}) or extra.get("properties", {})
58+
59+
# 이메일 채우기 (필수)
60+
email = data.get("email") or kakao_account.get("email")
61+
if email and not getattr(user, "email", None):
62+
user.email = email
3463

35-
nickname = sociallogin.account.extra_data.get("name", "")
36-
if nickname:
37-
user.username = slugify(nickname)
64+
# username 자동 생성 (없으면 닉네임/이메일 앞부분/랜덤)
65+
if not getattr(user, "username", None):
66+
base = (
67+
data.get("username")
68+
or data.get("name")
69+
or (profile.get("nickname") if isinstance(profile, dict) else None)
70+
or (email.split("@")[0] if email else "")
71+
)
72+
user.username = slugify(base or f"user-{uuid.uuid4().hex[:10]}")
73+
return user
3874

39-
if sociallogin.account:
75+
def save_user(self, request, sociallogin, form=None):
76+
"""
77+
저장 시 provider / provider_uid도 기록(있을 때만).
78+
"""
79+
user = super().save_user(request, sociallogin, form)
80+
if hasattr(user, "provider"):
4081
user.provider = sociallogin.account.provider
82+
if hasattr(user, "provider_uid"):
4183
user.provider_uid = sociallogin.account.uid
42-
4384
user.save()
4485
return user

apps/users/forms.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from django.contrib.auth.forms import PasswordResetForm
44
from django.contrib.auth import get_user_model
55
from django.utils.translation import gettext_lazy as _
6+
from allauth.account.forms import SignupForm
7+
from django.utils.text import slugify
68

79
User = get_user_model()
810

@@ -22,3 +24,28 @@ def clean_email(self):
2224
raise forms.ValidationError(_("비밀번호 재설정을 할 수 있는 계정이 아닙니다."))
2325

2426
return email
27+
28+
class EmailSignupForm(SignupForm):
29+
"""
30+
일반 회원가입 화면에서 보여줄 폼.
31+
기본적으로 email, password1, password2는 SignupForm에 포함되어 있음.
32+
username을 받고 싶으면 required를 켜주면 됨.
33+
"""
34+
def __init__(self, *args, **kwargs):
35+
super().__init__(*args, **kwargs)
36+
# 닉네임/아이디 받고 싶으면 True, 아니라면 False로 두면 숨김
37+
if "username" in self.fields:
38+
self.fields["username"].required = True
39+
self.fields["username"].label = "닉네임"
40+
self.fields["username"].help_text = ""
41+
42+
def save(self, request):
43+
user = super().save(request)
44+
# 로컬 가입 표시(비번 재설정 허용 기준과 일치)
45+
if hasattr(user, "provider") and not user.provider:
46+
user.provider = "local"
47+
# username unique 보호(필요 시 슬러그 처리)
48+
if hasattr(user, "username") and user.username:
49+
user.username = slugify(user.username) or user.username
50+
user.save()
51+
return user

config/settings.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ def _split_env_list(key):
104104
]
105105

106106
WSGI_APPLICATION = 'config.wsgi.application'
107-
TEMPLATES[0]["OPTIONS"]["context_processors"] += [
108-
"apps.places.context_processors.public_settings",
109-
]
110107

111108
# Database
112109
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
@@ -208,12 +205,17 @@ def _split_env_list(key):
208205

209206

210207
# 이메일 로그인 관련 설정
211-
ACCOUNT_LOGIN_METHODS = {"email"}
212-
ACCOUNT_SIGNUP_FIELDS = ["username*", "email*", "password1*", "password2*"]
208+
ACCOUNT_AUTHENTICATION_METHOD = "email"
209+
ACCOUNT_SIGNUP_FIELDS = []
213210
ACCOUNT_UNIQUE_EMAIL = True #이메일 중복 허용 불가
214211
SOCIALACCOUNT_AUTO_SIGNUP = True
215212
SOCIALACCOUNT_LOGIN_ON_GET = True
216213
SOCIALACCOUNT_ADAPTER = 'apps.users.adapter.MySocialAccountAdapter'
214+
ACCOUNT_EMAIL_REQUIRED = True
215+
ACCOUNT_USERNAME_REQUIRED = False # (소셜 자동가입 시 username 때문에 폼 안 뜨게)
216+
ACCOUNT_EMAIL_VERIFICATION = "none" # 개발 중엔 끔(운영 전환 시 "mandatory")
217+
SOCIALACCOUNT_EMAIL_REQUIRED = True
218+
SOCIALACCOUNT_QUERY_EMAIL = True
217219

218220
AUTHENTICATION_BACKENDS = (
219221
#추가 장고에서 사용자의 이름을 기준으로 로그인하도록 설정
@@ -255,7 +257,8 @@ def _split_env_list(key):
255257
# 제공하는 값이 다르기 때문에 가져올 데이터를 설정한 이후 추가/삭제 해보면 됩니다.
256258
# SCOPE값에 제공하지 않는 값을 넣거나 하는 이유로 오류가 나올 수 있음
257259
"SCOPE": [
258-
260+
"profile_nickname",
261+
"account_email",
259262
],
260263
#추가
261264
"AUTH_PARAMS": {
@@ -330,3 +333,8 @@ def _split_env_list(key):
330333
CSRF_TRUSTED_ORIGINS = _split_env_list("CSRF_TRUSTED_ORIGINS")
331334
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
332335

336+
# settings.py
337+
ACCOUNT_FORMS = {
338+
"signup": "apps.users.forms.EmailSignupForm",
339+
}
340+

static/css/main.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,9 @@ form[method="get"] > div button[type="submit"]:hover {
708708
cursor: pointer !important;
709709
box-shadow: none !important;
710710
gap: 8px;
711+
position: fixed;
712+
bottom: 20px;
713+
right: 20px;
711714
}
712715

713716
/* 실제 버튼 모양 */

static/css/style.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ nav{
4141
.logo_div { width:150px; height:75px; overflow:hidden;}
4242
.logo_div img { width:100%; height:100%; object-fit:contain; }
4343

44+
/* 죄측 네비 */
45+
.left_nav{margin-left: 10px;}
46+
4447
/* 우측 네비 */
4548
.right_nav{ display:flex; gap:10px; }
4649
.right_nav a{

0 commit comments

Comments
 (0)