blob storage랑 연동하는 부분에서 계속 에러가 나서 꽤 애를 먹었다.
class Profile(models.Model):
# ... 기존 항목
profile_image = models.URLField(blank=True, null=True) # 이미지 URL 저장 필드
여기서 내가 URLField로 받도록 설정해뒀었는데, forms에서 작업할 때 FileField로만 작업해서 오류가 났던 것,,
profile_image
는 URL을 저장하는 필드로, Azure Blob Storage에 업로드된 이미지의 URL이 저장된다.
class ProfileUpdateForm(forms.ModelForm):
# 실제 이미지 저장용 숨김 필드
profile_image = forms.URLField(
required=False,
widget=forms.HiddenInput())
# 실제 파일 업로드를 위한 필드
image_file = forms.FileField(
required=False,
widget=forms.FileInput(attrs={'class': 'form-control'})
}
class Meta:
model = Profile
fields = ['nickname', 'profile_image']
# 이미지 파일 유효성 검사 메서드
def clean_profile_image(self):
image = self.cleaned_data.get('image_file')
if image:
# 파일 크기 제한 (5MB)
if image.size > 5*1024*1024:
raise forms.ValidationError('이미지 크기가 5MB를 초과할 수 없습니다.')
# 이미지 파일 타입 검사
if not image.content_type.startswith('image'):
raise forms.ValidationError("이미지 파일만 업로드할 수 있습니다.")
return image
@login_required # 로그인한 사용자만 접근 가능
def profile_update(request):
if request.method == 'POST':
form = ProfileUpdateForm(request.POST, request.FILES, instance=reqeust.user.profile)
if form.is_valid():
profile = form.save(commit=False) # 데이터베이스 저장 보류
# 이미지 파일이 업로드된 경우 처리
if 'image_file' in request.FILES:
file = request.FILES['image_file']
# 고유한 파일명 생성
file_extension = file.name.split('.')[-1]
file_name = f"profile_{request.user.username}.{file_extension}"
try:
# Azure Blob Storage 클라이언트 설정
blob_service_client = BlobServiceClient.from_connection_string(
settings.AZURE_CONNECTION_STRING
)
container_name = settings.CONTAINER_NAME
blob_client = blob_service_client.get_blob_client(
container=container_name, blob=file_name
)
# 파일 업로드 및 URL 저장
blob_client.upload_blob(file, overwrite=True)
profile.profile_image = blob_client.url
except Exception as e:
print("==== Blob 업로드 실패 ====")
print("에러:", str(e))
profile.save() # 최종 저장
return redirect('index')
여기는 재이님이 작업하실 영역인데, 테스트용으로 일부 코드만 작성했다.
<div class="container mt-4">
<h2>프로필 수정</h2>
<div class="row">
<div class="col-md-6">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form form %}
{% if user.profile.profile_image %}
<div class="mb-3">
<p>현재 프로필 이미지:</p>
<img src="{{ user.profile.profile_image }}"
alt="프로필 이미지"
class="img-thumbnail"
style="max-width: 200px">
</div>
{% endif %}
<button type="submit" class="btn btn-primary">저장</button>
</form>
</div>
</div>
</div>
enctype="multipart/form-data"
설정프로필 사진 업로드 후 시간이 애매하게 남아서 게시글 삭제 기능을 추가했다.
@login_required
def delete_post(request: HttpRequest, pk: int) -> HttpResponse:
post = get_object_or_404(Post, pk=pk) # 존재하지 않는 게시글 처리
if request.method == "POST": # POST 요청일 때만 삭제 실행
post.delete()
return redirect("/app/") # 삭제 후 목록 페이지로 이동
# GET 요청의 경우 상세 페이지 표시
return render(request, "app/post_detail.html", {"post": post})
이것두 재이님 영역.
<button type="button" class="btn btn-danger"
data-bs-toggle="modal"
data-bs-target="#deleteModal">
삭제하기
</button>
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">게시글 삭제</h5>
<button type="button" class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
정말로 이 게시글을 삭제하시겠습니까?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal">취소</button>
<form action="{% url 'delete_post' post.id %}"
method="post" style="display: inline;">
{% csrf_token %}
<button type="submit" class="btn btn-danger">삭제</button>
</form>
</div>
</div>
</div>
</div>