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
├── 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.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'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
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.
from django.urls import path
from api import views
urlpatterns = [
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
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
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ ├── 0001_initial.py
├── models.py
├── templates
│ └── api
│ ├── 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),
path('todo/', include('api.urls')),
]
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>
<link rel="stylesheet" href="{% static 'lib/bootstrap/dist/css/bootstrap.min.css' %}">
<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>
<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
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
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
from api import views
from api import apiviews
urlpatterns = [
path('home/', views.home, name='home'),
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('[[');
$interpolateProvider.endSymbol(']]');
$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!");
$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">
<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>
{%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>
<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

Yorumlar