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