Django Rest Framework & AngularJS ile Todo List Web Uygulama Geliştirme

TodoApp_AngularJS_DRF.md

AngularJS (Angular 1) ve Django Rest Framework ile To-Do List Web Projesi Geliştirme

  • Bu yazımızda AngularJS(Front-End için) , Django Rest Framework(Back-End için) ve SQLite(Veritabanı) kullanarak basit bir web tabanlı to-do list (yapılacak listesi) uygulaması geliştireceğiz.

Geliştirme Ortamı

  • Geliştirme süresince Linux Mint OS kullanıldı. IDE olarak Visual Studio Code kullanıldı.

Requirements (Gereksinimler)

  • Python 3.6
  • pip (python paket yükleme programı)
  • virtualenv
  • Django 2.1
  • djangorestframework 3.9.0
  • AngularJs 1.7.X
  • angular-cookies 1.7.X
  • Bootstrap 4.3.1
  • JQuery 3.3.1

Projeye Başlangıç

  • Yukarıdaki Gereksinimler listesindekileri kurduktan sonra başlamaya hazırız.
  • Terminalden virtualenv -p python3 env diyerek env adında bir sanal ortam oluşturuyoruz. Virtualenv programı farklı python projelerinin birbirlerinden bağımsız ve izole çalışmasını sağlar. Böylece her proje için farklı versiyon framework, extension, üçüncü parti yazılımlar kurup kullanabilirsiniz.
  • env adındaki sanal ortama geçiş yapmak için terminal üzerinden source env/bin/activate yazıyoruz.
  • pip install Django==2.1 djangorestframework==3.9.0 diyerek Django ve Django Rest Framework'u kuruyoruz.
  • django-admin startproject todo diyerek projemizi başlatıyoruz.
  • cd todo diyerek proje içerisine geçip ls yazarsanız db.sqlite3 manage.py todo dosya ve klasörlerini göreceksiniz. Burada python manage.py runserver yazarsanız terminal üzerinden projeyi çalıştırır ve terminalde aşağıdaki çıktıları görürsünüz.
(env) adnan@ce:~/arge/projects/todo$ python manage.py runserver Performing system checks... System check identified no issues (0 silenced). You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. April 28, 2019 - 17:25:02 Django version 2.1, using settings 'todo.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. [28/Apr/2019 17:25:07] "GET / HTTP/1.1" 200 16348 [28/Apr/2019 17:25:07] "GET /static/admin/css/fonts.css HTTP/1.1" 200 423 [28/Apr/2019 17:25:07] "GET /static/admin/fonts/Roboto-Bold-webfont.woff HTTP/1.1" 200 82564 [28/Apr/2019 17:25:07] "GET /static/admin/fonts/Roboto-Light-webfont.woff HTTP/1.1" 200 81348 [28/Apr/2019 17:25:07] "GET /static/admin/fonts/Roboto-Regular-webfont.woff HTTP/1.1" 200 80304 Not Found: /favicon.ico [28/Apr/2019 17:25:07] "GET /favicon.ico HTTP/1.1" 404 1970
  • http://127.0.0.1:8000/ adresinde projemiz çalışmaktadır ve bu adrese tarayıcıdan giderseniz varsayılan Django sayfasını göreceksiniz.
  • Ctrl+C diyerek projeyi terminal üzerinden durdurabilirsiniz.
  • You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. mesajı django'dan gelen varsayılan(default) admin(yönetici) gibi modellerin henüz veritabanına implemente edilmediğini gösteriyor.
  • python manage.py migrate diyerek admin ve diğer modelleri veritabanına gönderebilirsiniz. Ekran çıktısı terminalde aşağıdaki gibi olacaktır.
^C(env) adnan@ce:~/arge/projects/todo$ python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying sessions.0001_initial... OK (env) adnan@ce:~/arge/projects/todo$
  • Şimdi admin yani tam yetkili yönetici oluşturabilirsiniz. python manage.py createsupersuer yazalım ve talimatları takip edelim. Sizden kullanıcı adı, email, parola oluşturmanızı isteyecektir.
(env) adnan@ce:~/arge/projects/todo$ python manage.py createsuperuser Username (leave blank to use 'adnan'): adnan Email address: adnan@kayace.com Password: Password (again): Superuser created successfully. (env) adnan@ce:~/arge/projects/todo$
  • Linux'ta parola yazdığınızda ekranda yazdıklarınız görülmez.
  • Şimdi bir adet api isminde application oluşturalım. python manage.py startapp api yazıyoruz.
  • Şimdiye kadarki proje klasör & dosya yapısı aşağıdaki gibidir. Linux'ta tree . yazarak terminal üzerinden görebilirsiniz.
(env) adnan@ce:~/arge/projects/todo$ tree . . ├── api │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── db.sqlite3 ├── manage.py └── todo ├── __init__.py ├── __pycache__ ├── settings.py # Burada düzenleme yapacağız. !!! ├── urls.py └── wsgi.py 7 directories, 19 files (env) adnan@ce:~/arge/projects/todo$
  • Yorum satırında da göreceğiniz gibi todo/settings.py modülünde düzenleme yapacağız. Django Rest Framework ve oluşturduğumuz api uygulamasını Django'ya tanımlamamız gerekiyor. INSTALLED_APPS = değişkenine gelerek aşağıdaki satırları ekleyelim.
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # rest 'rest_framework', # benim uygulamalarım 'api.apps.ApiConfig', ]

Model sınıfların tanımlanması

  • Şimdi api/models.py modülünü açalım ve model sınıfımızı tasarlayalım.
from django.db import models # Create your models here. class Todo(models.Model): FIRST = '1' SECOND = '2' THIRD = '3' PRIORITY_CHOICES = ( (FIRST, 'FIRST'), (SECOND, 'SECOND'), (THIRD, 'THIRD'), ) title = models.CharField(max_length=280) content = models.TextField() is_done = models.BooleanField(default=False) c_date = models.DateTimeField(auto_now_add=True) u_date = models.DateTimeField(auto_now=True) priority = models.CharField(max_length=1, choices=PRIORITY_CHOICES, default=FIRST) class Meta: db_table = 't_todo'
  • PRIORITY_CHOICES değişkenini birincil, ikincil, üçüncül derecede öncelikler için tanımladık.
  • Meta class'ı içerisinde db_table değişkenine t_todo değerini vererek veritabanında bu isimde tablo vermek istediğimizi belirtiyoruz.
  • Şimdi terminalden python manage.py makemigrations api diyerek Todo model class'ını veritabanına göndermeye hazır hale getirelim.
(env) adnan@ce:~/arge/projects/todo$ python manage.py makemigrations api Migrations for 'api': api/migrations/0001_initial.py - Create model Todo
  • Şimdi de model sınıfımızı veritabanında tablo olarak işlemesini söyleyelim. python manage.py migrate yazıyoruz.
(env) adnan@ce:~/arge/projects/todo$ python manage.py migrate Operations to perform: Apply all migrations: admin, api, auth, contenttypes, sessions Running migrations: Applying api.0001_initial... OK (env) adnan@ce:~/arge/projects/todo$

URL tanımlaması

  • api içerisinde urls.py adında bir dosya oluşturalım ve aşağıdaki satırları ekleyelim.
# django'ya ait importlar from django.urls import path # projeye ait importlar from api import views urlpatterns = [ # views path('', views.home, name='home'), ]
  • Kullanıcıların istek yapabileceği URL tanımlamalarını burada yapıyoruz.
  • path('home/', views.home, name='home'), satırındaki home/ url tanımıdır. Bu URL'ye yapıalcak istekler için api/views.py içerisindeki home methodu çağırılır.

Views methodları

  • api/views.py modülünde URL tanımı yapılırken çağırılacak olan home methodunu tanımlayalım.
from django.shortcuts import render # projeye ait importlar from api.models import Todo def home(request): context = { 'priority_choices': dict(Todo.PRIORITY_CHOICES) } return render(request, 'api/home.html', context)
  • Bu method'un bize söylediği " api klasörü altında home.html dosyası var ve onu render ediyorum ve Todo model sınıfındaki öncelik seçeneklerini context(içerik) olarak gönderirim".

HTML dosyaları ve templates

  • Django oluşturulan uygulamalar içerisindeki html dosyalarını templates klasörleri altında arar. Biz de bu sistem için api altında templates onun altında api isminde klasör ve içerisine de base.html ve home.html ismindeki dosyaları oluşturacağız. Yani yapı şöyle olacak.
(env) adnan@ce:~/arge/projects/todo$ tree api api # api uygulaması !!!! ├── admin.py ├── apps.py ├── __init__.py ├── migrations │ ├── 0001_initial.py ├── models.py ├── templates ###### │ └── api ## api klasörü │ ├── base.html │ └── home.html ├── tests.py ├── urls.py └── views.py
  • base.html aşağıdaki gibi olacaktır.
{% load static %} {% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>To-Do List Uygulaması</title> </head> <body> {% block body %} {% endblock body %} </body> </html>
  • load static ve load staticfiles ileride ekleyeceğimiz angular, bootstrap gibi javascript, CSS dosyaları için şimdiden eklendi.
  • home.html dosyası şimdilik aşağıdaki gibi olsun.
{% extends 'api/base.html' %} {% load staticfiles %} {%block body%} <div> <h1> Selam Türkiye (: </h1> </div> {% endblock body %}

Proje ana URL dosyasına api URL'lerini tanımlama

  • todo/urls.py modülüne api/urls.py içerisindeki URL adreslerini Django'nun tanıması için aşağıdaki satırları ekleyelim.
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), # varsayılan admin paneline gitmek için önceden tanımlı url path('todo/', include('api.urls')), # api uygulamamızın URL dosya yolu. ]

Projeyi yeniden çalıştıralım

  • python manage.py runserver diyerek projeyi çalıştıralım
(env) adnan@ce:~/arge/projects/todo$ python manage.py runserver Performing system checks... System check identified no issues (0 silenced). April 28, 2019 - 18:31:35 Django version 2.1, using settings 'todo.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
  • Eğer http://127.0.0.1:8000/ adresine giderseniz aşağıdaki hata mesajı ile karşılaşırsınız.
Page not found (404) Request Method: GET Request URL: http://127.0.0.1:8000/ Using the URLconf defined in todo.urls, Django tried these URL patterns, in this order: admin/ todo/ The empty path didn't match any of these.
  • Çünkü http://127.0.0.1:8000/ adresi hiçbir şey göstermemektedir.
  • Projemiz http://127.0.0.1:8000/todo/home adresindedir.
  • Ekranda Selam Türkiye (: mesajını gördüyseniz buraya kadar başarılı bir şekilde ulaştınız demektir.

AngularJS, Bootstrap vs.'nin projeye eklenmesi.

  • api içerisinde bir tane static adında klasör oluşturalım ve onun da içinde lib adında bir klasör oluşturalım. Angularjs, bootstrap, jquery.. gibi dosyaları burada muhafaza edeceğiz.
  • Proje yapımız aşağıdaki gibi oldu.
(env) adnan@ce:~/arge/projects/todo$ tree api api ├── admin.py ├── apps.py ├── __init__.py ├── migrations │ ├── 0001_initial.py │ ├── __init__.py ├── models.py ├── static │ └── lib │ ├── angular │ ├── angular-cookies │ ├── bootstrap │ └── jquery_3_3 ├── templates │ └── api │ ├── base.html │ └── home.html ├── tests.py ├── urls.py └── views.py
  • base.html dosyasını aşağıdaki gibi düzenleyelim
{% load static %} {% load staticfiles %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>To-Do List Uygulaması</title> <!-- BOOTSTRAP v4.3 CSS --> <link rel="stylesheet" href="{% static 'lib/bootstrap/dist/css/bootstrap.min.css' %}"> <!-- ANGULAR --> <script src="{% static 'lib/angular/angular.min.js' %}"></script> <script src="{% static 'lib/angular-cookies/angular-cookies.min.js' %}"></script> </head> <body> <div class="container-fluid"> {% block body %} {% endblock body %} </div> <!-- SCRIPTS --> <script src="{% static 'lib/jquery_3_3/dist/jquery.min.js' %}"></script> <script src="{% static 'lib/bootstrap/dist/js/bootstrap.min.js' %}"></script> </body> </html>

Front-End için Back-End serializer, apiviews sınıfların ve URL'lerin hazırlanması

  • api altında urls.py oluşturduğumuz gibi serializers.py adında bir dosya oluşturalım ve içerisini aşağdaki gibi düzenleyelim.
from rest_framework import serializers # projeye ait importlar from api.models import Todo class TodoSerializer(serializers.ModelSerializer): class Meta: model = Todo fields = '__all__'
  • api içerisinde apiviews.py adında bir modül daha oluşturalım ve içerisine aşağıdaki sınıfları ekleyelim.
from rest_framework import generics # projeye ait importlar from api.models import Todo from api.serializers import TodoSerializer class TodoList(generics.ListCreateAPIView): queryset = Todo.objects.all() serializer_class = TodoSerializer class TodoDetail(generics.RetrieveUpdateAPIView): queryset = Todo.objects.all() serializer_class = TodoSerializer
  • TodoList sınıfı GET, POST istekleri için, TodoDetail sınıfı da Retrieve, PUT istekleri için kullanılacaktır. Delete işlemi yapmayacağız.
  • Şimdi api/urls.py içerisini aşağıdaki gibi düzenleyelim
from django.urls import path # internal from api import views from api import apiviews urlpatterns = [ # views path('home/', views.home, name='home'), # apiviews path('list/', apiviews.TodoList.as_view()), path('list/<int:pk>', apiviews.TodoDetail.as_view()), ]
  • Projeyi çalıştırıp http://127.0.0.1:8000/todo/list/ adresine giderseniz Django Rest Framework' un sunduğu varsayılan web sayfasını göreceksiniz ve tanımladığınız GET, POST, Retrieve, PUT işlemlerini gerçekleştirebilirsiniz. Ancak biz kendi front-end tasarımımızı yapacağız.

Front-End AngularJS

  • api/static klasöründe client adında bir klasor oluşturalım. İçerisinde aşağıdaki javascript dosyalarını oluşturalım.
(env) adnan@ce:~/arge/projects/todo/api/static$ tree client/ client/ ├── todo.app.config.js ├── todo.app.module.js ├── todo.controller.js └── todo.factory.js
  • todo.app.module içerisine aşağıdaki angularjs modül tanımını yapalım.
angular .module("todoApp", ['ngCookies']);
  • todo.app.config içerisine aşağıdaki konfigurasyonları yazalım.
(function () { 'use strict'; angular .module("todoApp") .config(todoConfig); todoConfig.$inject = ['$interpolateProvider', '$httpProvider']; function todoConfig($interpolateProvider, $httpProvider) { $interpolateProvider.startSymbol('[['); // Django'daki süslü parantez {{}} ile Angular JS'in süslü parantezleri $interpolateProvider.endSymbol(']]'); // çakışmaması için AngularJS'de [[]] köşeli parantezleri kullanacağımızı belirtiyoruz. // CSRF-TOKEN $httpProvider.defaults.headers.common['X-CSRFToken'] = '{{ csrf_token|escapejs }}'; } })();

AngularJS factory tanımlaması

  • todo.factory.js içerisine aşağıdaki factory tanımını ve methodlarını yazalım.
(function () { 'use strict'; angular .module('todoApp') .factory('todoFactory', todoFactory); todoFactory.$inject = ['$http', '$cookies']; function todoFactory($http, $cookies) { let CSRFToken = $cookies.get('csrftoken'); let URL = 'http://127.0.0.1:8000/todo/list/'; return { getTodoList: getTodoList, postTodo: postTodo, updateTodo, updateTodo }; function getTodoList() { return $http.get(URL) .then(getTodoListComplete) .catch(getTodoListFailed); function getTodoListComplete(response) { return response.data; } function getTodoListFailed(error) { console.log("Hata: ", error.data); } } function postTodo(data) { return $http({ url: URL, method: 'POST', data: data, headers: { 'X-CSRFToken': CSRFToken }, }) .then(postTodoComplete) .catch(postTodoFailed); function postTodoComplete(response) { console.log("New Todo is successfully saved!"); return response.data; } function postTodoFailed(error) { console.log("postTodoFailed: ", error.data); } } function updateTodo(data) { return $http({ url: URL + data.id, method: 'PUT', data: data, headers: { 'X-CSRFToken': CSRFToken }, }) .then(updateTodoComplete) .catch(updateTodoFailed); function updateTodoComplete(response) { console.log("Todo is updated successfully!"); return response.data; } function updateTodoFailed(error) { console.log("updateTodoFailed: ", error.data); } } } })();

AngularJS controller

  • todo.controller.js içerisine aşağıdaki controller tanımını ve methodlarını yazalım.
(function () { 'use strict'; angular .module('todoApp') .controller('todoController', todoController); todoController.$inject = ['todoFactory','$window']; function todoController(todoFactory,$window) { var vm = this; vm.todo_array = []; vm.postTodo = postTodo; vm.updateTodo = updateTodo; activate(); function activate() { return getTodoList().then(function () { console.log("To-do list is called!"); }); }; function getTodoList() { return todoFactory.getTodoList() .then(function (data) { vm.todo_array = data; return vm.todo_array; }); }; function postTodo(todo_data) { if (todo_data.priority) { return todoFactory.postTodo(todo_data).then(function () { console.log("To-do post request is called!"); //activate(); $window.location.reload(); }); } else { vm.err_msg = "Priority must be selected!"; } } function updateTodo(todo_data) { todo_data.is_done = true; return todoFactory.updateTodo(todo_data).then(function () { console.log("To-do put request is called!"); activate(); }); } } })();

home.html sayfasının düzenlenmesi

  • home.html sayfasını aşağıdaki gibi düzenleyelim
{% extends "api/base.html" %} {% load staticfiles %} {% block body%} <div class="row m-3"> </div> <div class="row" ng-app='todoApp' ng-cloak> <div class="col" ng-controller="todoController as ctrl"> <!-- new todo form --> <div class="row mb-3"> <div class="col-md-5 "> <div class="p-1 m-1 shadow"> <div class="input-group input-group-sm mb-3"> <div class="input-group-prepend"> <label class="input-group-text" for="inputGroupSelectPriority">Öncelik Derecesi : </label> </div> {% if priority_choices%} <select class="custom-select" ng-model="ctrl.todo.priority" id="inputGroupSelectPriority"> <option value="">Seçiniz ....</option> <!-- Django context'ten gelen --> {%for k,v in priority_choices.items%} <option value="{{k}}">{{v}}</option> {%endfor%} </select> {%endif%} </div> <div class="m-1"> <b class="text-danger" ng-show="!ctrl.todo.priority">[[ctrl.err_msg]]</b> </div> <div class="input-group input-group mb-3"> <div class="input-group-prepend"> <label class="input-group-text" for="inputGroupTitle">Başlık : </label> </div> <input class="form-control" type="text" ng-model="ctrl.todo.title" id="inputGroupTitle"> </div> <label class="mb-1" for="inputGroupContent">Metin içeriğini yazınız ...</label> <div class="input-group input-group-sm mb-3"> <textarea cols="60" rows="5" class="form-control" ng-model="ctrl.todo.content" id="inputGroupContent"></textarea> </div> <button class="btn btn-primary m-2" ng-show="(ctrl.todo.title.length>3 && ctrl.todo.content.length>3)" type="button" ng-click="ctrl.postTodo(ctrl.todo)"> Kaydet </button> </div> </div> <div class="col-md-2"> <select class="custom-select shadow" ng-model="ctrl.filter"> <option value="">Önceliğe göre filtrele</option> <option value="1">FIRST</option> <option value="2">SECOND</option> <option value="3">THIRD</option> </select> </div> <div class="col" ng-repeat="todo in ctrl.todo_array | orderBy : '-c_date' | filter: {priority:ctrl.filter} track by $index "> <div class="card shadow-sm mb-3" style="max-width: 18rem;"> <div class="card-header text-white bg-info" ng-class="{'bg-danger':todo.priority==='1', 'bg-warning':todo.priority==='2', 'bg-light':todo.is_done}"> <span class="text-dark" ng-show="todo.is_done">Bitti</span> <span class="" ng-show="!todo.is_done"> <button ng-click="ctrl.updateTodo(todo)" class="btn badge text-white shadow p-1">Bitti mi ?</button> </span> </div> <div class="card-body " ng-class="{'bg-light':todo.is_done}"> <h6 class="card-title">[[todo.title]]</h6> <small class="card-text"> [[todo.content]] </small> </div> </div> </div> </div> </div> </div> <!-- MY ANGULAR SCRIPTS --> <script src="{% static 'client/todo.app.module.js' %}"></script> <script src="{% static 'client/todo.app.config.js' %}"></script> <script src="{% static 'client/todo.factory.js' %}"></script> <script src="{% static 'client/todo.controller.js' %}"></script> {% endblock body%}

Proje Demo Videosu

Todo List App

Yorumlar