본문 바로가기
web

[django] DRF + Kotlin + Retrofit2 사용 login 기능 가진 앱 만들기 실습(완성 실패)

by ra._.nie 2023. 3. 16.

개인학습용으로 정리한 내용임. 해당 블로그의 글 대부분 참고하여 진행함.

 

dJango로 restful API 서버만들기 [1] - django 서버 생성

소스코드 GIT : https://github.com/tkdlek11112/django_restful 강의영상 YouTube restfulAPI 서버란? API 서버인데 일종의 규칙을 적용하여 사용하기 편리하게 만든것! (자세한것은 구글링이나 영상 참조 >

cholol.tistory.com


1. 가상환경 만들기

$ python -m venv venv

 

2. 가상환경 활성화

$ source venv/Scripts/activate

 

3. django 설치

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

해당 블로그 글에서의 django version에 맞춰 설치함.

 

4. DRF 설치하기

$ python -m pip install djangorestframework

 

5. project 생성하기

$ django-admin startproject mobile_login_using_drf .

 

6. app 생성하기

$ django-admin startapp addresses

 

7. INSTALLED_APPS에 app 및 rest_framework 추가 + 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. model 설정(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. serializer 만들기(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. view 작성(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: cannot import name 'url' from 'django.conf.urls' 오류가 발생해서 내 상황에 맞게 코드 수정함[1].

 

12. migrate해주기

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

 

13. superuser 등록

$ ./manage.py createsuperuser

 

14. admin에 model 등록(addresses/admin.py)

admin 계정을 통해 model에 맞게 데이터 추가해보기. 등록된 데이터가 있어야 안드로이드 연동 후 실제로 연동되었는지 확인하는데 용이하니까.

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에 retrofit 설정 추가(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. output용 파일 만들기(Login.kt)

output 정의용.

package com.example.mobile_login_android_test

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

 

19. input용 파일 만들기(LoginService.kt)

input 정의용.

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. retrofit 객체 생성(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. interface 객체 생성(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()
                }
            })
        }
    }
}

안드로이드 스튜디오 상에서 자동으로 override function 작성이 계속 안 되길래 그냥 다 직접 작성함.

 

▶▶▶ 여기서부터는 실습 영상과는 달리 내 코드에서 계속 오류 뜨는 것들 해결하는 과정.

 

23. active_main.xml에 있는 BUTTON 등의 ID값 Main activity로 가져오기 위한 설정하기(build.gradle(:app)) [2]

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

해당 설정 후 Gradle과의 sync 맞춰주니 해당 각 컴포넌트?들의 이름에 뜨던 빨간 줄 사라짐.

MainActivity.kt 코드 중 일부

 

24. '함수이름' overrides nothing 오류 해결하기(MainActivity.kt)

MainActivity.kt 코드 중 일부

Callback과 Call이 import되지 않았어서 생긴 문제였음.

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. None of the following functions can be called with the arguments supplied. 오류 해결하기

MainActivity.kt 코드 중 일부

해당 오류의 의미는 내가 입력한 해당 인수로 baseUrl이라는 함수를 호출하는게 불가능하다는 뜻.

baseUrl의 인수에 대해 '가 아니라 "로 수정해주니 해결됨[4].

java나 kotlin 등에서는 하나의 문자일 때만 '(따옴표), 2개 이상의 단어일 때는 "(쌍따옴표)를 사용하는 듯. 주의할 것.

 

26. 25 error 해결하면서 발생한 오류 해결하기

MainActivity.kt 코드 중 일부

Not enough information to infer type variable T.

인자가 제대로 작성되지 않아서 발생하는 문제인 듯[5]. 인자에 대해서 디테일하게 작성해주면 사라지는 오류라고 함. kotlin을 사용하다보면 초기에 거의 무조건 발생하는? 오류라고 함.

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

이렇게 디테일하게 작성해주니 create에서는 빨간줄 사라짐.

 

27. Unresolved reference: java 오류 해결하기

MainActivity.kt 코드 중 일부

내 kotlin 버전은 1.5.20임. [3]을 참고하여 androidx.core:core-ktx:1.9.0를 다운그레이드하여 1.5.2 또는 1.3.2로 한 후 sync까지 돌렸으나 효과 없음.

해당 부분 참고하여 1.5.1로도 수정하고 다시 sync 맞춰봤는데도 효과 없었음.

 

일단 다 적용해도 안 되니 github에 private으로 코드 올려뒀다가 안드로이드 다시 설치하고 코드 돌려보도록 해 볼 것.

 

효과 없음..

 

▶▶▶ 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라는 interface를 API 삼아 통신?하기 위해 작성.

 

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

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')),
]

 

일단 웹서버나 제대로 완성하자. 제대로 구동되는지 보고 싶으면 자체적으로 unittest하는 걸로.


<reference>

[1] 참고 글1

 

[Error] importError: django.conf.url 에서 'url' 호출 실패

Django로 개인 프로젝트를 진행하려던 찰나에 , 기초 셋팅 과정에서 아래와 같은 오류를 만났습니다..ImportError: cannot import name 'url' from 'django.conf.urls' 해당 오류는 Django Version 4에서 더 이상

velog.io

[2] 참고 글2

 

Android Kotlin - Unresolved reference Error

active_main.xml 에 정의되어있는 TextView나 Button 의 ID값을, MainActivity 에서 바로 불러오려는데, * Ex) id 값이 @+id/box_one_text 면, 그냥 box_one_text 를 쓰려는 것임. 안되는거다. 위와같은 에러가 뜨는데, 해

ding-dong-in-future.tistory.com

[3] 참고 글3

 

Unresolved reference: java 오류

최근 개인 프로젝트를 위해 프로젝트를 새로 생성하고Activity를 이동하는 코드를 짜던 중 오류가 발생했다.위 코드에서 .java 부분에서 에러가 발생했는데..Unresolved reference: java으응?라이브러리를

velog.io

[4] 참고 글4

 

Too many characters in character literal 에러

기존에 있던것을 재개발해야해서 아무래도 파이썬보다는 자바쪽으로 하고싶어서 만지는도중 이런 에러를 발견함 4번째 라인에 빨간색으로 "Too many characters in character literal" 이라고 적혀있는데..

vesselsdiary.tistory.com

[5] 참고 글5

 

[Kotlin] Not enough information to infer type variable T 에러

Kotlin으로 안드로이드 프로그래밍은 처음 시작한다면 이 에러를 거의 100% 확률로 접할 수 있다. 물론 초기만 당하고, 한 번 당해보면 그 뒤부터는 당할 일이 없는 에러이기도 하다. 위의 에러를

like-tomato.tistory.com

 

댓글