osamhack2022 / WEB_SharedDiary_Nuri

์•„์นด์ด๋ธŒ๋ฅผ ๊ฐ•ํ™”ํ•œ SNSํ˜•ํƒœ์˜ ๊ณต์œ ํ˜• ์ผ๊ธฐ์žฅ
MIT License
0 stars 2 forks source link

๐ŸŒฑ ๋ˆ„๋ฆฌ

์•„์นด์ด๋ธŒ๋ฅผ ๊ฐ•ํ™”ํ•œ SNSํ˜•ํƒœ์˜ ๊ณต์œ ํ˜• ์ผ๊ธฐ์žฅ


์ €์žฅ์†Œ ๊ทœ์น™๋ช…์‹œ
WEB(FE) : Project/frontend
WEB(BE) : Project/backend


Index


  1. ์™œ ๋งŒ๋“ค์—ˆ๋‚˜? ๐Ÿš€
  2. ๋ญ˜๋กœ ๋งŒ๋“ค์—ˆ๋‚˜? ๐Ÿ’ป
  3. ์‹คํ–‰ ์ฃผ์˜์‚ฌํ•ญ? โ—
  4. ์„ค์น˜๋ฐฉ๋ฒ•? โš™๏ธ
  5. ํŒ€ ๊ตฌ์„ฑ์›? ๐Ÿ”ฅ
  6. ๊ธฐ๋Šฅ ์„ค๋ช…? ๐Ÿ…
  7. ๊ฐœ๋ฐœ ๋ฌธ์„œ? ๐Ÿ“š



2022_10_30


๐Ÿš€ Service Needs

์ผ๊ธฐ๋ฅผ ์“ฐ๋ฉด์„œ ๋‚ด๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์€ ์–ด๋–ค ๊ธ€์„ ์ ์„๊นŒ. ๋‚˜์™€ ์–ด๋–ค ๋‹ค๋ฅธ ์ƒ๊ฐ์„ ๊ฐ€์ง€๊ณ  ์žˆ์„๊นŒ ๊ณ ๋ฏผํ•œ ๊ฒฝํ—˜์ด ๋งŽ๋‹ค. ์Šค๋งˆํŠธํฐ์ด ๋Œ€์ค‘ํ™”๋˜๋ฉด์„œ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์–ดํ”Œ์— ์ผ๊ธฐ๋ฅผ ์ ๊ณ  ์žˆ๊ณ , ์ด๋Ÿฐ ๊ณ ๋ฏผ์„ ๋ฐ˜์˜ํ•ด ๊ณต์œ  ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•œ ์„œ๋น„์Šค๋„ ์ด๋ฏธ ์กด์žฌํ•œ๋‹ค. ํ•˜์ง€๋งŒ ๊ณต์œ ๊ธฐ๋Šฅ์„ ์˜๋„๋Œ€๋กœ ์‚ด๋ฆฌ๊ณ  ์œ ์ € ๊ฐ„์— ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜์„ ํ™œ๋ฐœํ•˜๊ฒŒ ์ด๋Œ์–ด๋‚ด๋Š”๋ฐ ์„ฑ๊ณตํ•œ ์„œ๋น„์Šค๋Š” ์•„์ง๊นŒ์ง€ ๋ณด์ด์ง€ ์•Š๋Š”๋‹ค. ๋น„๋ฐ€์Šค๋Ÿฌ์šด ๊ธ€์ด๋ผ๋Š” ์ผ๊ธฐ์˜ ํŠน์„ฑ ๋•Œ๋ฌธ์ผ์ง€๋„ ๋ชจ๋ฅด์ง€๋งŒ ๋‚˜๋Š” ์•„์ง๊นŒ์ง€ '๊ณต์œ '์— ์ดˆ์ ์„ ๋‘” ์„œ๋น„์Šค๊ฐ€ ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋ผ๊ณ  ํŒŒ์•…ํ•œ๋‹ค. SNS๊ฐ€ ์–ด๋–ป๊ฒŒ ์œ ์ € ๊ฐ„์— ๊ฒฐ์†์„ ๋งŒ๋“ค๊ณ  ๊ฐ•ํ•œ ํ™•์‚ฐ๋ ฅ์„ ๋งŒ๋“ค์–ด๋‚ด๋Š”์ง€, ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  SNS๊ฐ€ ์ผ๊ธฐ์žฅ์„ ๋Œ€์ฒดํ•  ์ˆ˜ ์—†๋Š” ์ด์œ ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๋ง์ด๋‹ค.

๊ณต์œ ๋ผ๋Š” ์ปจ์…‰์˜ ํ•ต์‹ฌ์ด ๋  SNS์˜ ํ™•์‚ฐ๋ ฅ๊ณผ ์œ ์ € ๊ฐ„ ๊ฒฐ์†์„ ์ผ๊ธฐ์žฅ์— ๋ฐ˜์˜ํ•˜๊ณ  SNS๊ฐ€ ๊ฐ€์ง€์ง€ ๋ชปํ•œ ์•„์นด์ด๋ธŒ์˜ ๊ธฐ๋Šฅ์„ ๊ฐ•ํ™”ํ•˜์—ฌ ์ง€๊ธˆ๊นŒ์ง€์˜ ๋‹จ์ˆœํ•œ ์ผ๊ธฐ์–ดํ”Œ์ด ์•„๋‹Œ ์‚ฌ๋žŒ๋“ค์ด ์ผ์ƒ๊ณผ ์ƒ๊ฐ์„ ๊ณต์œ ํ•˜๊ณ  ๊ทธ๋“ค์˜ ์ƒํ™œ์ด ๋  ์ˆ˜ ์žˆ๋Š” '์ผ๊ธฐ ๊ณต์œ  ํ”Œ๋žซํผ'์˜ ๊ฐœ๋ฐœ์„ ๋ชฉํ‘œ๋กœ ํ•œ๋‹ค.

๐Ÿ’ป Technique Used





โ— Compatibility

โš™๏ธ Installation

$ git clone "https://github.com/osamhack2022/WEB_SharedDiary_Nuri.git"

// ์„œ๋ฒ„ ์‹คํ–‰ ์•ˆ๋‚ด
$ cd Project
$ python3 -m venv venv
$ source venv/bin/activate
$ python3 -m pip install --upgrade pip
$ pip install -r requirements.txt

$ cd backend
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py runserver

// ํ”„๋ก ํŠธ์—”๋“œ ์‹คํ–‰ ์•ˆ๋‚ด
$ cd Project/frontend
$ npm install
$ npm start



๐Ÿ”ฅ Crew

$ cd Aiden-Kwak
$ cat ๊ณฝ๋ณ‘ํ˜.json

{
    "name" : "Kwak Byeong Hyeok (jeff721@cnsh.hs.kr)",
    "Github" : "@Aiden-Kwak",
    "Role" : ["FullStack", "UI/UX", "Product design"]
}

$ cd mingi123
$ cat ์ •๋ฏผ๊ธฐ.json

{
    "name" : "Jung MinGi (alsrl123488@gmail.com)",
    "Github" : "@mingi123",
    "Role" : ["UI/UX", "Video Editing"]
}



๐Ÿ… Functions



๐Ÿ“š DevDocs


  1. DB Schema
  2. ER-diagram
  3. ํšŒ์›๊ฐ€์ž… ๊ตฌํ˜„
  4. ๋กœ๊ทธ์ธ, ์ƒํƒœ์œ ์ง€, ์ธ์ฆ ๊ตฌํ˜„
  5. ํŒ”๋กœ์šฐ ๊ตฌํ˜„
  6. ์ผ๊ธฐ์žฅ ๊ตฌํ˜„
  7. ์ผ๊ธฐ ๊ตฌํ˜„
  8. ํ”„๋กœํ•„ ๊ฒ€์ƒ‰ ๊ตฌํ˜„


1. DB Schema

1. ๊ณ„์ •๊ด€๋ จ (์œ ์ €, ํ”„๋กœํ•„)

Table User {
  id int [PK]
  username char(20) [unique, not null]
  email varchar [unique, not null]
  created_at datetime
  updated_at datetime
}

Table Profile {
  user int [PK]
  nickname char(20) [unique, not null]
  self_intro char(250)
  profile_image image
  background_image image
  following int
  follower int
  created_at datetime
  updated_at datetime
}

Ref: Profile.user - User.id
Note: 'One-to-One relation'
Ref: Profile.following > Profile.user
Note: 'Many-to-Many relation'
Ref: Profile.follower > Profile.user
Note: 'Many-to-Many relation'

2. ์ผ๊ธฐ๊ด€๋ จ(์ผ๊ธฐ์žฅ, ์ผ๊ธฐ)

Table Diary {
  id int [PK]
  writer int
  title char(45) [not null]
  content varchar [not null]
  note int
  created_at datetime
  updated_at datetime
  to_open boolean
} Note: '์ผ๊ธฐ๋ชจ๋ธ'

Table Note {
  id int [PK]
  writer int
  title char(45)
  description char(150)
  image image
  diary int
  created_at datetime
  updated_at datetime
  to_open boolean
} Note: '์ผ๊ธฐ์žฅ ๋ชจ๋ธ'

Ref: Diary.writer - User.id
Note: 'One-to-One relation'
Ref: Diary.note - Note.id
Note: 'One-to-One relation'
Ref: Note.diary > Diary.id
Note: 'One-to-One relation'


2. ER-diagram

image

3. ํšŒ์›๊ฐ€์ž… ๊ตฌํ˜„

jwt(Json Web Token) ๋ฐฉ์‹์˜ ์ธ์ฆ์„ ๊ตฌํ˜„ํ•˜์˜€๋‹ค. ์•„๋ž˜๋Š” ๊ตฌํ˜„๊ณผ์ •์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ์„ค๋ช…์ด๋‹ค.


1. ํ† ํฐ ์ƒ์„ฑ

@property def token(self): return self._generated_jwt_token()

def _generated_jwt_token(self): dt = datetime.now()+timedelta(days=60)

token = jwt.encode({
    'id':self.pk,
    'exp':dt.utcfromtimestamp(dt.timestamp())
}, settings.SECRET_KEY, algorithm='HS256')

return token
 > accountapp/models.py์˜ ์ผ๋ถ€์ด๋‹ค. token์€ ์œ ์ €id, 60์ผ๋กœ ์„ค์ •ํ•œ ๋งŒ๋ฃŒ์‹œ๊ฐ„(dt), ์ ์šฉ์•Œ๊ณ ๋ฆฌ์ฆ˜(HS256)์„ ์ธ์ฝ”๋”ฉํ•œ ๊ฐ’์„ ํ•ฉ์นœ๋’ค SECRET_KEY๋กœ hashing๋˜์–ด ๋งŒ๋“ค์–ด์ง„๋‹ค.

 ```python

1. ์œ ์ € ์ƒ์„ฑ

class RegistrationAPIView(APIView):
    permission_classes = (AllowAny,)
    serializer_class = RegistrationSerializer
    renderer_classes = (UserJSONRenderer,)

    def post(self, request):
        user = request.data

        serializer = self.serializer_class(data=user)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data, status=status.HTTP_201_CREATED)

accountapp/views.py ์˜ ์ผ๋ถ€์ด๋‹ค. post ์š”์ฒญ์„ ๋ฐ›๊ฒŒ ๋˜๋ฉด RegistrationSerializer์— ์š”์ฒญ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋„˜๊ธฐ๊ณ  ์œ ํšจ์„ฑ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•˜๋ฉด ์œ ์ €๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. RegistrationSerializer๋Š” accountapp/serializers.py ์ฐธ๊ณ .



4. ๋กœ๊ทธ์ธ& ์ƒํƒœ์œ ์ง€ ๊ตฌํ˜„

๋กœ๊ทธ์ธ๊ธฐ๋Šฅ, ๊ทธ๋ฆฌ๊ณ  ๋กœ๊ทธ์ธ ์ดํ›„ ํŽ˜์ด์ง€๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋˜๊ฑฐ๋‚˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋˜์–ด๋„ ๋กœ๊ทธ์ธ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๋‹ค.


1. ๋กœ๊ทธ์ธ

class LoginSerializer(serializers.Serializer): email = serializers.EmailField() username = serializers.CharField(max_length=20, read_only=True) password = serializers.CharField(max_length=128, write_only=True) last_login = serializers.CharField(max_length=255, read_only=True) token = serializers.CharField(max_length=255, read_only=True) id = serializers.ReadOnlyField()

def validate(self, data):
    email = data.get('email', None)
    password = data.get('password', None)

    if email is None:
        raise serializers.ValidationError(
            'email address is required to Login'
        )
    if password is None:
        raise serializers.ValidationError(
            'password is required to Login'
        )

    user = authenticate(username=email, password=password)

    if user is None:
        raise serializers.ValidationError(
            'user with this email and password was not found'
        )

    if not user.is_active:
        raise serializers.ValidationError(
            'This user has been deactivated'
        )

    user.last_login = timezone.now()
    user.save(update_fields=['last_login'])

    return {
        'id': user.pk,
        'email':user.email,
        'username': user.username,
        'last_login': user.last_login,
        'token': user.token,
    }
> ๋‹ค์Œ์€ accountapp/serializers.py์˜ ์ผ๋ถ€์ด๋‹ค. ๋กœ๊ทธ์ธ๊ธฐ๋Šฅ์˜ ํ•ต์‹ฌ์€ ์œ ์ €๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์ธ์ฆ์ •๋ณด๋ฅผ ์ž…๋ ฅํ–ˆ๋Š”์ง€์˜ ํ™•์ธ, ๊ทธ๋ฆฌ๊ณ  ์ธ์ฆ๊ณผ์ •์„ ์œ„ํ•œ ํ† ํฐ ๋ฐ ์œ ์ €์ •๋ณด ๋ฐ˜ํ™˜์ด๋‹ค.<br> 
> django์˜ authenticate ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด usernameํ•„๋“œ๋กœ ์„ค์ •ํ•œ email, ์ž…๋ ฅ๋ฐ›์€ password์˜ ์กฐํ•ฉ์„ DB์™€ ๋งค์นญํ•ด ํ™•์ธํ•œ๋‹ค.
> accountapp/views.py์˜ LoginAPIView์—์„œ๋Š” ์ด serializer์— ์š”์ฒญ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๊ณ  ์œ ํšจ์„ฑ๊ฒ€์‚ฌ๋ฅผ ๊ฑฐ์นœ๋’ค ๋ฆฌํ„ดํ•œ๋‹ค.

<br>

```python
2. ์ƒํƒœ์œ ์ง€ ๊ตฌํ˜„

const onSubmit = async() => {
    const url = "/accounts/login";
    const userdata = {
        'email': email,
        'password': password
    };
    const config = {
        "Content-Type": 'application/json'
    };

    await axios 
        .post(url, userdata, config)
        .then(function (res) {
            if (res.data.user.token) {
                localStorage.setItem('userdata', JSON.stringify(res.data));
                localStorage.setItem('token', res.data.user.token);
            }
        })
        .catch(function (error) {
            console.log(error);
            setLoginError(true);
        });
};

์œ„ ์ฝ”๋“œ๋Š” frontend/src/account/Login.jsx์˜ ์ผ๋ถ€์ด๋‹ค.
๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํด๋ฆญ์‹œ ์ž‘๋™ํ•˜๋Š” ํ•จ์ˆ˜๋กœ ๋กœ๊ทธ์ธ ์ž…๋ ฅ์ •๋ณด๋ฅผ ๋‹ด์•„ ์„œ๋ฒ„์— post ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค. ๋กœ๊ทธ์ธ์„ ์œ ์ง€ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๊ณ„์†ํ•ด ๋กœ๊ทธ์ธํ•œ ์œ ์ €์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋ฅผ ์œ„ํ•ด axios ์š”์ฒญ์ด ์„ฑ๊ณตํ–ˆ์„๋•Œ ๋ฐ˜ํ™˜๋ฐ›์€ ์š”์ฒญ์„ ๋ธŒ๋ผ์šฐ์ €์˜ localStorage์— ๋‹ด์•˜๋‹ค. ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ๋กœ๊ทธ์ธ ์ดํ›„ ์ €์žฅ๋œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ๋™์ž‘ํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

1. ์ธ์ฆ๊ตฌํ˜„

class JWTAuthentication(authentication.BaseAuthentication):
    authentication_header_prefix = 'Token'

    def authenticate(self, request):
        auth_header = authentication.get_authorization_header(request).split()
        auth_header_prefix = self.authentication_header_prefix.lower()

        if not auth_header:
            return None    
        if len(auth_header) == 1:
            return None 
        elif len(auth_header) > 2:
            return None

        prefix = auth_header[0].decode('utf-8')
        token = auth_header[1].decode('utf-8')

        if prefix.lower() != auth_header_prefix:
            return None

        return self._authenticate_credentials(request, token)

    def _authenticate_credentials(self, request, token):
        try:
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
        except:
            msg = 'Invalid authentication. Could not decode token.'
            raise exceptions.AuthenticationFailed(msg)

        try:
            user = User.objects.get(pk=payload['id'])
        except User.DoesNotExist:
            msg = 'No user matching this token was found.'
            raise exceptions.AuthenticationFailed(msg)

        if not user.is_active:
            msg = 'This user has been deactivated.'
            raise exceptions.AuthenticationFailed(msg)

        return (user, token)

๋‹ค์Œ์€ backend/accountapp/backends.py์˜ ์ผ๋ถ€์ด๋‹ค.
์ผ๊ธฐ์˜ ๋‚ด์šฉ์€ ๋กœ๊ทธ์ธ์„ ํ•˜์ง€ ์•Š์€ ์œ ์ €๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ผ๊ธฐ์ž‘์„ฑํŽ˜์ด์ง€์— ๋กœ๊ทธ์ธ์„ ํ•˜์ง€ ์•Š์€ ์œ ์ €๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด์„œ๋Š” ์•ˆ๋œ๋‹ค. Django๋‚˜ DRF๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ JWT ์ธ์ฆ์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์œ„์˜ ์ฝ”๋“œ๋Š” ์ด๋ฅผ ์œ„ํ•œ JWT ์ธ์ฆ ์ฝ”๋“œ์ด๋‹ค.
authenticate ํ•จ์ˆ˜๋Š” ์ธ์ฆ ํ•„์š”์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ๋ชจ๋“  ์š”์ฒญ์—์„œ ํ˜ธ์ถœ์ด ๋œ๋‹ค. ์ธ์ฆ์‹คํŒจ์‹œ None์„, ์„ฑ๊ณต์‹œ (user, token) ์กฐํ•ฉ์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•œ๋‹ค. _authenticate_credentials ๋ฅผ ํ†ตํ•ด token์„ decodeํ•˜๊ณ  payload์— ๋‹ด๊ธด id์˜ ์œ ํšจ์„ฑ์„ ํ™•์ธํ•ด ์ตœ์ข…์ ์œผ๋กœ ์ธ์ฆ์„ ๋งˆ๋ฌด๋ฆฌํ•œ๋‹ค.


5. ํŒ”๋กœ์šฐ ๊ตฌํ˜„

1. ํ”„๋กœํ•„ ๋ชจ๋ธ(ํŒ”๋กœ์ž‰๊ณผ ํŒ”๋กœ์›Œ ํ•ƒ๋“œ)

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    username = models.CharField(max_length=20, unique=True, blank=False, null=True)
    nickname = models.CharField(max_length=20, unique=True, blank=False)
    slug = models.SlugField(null=True, unique=True)
    self_intro = models.TextField(blank=True)
    profile_image = models.ImageField(upload_to='profile/')
    background_image = models.ImageField(upload_to='profile/', null=True, blank=True)
    following = models.ManyToManyField('self', symmetrical=False, related_name='followings', blank=True, null=True)
    follower = models.ManyToManyField('self', symmetrical=False, related_name='followers', blank=True, null=True)

ํ”„๋กœํ•„ ๋ชจ๋ธ์•ˆ์— following๊ณผ follower ํ•„๋“œ๋ฅผ Profile๋ชจ๋ธ์— ๋Œ€ํ•˜์—ฌ M:N ๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•˜์˜€๋‹ค.

2. ํŒ”๋กœ์šฐ API
class FollowAPIView(APIView):
    permission_classes = (IsAuthenticated,)
    renderer_classes = (UserJSONRenderer,)
    serializer_class = ProfileSerializer
    def post(self, request, *args, **kwargs):
        user = request.user
        profile = Profile.objects.get(user=user)
        another_user = User.objects.get(id=request.data['id'])
        another_profile = Profile.objects.get(user=another_user)
        profile.following.add(another_profile)
        another_profile.follower.add(profile)
        serializer = self.serializer_class(Profile.objects.get(user=user))
        return Response(serializer.data, status=status.HTTP_200_OK)

๋‹ค์Œ์€ accountapp/views.py์˜ ์ผ๋ถ€์ด๋‹ค. ํด๋ผ์ด์–ธํŠธ์—์„œ axios post์š”์ฒญ์„ ํ†ตํ•ด ๋Œ€์ƒ์˜ user_id๋ฅผ ์š”์ฒญ๋ฐ›์„ ๊ฒƒ์ด๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ๋ณธ์ธ(profile), ์ƒ๋Œ€(another_profile)์„ ์„ ์–ธํ•˜๊ณ  ํ•„๋“œ์— add()ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ๋ณธ์ธ์˜ followingํ•„๋“œ์— another_profile์„, ์ƒ๋Œ€์˜ followerํ•„๋“œ์— profile์„ add() ํ•œ๋‹ค.

6. ์ผ๊ธฐ์žฅ ๊ตฌํ˜„

1. ์ผ๊ธฐ์žฅ ๋ชจ๋ธ

class Note(TimestampedModel):
    writer = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=45, null=True)
    description = models.TextField(max_length=150, blank=False, null=True)
    image = models.ImageField(upload_to='note/', null=True, blank=True)
    diary = models.ManyToManyField('accountapp.Diary', related_name='note', blank=True, null=True)
    to_open = models.BooleanField(default=True, choices=OPEN_CHOICES) #๋ฏธ๊ตฌํ˜„

    @property
    def writer_pk(self):
        return self.writer.id

์ผ๊ธฐ์žฅ ๋ชจ๋ธ์€ ์ž‘์„ฑ์ž, ์ œ๋ชฉ, ์„ค๋ช…, ์ด๋ฏธ์ง€, ์ผ๊ธฐ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค.writer๋Š” User์˜ ์™ธ๋ž˜ํ‚ค๋กœ, diary๋Š” Note์™€์˜ ๋‹ค๋Œ€๋‹ค๊ด€๊ณ„๋กœ ํ˜•์„ฑ๋œ๋‹ค.

7. ์ผ๊ธฐ ๊ตฌํ˜„

1. ์ผ๊ธฐ ์ƒ์„ฑ API

class DiaryCreateView(APIView):
    permission_classes = (IsAuthenticated,)
    serializer_class = DiarySerializer

    def post(self, request, *args, **kwargs):
        serializer = DiarySerializer(data=request.data)
        note_id = request.data['id']
        note = Note.objects.get(id=note_id)
        user=request.user 

        if serializer.is_valid():
            post = Diary.objects.create(
                writer=user,
                title=request.data['title'],
                content=request.data['content'],
                note=note,
                image=request.data['image'],
                to_open=request.data['to_open']
            )
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

์ผ๊ธฐ์žฅ์•ˆ์—์„œ ์ผ๊ธฐ๋ฅผ ์ƒ์„ฑํ• ๋•Œ ์‚ฌ์šฉ๋˜๋Š” API๋‹ค. ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์š”์ฒญ๋ฐ›์€ ์ผ๊ธฐ์žฅ์˜ id๋กœ Note object๋ฅผ ์ฐพ๊ณ  serializer์˜ ์œ ํšจ์„ฑ๊ฐ์‚ฌ๋ฅผ ๊ฑฐ์นœ๋’ค Diary object๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
์ด๋กœ์จ ์‚ฌ์šฉ์ž๋Š” ์ข…๋ฅ˜๋ณ„๋กœ ์ผ๊ธฐ์žฅ์„ ์ƒ์„ฑํ•˜๊ณ  ์ผ๊ธฐ์žฅ ์•ˆ์— ๊ทธ์— ๋งž๋Š” ์ผ๊ธฐ๋“ค์„ ์ ์„ ์ˆ˜ ์žˆ๋‹ค.

8. ํ”„๋กœํ•„ ๊ฒ€์ƒ‰ ๊ตฌํ˜„

1. ๋‹‰๋„ค์ž„, ์œ ์ €๋„ค์ž„์œผ๋กœ ํ”„๋กœํ•„ ๊ฒ€์ƒ‰ํ•˜๋Š” API

class ProfileSearchAPIView(APIView):
    serializer_class = ProfileSerializer
    def get(self, request, *args, **kwargs):
        input_data = request.query_params['inputData']
        profile = Profile.objects.filter(Q(nickname__icontains=input_data) and Q(username__icontains=input_data))
        serializer = self.serializer_class(profile, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

ํด๋ผ์ด์–ธํŠธ์—์„œ Onchange event๋กœ axios get์š”์ฒญ์„ ๋ฐ›์•„ params๋กœ ์ „์†ก๋œ input ๋ฐ์ดํ„ฐ๋ฅผ profile db์™€ ๋Œ€์กฐํ•œ๋‹ค. nickname๊ณผ username์— ๋Œ€ํ•˜์—ฌ input์˜ ๋ฌธ์ž์—ด์ด ํฌํ•จ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.