Despliega tu API en Heroku (Python o PHP) en menos de los piensas
Crear la app, por ejemplo: ironhack-api-python-demo, la app estará corriendo en:
http://ironhack-api-python-demo.herokuapp.com/
Nos traemos una copia del proyecto (vacío por ahora):
Instalamos heroku toolbelt:
Instalamos virtualenvwrapper:
> pip install virtualenvwrapper
Creamos un virtualenv (por ejemplo con el nombre api_env) donde estará nuestra app django:
Para activar ese virtualenv:
Instalamos MySql para Python:
> pip install MySQL-python
Ahora vamos a instalar una serie de paquetes necearios para Heroku:
> pip install dj-database-url > pip install dj-static > pip install gunicorn > pip install static
Guardamos los requisitos para Heroku sepa lo que tiene que instalar:
> pip freeze > requirements.txt
Por ejemplo lo llamaré apidemo
> django-admin.py startproject apidemo .
Tenemos que crear un archico Procfile para especificar a Heroku que en la máquina va a correr una app django, lo creamos en la raiz del proyecto con este contenido:
web: gunicorn apidemo.wsgi
Y probamos arrancando la app en local:
> python manage.py runserver
Si todo ha ido bien, podrás ver el sitio corriendo en http://127.0.0.1:8000/
Subimos a producción para probar
Subimos los cambios al repositorio:
> git add * > git commit -m 'Django app created'
Y hacemos push en producción:
Cuando acabe podremos ver en http://ironhack-api-python-demo.herokuapp.com/ un mensaje alentador: It worked!
Creamos base de datos local
Con nuestro cliente mysql, creamos una base de datos en localhost, llamada por ejemplo: ironhack-api-demo-test, luego creamos un usuario con permisos grant para esa base de datos. En el ejemplo el usuario será ironhackdemo y contraseña ironhackdemo.
Vamos a configurar django para que se conecte a la base de datos, abrimos /apidemo/settings.py y editamos el contenido del diccionario DATABASES:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'ironhack-api-demo-test', 'USER': 'ironhackdemo', 'PASSWORD': 'ironhackdemo', 'HOST': '127.0.0.1', 'PORT': '', } }
Creamos la app que será nuestra API
En Django los proyectos los conforman una o varias apps. Vamos a crearnos nuestra app, que se llamará por ejemplo api:
> python manage.py startapp api
Esto crea un nuevo directorio en nuestro proyecto, pero debemos indicarle al proyecto que incluya esta nueva app. Editamos /apidemo/settings.py de nuevo para añadir a la lista INSTALLED_APPS nuestra nueva app:
INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'api' )
Vamos a crean el api/models.py estos dos modelos, Ironhackers que representan a los estudiantes del IronHack y Teams que representa por ejemplo posibles equipos en los que agruparnos:
class Ironhackers(models.Model): name = models.CharField(default="",max_length=50) email = models.EmailField(max_length=100,default="") team = models.ForeignKey('Teams',null=True,blank=True,on_delete = models.SET_NULL) def __unicode__(self): return self.name class Teams(models.Model): team = models.CharField(default="",max_length=50) color = models.CharField(default="",max_length=50) def __unicode__(self): return self.team
Una vez creados los modelos, django se encarga crear las tablas en la base de datos, con el comando:
> python manage.py syncdb
Creamos las rutas que servirán de endpoints para nuestra API
Las rutas en Django se gestionan desde el archivo urls.py, lo abrimos y añadimos por ejemplo estas urls:
url(r'^ironhackers/add$', 'api.endpoints.ironhackers.add'), url(r'^ironhackers/delete$', 'api.endpoints.ironhackers.delete'), url(r'^ironhackers/list$', 'api.endpoints.ironhackers.list'), url(r'^ironhackers/orphans$', 'api.endpoints.ironhackers.orphan'), url(r'^ironhackers/link_to_team$', 'api.endpoints.ironhackers.link_to_team'), url(r'^teams/add$', 'api.endpoints.ironhackers.add'), url(r'^teams/delete$', 'api.endpoints.ironhackers.delete'), url(r'^teams/list$', 'api.endpoints.ironhackers.list'),
O sea en este ejemplo estamos añadiendo a nuestra api los métodos add, delete, list, orphans y link_to_team para ironhackers y add, delete y list para teams.
Implementación de los métodos
Para implementar los métodos de ironhackers, creamos el archivo /api/endpoints/ironhackers.py:
Añadir un ironhacker dados el name y el email:
def add(request): try: student=Ironhackers() student.name=request.POST['name'] student.email=request.POST['email'] student.save() data=json.dumps({'status': 'success', 'response':'added', 'data':{'id':student.id}}) except Exception as e: data = json.dumps({ 'status':'failed', 'response': e.args[0] }) return HttpResponse(data, content_type="application/json")
Eliminar un ironhacker dado su id:
def delete(request): try: student=Ironhackers.objects.get(id=request.POST["id"]) student.delete() data=json.dumps({'status': 'success', 'response':'deleted'}) except Exception as e: data = json.dumps({ 'status':'failed', 'response': e.args[0] }) return HttpResponse(data, content_type="application/json")
def list(request): try: students=Ironhackers.objects.all() list_students=[] for student in students: list_students.append({'id':student.id, 'name':student.name, 'email':student.email }) data=json.dumps({'status': 'success', 'response':'list', 'data': list_students }); except Exception as e: data = json.dumps({ 'status':'failed', 'response': e.args[0] }) return HttpResponse(data, content_type="application/json")
Lista de ironhackers sin equipo asignado:
def orphans(request): try: students=Ironhackers.objects.filter(team__isnull=True) list_students=[] for student in students: list_students.append({'id':student.id, 'name':student.name, 'email':student.email }) data=json.dumps({'status': 'success', 'response':'orphans', 'data': list_students }); except Exception as e: data = json.dumps({ 'status':'failed', 'response': e.args[0] }) return HttpResponse(data, content_type="application/json")
Vincular un ironhacker a un equipo dados el id del estudiante y el id del equipo:
def link_to_team(request): try: student=Ironhackers.objects.get(id=request.POST["student_id"]) team = Teams.objects.get(id=request.POST["team_id"]) student.team = team student.save() data=json.dumps({'status': 'success', 'response':'linked'}) except Exception as e: data = json.dumps({ 'status':'failed', 'response': e.args[0] }) return HttpResponse(data, content_type="application/json")
Para implementar los métodos de teams, creamos el archivo /api/endpoints/teams:
Añadir un equipo dados el team y el color:
def add(request): try: team=Teams() team.team=request.POST['team'] team.color=request.POST['color'] team.save() data=json.dumps({'status': 'success', 'response':'added', 'data':{'id':team.id}}) except Exception as e: data = json.dumps({ 'status':'failed', 'response': e.args[0] }) return HttpResponse(data, content_type="application/json")
Eliminar un equipo dado su id:
def delete(request): try: team=Teams.objects.get(id=request.POST["id"]) team.delete() data=json.dumps({'status': 'success', 'response':'deleted'}) except Exception as e: data = json.dumps({ 'status':'failed', 'response': e.args[0] }) return HttpResponse(data, content_type="application/json")
def list(request): try: teams=Teams.objects.all() list_teams=[] for team in teams: list_teams.append({'id':team.id, 'team':team.team, 'color':team.color }) data=json.dumps({'status': 'success', 'response':'list', 'data': list_teams }); except Exception as e: data = json.dumps({ 'status':'failed', 'response': e.args[0] }) return HttpResponse(data, content_type="application/json")
Con estas implementaciones tenemos en local estos métodos de nuestra API:
http://127.0.0.1:8000/ironhackers/add
http://127.0.0.1:8000/ironhackers/delete
http://127.0.0.1:8000/ironhackers/list
http://127.0.0.1:8000/ironhackers/orphans
http://127.0.0.1:8000/ironhackers/link_to_team
http://127.0.0.1:8000/teams/add
http://127.0.0.1:8000/teams/delete
http://127.0.0.1:8000/teams/list
Configurar la base de datos de producción
En la terminal, creamos una base de datos en producción con el comando:
> heroku addons:add cleardb:ignite
Para saber los datos de acceso de la base de datos recien creada:
> heroku config | grep CLEARDB_DATABASE_URL
Con esas credenciales, accedemos desde un cliente mysql e importamos la base de datos local. También cambiamos esas credenciales en el settings.py de nuestro proyecto.
Subimos todo a producción:
> git add * > git commit -m 'Django app created' > git push heroku master
Si todo ha udo bien, debériamos tener estos métodos listos para ser usados:
http://ironhack-api-python-demo-test.herokuapp.com//ironhackers/add
http://ironhack-api-python-demo-test.herokuapp.com//ironhackers/delete
http://ironhack-api-python-demo-test.herokuapp.com//ironhackers/list
http://ironhack-api-python-demo-test.herokuapp.com//ironhackers/orphans
http://ironhack-api-python-demo-test.herokuapp.com//ironhackers/link_to_team
http://ironhack-api-python-demo-test.herokuapp.com//teams/add
http://ironhack-api-python-demo-test.herokuapp.com//teams/delete
http://ironhack-api-python-demo-test.herokuapp.com//teams/list
Para probar, podéis usar http://www.hurl.it/
Para descargar el proyecto entero, https://github.com/rafaparadela/ironhack-api-python-demo
Crear la app, por ejemplo: ironhack-api-php-demo, la app estará corriendo en:
http://ironhack-api-php-demo.herokuapp.com/
Nos traemos una copia del proyecto (vacío por ahora):
Debemos tener Apache, PHP y MySQL corriendo en nuestro servidor local.
Descargamos y descomprimimos una instancia de CodeIgniter en el directorio donde clonamos el repositorio. (Podemos borrar el directorio user_guiade) y vemos en el navegador la pantalla por defecto de CodeIgniter.
Subimos a producción para probar
Subimos los cambios al repositorio:
> git add * > git commit -m 'Django app created'
Y hacemos push en producción:
Cuando acabe podremos ver en http://ironhack-api-php-demo.herokuapp.com/
Creamos base de datos local
Con nuestro cliente mysql, creamos una base de datos en localhost, llamada por ejemplo: ironhack-api-demo-test, luego creamos un usuario con permisos grant para esa base de datos. En el ejemplo el usuario será ironhackdemo y contraseña ironhackdemo.
Vamos a configurar codeigniter para que se conecte a la base de datos, abrimos /applications/config/database.php y editamos el array db
$db['default']['hostname'] = '127.0.0.1'; $db['default']['username'] = 'ironhackdemo'; $db['default']['password'] = 'ironhackdemo'; $db['default']['database'] = 'ironhack-api-demo-test';
Creamos los controladores
Primero vamos a crear los controladores. Para entender el sistema de rutas, vamos a crearnos en application/controllers/micontrolador.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); class Micontrolador extends CI_Controller { public function index() { echo 'How cool is that!'; } public function una() { echo 'Pollito!'; } public function dos($tres='') { echo 'Palote! '.$tres; } }
Para acceder a los métodos de los controladores, hay que poner la base_url de nuestra app, seguido del nombre del controlador, seguido del método a llamar. Si no le pasamos el segmento 'método' pues ejecuta función index();
Para llamar por ejemplo a la función una():
[base_url]/micontrolador/una
Para llamar por ejemplo a la función dos():
[base_url]/micontrolador/dos
Para pasarle el parámetro $tres a la función dos():
[base_url]/micontrolador/dos/mrpotato
La base_url por defecto incluye el punto de entrada de la app, o sea el archivo index.php. Para evitar que aparezca esto en nuetras rutas, debemos configurar algunas reglas de apache, lo más sencillo es crear un archivo .htaccess con estas reglas:
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php/$1 [L]
Vamos a crear ahora el controlador que contendrá los métodos de los estudiantes, lo llamamos ironhackers.php:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); class Ironhackers extends CI_Controller { public function index() { return false; } public function add() { return false } public function delete() { return false } public function list_students() { return false } public function link_to_team() { return false } }
Lo mismo con los equipos. Lo llamamos teams.php:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); class Teams extends CI_Controller { public function index() { return false; } public function add() { return false } public function delete() { return false } public function list_teams() { return false } }
Para que el controlador interactúe con la base de datos, lo hace a través de los modelos.
Vamoa a empezar creando el modelo para los estudiantes. En models, creamos ironhackers_model.php con una clase que extienda al CI_Model:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); class ironhackers_model extends CI_Model { function __construct() { parent::__construct(); } function get($id){ $this->db->where('id', $id); $this->db->limit(1); $query=$this->db->get('ironhackers'); if ($query->num_rows() > 0) return $query->row(); else return FALSE; } }
Con esto estamos creando una función get(), disponible para todo aquel que importe el modelo ironhackers, para que dado un id, te devuelva los campos de ese estudiante. El resto de funciones podría ser get_list para que devuelva todos los estudiantes, add para añadir uno, set para cambiar algun campo de un estudiante y delete para borrar un estudiante. Quedaría así:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); class ironhackers_model extends CI_Model { function __construct() { parent::__construct(); } function get($id){ $this->db->where('id', $id); $this->db->limit(1); $query=$this->db->get('ironhackers'); if ($query->num_rows() > 0) return $query->row(); else return FALSE; } function get_list(){ // $this->db->order_by('ironhacker','asc'); $query=$this->db->get('ironhackers'); if ($query->num_rows() > 0) return $query->result(); else return FALSE; } function add($ironhacker) { $this->db->insert('ironhackers', $ironhacker); return $this->db->insert_id(); } function set($id_ironhacker,$field=FALSE,$value=FALSE) { if($field) $this->db->set($field,$value); // $this->db->set('update','NOW()', FALSE); $this->db->where('id', $id_ironhacker); return $this->db->update('ironhackers'); } function delete($id_ironhacker) { $this->db->where('id', $id_ironhacker); return $this->db->delete('ironhackers'); } }
Y el modelo de Teams quedaría así:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); class teams_model extends CI_Model { function __construct() { parent::__construct(); } function get($id){ $this->db->where('id', $id); $this->db->limit(1); $query=$this->db->get('teams'); if ($query->num_rows() > 0) return $query->row(); else return FALSE; } function get_list(){ // $this->db->order_by('team','asc'); $query=$this->db->get('teams'); if ($query->num_rows() > 0) return $query->result(); else return FALSE; } function add($team) { $this->db->insert('teams', $team); return $this->db->insert_id(); } function set($id_team,$field=FALSE,$value=FALSE) { if($field) $this->db->set($field,$value); // $this->db->set('update','NOW()', FALSE); $this->db->where('id', $id_team); return $this->db->update('teams'); } function delete($id_team) { $this->db->where('id', $id_team); return $this->db->delete('teams'); } }
Ya podemos implementar los métodos. Ironhackers quedaría así:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); class Ironhackers extends CI_Controller { function __construct() { parent::__construct(); // $this->output->enable_profiler(TRUE); } public function index() { return false; } public function add() { $this->load->model('ironhackers_model'); $student = array( 'name' => $_POST['name'], 'email' => $_POST['email'] ); $id = $this->ironhackers_model->add($student); $dev = array( 'status' => 'success', 'response' => 'added' , 'data' => $id ); echo json_encode($dev); } public function delete() { $this->load->model('ironhackers_model'); $id = $this->ironhackers_model->delete($_POST['id']); $dev = array( 'status' => 'success', 'response' => 'deleted' ); echo json_encode($dev); } public function list_students() { $this->load->model('ironhackers_model'); $students=$this->ironhackers_model->get_list(); $my_students=array(); foreach ($students as $student) { $item=array( 'id'=>$student->id, 'name'=> $student->name, 'email'=> $student->email ); array_push($my_students,$item); } $dev = array( 'status' => 'success', 'response' => 'deleted', 'data' => $my_students ); echo json_encode($dev); } public function link_to_team() { $this->load->model('ironhackers_model'); $this->ironhackers_model->set($_POST['student_id'], $field='team_id', $value=$_POST['team_id'] ); $dev = array( 'status' => 'success', 'response' => 'linked' ); echo json_encode($dev); } }
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); class Teams extends CI_Controller { function __construct() { parent::__construct(); // $this->output->enable_profiler(TRUE); } public function index() { return false; } public function add() { $this->load->model('teams_model'); $team = array( 'team' => $_POST['team'], 'color' => $_POST['color'] ); $id = $this->teams_model->add($team); $dev = array( 'status' => 'success', 'response' => 'added' , 'data' => $id ); echo json_encode($dev); } public function delete() { $this->load->model('teams_model'); $id = $this->teams_model->delete($_POST['id']); $dev = array( 'status' => 'success', 'response' => 'deleted' ); echo json_encode($dev); } public function list_teams() { $this->load->model('teams_model'); $teams=$this->teams_model->get_list(); $my_teams=array(); foreach ($teams as $team) { $item=array( 'id'=>$team->id, 'team'=> $team->team, 'color'=> $team->color ); array_push($my_teams,$item); } $dev = array( 'status' => 'success', 'response' => 'list', 'data' => $my_teams ); echo json_encode($dev); } }
Este ejemplo está en GitHub: https://github.com/rafaparadela/ironhack-api-php-demo
Para subirlo a Heroku, el .htaccess debería ser así:
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule>
Si hay problema para conectar con Heroku por las credenciales:
> ssh-keygen -t rsa -C "[email protected]" -f ~/.ssh/id_rsa_heroku > ssh-add ~/.ssh/id_rsa_heroku > heroku keys:add ~/.ssh/id_rsa_heroku.pub