(백엔드) DRF + Kotlin + Retrofit2 사용 로그인 기능 실습(실패)

개인 공부를 위해 구성되어 있습니다. 관련된 블로그 항목 대부분이 참조됩니다.


1. 가상 환경 생성

$ python -m venv venv

2. 가상 환경 활성화

$ source venv/Scripts/activate

3. 장고 설치

$ python -m pip install django=="2.2.6"

블로그 게시물의 Django 버전에 따라 설치하십시오.

4. DRF 설치

$ python -m pip install djangorestframework

5. 프로젝트 생성

$ django-admin startproject mobile_login_using_drf .

6. 애플리케이션 생성

$ django-admin startapp addresses

7. INSTALLED_APPS에 앱 및 rest_framework 추가 + 나머지 프레임워크에 대한 설정 추가(mobile_login_using_drf/settings.py)

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'addresses', # 코드 추가
    'rest_framework', # 코드 추가
)

# 코드 추가
REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    )
}

8. 모델 구축(addresses/models.py)

from django.db import models

# 코드 추가
class Addresses(models.Model):
    name = models.CharField(max_length=10)
    phone_number = models.CharField(max_length=13)
    address = models.TextField()
    created = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('created')

9. 직렬 변환기 생성(addresses/serializers.py)

# 코드 추가
from rest_framework import serializers
from .models import Addresses

class AddressesSerializer(serializers.ModelSerializer):
    class Meta:
        model = Addresses
        fields = ('name', 'phone_number', 'address')

10. 쓰기 보기(addresses/views.py)

from django.shortcuts import render
# 코드 추가
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .models import Addresses
from .serializers import AddressesSerializer
from rest_framework.parsers import JSONParser

@csrf_exempt
def address_list(request):
    if request.method == 'GET':
        query_set = Addresses.objects.all()
        serializer = AddressesSerializer(query_set, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = AddressesSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)


@csrf_exempt
def address(request, pk):

    obj = Addresses.objects.get(pk=pk)

    if request.method == 'GET':
        serializer = AddressesSerializer(obj)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = AddressesSerializer(obj, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        obj.delete()
        return HttpResponse(status=204)


@csrf_exempt
def login(request):
    if request.method == 'POST':
        data = JSONParser().parse(request)
        search_name = data('name')
        obj = Addresses.objects.get(name=search_name)

        if data('phone_number') == obj.phone_number:
            return HttpResponse(status=200)
        else:
            return HttpResponse(status=400)

11. URL 설정(mobile_login_using_drf/urls.py)

from addresses import views
from django.urls import re_path, include
from django.contrib import admin


urlpatterns = (
    re_path('admin/', admin.site.urls),
    re_path('', include('addresses.urls')), # 그냥 localhost:8000으로 호출하면 아무것도 없으니까 계속 importerror 등등 발생해서 추가해줌
    re_path('addresses/', views.address_list),
    re_path('addresses/<int:pk>/', views.address),
    re_path('login/', views.login),
    re_path(r'^api-auth/', include('rest_framework.urls', namespace="rest_framework")),
)

계속해 ImportError: name ‘url’ from ‘django.conf.urls’ can be imported 오류가 발생하여 상황에 맞게 코드를 수정했습니다(1).

12. 마이그레이션

$ python manage.py makemigrations todo_app
$ python manage.py migrate

13. 슈퍼유저 등록

$ ./manage.py createsuperuser

14. 관리자에서 모델 등록(addresses/admin.py)

관리자 계정을 통해 모델에 따라 데이터를 추가하십시오. 데이터가 실제로 연결되었는지 여부는 등록된 데이터가 있는 경우 Android에 연결한 후에만 쉽게 확인할 수 있습니다.

from django.contrib import admin
from todo_app.models import Addresses # 코드 추가

admin.site.register(Addresses) # 코드 추가

14. 간단한 안드로이드 로그인 화면(activity_main.xml)


15. 생성할 로그인 화면 프레임 입력(MainActivity.kt)

package com.example.mobile_login_android_test

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AlertDialog // 코드 추가
import kotlinx.android.synthetic.main.activity_main.* // 코드 추가

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 코드 추가
        button.setOnClickListener {
            var textId = editTextTextPersonName.text.toString()
            var textPw = editTextTextPassword.text.toString()

            var dialog = AlertDialog.Builder(this)
            dialog.setTitle("알람!")
            dialog.setMessage("id = " + textId + "pw = " + textPw)
            dialog.show()
        }
    }
}

16. Gradle에 애프터마켓 구성 추가(build.gradle(:app))

dependencies {

    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    // 코드 추가
    // Retrofit
    implementation 'com.google.code.gson:gson:2.8.5'
    implementation 'com.squareup.retrofit2:retrofit:2.6.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
}

17. 인터넷 권한 설정 추가(AndroidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mobile_login_android_test">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Mobile_login_android_test">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET" /> <!-- 코드 추가 -->

</manifest>

18. 출력용 파일 생성(Login.kt)

초기 정의의 경우.

package com.example.mobile_login_android_test

// 코드 추가
data class Login(
    var code : String,
    var msg : String
)

19. 입력용 파일 생성(LoginService.kt)

입력 정의용.

package com.example.mobile_login_android_test

// 코드 추가
import retrofit2.Call

interface LoginService {

    @FormUrlEncoded
    @POST("/app_login/")
    fun requestLogin(
        @Field("userid") userid:String,
        @Field("userpw") userpw:String
    ) : Call<Login>
}

20. 개조 객체 생성(MainActivity.kt)

Django 서버와 통신하려면 객체를 생성해야 합니다.

package com.example.mobile_login_android_test

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Retrofit // 코드 추가

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

		// 코드 추가
        var retrofit = Retrofit.Builder()
            .baseUrl('http://172.30.1.27:8000') // 웹서버 실행되는 링크
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        button.setOnClickListener {
            var textId = editTextTextPersonName.text.toString()
            var textPw = editTextTextPassword.text.toString()

            var dialog = AlertDialog.Builder(this)
            dialog.setTitle("알람!")
            dialog.setMessage("id = " + textId + "pw = " + textPw)
            dialog.show()
        }
    }
}

baseUrl의 경우 cmd 창에 ipconfig를 입력하고 IPv4 부분에 대한 링크를 제공합니다.

21. 인터페이스 객체 생성(MainActivity.kt)

var retrofit = Retrofit.Builder()
            .baseUrl('http://172.30.1.27:8000') // 웹서버 실행되는 링크
            .addConverterFactory(GsonConverterFactory.create())
            .build()

// 코드 추가
var loginService = retrofit.create(LoginService::class.java)

22. 통신 상황에 따른 응답 설정(MainActivity.kt)

package com.example.mobile_login_android_test

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var retrofit = Retrofit.Builder()
            .baseUrl('http://172.30.1.27:8000') // 웹서버 실행되는 링크
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        var loginService = retrofit.create(LoginService::class.java)

        button.setOnClickListener {
            var textId = editTextTextPersonName.text.toString()
            var textPw = editTextTextPassword.text.toString()

            loginService.requestLogin(textId, textPw).enqueue(object:Callback<Login>{
                // 통신 실패 시
                override fun onFailure(call: Call<Login>, t: Throwable) {
                    var dialog = AlertDialog.Builder(this@MainActivity)
                    dialog.setTitle("실패!")
                    dialog.setMessage("통신에 실패했습니다.")
                    dialog.show()
                }
                // 통신 성공 시
                override fun onResponse(call: Call<Login>, response: Response<Login>) {
                    var login = response.body() // code, msg
                    var dialog = AlertDialog.Builder(this@MainActivity)
                    dialog.setTitle("알람!")
                    dialog.setMessage("code = " + login?.code + "msg = " + login?.msg)
                    dialog.show()
                }
            })
        }
    }
}

Android Studio에서 override 함수를 자동으로 계속 작성하고 싶지 않아서 그냥 다 직접 작성했습니다.

여기서부터는 튜토리얼 영상과 달리 제 코드에서 계속 나타나는 버그를 수정하는 과정입니다.

23. active_main.xml의 BUTTON과 같은 ID 값을 메인 액티비티로 가져오기 설정(build.gradle(:app)) (2)

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions' # 코드 추가
}

일단 설정되면 Gradle과 동기화되고 각 구성 요소 이름의 빨간색 선이 사라집니다.


MainActivity.kt 코드의 일부

24. “함수 이름”을 해결하면 “Nothing” 오류를 덮어씁니다(MainActivity.kt).


MainActivity.kt 코드의 일부

콜백과 콜을 불러오지 못해서 발생한 문제였습니다.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Call # 코드 추가
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.Retrofit.Builder
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.Callback # 코드 추가

25. 다음 함수는 주어진 인수로 호출할 수 없습니다. 오류 수정


MainActivity.kt 코드의 일부

오류의 의미는 내가 입력한 적절한 인수로 baseUrl이라는 함수를 호출할 수 없다는 의미입니다.

인수를 baseUrl에서 “(4)” 대신 “로 변경하여 해결했습니다.

Java 또는 Kotlin에서 “(따옴표)는 한 문자에만 사용되며 “(큰따옴표)는 두 단어 이상에 사용됩니다. 조심하세요.

26. 25개 오류 수정 중 발생한 오류 수정


MainActivity.kt 코드의 일부

유형 변수 T를 추론할 정보가 충분하지 않습니다.

인수의 철자가 정확하지 않아서 문제가 발생한 것 같습니다(5). 인수를 장황하게 작성하면 사라지는 오류입니다. Kotlin을 사용하기 시작하면 거의 무조건적으로 어떤 일이 발생합니까? 오류라고.

var loginService: LoginService = 
            retrofit.create(LoginService::class.java)

이렇게 자세히 써보면 빨간선이 생성됩니다.

27. 해결되지 않은 참조 해결: Java 오류


MainActivity.kt 코드의 일부

내 Kotlin 버전은 1.5.20입니다. (3)과 관련하여 androidx.core:core-ktx:1.9.0을 1.5.2 또는 1.3.2로 다운그레이드하고 동기화를 실행했지만 아무런 효과가 없습니다.


이 부분에 대해서는 1.5.1로 변경해서 다시 동기화를 해보았는데 안되네요.

한 번에 모두 적용할 수 없기 때문에 코드를 github에 비공개로 업로드하고 Android를 다시 설치한 다음 코드를 실행해 봅니다.

효과 없음..

Django 서버에 기능 및 URL 추가

28. app_login 작성(views.py)

@csrf_exempt
def app_login(request):

    if request.method == 'POST':
        print("리퀘스트 로그" + str(request.body))
        id = request.POST.get('userid', '')
        pw = request.POST.get('userpw', '')
        print("id = " + id + " pw = " + pw)

        result = authenticate(username=id, password=pw)

        if result:
            print("로그인 성공!")
            return JsonResponse({'code': '0000', 'msg': '로그인성공입니다.'}, status=200)
        else:
            print("실패")
            return JsonResponse({'code': '1001', 'msg': '로그인실패입니다.'}, status=200)

Android의 LoginService 인터페이스를 통해 API로 통신하기 위해 생성되었습니다.

29. app_login(urls.py) 관련 URL 추가

urlpatterns = (
    re_path('admin/', admin.site.urls),
    re_path('', include('addresses.urls')),
    re_path('addresses/', views.address_list),
    re_path('addresses/<int:pk>/', views.address),
    re_path('login/', views.login),
    re_path('app_login/', views.app_login), # 코드 추가
    re_path(r'^api-auth/', include('rest_framework.urls', namespace="rest_framework")),
)

먼저 웹 서버를 제대로 완성합시다. 제대로 실행되는지 확인하려면 직접 테스트해 볼 수 있습니다.


(하나) 참조 1

(오류) importError: django.conf.url에서 ‘url’ 호출 오류

Django로 개인 프로젝트를 시작하려던 순간 기본 설정 과정에서 다음과 같은 오류가 발생했습니다.

velog.io

(2) 참조 2

Android Kotlin – 해결되지 않은 참조 오류

Active_main.xml에 정의된 TextView나 Button의 id 값을 MainActivity에서 직접 호출하려고 합니다. * ex.) id 값이 @+id/box_one_text인 경우 box_one_text만 사용하고 싶습니다. 위의 에러가 뜨는데,

ding-dong-in-future.tistory.com

(삼) 참조 3

해결되지 않은 참조: Java 오류

최근 개인 프로젝트를 위한 신규 프로젝트 생성 및 액티비티 이동을 위한 코드 작성 시 오류가 발생하였습니다. 위 코드의 .java 부분에서 오류가 발생했습니다.

velog.io

(4) 참조 4

문자 리터럴 오류에 문자가 너무 많습니다.

기존 것을 다시 개발해야 해서 파이썬이 아닌 자바로 하고 싶어서 만져보니 이런 에러가 났습니다. 4번째 줄에 “문자 리터럴에 너무 많은 문자가 있습니다”라고 빨간색으로 표시됩니다…

vesseldiary.tistory.com

(5) 참조 5

(Kotlin) 유형 변수 T 오류를 추론하기 위한 정보가 충분하지 않음

Kotlin을 사용한 Android 프로그래밍이 처음이라면 이 오류가 발생할 가능성이 거의 100%입니다. 물론 초반에만 발생하는 버그이기도 하고, 한 번 맞으면 이후에 할 수 있는 일이 없다. 위의 오류

like-tomato.tistory.com