728x90
인스타그램 비슷하게 동작하는 프로그램을 만들어보자
러스트 프레임워크 말고 장고 내부 프레임워크만을 이용해서 만들것
Pystagram Project
1. 기능 설정
- 인증 시스템
- 피드 페이지
- 글과 댓글
- 동적 URL
- 해시 태크
- 글 상세 페이지
- 좋아요 기능
- 팔로우/팔로잉 기능
2. 환경 설정
- 가상환경 생성(Terminal)
python -m venv pystagram
cd pystagram
source ./bin/activate
|
- 라이브러리 설치(Terminal)
pip install django Pillow
|
- 프로젝트 생성(Terminal)
django-admin startproject config .
|
- 기능별 디렉토리 설정(Terminal)
mkdir templates
mkdir static
|
- 환경설정(config/settings.py)
TEMPLATES_DIR = BASE_DIR / "templates"
...
TEMPLATES = [
{
...
"DIRS": [TEMPLATES_DIR],
}
]
|
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
|
- 환경설정(config/urls.py)
from django.conf import settings
from django.conf.urls.static import static
# 기존에 등록된 urlpatterns에 추가 설정
urlpatterns += static(
prefix=settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT,
)
|
3. 인덱스 페이지 구성
- localhost:8000 뒤에 아무런 경로 추가 없이 기본적으로 보여 줄 인덱스 페이지 구성
- config/views.py
from django.shortcuts import render
def index(request):
return render(request, "index.html")
|
- templates/index.html
<!doctype html>
<html lang="ko">
<body>
<h1>pystagram</h>
</body>
</html>
- config/urls.py
from django.contrib import admin
from django.urls import path
from config.views import index
urlpatterns = [
path("admin/", admin.site.urls),
path("", index),
]
urlpatterns += static(
prefix=settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT,
)
|
- Terminal
python manage.py migrate
python manage.py runserver
|
4. 인증 시스템
4.1 CustomUser 모델 설정
4.1.1 App 생성
- Terminal
python manage.py startapp users
|
- config/settings.py
INSTALLED_APPS = [
"users",
...
]
|
4.1.2 User 모델 생성
- users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
pass
|
- AbstractUser 모델의 필드
- username (사용자명, 로그인 할 때의 아이디)
- password (비밀번호)
- first_name (이름)
- last_name (성)
- emil (이메일)
- is_staff (관리자 여부)
- is_active (활성화 여부)
- date_joined (가입일시)
- last_login (마지막 로그인 일시)
- config/settings.py
from pathlib import Path
AUTH_USER_MODEL = "users.User"
|
- Terminal
# Migration 생성 및 적용
python manage.py makemigrations
python manage.py migrate
|
# 관리자 생성
python manage.py createsuperuser
|
# 서버 실행
python manage.py runserver
|
4.1.3 관리자 페이지에 모델 등록
- users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from users.models import User
@admin.register(User)
class CustomUserAdmin(UserAdmin):
pass
|
- config/settings.py
# 관리자 페이지 한글 적용
LANGUAGE_CODE = "ko-kr"
TIME_ZONE = "Asia/Seoul"
|
4.1.4 CustomUser 모델에 Field 추가
- users/models.py
class User(AbstractUser):
profile_image = models.ImageField("프로필 이미지", upload_to="users/profile", blank=True)
short_description = models.TextField("소개글", blank=True)
|
- Terminal
# Migration 생성 및 적용
python manage.py makemigrations
python manage.py migrate
|
- users/admin.py
@admin.register(User)
class CustomUserAdmin(UserAdmin):
fieldsets = [
(None, {"fields": ("username", "password")}),
("개인정보", {"fields": ("first_name", "last_name", "email")}),
("추가필드", {"fields": ("profile_image", "short_description")}),
("권한", {"fields": ("is_active", "is_staff", "is_superuser")}),
("중요한 일정", {"fields": ("last_login", "date_joined")}),
]
|
4.2 로그인/피드 페이지 기본 구조
4.2.1 로그인 페이지
- 기본 구조 구성
- View: login_view
- Template: templetes/users/login.html
- URL: /users/login/
- users/views.py
from django.shortcuts import render
def login_view(request):
return render(request, "users/login.html")
|
- templates/users/login.html
<!doctype html>
<html lang="ko">
<body>
<h1>Login</h>
</body>
</html>
|
- users/urls.py
from django.urls import path
from users.views import login_view
urlpatterns = [
path("login/", login_view)
]
|
- config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("users/", include("users.urls")),
path("", index),
]
urlpatterns += static(
...
|
4.2.2 피드 페이지
- 조건에 따른 View 동작 제어
- 이미 사용자가 브라우저에서 로그인을 했다면
- 피드 페이지(새글 목록)으로 이동
- 사용자가 로그인을 한 적이 없거나 로그아웃을 했다면
- 로그인 페이지로 이동
- 이미 사용자가 브라우저에서 로그인을 했다면
새글은 post로 들어가야하니 post를 위한 app하나 생성
- Terminal
# App 생성
python manage.py startapp posts
|
- config/settings.py
INSTALLED_APPS = [
"users",
"posts",
...
]
|
- posts/views.py
from django.shortcuts import render
def feeds(request):
return render(request, "posts/feeds.html")
|
- posts/urls.py
from django.urls import path
from posts.views import feeds
urlpatterns = [
path("feeds/", feeds),
]
|
- templates/posts/feeds.html
<!doctype html>
<html lang="ko">
<body>
<h1>Feeds</h>
</body>
</html>
|
- config/urls.py
urlpatterns = [
path("admin/", admin.site.urls),
path("posts/", include("posts.urls")),
path("users/", include("users.urls")),
path("", index),
]
|
4.3 로그인 여부에 따른 접속 제한
4.3.1 관리자 페이지를 사용한 로그인/로그아웃
- posts/views.py
from django.shortcuts import render
def feeds(request):
user = request.user
is_authenticated = user.is_authenticated
print("user: ", user)
print("is_authenticated: ", is_authenticated)
return render(request, "posts/feeds.html")
|
4.3.2 로그인 여부에 따라 페이지 이동시키기
- posts/views.py
from django.shortcuts import render, redirect
def feeds(request):
if not request.user.is_authenticated:
return redirect("/users/login/")
return render(request, "posts/feeds.html")
|
- users/views.py
from django.shortcuts import render, redirect
def login_view(request):
if request.user.is_authenticated:
return redirect("/posts/feeds/")
return render(request, "users/login.html")
|
4.3.3 루트 경로에서 로그인 여부에 따라 페이지 이동
- config/views.py
from django.shortcuts import redirect
def index(request):
if request.user.is_authenticated:
return redirect("/posts/feeds/")
else:
return redirect("/users/login/")
|
4.4 로그인 기능
4.4.1 Form 클래스를 사용한 로그인 페이지 구성
- user/forms.py
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(min_length=3)
password = forms.CharField(min_length=4)
|
- Terminal
python manage.py shell
- Terminal - Shell
from users.forms import LoginForm
login_data = {"username": "u", "password": "p"}
form = LoginForm(data=login_data)
form.is_valid()
form.errors
login_data2 = {"username": "Sample username", "password": "Sample password"}
form2 = LoginForm(data=login_data2)
form2.is_valid()
form2.errors
|
- users/views.py
from django.shortcuts import render, redirect
from users.forms import LoginForm
def login_view(request):
if request.user.is_authenticated:
return redirect("/posts/feeds/")
form = LoginForm()
context = {"form": form,}
return render(request, "users/login.html", context)
|
- templates/users/login.html
...
<body>
<h1>Login</h>
{{ form.as_p }}
</body>
...
|
- templates/users/login.html
...
<body>
<h1>Login</h>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">로그인</button>
</form>
</body>
...
|
4.4.2 View에 전달된 데이터를 Form으로 처리하기
- 예제
<form>
# name이 food인 Input 3개
<input name="food" value="햄버거">
<input name="food" value="제육볶음">
<input name="food" value="돈까스">
# name이 drink인 input 1개
<input name="drink" value="제로콜라">
</form>
|
- URL 전달 예제
- users/views.py
def login_view(request):
if request.user.is_authenticated:
return redirect("/posts/feeds/")
if request.method == "POST":
form = LoginForm(data=request.POST)
print("form.is_valid(): ", form.is_valid())
print("form.cleaned_data(): ", form.cleaned_data())
context = {"form": form}
return render(request, "users/login.html", context)
else:
form = LoginForm()
context = {"form": form}
return render(request, "users/login.html", context)
|
4.4.3 View에서 로그인 처리
- users/views.py
from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect
from users.forms import LoginForm
def login_view(request):
if request.user.is_authenticated:
return redirect("/posts/feeds/")
if request.method == "POST":
form = LoginForm(data=request.POST)
if form.is_valid():
username = form.cleaned_data["username"]
password = form.cleaned_data["password"]
user = authenticate(username=username, password=password)
if user:
login(request, user)
return redirect("/posts/feeds/")
else:
print("로그인에 실패했습니다.")
context = {"form": form}
return render(request, "users/login.html", context)
else:
form = LoginForm()
context = {"form": form}
return render(request, "users/login.html", context)
|
- Terminal
# Authenticate
python manage.py shell
from django.contrib.auth import authenticate
user = authenticate(username='a', password='b')
print(user)
user = authenticate(username='pystagram', password='1234')
print(user)
exit()
|
4.5 로그아웃 구현 및 로그인 개선
4.5.1 로그아웃 기능
- 로그아웃 기본 구조 구현
- View: logout_view
- URL: /users/logout/
- Template: 없음
- users/views.py
from django.contrib.auth import authenticate, login, logout
def login_view(request):
...
def logout_view(request):
logout(request)
return redirect("/users/login/")
|
- users/urls.py
from users.views import login_view, logout_view
urlpatterns = [
path("login/", login_view),
path("logout/", logout_view),
]
|
- templates/posts/feeds.html
<!doctype html>
<html lang="ko">
<body>
<h1>Feeds</h1>
<a href="/users/logout/">로그아웃</a>
</body>
</html>
|
4.5.2 로그인 개선
- templates/posts/feeds.html
- 피드 페이지에 로그인 상태 표시
<!doctype html>
<html lang="ko">
<body>
<h1>Feeds</h1>
<div>{{ user.username }} (ID: {{ user.id }})</div>
<a href="/users/logout/">로그아웃</a>
</body>
</html>
|
- users/views.py
- 로그인 실패 시 정보 표시
def login_view(request):
...
if request.method == "POST":
form = LoginForm(data=request.POST)
if form.is_valid():
...
if user:
...
else:
form.add_error(None, "입력한 자격증명에 해당하는 사용자가 없습니다.")
|
- templates/users/login.html
- 로그인 페이지 CSS 스타일링, Form 기능 추가
- 해당 style.css 와 splide.css 파일 다운받아서 파일에 적용
- static/css/style.css
- static/splide/ splide.css
{% load static %} # static에대한 자료 읽어오기
<!doctype html>
<html lang="ko">
<head>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<div id="login">
<form method="POST">
{% csrt_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-login">로그인</button>
</form>
</div>
</body>
</html>
|
- users/forms.py
from django import forms
class LoginForm(forms.Form):
username = forms.CharField(
min_length=3,
widget=forms.TextInput(
attrs={"placeholder": "사용자명 (3자리 이상)"},
),
)
password = forms.CharField(
min_length=4,
widget=forms.PasswordInput(
attrs={"placeholder": "비밀번호 (4자리 이상)"},
),
)
|
4.6 회원가입
4.6.1 기본 구조 생성
- View: signup
- URL: /users/signup/
- Template: templates/users/signup.html
- users/views.py
def signup(request):
return render(request, "users/signup.html")
|
- users/urls.py
from django.urls import path
from users.views import login_view, logout_view, signup
urlpatterns = [
path("login/", login_view),
path("logout/", logout_view),
path("signup/", signup),
]
|
- templates/users/signup.html
<!doctype html>
<html lang="ko">
<body>
<h1>Sign up</h1>
</body>
</html>
|
4.6.2 SignupForm을 사용한 Tempalte 구성
- users/forms.py
- SighupForm 클래스 정의
class SignupForm(forms.Form):
username = forms.CharField()
password1 = forms.CharField(widget=forms.PasswordInput)
password2 = forms.CharField(widget=forms.PasswordInput)
profile_image = forms.ImageField()
short_description = forms.CharField()
|
- users/views.py
- View에서 Template에 SignupForm 전달
from users.forms import LoginForm, SignupForm
def signup(request):
form = SignupForm()
context = {"form": form}
return render(request, "users/signup.html", context)
|
- templates/signup.html
<!doctype html>
<html lang="ko">
<body>
<h1>Sign up</h1>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">회원가입</button>
</form>
</body>
</html>
|
4.6.3 View에 회원가입 로직 구현
- Terminal
- create_user 메서드
python manage.py shell
from users.models import User
user = User.objects.create(username="sample", password="sample")
print(user.id, user.username, user.password)
|
from django.contrib.auth import authenticate
result = authenticate(username="sample", password="sample")
print(result)
|
- users/views.py
- SignupForm의 데이터 가져오기
def signup(request):
if request.method == "POST":
print(request.POST)
print(request.FILES)
form = SignupForm()
context = {"form": form}
return render(request, "users/signup.html", context)
|
- users/views.py
def signup(request):
if request.method == "POST":
form = SignupForm(data=request.POST, files=request.FILES)
if form.is_valid():
username = form.cleaned_data["username"]
password1 = form.cleaned_data["password1"]
password2 = form.cleaned_data["password2"]
profile_image = form.cleaned_data["profile_image"]
short_description = form.cleaned_data["short_description"]
print(username)
print(password1, password2)
print(profile_image)
print(short_description)
user = form.save()
login(request, user)
return redirect("posts:feeds")
context = {"form": form}
return render(request, "users/signup.html", context)
form = SignupForm()
context = {"form": form}
return render(request, "users/signup.html", context)
|
- Terminal
- User 생성하기
python manage.py shell
from users.models import User
User.objects.filter(username="root")
User.objects.filter(username="root").exists()
User.objects.filter(username="no_user")
User.objects.filter(username="no_user").exists()
|
- users/views.py
from users.models import User
def signup(request):
if request.method == "POST":
form = SignupForm(data=request.POST, files=request.FILES)
if form.is_valid():
username = form.cleaned_data["username"]
password1 = form.cleaned_data["password1"]
password2 = form.cleaned_data["password2"]
prifile_image = form.cleaned_data["profile_image"]
short_description = form.cleaned_data["short_description"]
if password1 != password2:
form.add_error("password2", "비밀번호와 비밀번호 확인란의 값이 다릅니다.")
if User.objects.filter(username=username).exists():
form.add_error("username", "입력한 사용자명은 이미 사용중입니다.")
if form.errors:
context = {"form": form}
return render(request, "users/signup.html", context)
else:
user = User.objects.create_user(
username = username,
password = password1,
profile_image = profile_image,
short_description = short_description,
)
login(request, user)
return redirect("/posts/feeds/")
else:
form = SignupForm()
context = {"form": form}
return render(request, "users/signup.html", context)
|
728x90
'PYTHON-BACK' 카테고리의 다른 글
#파이썬 34일차_이커머스 클론코딩4_(1) (0) | 2024.08.21 |
---|---|
#파이썬 33일차_이커머스 클론코딩3 (0) | 2024.08.20 |
#파이썬 31일차_이커머스 클론코딩1 (0) | 2024.08.16 |
#파이썬 31일차_게시판 만들기3 (0) | 2024.08.16 |
#파이썬 30일차_게시판 만들기2 (0) | 2024.08.14 |