장고 필드(TextChoices, ImageField, Validators)를 활용한 프로필 수정

guava·2022년 1월 3일
1
post-custom-banner

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.

장고의 CharFieldvalidators를 지정해서 입력을 보다 타이트하게 지정할 수 있다.

또한 choices인자에 TextChoices를 전달함으로서 유저가 텍스트를 선택하도록 할 수 있다. (ex. 성별: 남, 여 중 선택)

ImageField를 통해서 파일을 입력받을 수도 있다.

본 글에서는 유저의 프로필 모델을 정의해보면서 위에서 설명한 CharField의 validators, choices인자ImageField를 사용해본다.

프로필 모델에 구현할 필드는 다음과 같다.

필드설명장고 필드
phone_number휴대폰 번호CharField(validators=?)
gender성별CharField(choices=?)
avatar프로필 이미지ImageField()

모델

# accounts/models.py

from django.contrib.auth.models import AbstractUser
from django.core.validators import RegexValidator
from django.db import models
from django.template.loader import render_to_string

class User(AbstractUser):
    class GenderChoices(models.TextChoices):
        MALE = 'M', '남성'
        FEMALE = 'F', '여성'
    # ...
    phone_number = models.CharField(validators=[RegexValidator(r'010-?[1-9]\d{3}-?\d{4}$')], max_length=13, blank=True)
    gender = models.CharField(choices=GenderChoices.choices, max_length=1, blank=True)
    avatar = models.ImageField(blank=True, upload_to='accounts/avatar/%Y/%m/%d',
                               help_text='48px * 48px 크기의 png/jpg 파일을 업로드해주세요')
    # ...

phone_number

RegexValidator를 활용해 정규표현식을 정의하였다. 그리고 CharFieldValidators 인자에 넣어주었다.

지정한 정규표현식 규칙

  • 010으로 시작
  • 하이픈 하나가 있을수도 없을수도 있다
  • 1~9까지의 한글자
  • 숫자 3개
  • 하이픈 하나가 있을수도 없을수도 있다
  • 숫자 4개

blank=True을 통해 빈 스트링이 입력 가능하도록 하였다.

phone_number = models.CharField(validators=[RegexValidator(r'010-?[1-9]\d{3}-?\d{4}$')], max_length=13, blank=True)

gender

TextChoices를 상속받은 GenderChoices를 정의하였다.

  • 폼을 생성할 때 TextChoices에서 정의한 텍스트가 모델 유효성 검사에 적용된다.
  • 폼 위젯에서 표준 텍스트 필드가 아닌 선택 사항이 있는 Select Box로 정의된다.
  • TextChoices에서 튜플의 첫번째 값이 실제 데이터베이스에 저장되는 값이다.

blank=True을 통해 빈 스트링이 입력 가능하도록 하였다.

gender = models.CharField(choices=GenderChoices.choices, max_length=1, blank=True)

avatar

ImageFieldCharField를 래핑해서 만든 필드다. 내부적으로 max_length는 설정되어 있다.

upload_to를 통해 저장 경로를 입력해준다. (upload_to에 함수를 지정하는 방법)

blank=True을 통해 빈 스트링이 입력 가능하도록 하였다.

이미지필드는 django-imagekit이라는 라이브러리를 통해 구현할 수 있다. 다양한 유효성 검사를 지원하고 이미지 변환 또한 지원한다.

avatar = models.ImageField(blank=True, upload_to='accounts/avatar/%Y/%m/%d', help_text='48px * 48px 크기의 png/jpg 파일을 업로드해주세요')

모델에서 정의한 필드를 폼에도 추가해준다.

# accounts/forms.py

class ProfileForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['avatar', 'phone_number', 'gender', # ...]

파일을 저장하려면 request.FILES를 폼에 입력해줘야 한다.

ProfileForm()과 같이 아무런 인자를 넣지 않고 폼을 호출하면 안된다. (빈 프로필을 생성하려고 한다.)

# accounts/views.py

@login_required
def profile_edit(request):
    if request.method == 'POST':
        form = ProfileForm(request.POST, request.FILES, instance=request.user)
        if form.is_valid():
            form.save()
            messages.success(request, '프로필을 수정/저장했습니다.')
            return redirect('profile_edit')
    else:
        form = ProfileForm(instance=request.user)
    return render(request, 'accounts/profile_edit_form.html', {
        'form': form
    })
# urls

urlpatterns = [
    # ...
    path('edit/', views.profile_edit, name='profile_edit'),
]

템플릿

askcompany/templates/layout.html

user에 avatar가 있다면, img태그의 src인자에 url을 넣어준다.

...

<a class="p-2 text-dark" href="{% url 'profile_edit' %}">
  {% if user.avatar %}
    <img src="{{ user.avatar.url }}" alt="">
  {% else %}
    <img src="{% url 'pydenticon_image' data=user.username %}" style="width: 24px; height: 24px">
  {% endif %}
</a>

...

askdjango/templates/_form.html

파일 업로드를 하려면 enctypemultipart/form-data로 지정되어 있어야 한다.

{% load bootstrap4 %}

<div class="card">
  {% if form_title %}
    <div class="card-header">
      {{ form_title }}
    </div>
  {% endif %}
  <div class="card-body">
    {% if form %}
      <form action="" method="POST" enctype="multipart/form-data">
      {% csrf_token %}
      {% bootstrap_form form %}
      {% buttons %}
        <button type="submit" class="btn btn-primary">
          {{ submit_label|default:"Submit" }}
        </button>
      {% endbuttons %}
    </form>
    {% else %}
      <div class="alert alert-danger">
        form 객체를 지정해주세요
      </div>
    {% endif %}
  </div>
</div>

accounts/templates/accounts/layout.html

{% extends 'layout.html' %}

accounts/templates/accounts/profile_edit_form.html

{% extends 'accounts/layout.html' %}

{% block content %}
  <div class="container">
    <div class="row">
      <div class="col-sm-6 offset-sm-3">
        {% include '_form.html' with form_title='프로필 수정' submit_label='프로필 수정' %}
      </div>
    </div>
  </div>
{% endblock %}
post-custom-banner

0개의 댓글