Python 부트캠프(멀티잇 데이터 분석&엔지니어링 캠프) 에서 배운 django수업을 기반으로 직접 2023년 8월경 다른 블로그에 작성한 글을 가져왔습니다.
github
프로젝트이름: insta
APP이름: posts
python -m venv venv
source venv/bin/activate
pip install django
django-admin startproject insta .
django-admin startapp posts
settings.py 의 INSTALLED_APPS 추가
settings.py 의 TEMPLATES [BASE_DIR/'templates'], 추가
상위폴더 > templates폴더 생성 > base.html 생성
.gitignore생성
README.md생성
posts>models.py
class Post(models.Model):
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
image = models.ImageField(upload_to='image/')
# ImageField는 Pillow함수가 있어야 사용가능하다.
사진을 다운받을 수 있는 라이브러리 다운받기
pip install pillow
여기서 잠깐 tip!
현재 작동되는 버전 확인
pip freeze해당 내용들을 txt로 저장
pip freeze >> requirement.txtclone후 다른사람 사용버전 그대로 다운받기
pip install -r requrements.txt
MEDIA_ROOT와 MEDIA_URL
settings.py 하단
# 업로드한 사진을 저장할 위치
# media라는 폴더를 만들거고, 그 안에 사진들을 저장할거야.
MEDIA_ROOT = BASE_DIR / 'media'
# 미디어 경로를 처리할 URL
# 그리고 그 저장된 사진들을 찾을 때는,
MEDIA_URL = '/mdeia/'
models.py에 추가했거나, 변형 일어날 경우마다 해야함
python manage.py makemigrations
python manage.py migrate
admin
from django.contrib import admin
from .models import Post
# Register your models here.
admin.site.register(Post)
python manage.py createsuperuser
python manage.py runserver
http://127.0.0.1:8000/admin/
Posts클릭후 이미지 및 게시글 생성

포스팅 후, 상위폴더에 media/image폴더 생성되고, 포스팅한 이미지가 저장됨을 확인할 수 있다.


현재 MEDIA_ROOT를 보면,
BASE_DIR 전체안에 -> media폴더 안에 -> upload_to 라고 image폴더를 만들어서 거기 안에 넣겠다.
models.py
image = models.ImageField(upload_to='image/%Y/%m')
models.py에 수정이 일어났으니 다시 db접근
python manage.py makemigrat
ions
python manage.py migrate
python manage.py runserver

날짜가 추가된 별도폴더에 저장된다.
저렇게 회색으료 표시 = 자체 gitignore
.gitignore파일에 ###Django### 를 보면
media가 포함되어 있다.
urls.py
from django.contrib import admin
from django.urls import path, include # include추가
urlpatterns = [
path("admin/", admin.site.urls),
path('posts/', include('posts.urls')),
posts> urls.py 생성
from django.urls import path
from . import views
app_name = 'posts'
urlpatterns - [
path('',views.index, name='index'),
]
views.py
def index(request):
pass
base.html
! 하고 tab 으로 기본폼 불러오기
bootstrap으로 꾸밀거라 CSS코드,JS코드 붙혀넣기
CSS코드는 </head> 바로 전에, JS코드는 </body> 바로 전에 붙힌다.
{빈공간} 만들고 위아래로 container박스화해서 꾸미자
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
</head>
<body>
<div class="container">
{% block body %}
{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
</body>
</html>
templates폴더 > _nav.html생성
bootstrap에서 NavBar맘에드는 코드
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link active" aria-current="page" href="#">Home</a>
<a class="nav-link" href="#">Features</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
</div>
</div>
</div>
</nav>
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
</head>
<body>
{% include '_nav.html' %} <!--여기 넣읍니다-->
<div class="container">
{% block body %}
{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
</body>
</html>
views.py
from .models import Post
def index(request):
posts = Post.objects.all()
context = {
'posts' : posts,
}
return render(request, 'index.html', context)
posts>templates폴더>index.html
{% extends 'base.html' %}
{% block body %}
{{posts}}
{% endblock %}
확인
http://127.0.0.1:8000/posts/

posts>templates>_card.html생성
bootstrap card코드
<div class="card" style="width: 18rem;">
<img src="..." class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>
index.html
{% extends 'base.html' %}
{% block body %}
{% for post in posts %}
{% include '_card.html' %}
{% endfor %}
{% endblock %}
확인
http://127.0.0.1:8000/posts/

_card.html 잠깐 수정
<div class="card" style="width: 18rem;">
<img src="..." class="card-img-top" alt="...">
<div class="card-body">
<!--<h5 class="card-title">Card title</h5>-->
<p class="card-text">{{post.content}}</p>
<!--<a href="#" class="btn btn-primary">Go somewhere</a>-->
</div>
</div>

여기서 잠깐!
image는 표 형식이 아닌 정보이다.
post.image를 적어 출력하면, 그냥 그 image의 이름이 나온다.
이제 우리는 이미지의모습과 저장되어있는이미지를 일치화한다.
insta>urls.py
from django.contrib import admin
from django.urls import path, include # include추가
from django.conf import settings # 이미지 출력시
from django.conf.urls.static import static # 이미지출력시
urlpatterns = [
path("admin/", admin.site.urls),
path('posts/', include('posts.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# 이 image를 처리할 때, urls주소를 저장하고, 그 이미지의 위치는 저기 settings.py안에 있어요.

_card.html
{{post.image.url}} 를 추가해보자.
<div class="card" style="width: 18rem;">
<img src="..." class="card-img-top" alt="...">
<div class="card-body">
<!--<h5 class="card-title">Card title</h5>-->
<p class="card-text">{{post.content}}</p>
<p>{{post.image}}</p>
<p>{{post.image.url}}</p>
<!--<a href="#" class="btn btn-primary">Go somewhere</a>-->
</div>
</div>


img src="{{post.image.url}}" 을 추가해줘야한다.
_card.html
<div class="card" style="width: 18rem;">
<img src="{{post.image.url}}" class="card-img-top" alt="...">
<div class="card-body">
<!--<h5 class="card-title">Card title</h5>-->
<p class="card-text">{{post.content}}</p>
<!--<a href="#" class="btn btn-primary">Go somewhere</a>-->
</div>
</div>

db인 sql상에서 저장되있는 이미지 url주소와
static이라는 정적파일(표가 아닌) 로 경로로 만들고, 실제 파일이 저장되있는 위치를 알려준다.
어떤 경로로 들어왔을 때, 실제 실행할 (보여줄) 파일을 알려준다.
그래야 화면에 출력시, 이미지를 출력하는게 아니라
static이 만들어준 경로로 따라,
사진을 가져오고, 화면에 보여준다.
_nav.html
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'posts:index' %}">Home</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link" href="{% url 'posts:create' %}">Create</a>
<a class="nav-link" href="#">Features</a>
<a class="nav-link" href="#">Pricing</a>
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
</div>
</div>
</div>
</nav>
posts>urls.py
from django.urls import path
from . import views
app_name = 'posts'
urlpatterns = [
path('',views.index, name='index'),
path('create/',views.create, name='create'),
]
views.py
def create(request):
if request.method == 'POST':
pass
else:
pass
posts>forms.py생성
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = '__all__'
views.py
from .forms import PostForm
def create(request):
if request.method == 'POST':
pass
else:
form = PostForm()
context = {
'form': form,
}
return render(request,'form.html',context)
posts>templates폴더>form.html
{% extends 'base.html' %}
{% block body %}
{% endblock %}
이제 조건문으로 게시글 하나하나
{% extends 'base.html' %}
{% block body %}
<form action="" method="POST">
{% csrf_token %}
{{form}}
<input type="submit">
</form>
{% endblock %}
확인
http://127.0.0.1:8000/posts/create/

but!
작성후 이미지 넣고 제출하면error사진을 넣는게 아니라, 사진있는 위치를 알려줘야한다.
form.html
enctype="multipart/form-data" 추가해주기
{% extends 'base.html' %}
{% block body %}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{form}}
<input type="submit">
</form>
{% endblock %}
http://127.0.0.1:8000/posts/create/
게시글 작성 및 이미지 넣고 제출 하면 해당 error 발생되어야함

안에 파일(값)은 들어가 있는 상태이다.
이제 사용자가 입력한 사진(get)요청을 -> post화 하여 출력시키자
views.py
def create(request):
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
# request.POST 사용자가 입력한 사진
# request.FILES 그 사진을 파일화 함
if form.is_valid():
form.save()
return redirect('posts:index')
from django.shortcuts import render, redirect # redirect추가
확인


목표: 
_card.html
{{post.content}} 아래에 내용 추가
<p class="card-text">{{post.created_at}}</p>

timesince를 이용해보자
<p class="card-text">{{post.created_at |timesince}}</p>

order_by()적용
views.py
def index(request):
posts = Post.objects.all()
# 모든 값을 보여줘
posts = Post.objects.all().order_by('-id')
# id값(작성순서)를 역순으로 보여줘
# 최신작성된 거 먼저 보여줘

현재는 업로드한 사진들을 원본 그대로 저장하기때문에, 비율이 제각각이다.
외부라이브러리를 이용해서, 크롭화 기능을 시키자
django resized
잠시 ctrl+c로 기능 끄고 설치하자.
pip install django-resized
그리고 설치한 걸 기록화 해주자
pip freeze >> requirements.txt

models.py
from django.db import models
from django_resized import ResizedImageField # 이미지크롭화
class Post(models.Model):
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# image = models.ImageField(upload_to='image/%Y/%m')
# 그리고 이걸 추가
image = ResizedImageField(
size=[500,500],
crop=['middle', 'center'],
upload_to='image/%Y/%m'
)
models.py에 수정이 생겼으니, db 도 업데이트
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
확인

원본사진 그대로가 아닌, 규격화한 사이즈대로 포스팅된다.
규격화하는 외부라이브러리는
이 외에도 많다.
그나마 쉬워보이는 걸로 적용하긴 했다.
django-admin startapp accounts
settings.py
INSTALLED_APPS = [
"accounts",
]
insta>urls.py
path('accounts/', include('accounts.urls')),
accounts>urls.py생성
from django.urls import path
from . import views
app_name = 'accounts'
urlpatterns = [
path('signup/', views.signup, name='signup'),
]
accountsviews.py
from django.shortcuts import render
# Create your views here.
def signup(request):
if request.method == 'POSt':
pass
else:
pass
accountsmodels.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django_resized import ResizedImageField
# Create your models here.
class User(AbstractUser):
# 컬럼은 일단 하나만 추가
profile_image = ResizedImageField(
size=[500,500],
crop=['middle','center'],
upload_to='profile',
)
settings.py
AUTH_USER_MODEL = 'accounts.USER' # 추가

빨간거 삭제
``
python manage.py makemigrations
python manage.py migrage
그래도 안되면 db.sqlite3 삭제

python manage.py migrage

accounts 의 models.py
posts 의 modesl.py
를 1: N 연결
postsmodels.py
from django.conf import settings
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)



models.py 두 개나 수정했으니,
다음은 뭐다????
accounts> migrations > 0001 파일 삭제
posts> migrations > 0001 파일 삭제
db.sqlite3 삭제
다시 재설치
python manage.py makemigrations
python manage.py migrate
accounts>forms.py 생성
from django.contrib.auth.forms import UserCreationForm
from .models import User
from django.contrib.auth import get_user_model
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = get_user_model()
# model = User 를 대신 사용할 수 있는데
# 다만, get_user_model() 쓰는게 유지보수하기 좋다.
fields = ('username', 'profile_image',)
# profile_image = 사용자가 회원가입할때 프로필사진 올릴수있도록
views.py
from django.shortcuts import render
from .forms import CustomUserCreationForm # 모델폼 추가
# Create your views here.
def signup(request):
if request.method == 'POST':
pass
else:
form = CustomUserCreationForm() # 모델폼으로 보여주게 해줘(작성할수있도록)
context = {
'form': form,
}
return render(request, 'form.html', context)
# 아직 form.html 파일을 만들지 않았는데 ,
# 여기까지 후 작동하게 되면
아직form.html 파일을 만들지 않았는데
여기까지 후 작동하게 되면 작성할수있는어떤 form이 나온다!
이때 나오는건,posts>templates>forms.html이다.
전체프로젝트폴더 내의 templates 의 .html 을 모아놓고
해당 요청하는 form.html 똑같은 이름이 있으니까 보여주는거다.폴더 순서대로있는 .html 으로 조회된다.
1. 파일의 이름을 다르게 해도 되고,
2. templates폴더>accounts폴더로 생성>form.html생성 하면
accounts>
views.pyreturn render(request, 'accounts/form.html', context)
accounts>templates>accounts폴더생성>form.html 생성
{% extends 'base.html' %}
{% block body %}
<h1>signup</h1>
<form action="" method="POST" >
{% csrf_token %}
{{form}}
<input type="submit">
</form>
{% endblock %}
뭐 어자피 현재로서는 form.py 안의 내용물은 똑같아서 똑같이 출력되긴 한데,,, 헷갈리니까 signup의 form.html은 signup을 추가해보자

bootstrap코드 불러오기
pip install django-bootstrap-v5
뭔가 install했을때는 그걸 저장해주자
pip freeze >> requirements.txt
settings.py
INSTALLED_APPS = [
'bootstrap5'
]
accounts>templates>accounts>views.html for signup
{% extends 'base.html' %}
{% load bootstrap5 %}
{% block body %}
<h1>signup</h1>
<form action="" method="POST" >
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit">
</form>
{% endblock %}

views.py
from django.shortcuts import render, redirect # redirect 추가
def signup(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return redirect('posts:index')
form.html
{% extends 'base.html' %}
{% load bootstrap5 %}
{% block body %}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit">
</form>
{% endblock %}
_nav.html
signup, login 버튼생성
<a class="nav-link" href="{% url 'posts:create' %}">Create</a>
<a class="nav-link" href="{% url 'accounts:signup' %}">Signup</a>
<a class="nav-link" href="{% url 'accounts:login' %}">Login</a>
accountsurls.py
path('login/',views.login, name='login'),
]
accountsviews.py
def login(request):
if request.method == 'POST':
pass
else:
pass
accountsforms.py
from django.contrib.auth.forms import UserCreationForm , AuthenticationForm
# AuthenticationForm 추가
class CustomAuthenticationForm(AuthenticationForm):
pass
accountsviews.py
from .forms import CustomUserCreationForm , CustomAuthenticationForm
def login(request):
if request.method == 'POST':
pass
else:
form = CustomAuthenticationForm()
context = {
'form': form,
}
return render(request, 'accounts/form.html', context)
추가
views.py
from .forms import CustomUserCreationForm , CustomAuthenticationForm
from django.contrib.auth import login as auth_login
def login(request):
if request.method == 'POST':
form = CustomAuthenticationForm(request, request.POST)
if form.is_valid():
user = form.get_user()
auth_login(request,user)
return redirect('posts:index')
_nav.html
{{user}} 추가
<div class="navbar-nav">
<a class="nav-link" href="{% url 'posts:create' %}">Create</a>
<a class="nav-link" href="{% url 'accounts:signup' %}">Signup</a>
<a class="nav-link" href="{% url 'accounts:login' %}">Login</a>
<a class="nav-link disabled" aria-disabled="true">{{user}}</a>
</div>
조건문 추가
user가 로그인했나요?
<div class="navbar-nav">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'posts:create' %}">Create</a>
<a class="nav-link disabled" aria-disabled="true">{{user}}</a>
{% else %}
<a class="nav-link" href="{% url 'accounts:signup' %}">Signup</a>
<a class="nav-link" href="{% url 'accounts:login' %}">Login</a>
{% endif %}
</div>


postsforms.py
# fields='__all__'대신,
exclude = ('user',)

postsviews.py
def create(request):
if request.method == 'POST':
form = PostForm(request.POST, request.FILES)
if form.is_valid():
post = form.save(commit=False)
post.user = request.user
post.save()
return redirect('posts:index')


_card.html
<div class="card" >
<div class="card-header">
<img src="{{post.user.profile_image.url}}" alt="">
<p>{{post.user}}</p>
</div>
<img src="{{post.image.url}}" alt="...">
<div class="card-body">
<!--<h5 class="card-title">Card title</h5>-->
<p class="card-text">{{post.content}}</p>
<p class="card-text">{{post.created_at |timesince}}</p>
<!--<a href="#" class="btn btn-primary">Go somewhere</a>-->
</div>
</div>
이렇게 되면 프로필사진 + 작성사진 까지 크~게 나온다.
<img src="{{post.user.profile_image.url}}" alt="" class="rouned-circle" width="30px">
<div class="card" style="width: 18rem;">
<div class="card-header">
<p>
<img src="{{post.user.profile_image.url}}" alt="" class="rounded-circle" width="50px">
{{post.user}}
</p>
</div>
<img src="{{post.image.url}}" alt="...">
<div class="card-body">
<!--<h5 class="card-title">Card title</h5>-->
<p class="card-text">{{post.content}}</p>
<p class="card-text">{{post.created_at |timesince}}</p>
<!--<a href="#" class="btn btn-primary">Go somewhere</a>-->
</div>
</div>

유저 클릭하면, 그 유저의 프로필페이지를 만들어보자.
accountsurls.py
path('<str:username>/', views.profile, name='profile'),
# 여기에는 accounts/가 앞에 생략되었다.
accountsviews.py
from .models import User # 이렇게 직접가져오는것 보다는 유지보수 차원에서 아래 추천
from django.contrib.auth import get_user_model
def profile(request, username):
User = get_user_model()
# 해당 프로필에 적용되는 일단, 모든 유저를 불러와줘
# User.objects.get(username=username)
# id로 찾는게 아니라, username으로 찾아야한다.
user_info = User.objects.get(username=username)
context = {
'user_info': user_info
}
return render(request, 'accounts/profile.html', context)
accounts>templates>accounts>profile.html생성
{% extends 'base.html' %}
{% load bootstrap5 %}
{% block body %}
{{user_info}}
{% endblock %}
http://127.0.0.1:8000/accounts/유저이름/

profile.html
{% extends 'base.html' %}
{% load bootstrap5 %}
{% block body %}
<div class="row">
<div class="col-4">
<img src="{{user_info.profile_image.url}}" alt="">
</div>
<div class="col-8">
2
</div>
</div>
{% endblock %}

사진 둥글게 + 사이즈줄이면 사진도줄이는 fluid반응형기능
<img src="{{user_info.profile_image.url}}" alt="" class="img-fluid rounded-circle">


profile.html
<div class="col-8"> <!--8칸-->
<div class="row"></div>
<div class="col-4">{{user_info.username}}</div>
<div class="col-4"><a href="">팔로우</a></div>
<div class="row"></div>
하단
<div class="row">
<div class="col">게시물</div>
<div class="col">팔로잉</div>
<div class="col">팔로우</div>
</div>

더 하단
이제 포스트 한 갯수만큼 이미지
{% for post in user_info.post_set.all %}
{% endfor %}
{% endblock %}
근데 이걸 카드화
{% for post in user_info.post_set.all %}
<div>
<img src="{{post.image.url}}" alt="">
</div>
{% endfor %}

한 줄에 3개씩
div.row.row-col3 누르고 tab
profile.html
<div class="row row-cols-3">
{% for post in user_info.post_set.all %}
<div class='col'>
<div class="card">
<img src="{{post.image.url}}" alt="">
</div>
</div>
{% endfor %}
</div>
{% endblock %}


_card.html
{{post.user}} 에 이동페이지 url
<a href="{% url 'accounts:profile' username=post.user %}">{{post.user}}</a>


http://127.0.0.1:8000/posts/에서 어떤 유저 클릭
-> http://127.0.0.1:8000/accounts/blue/ 그 유저의 프로필사이트로 이동
_nav.html
<a class="nav-link" href="{% url 'accounts:profile' username=user %}" >{{user}}</a>


_card.html
class="text-reset"
text-decoration-none추가
<a href="{% url 'accounts:profile' username=post.user %}" class="text-reset text-decoration-none">{{post.user}}</a>
