├── LICENSE ├── README.md ├── capitulo00.md ├── capitulo01.md ├── capitulo02.md ├── capitulo03.md ├── capitulo04.md ├── capitulo05.md ├── capitulo06.md ├── capitulo07.md ├── capitulo08.md ├── capitulo09.md ├── capitulo10.md ├── capitulo11.md ├── images └── django.jpg └── screenshots ├── 00.png ├── 01.png ├── 02.png ├── 03.png ├── 04.png ├── 05.png ├── 06.png ├── 07.png ├── 08.png ├── 09.png ├── 10.png └── 11.png /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 MapCake 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tutorial-django-angularjs-thinkster-spanish 2 | Traducción al español del tutorial "Building Web Applications with Django and AngularJS" de la web https://thinkster.io/ 3 | 4 | En este tutorial vamos a construir un clon simplificado de Google+ llamada "Not Google Plus" con Django y AngularJS. 5 | In this tutorial you will build a simplified Google+ clone called “Not Google Plus” with Django and AngularJS. 6 | 7 | ## Capitulos 8 | 9 | 1. [Introducción](capitulo00.md) 10 | 2. [Extendiendo el modelo User por defecto de Django](capitulo01.md) 11 | 3. [Serializando los modelos User y UserProfile](capitulo02.md) 12 | 4. [Registrando nuevos usuarios](capitulo03.md) 13 | 5. [Conectando a los usuarios](capitulo04.md) 14 | 6. [Desconcetando a los usuarios](capitulo05.md) 15 | 7. [Haciendo un modelos Post](capitulo06.md) 16 | 8. [Renderizando objetos Post](capitulo07.md) 17 | 9. [Creando nuevos posts](capitulo08.md) 18 | 10. [Mostrando el perfil de usuario](capitulo09.md) 19 | 11. [Actualizando los Perfiles de Usuario](capitulo10.md) 20 | 12. [¡Felicidades!¡Lo conseguiste!](capitulo11.md) 21 | -------------------------------------------------------------------------------- /capitulo00.md: -------------------------------------------------------------------------------- 1 | # Aprendiendo Django y AngularJS 2 | 3 | {intro-video: django-intro} 4 | 5 | En este tutorial vamos a crear un clon simplificado de Google+ con Django y AngularJS. Lo Llamaremos “Not Google Plus”. 6 | 7 | Antes de empezar a construir aplicaciones web modernas y ricas con Django y AngularJS, vamos a tomar un momento en explorar la motivación detrás de este tutorial y como puedes obtener el máximo partido de este. 8 | 9 | ## ¿Cual es el objetivo de este tutorial? 10 | En thinkster, nos esforzamos por crear gran valor, en contenido de profundidad, manteniendo una barrera de entrada baja. Publicamos este contenido de forma gratuita, con la esperanza de que puedas encontrarlo excitante e informativo. 11 | 12 | Cada tutorial que publicamos tiene un objetivo especifico. En este, el objetivo es dar una breve introducción de Django y AngularJS comunicándose juntos y como estas tecnologías pueden combinarse para construir aplicaciones web increíbles. Ademas, ponemos énfasis en construir buenos hábitos de ingeniería. Esto incluye la consideración de ventajas y desventajas derivadas de las decisiones de arquitectura, para mantener la alta calidad del código a través de tus proyectos. Aunque estas cosas no parezcan muy divertidas, son la clave para convertirse en un buen desarrollados de software. 13 | 14 | ## ¿Para quien es este tutorial? 15 | Todos los autores deben responder a esta complicada pregunta. Nuestro objetivo es hacer este tutorial util para desarrolladores novatos y expertos. 16 | 17 | Para aquellos que empezáis con vuestra carrera de desarrolladores, hemos intentado ser exhaustivos y lógicos en nuestras explicaciones en la medida de lo posible, intentando que el texto sea fluido; también tratamos de evitar saltos intuitivos aunque tengan sentido. 18 | 19 | Para aquellos que ya tienen algo de experiencia, y quizás esta simplemente interesados en aprender mas sobre Django o AngularJS, sabemos que no necesitas que te expliquemos las bases. Uno de nuestros objetivos al escribir este tutorial era hacerlo lo mas sencillo posible. Esto hace que con tus conocimientos actuales puedas leerlo mas rápido e identificar los conceptos con los que no estas familiarizado para que puedas asimilarlos rápidamente y continuar. 20 | 21 | Queremos hacer este tutorial accesible a cualquiera con el interés suficiente para tomar el tiempo necesario para aprender y entender los conceptos presentados. 22 | 23 | ## Un breve interludio acerca del formato 24 | A lo largo de este tutorial, nos esforzamos por mantener un formato coherente. En esta sección detallamos como debe ser el formato y que significa. 25 | 26 | * Cuando mostremos un nuevo retazo de código, lo presentaremos por completo y después lo recorreremos linea por linea si es necesario para cubrir los nuevos conceptos. 27 | * Los nombre de variables y ficheros aparecen con el formato especial siguiente:`thinkster_django_angular/settings.py`. 28 | * Los retazos de código largos aparecen en sus propias lineas: 29 | 30 | def is_this_google_plus(): 31 | return False 32 | 33 | * Los comandos del terminal también aparecen en sus propias lineas, con el prefijo `$`: 34 | 35 | $ python manage.py runserver 36 | 37 | * Si no se especifica de otra manera, debes asumir que todos los comandos del terminal deben ejecutarse desde el directorio principal de tu proyecto. 38 | 39 | ## Unas palabras sobre el estilo del código 40 | Cuando es posible, optamos por seguir la guía de estilo creado por las comunidades de Django y Angular. 41 | 42 | Para Django, seguimos el estilo [PEP8](http://legacy.python.org/dev/peps/pep-0008/) estrictamente e intentamos seguir el [estilo de Codigo de Django] (https://docs.djangoproject.com/en/1.7/internals/contributing/writing-code/coding-style/). 43 | 44 | Para Angular JS, hemos adoptado la [guía de estilo de John Papa para AngularJS](https://github.com/johnpapa/angularjs-styleguide). También utilizamos la [guía de estilo de Google para JavaScript] (https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) cuando es lógico utilizarlo. 45 | 46 | ## Una humilde petición de retroalimentación 47 | A riesgo de sonar a cliché, no tendríamos una razón para hacer este tutorial si no fuera por ti. Porque creemos que tu éxito es nuestro éxito, te invitamos a ponerte en contacto con nosotros con cualquier idea que tengas sobre el tutorial. Puedes comunicarte con nosotros (en inglés) a través de la caja en la esquina inferior derecha de la pantalla, a través de Twitter en [@jamesbrwr](http://twitter.com/jamesbrwr) o en @GoThinkster](http://twitter.com/gothinkster), o enviando un correo electrónico [support@thinkster.io](mailto:support@thinkster.io). 48 | 49 | Damos la bienvenida a la crítica abierta y aceptamos alagos si crees que es merecido. Estamos interesados en saber lo que te gusta, lo que no te gusta, sobre los temas que quiere saber más, y cualquier cosa que consideres relevante. 50 | 51 | Si estás demasiado ocupado para llegar a nosotros, está bien. Sabemos que el aprendizaje requiere un montón de trabajo. Si, por el contrario, quieres ayudarnos a construir algo increíble, estamos a la espera de tu correo. 52 | 53 | Si tus comentarios son sobre esta traduccion al español, no dudes en escribir en español a [damarmo@gmail.com](mailto:damarmo@gmail.com). 54 | 55 | ## Unas últimas palabras antes de empezar 56 | Es nuestra experiencia que los desarrolladores que obtienen el mayor beneficio de nuestros tutoriales son los que toman un enfoque activo de su aprendizaje. 57 | 58 | Te recomendamos **energicamente** que escribas el código tu mismo. Al copiar y pegar el código, no interactúas con él y esta interacción es a su vez, lo que te hace un mejor desarrollador. 59 | 60 | Además de escribir el código por ti mismo, no tengas miedo de ensuciarte las manos; saltar y jugar, romper cosas y construir características que faltan. Si encuentras un error, explora y averigua lo que lo está causando. Estos son los obstáculos que nosotros como ingenieros deben abordar varias veces al día, por lo que hemos aprendido a abrazar estas exploraciones como la mejor fuente de aprendizaje. 61 | 62 | Vamos a construir algo de software. 63 | 64 | # Configurando el entorno de trabajo 65 | 66 | {video: setup-environment} 67 | 68 | La aplicación que vamos a construir requiere una gran cantidad de trabajo repetitivo y no trivial. En vez de pasar tiempo configurando el entorno, que no es el propósito de este tutorial, hemos creado una plantilla del proyecto para empezar. 69 | 70 | Puedes encontrar la plantilla del proyecto en Github en [brwr/thinkster-django-angular-boilerplate](https://github.com/brwr/thinkster-django-angular-boilerplate). El repositorio incluye una lista de comandos que necesitas ejecutar para tener todo en marcha. 71 | 72 | Nota 73 | Si estas interesado en un apéndice detallado en configurar tu entorno de trabajo háznoslo saber en el Twitter http://twitter.com/jamesbrwr. 74 | 75 | Ve al repositorio y sigue las instrucciones ahora. 76 | 77 | {x: set_up_envrionment} 78 | Sigue las instrucciones para configurar tu entorno de trabajo. 79 | 80 | ## Punto de Control 81 | 82 | Si todo ha ido bien, ejecutando el servidor con el comando `python manage.py runserver`, deberías poder visitar el enlace `http://localhost:8000/` en un navegador . La pagina debería estar en blanco excepto una barra de navegación en la parte superior. El enlace en la barra de navegación no hace nada por el momento. 83 | 84 | {x: checkpoint_environment} 85 | Asegurate que tu entorno de trabajo esta funcionando, visitando el enlace `http://localhost:8000/` 86 | -------------------------------------------------------------------------------- /capitulo01.md: -------------------------------------------------------------------------------- 1 | # Extendiendo el modelo User por defecto de Django 2 | Django posee un modelo `User` que ofrece muchas funcionalidades. El problema con este modelo `User` es que no se puede ampliar para incluir mas información. Por ejemplo, estaremos dando a nuestros usuarios un lema que se mostrará en su perfil. El modelo `User` de Django no tiene este atributo y no podemos añadir. 3 | 4 | El modelo `User` hereda de `AbstractBaseUser`. De aqui es de donde `User` obtiene la mayoría de sus funcionalidades. Creando un nuevo modelo llamado ``Account`` y heredando de `AbstractBaseUser`, tendremos las funcionalidades necesarias de `User` (ocultado de password, gestión de sesiones, etc) y seremos capaces de extender ``Account`` para incluir información extra, como el lema (tagline). 5 | 6 | En Django, el concepto de “app” se usa para organizar el código de forma significativa. Una app es un module que alberga el código de modelos, vistas, serializadores, etc que están relacionados de alguna manera. En forma de ejemplo, nuestro primer paso en construir nuestra aplicación web en Django y AngularJS sera crear una app llamada `authentication`. La aplicación `authentication` contendrá el código relacionado con el modelo `Account` que acabamos de hablar, así como vistas para conectarse, desconectarse y registrarse. 7 | 8 | Hacer una nueva aplicación llamada authentication ejecutando el comando siguiente: 9 | 10 | $ python manage.py startapp authentication 11 | 12 | {x: startapp_authentication} 13 | Crea una aplicación Django llamada authentication 14 | 15 | ## Creando el modelo Account 16 | 17 | {video: account-manager} 18 | 19 | Para empezar, vamos a crear el modelo `Account` del que acabamos de hablar. 20 | 21 | Abre `authentication/models.py` en tu editor de texto favorito y y editalo con el siguiente contenido: 22 | 23 | from django.contrib.auth.models import AbstractBaseUser 24 | from django.db import models 25 | 26 | class Account(AbstractBaseUser): 27 | email = models.EmailField(unique=True) 28 | username = models.CharField(max_length=40, unique=True) 29 | 30 | first_name = models.CharField(max_length=40, blank=True) 31 | last_name = models.CharField(max_length=40, blank=True) 32 | tagline = models.CharField(max_length=140, blank=True) 33 | 34 | is_admin = models.BooleanField(default=False) 35 | 36 | created_at = models.DateTimeField(auto_now_add=True) 37 | updated_at = models.DateTimeField(auto_now=True) 38 | 39 | objects = AccountManager() 40 | 41 | USERNAME_FIELD = 'email' 42 | REQUIRED_FIELDS = ['username'] 43 | 44 | def __unicode__(self): 45 | return self.email 46 | 47 | def get_full_name(self): 48 | return ' '.join([self.first_name, self.last_name]) 49 | 50 | def get_short_name(self): 51 | return self.first_name 52 | 53 | 54 | x: create_user_profile_model} 55 | Crea el modelo `Account` en `authentication/models.py` 56 | 57 | Vamos a examinar con detalle cada atributo y método por turnos. 58 | 59 | email = models.EmailField(unique=True) 60 | 61 | # ... 62 | 63 | USERNAME_FIELD = 'email' 64 | 65 | El modelo `User` de Django por defecto, requiere un username o nombre de usuario. Este nombre de usuario se utiliza para conectar al usuario a la aplicación. Por el contrario, nuestra aplicación utilizará la dirección de email del usuario para este proposito. 66 | 67 | Para decirle a Django que queremos utilizar el campo de email como el `username` para este modelo, seleccionamos el atributo `USERNAME_FIELD` a `email`. El campo especificado en `USERNAME_FIELD` debe ser único, así que pasamos el argumento `unique=True` en el campo email. 68 | 69 | username = models.CharField(max_length=40, unique=True) 70 | 71 | Aunque el usuario utilice el email para conectarse, seguiremos queriendo que el usuario tenga un nombre de usuario. Necesitaremos uno para mostrarlo en sus post y pagina de perfil. También lo utilizaremos en nuestras URLs, así que deberá ser único. Así que pasaremos el argumento `unique=True` en el campo username con este fin. 72 | 73 | first_name = models.CharField(max_length=40, blank=True) 74 | last_name = models.CharField(max_length=40, blank=True) 75 | 76 | Idealmente deberíamos tener una forma mas personal para referirnos a nuestros usuarios. También entendemos que no todos los usuarios están comodos dando detalles personales, así que hacemos opcionales los campos `first_name` y `last_name` pasando el argumento `blank=True`. 77 | 78 | tagline = models.CharField(max_length=140, blank=True) 79 | 80 | como hemos mencionado antes, el atributo `tagline` se mostrará en el perfil de usuario. Esto da al perfil de usuario un toque personal, así que merece la pena incluirlo. 81 | 82 | created_at = models.DateTimeField(auto_now_add=True) 83 | updated_at = models.DateTimeField(auto_now=True) 84 | 85 | El campo `created_at` guarda la fecha y hora en la que el objeto `Account` fue creado. Pasando como argumento `auto_now_add=True` a `models.DateTimeField`, le estamos diciendo a Django que este campo debe rellenarse de forma automática una vez que se crea el objeto y que no es editable después de esto. 86 | 87 | De forma similar a created_at, updated_at se rellena de forma automática por Django. La diferencia entre `auto_now_add=True` y `auto_now=True` es que `auto_now=True` se actualiza cada vez que el objeto se guarda. 88 | 89 | objects = AccountManager() 90 | 91 | Cuando queremos obtener una instancia a un modelo en Django, usamos una expresión de tipo `Model.objects.get(**kwargs)`. El objeto, aquí es una clase `Manager` cuyo nombre sigue la convención `Manager`. En nuestro caso , crearemos un clase `AccountManager`. Haremos esto en unos instantes. 92 | 93 | REQUIRED_FIELDS = ['username'] 94 | 95 | Vamos a mostrar el nombre de usuario en varios sitios. Por eso, tener un nombre de usuario no es opcional, así que lo incluimos en la lista de campos requerido `REQUIRED_FIELDS`. Normalmente el argumento `required=True` cumple con este propósito, pero como este modelo esta reemplazando al modelo `User`, Django requiere que especifiquemos los campos obligatorios de esta forma. 96 | 97 | def __unicode__(self): 98 | return self.email 99 | 100 | Cuando trabajamos en la linea de comandos, como veremos en breve, la representacion de la cadena de un objeto `Account` se parece a algo asi ``. Esto no sera de mucha ayuda ya que vamos a tener diferentes tipos de cuentas. Sobreescribiendo `__unicode__()` cambiara el comportamiento por defecto. Aqui hemos decidido mostrar el email del usuario en su lugar. Asi que la representacion de la cadena de una cuenta de usario con el siguiente email `james@notgoogleplus.com` sera ahora ``. 101 | 102 | def get_full_name(self): 103 | return ' '.join([self.first_name, self.last_name]) 104 | 105 | def get_short_name(self): 106 | return self.first_name 107 | 108 | `get_full_name()` y `get_short_name()` son convenciones de Django. No vamos a utilizar estos methods, pero sigue siendo una buena idea incluirlos para cumplir con las convenciones de Django. 109 | 110 | ## Haciendo una clase Manager para Account 111 | Cuando sustituimos el modelo user por uno personalizado, se requiere definir una clase `Manager` que sobrescrito los métodos `create_user()` y `create_superuser()`. 112 | 113 | Como el fichero `authentication/models.py` sigue abierto, añadiremos la siguiente clase antes dela clase `Account`: 114 | 115 | from django.contrib.auth.models import BaseUserManager 116 | 117 | 118 | class AccountManager(BaseUserManager): 119 | def create_user(self, email, password=None, **kwargs): 120 | if not email: 121 | raise ValueError('Users must have a valid email address.') 122 | 123 | if not kwargs.get('username'): 124 | raise ValueError('Users must have a valid username.') 125 | 126 | account = self.model( 127 | email=self.normalize_email(email), username=kwargs.get('username') 128 | ) 129 | 130 | account.set_password(password) 131 | account.save() 132 | 133 | return account 134 | 135 | def create_superuser(self, email, password, **kwargs): 136 | account = self.create_user(email, password, **kwargs) 137 | 138 | account.is_admin = True 139 | account.save() 140 | 141 | return account 142 | 143 | Como hicimos con `Account`, vamos a explicar esto linea por linea. Pero esta vez solo cubriremos la nueva información. 144 | 145 | if not email: 146 | raise ValueError('`User`s must have a valid email address.') 147 | 148 | if not kwargs.get('username'): 149 | raise ValueError('`User`s must have a valid username.') 150 | 151 | Como se requiere que los usuarios tengan email y username, lanzaremos un error si alguno de estos dos atributos no se incluyen. 152 | 153 | account = self.model( 154 | email=self.normalize_email(email),username=kwargs.get('username') 155 | ) 156 | 157 | Desde el momento en que no definimos un atributo `model` en la clase `AccountManager`, `self.model` se refiere al atributo `model` de Base`UserManager`. Este es por defecto `settings.AUTH_USER_MODEL`, que cambiaremos en un momento para que señale a la clase `Account`. 158 | 159 | account = self.create_user(email, password, **kwargs) 160 | 161 | account.is_admin = True 162 | account.save() 163 | 164 | Ya que escribir varias veces lo mismo es pesado. En vez de copiar todo el código del método `create_account` y pegarlo en `create_superuser`, simplemente dejamos que el método `create_user` haga la creación. Asi, que de lo único que debemos preocuparnos es de que el método `create_superuser` convierte en superusuario el objeto `Account`. 165 | 166 | ## Cambiar la propiedad AUTH_USER_MODEL de Django 167 | A pesar de haber creado el modelo `Account`, el comando `python manage.py createsuperuser` (del cual hablaremos en breve) sigue creando objetos `User`. Esto es por que en este punto, Django sigue pensando que el modelo a utilizar para autentificar usuarios sigue siendo `User`. 168 | 169 | Para poner las cosas claras y empezar a utilizar `Account` como modelo de autentificación, tendremos que actualizar `settings.AUTH_USER_MODEL`. 170 | 171 | Abrimos `thinkster_django_angular_tutorial/settings.py` y añadimos al final del archivo la siguiente linea: 172 | 173 | AUTH_USER_MODEL = 'authentication.Account' 174 | 175 | {x: auth_user_model_setting} 176 | Cambia settings.AUTH_USER_MODEL para utilizar `Account` en vez de `User` 177 | 178 | Esta linea le dice a Django que mire en la aplicación `authentication` y busque el modelo llamado `Account`. 179 | 180 | ## Instalando la primera app 181 | En Django, debes declara explicitamente que aplicaciones serán utilizadas. Como no hemos añadido nuestra aplicación `authentication` a la lista de aplicaciones instaladas aun, vamos a hacerlo ahora. 182 | 183 | Abre `thinkster_django_angular_boilerplate/settings.py` y añadiremos `'authnetication'`, a `INSTALLED_APPS` como sigue: 184 | 185 | INSTALLED_APPS = ( 186 | …, 187 | 'authentication', 188 | ) 189 | 190 | {x: install_authentication_app} 191 | Instala la aplicación `'authentication'` 192 | 193 | ## Migrando nuestra primera aplicación 194 | Cuando se lanzo la version Dajngo1.7, fue como Navidad en Septiembre. ¡Habían llegado las Migraciones! 195 | 196 | Cualquiera que tenga conocimiento en Rails estará familiarizado con el concepto de migraciones. En breve, la migraciones toman el código SQL necesario para actualizar el esquema de la base de datos, así que no tendremos que hacerlo nosotros. Como ejemplo, imagina nuestro modelo `Account` que acabamos de crear. Este modelo debe ser guardado en la base de datos, pero nuestra base de datos, no tiene tabla para guardar objetos `Account` aun. ¿Como lo hacemos? !Creamos nuestra primera migración! La migración añadir las tablas a la base de datos y nos da la posibilidad de volver atrás en los cambios si cometemos alguna error. 197 | 198 | Cuando estes preparado, genera la migración para la aplicación `authentication` y aplicada: 199 | 200 | $ python manage.py makemigrations 201 | Migrations for 'authentication': 202 | 0001_initial.py: 203 | - Create model Account 204 | $ python manage.py migrate 205 | Operations to perform: 206 | Synchronize unmigrated apps: rest_framework 207 | Apply all migrations: admin, authentication, contenttypes, auth, sessions 208 | Synchronizing apps without migrations: 209 | Creating tables... 210 | Installing custom SQL... 211 | Installing indexes... 212 | Running migrations: 213 | Applying authentication.0001_initial... OK 214 | 215 | 216 | {info} 217 | A partir de ahora, no se incluirá la salida de los comandos de migración para abreviar. 218 | 219 | {x: initial_migration} 220 | Genera la migración para la aplicación authentication y aplicala. 221 | 222 | ## Hazte superusuario 223 | Hablemos algo mas sobre el comando `python manage.py createsuperuser`. 224 | 225 | Usuarios diferentes tienen niveles diferentes de acceso en cualquier aplicación. Algunos usuarios son administradores y pueden ir donde quieran, mientras que otros son usuarios normales y sus acciones deben estar limitadas. En Django, un super usuario es el nivel mas alto de acceso que puedes tener. Como queremos tener la posibilidad de trabajar sin restricciones en la aplicación, vamos a crear un super usuario. Esto es lo que hace el comando python manage.py createsuperuser. 226 | 227 | Después de ejecutar el comando, Django te preguntará algunas informaciones y creará una cuanta `Account` con privilegios de superusuario. Pruébalo. 228 | 229 | $ python manage.py createsuperuser 230 | 231 | {x: create_superuser} 232 | Haz una cuenta de superusuario 233 | 234 | ## Punto de control 235 | Para estar seguros de que todo esta bien configurado, toma una pequeña pausa y abre una consola de Django: 236 | 237 | $ python manage.py shell 238 | 239 | Deberías ver una nueva linea de comandos con: `>>>`. Dentro de esta linea de comandos podemos obtener el objeto `Account` que acabamos de crear: 240 | 241 | >>> from authentication.models import Account 242 | >>> a = Account.objets.latest('create_at') 243 | 244 | Si todo a ido bien, deberías ser capaz de acceder a los atributos del objeto `Account`: 245 | 246 | >>> a 247 | >>> a.email 248 | >>> a.username 249 | 250 | {x: get_new_account} 251 | Accede al objeto `Account` que acabamos de crear 252 | -------------------------------------------------------------------------------- /capitulo02.md: -------------------------------------------------------------------------------- 1 | # Serializando los modelos User y UserProfile 2 | La aplicación AngularJS que estamos construyendo, hará peticiones AJAX al servidor para obtener los datos que debería mostrar. Antes de poder enviar estos datos al cliente, necesitamos darles un formato de forma que el cliente puede interpretarlos; en este caso hemos elegido JSON. El proceso de transformar los modelos Django en JSON se llama serialización y es de lo que vamos a hablar a continuación. 3 | 4 | {video: serialize-account-model} 5 | 6 | Como el modelo que queremos serializar se llama `Account`, el serializador que vamos a crear se llamará `AccountSerializer`. 7 | 8 | ## Django REST Framework 9 | Como parte del proyecto modelo que hemos clonados antes, hemos incluido un proyecto llamado Django REST Framework. Django REST Framework es un conjunto de herramientas que propone un numero de características comunes a la gran mayoría de plantaciones web, incluyendo los serializadores. Vamos a utilizar estas características durante el tutorial para ahorrarnos tiempo y frustraciones. Nuestro primer vistazo a Django REST Framework empieza aquí. 10 | 11 | ## AccountSerializer 12 | Antes de escribir nuestro serializador, creemos un fichero `serializers.py` dentro de nuestra aplicación `authentication`: 13 | 14 | $ touch authentication/serializers.py 15 | 16 | {x: create_serializers_module} 17 | Crea el fichero `serializers.py` dentro de la aplación `authentication` 18 | 19 | Abre `authentication/serializers.py` y añade el siguiente código e `imports`: 20 | 21 | from django.contrib.auth import update_session_auth_hash 22 | 23 | from rest_framework import serializers 24 | 25 | from authentication.models import Account 26 | 27 | 28 | class AccountSerializer(serializers.ModelSerializer): 29 | password = serializers.CharField(write_only=True, required=False) 30 | confirm_password = serializers.CharField(write_only=True, required=False) 31 | 32 | class Meta: 33 | model = Account 34 | fields = ('id', 'email', 'username', 'created_at', 'updated_at', 35 | 'first_name', 'last_name', 'tagline', 'password', 36 | 'confirm_password',) 37 | read_only_fields = ('created_at', 'updated_at',) 38 | 39 | def create(self, validated_data): 40 | return Account.objects.create(**validated_data) 41 | 42 | def update(self, instance, validated_data): 43 | instance.username = validated_data.get('username', instance.username) 44 | instance.tagline = validated_data.get('tagline', instance.tagline) 45 | 46 | instance.save() 47 | 48 | password = validated_data.get('password', None) 49 | confirm_password = validated_data.get('confirm_password', None) 50 | 51 | if password and confirm_password and password == confirm_password: 52 | instance.set_password(password) 53 | instance.save() 54 | 55 | update_session_auth_hash(self.context.get('request'), instance) 56 | 57 | return instance 58 | 59 | {x: create_account_serializer} 60 | Crea un serializador llamado Account Serializer en authentication/serializers.py 61 | 62 | {info} 63 | A partir de ahora, vamos a declarar los imports que utilizaremos en cada retazo de código. Esto también debe estar presente en el fichero. Así que, no debes añadirlo por segunda vez. 64 | 65 | Vamos a ver el código en detalle 66 | 67 | password = serializers.CharField(write_only=True, required=False) 68 | confirm_password = serializers.CharField(write_only=True, required=False) 69 | 70 | En vez de incluir `password` en la tupla `fields`, de la cual hablaremos después, definimos explícitamente el campo en la parte superior de la clase `AccountSerializer`. La razón de hacer esto es poder pasar el argumento `required=False`. Cada campo en `fields` es obligatorio, pero no queremos actualizar el campo password hasta que no se especifique uno nuevo. 71 | 72 | `confirm_pssword` es similar a `password` y se utiliza solo para verificar que el usuario no comete errores de escritura. 73 | 74 | Observa también el uso del argumento `write_only=True`. El password de usuario, también esta oculto, no debe ser visible por el cliente en la respuesta AJAX. 75 | 76 | class Meta: 77 | 78 | La subclase `Meta` define metadatos del serializador para operar. Hemos definido algunos atributos comunes de la clase Meta. 79 | 80 | model = Account 81 | 82 | Ya que el serializador hereda de `serilizers.ModelSerilaizer`, debería tiener sentido que le digamos al serializador que modelo utilizar. Al especificar el modelo garantizamos que solo los atributos de ese modelo o los campos creados explícitamente pueden serializarse. Cubriremos la serializacion de atributos de un modelo ahora y la de campos creados explícitamente en breve. 83 | 84 | fields = ('id', 'email', 'username', 'created_at', 'updated_at', 85 | 'first_name', 'last_name', 'tagline', 'password', 86 | 'confirm_password',) 87 | 88 | El atributo `fields` de la clase `Meta` es donde especificaremos que atributos del modelo `Account` deben serializarse. Debemos tener cuidado al especificar los campos a serializar, ya que algunos campos, como `is_superuser`, no debería estar disponible para el cliente por razones de seguridad. 89 | 90 | read_only_fields = ('created_at', 'updated_at',) 91 | 92 | Si lo recuerdas, cuando creamos el modelo `Account`, hicimos que los campos `created_at` y `updated_at` se actualizaran de forma automática. Debido a esta propiedad, cuando los añadimos a la lista deben ser campos de solo lectura. 93 | 94 | def create(self, validated_data): 95 | # ... 96 | 97 | def update(self, instance, validated_data): 98 | # ... 99 | 100 | Anteriormente habíamos mencionado que a veces querríamos transformar objetos JSON en objetos Python. Esto se llama deserialización y lo gestionan los métodos `.create()` y `.update()`. Cuando creemos un objeto nuevo, como lo es el objeto `Account`, se utilizará `.create()`. Cuando mas tarde actualicemos ese objeto `Account`, se utilizara el método `.update()`. 101 | 102 | instance.username = validated_data.get('username', instance.username) 103 | instance.tagline = validated_data.get('tagline', instance.tagline) 104 | 105 | Dejaremos que el usuario pueda actualizar los atributos de nombre de usuario y su lema por el momento. Si estos elementos están presentes en el diccionario del array, utilizaremos el nuevo valor. Si no, se utilizara el valor actual del objeto `instance`. Aquí, `instance` es de tipo `Account`. 106 | 107 | password = validated_data.get('password', None) 108 | confirm_password = validated_data.get('confirm_password', None) 109 | 110 | if password and confirm_password and password == confirm_password: 111 | instance.set_password(password) 112 | instance.save() 113 | 114 | Antes de actualizar el password de usuario necesitamos confirmar que el usuario a proporcionado valores para ambos campos `password` y `password_confirmation`. Así que verificamos que el contenido de ambos campos idéntico. 115 | 116 | Después de haber verificado esto, debemos actualizar el password y utilizamos `Account.set_password()` para actualizar. `Account.ser_password()` se encarga de almacenar el password de forma segura. Es importante también saber que debemos guardar de forma explicita el modelo después de actualizar el password. 117 | 118 | {info} 119 | Esta es una impletencion simple de como validar un password. No te recomendamos utilizar este forma en un sistema de producción, pero para nuestro propósito esta bien. 120 | 121 | update_session_auth_hash(self.context.get('request'), instance) 122 | 123 | Cuando se actualiza el password de usuario, la autenticación de usuario debe estar enmascarada de forma explicita. Si no lo hacemos, el usuario no sera autentificado en su siguiente petición al servidor y tendrá que volver a conectarse otra vez. 124 | 125 | ## Punto de control 126 | Por el momento no deberíamos tener problemas al visualizar la serializacion en JSON de un objeto `Account`. Abre el terminal de Django ejecutando `pyhton manage.py shell` e intenta escribir los comandos siguientes: 127 | 128 | >>> from authentication.models import Account 129 | >>> from authentication.serializers import AccountSerializer 130 | >>> account = Account.objects.latest('created_at') 131 | >>> serialized_account = AccountSerializer(account) 132 | >>> serialized_account.data.get('email') 133 | >>> serialized_account.data.get('username') 134 | 135 | {x: checkpoint_auth_serializers} 136 | Asegurate que tu serializador AccountSerializer funciona -------------------------------------------------------------------------------- /capitulo03.md: -------------------------------------------------------------------------------- 1 | # Registrando nuevos usuarios 2 | 3 | {video: register-user-1} 4 | 5 | En este punto tenemos modelos y serializadores necesarios para representar usuarios. Ahora necesitamos construir un sistema de autentificacion. Esto implica la creación de varias vistas e interfaces para registrarse, conectarse y desconectarse. También tocaremos un servicio de AngularJS de `Authentication`y algunos controles. 6 | 7 | Como no podemos conectarnos con usuarios que no existen, tiene sentido empezar con el registro de usuarios. 8 | 9 | Para registrar a un usuario necesitamos un punto final de la API que creará un objeto `Account`, un servicio AngularJS para hacer peticiones AJAX a la API y un formulario de registro. Vamos a crear el punto final de la API primero. 10 | 11 | ## Construyendo el set de vistas de la API de account 12 | Abre `authentication/views.py` y reemplaza su contenido por el siguiente código: 13 | 14 | from rest_framework import permissions, viewsets 15 | 16 | from authentication.models import Account 17 | from authentication.permissions import IsAccountOwner 18 | from authentication.serializers import AccountSerializer 19 | 20 | 21 | class AccountViewSet(viewsets.ModelViewSet): 22 | lookup_field = 'username' 23 | queryset = Account.objects.all() 24 | serializer_class = AccountSerializer 25 | 26 | def get_permissions(self): 27 | if self.request.method in permissions.SAFE_METHODS: 28 | return (permissions.AllowAny(),) 29 | 30 | if self.request.method == 'POST': 31 | return (permissions.AllowAny(),) 32 | 33 | return (permissions.IsAuthenticated(), IsAccountOwner(),) 34 | 35 | def create(self, request): 36 | serializer = self.serializer_class(data=request.data) 37 | 38 | if serializer.is_valid(): 39 | Account.objects.create_user(**serializer.validated_data) 40 | 41 | return Response(serializer.validated_data, status=status.HTTP_201_CREATED) 42 | 43 | return Response({ 44 | 'status': 'Bad request', 45 | 'message': 'Account could not be created with received data.' 46 | }, status=status.HTTP_400_BAD_REQUEST) 47 | 48 | 49 | {x: create_account_viewset} 50 | Crea el set de vistas llamado `AccountViewSet` en `authentication/views.py` 51 | 52 | Veamos que es cada cosa linea por linea: 53 | 54 | class AccountViewSet(viewsets.ModelViewSet): 55 | 56 | El Framework REST de Django tienen una característica llamada viewsets. Un viewset, como el nombre en ingles implica, es un conjunto de vistas. Especificamente, `ModelViewSet` ofrece una interface para listar, crear, recuperar , actualizar y suprimir objetos de un modelo determinado. 57 | 58 | lookup_field = 'username' 59 | queryset = Account.objects.all() 60 | serializer_class = AccountSerializer 61 | 62 | Aqui hemos definido el queryset y el serializador con el que va a trabajar el viewset. Django REST Framework utiliza el queryset especificado y el serializador para realizar las acciones comentadas anteriormente. Observa que también especificamos un atributo `lookup_field`. Como hemos dicho antes, utilizaremos el atributo `username` del modelo `Account` para buscar en las cuentas en vez de el atributo `id`. Al sobreescribir el atributo `lookup_filed` estamos hacen esto. 63 | 64 | def get_permissions(self): 65 | if self.request.method in permissions.SAFE_METHODS: 66 | return (permissions.AllowAny(),) 67 | 68 | if self.request.method == 'POST': 69 | return (permissions.AllowAny(),) 70 | 71 | return (permissions.IsAuthenticated(), IsAccountOwner(),) 72 | 73 | El unico usuario que puede llamar a métodos "peligrosos" (como `update()` y `delete()`) es el propietario de la cuenta. Primero comprobamos si el usuario esta autentificado y entonces llamamos a un permiso personalizado que escribiremos en unos instantes. Este caso no se cumple cuando el método HTTP es `POST`. queremos que cualquier usuario pueda crear una cuenta. 74 | 75 | Si el metodo HTTP de la petición ('GET', 'POST', etc) es "seguro", entonces cualquiera puede utilizar el punto final de la API. 76 | 77 | def create(self, request): 78 | serializer = self.serializer_class(data=request.data) 79 | 80 | if serializer.is_valid(): 81 | Account.objects.create_user(**serializer.validated_data) 82 | 83 | return Response(serializer.validated_data, status=status.HTTP_201_CREATED) 84 | return Response({ 85 | 'status': 'Bad request', 86 | 'message': 'Account could not be created with received data.' 87 | }, status=status.HTTP_400_BAD_REQUEST) 88 | 89 | 90 | Cuando creamos un objeto utilizando el método del serializador `.save()`, los atributos del objeto se guardan de forma literal. Esto significa que que un usuario registrándose con la contraseña `'password'` tendrá su contraseña guardada como `'password'`. Esto no es buena por idea por dos razones: 1) Almacenar contraseñas en texto plano es un problema de seguridad masivo. 2) Django enmascara las contraseñas antes de compararlas, así que el usuario no sera capaz de conectarse a su cuenta utilizando la contraseña `'password'`. 91 | 92 | Resolvemos este problema sobreescribiendo el metodo .create() de este conjunto de vistas utilizando `Account.objects.create_user()` para crear un objeto `Account`. 93 | 94 | ##Construyendo los permisos de IsAccountOwner. 95 | Vamos a crear los permisos de `IsAccountOwner()` para la vista que acabamos de crear. 96 | 97 | Crea un fichero llamado `authentication/permissions.py` con el siguiente contenido: 98 | 99 | from rest_framework import permissions 100 | 101 | 102 | class IsAccountOwner(permissions.BasePermission): 103 | def has_object_permission(self, request, view, account): 104 | if request.user: 105 | return account == request.user 106 | return False 107 | 108 | {x: is_account_owner_permission} 109 | Crea los permisos llamados `IsAccountOwner` en `authentication/permissions.py` 110 | 111 | Estos permisos son muy básicos. Si existe un usuario asociado con la petición actual, comprobamos que ese usuario es el mismo que el del objeto `account`. Si no hay un usuario asociado con esta petición, simplemente devolvemos `False`. 112 | 113 | ## Añadiendo un punto final a la API 114 | Ahora que hemos creado la vista, necesitamos añadirla al fichero de URLs. Abre `thinkster_django_angular_boilerplate/urls.py` y actualizalo para que parezca: 115 | 116 | # .. Imports 117 | from rest_framework_nested import routers 118 | 119 | from authentication.views import AccountViewSet 120 | 121 | router = routers.SimpleRouter() 122 | router.register(r'accounts', AccountViewSet) 123 | 124 | urlpatterns = patterns( 125 | '', 126 | # ... URLs 127 | url(r'^api/v1/', include(router.urls)), 128 | 129 | url('^.*$', IndexView.as_view(), name='index'), 130 | ) 131 | 132 | {x: url_account_view_set} 133 | Añade el punto final de la API para `AccountViewSet` 134 | 135 | {info} 136 | Es importante que la ultima URL de la lista de URLs anterior sea siempre la ultima URL. Esto se conoce como passthrough or catch-all route. Acepta todas las peticiones que no corresponden con ninguna de las reglas anteriores y pasa la petición al router de AngularJS para procesarla. El orden de las URLs normalmente no tiene importancia. 137 | 138 | ## Un servicio Angular para registrar nuevos usuarios 139 | Con el punto final de la API configurado, podemos crear el servicio AngulareJS que gestionará la comunicación entre el cliente y el servidor. 140 | 141 | Crea un fichero en `static/javascript/authentication/services` llamado `authentication.service.js` y añade el siguiente código: 142 | 143 | {info} 144 | Siente libre de quitar los comentarios de tu código. Toma mucho tiempo escribirlos todos! pero recomiendo comentar el código! 145 | 146 | /** 147 | * Authentication 148 | * @namespace thinkster.authentication.services 149 | */ 150 | (function () { 151 | 'use strict'; 152 | 153 | angular 154 | .module('thinkster.authentication.services') 155 | .factory('Authentication', Authentication); 156 | 157 | Authentication.$inject = ['$cookies', '$http']; 158 | 159 | /** 160 | * @namespace Authentication 161 | * @returns {Factory} 162 | */ 163 | function Authentication($cookies, $http) { 164 | /** 165 | * @name Authentication 166 | * @desc The Factory to be returned 167 | */ 168 | var Authentication = { 169 | register: register 170 | }; 171 | 172 | return Authentication; 173 | 174 | //////////////////// 175 | 176 | /** 177 | * @name register 178 | * @desc Try to register a new user 179 | * @param {string} username The username entered by the user 180 | * @param {string} password The password entered by the user 181 | * @param {string} email The email entered by the user 182 | * @returns {Promise} 183 | * @memberOf thinkster.authentication.services.Authentication 184 | */ 185 | function register(email, password, username) { 186 | return $http.post('/api/v1/accounts/', { 187 | username: username, 188 | password: password, 189 | email: email 190 | }); 191 | } 192 | } 193 | })(); 194 | 195 | {x: angularjs_authentication_service} 196 | Construye una factoría llamada Authentication en `static/javascript/authentication/services/authentivation.service.js` 197 | 198 | Vamos a ver este código paso por paso: 199 | 200 | angular 201 | .module('thinkster.authentication.services') 202 | 203 | AngularJS soporta el uso de módulos. Modularidad es una gran característica ya que promueve la encapsulación y desata el enganche. Haremos un uso exhaustivo del sistema de modelos de AngularJS en el tutorial. Por el momento, todo lo que debes saber es que este servicio esta en el modulo `thinkster.authentication.services`. 204 | 205 | .factory('Authentication', Authentication); 206 | 207 | Esta linea registra una factoría llamada `Authentication` en el modulo de la linea anterior. 208 | 209 | function Authentication($cookies, $http) { 210 | 211 | Aqui definimos la factoría que acabamos de registrar. Inyectamos los servicios `$cookies` y `$http` como dependencias. Utilizaremos `$cookies` después. 212 | 213 | var Authentication = { 214 | register: register 215 | }; 216 | 217 | Esto es una preferencia personal, pero encuentro que es mas legible definir tu servicio como el objeto definido anteriormente y devolverlo, dejando los detalles mas abajo en el fichero. 218 | 219 | function register(email, password, username) { 220 | 221 | En este punto, el servicio Authentication solo tiene un método: `register`, que toma como parámetros un `username`, `password` y un `email`. Añadiremos mas métodos al servicio mas adelante. 222 | 223 | return $http.post('/api/v1/accounts/', { 224 | username: username, 225 | password: password, 226 | email: email 227 | }); 228 | 229 | Como hemos mencionado antes, necesitamos hacer una petición AJAX al punto final de la API que hemos hecho antes. Como datos, incluiremos `username`, `password` y `email` que son los parámetros que recibe este método. No tenemos ninguna razón de hacer nada particular con la respuesta, así que que dejaremos la llamada de `Authentication.register` devolver la respuesta. 230 | 231 | ## Creando una interfaz para registrar nuevos usuarios 232 | 233 | {video: register-user-2} 234 | 235 | Empecemos creando la interfaz que los usuarios utilizaran para registrarse. Crearemos un fichero en `static/templates/authnetication` llamado `register.html` con el siguiente contenido: 236 | 237 |
238 |
239 |

Register

240 | 241 |
242 |
243 |
244 | 245 | 246 |
247 | 248 |
249 | 250 | 251 |
252 | 253 |
254 | 255 | 256 |
257 | 258 |
259 | 260 |
261 |
262 |
263 |
264 |
265 | 266 | {x: angularjs_register_template} 267 | Crea la plantilla register.html 268 | 269 | No vamos a dar demasiados detalles esta vez por que es HTML básico. muchas de las clases tienen de Bootstrap, que esta incluido en el proyecto de muestra. Solo hay dos lineas en las que vamos a fijarnos: 270 | 271 |
272 | 273 | Esta es la linea responsable de llamar a `$scope.register`, que hemos configurado en nuestro controlador. `ng-submit` llamará a `vm.register` cuando el formulario se envíe. Si has utilizado Angular con anterioridad, probablemente hayas utilizado `$scope`. En este tutoría, optamos por evitar el uso de `$scope` cuando sea posible en favor de `vm` de ViewModel. Observa en la sección [Controllers](https://github.com/johnpapa/angularjs-styleguide#controllers) de la guía de estilo de AngularJS de John Papa’s para mas información sobre esto. 274 | 275 | 276 | 277 | En cada ``, veras otra directiva, `ng-model`. `ng-model` es responsable para almacenar el valor de la entrada en ViewModel. Esto es como obtenemos username, password y email cuando se llama a `vm.register`. 278 | 279 | ## Controlando la interfaz con RegisterController 280 | Con el servicio y la interfaz preparados, necesitaremos enlazar ambos. El controlador que vamos a crear, `RegisterController` nos permitirá llamar al método register del servicio `Authentication` cuando un usuario envía el formulario que acabamos de construir. 281 | 282 | Crea un fichero en `static/javascripts/authentication/controllers/` que se llame `register.controller.js` y añade lo siguiente: 283 | 284 | /** 285 | * Register controller 286 | * @namespace thinkster.authentication.controllers 287 | */ 288 | (function () { 289 | 'use strict'; 290 | 291 | angular 292 | .module('thinkster.authentication.controllers') 293 | .controller('RegisterController', RegisterController); 294 | 295 | RegisterController.$inject = ['$location', '$scope', 'Authentication']; 296 | 297 | /** 298 | * @namespace RegisterController 299 | */ 300 | function RegisterController($location, $scope, Authentication) { 301 | var vm = this; 302 | 303 | vm.register = register; 304 | 305 | /** 306 | * @name register 307 | * @desc Register a new user 308 | * @memberOf thinkster.authentication.controllers.RegisterController 309 | */ 310 | function register() { 311 | Authentication.register(vm.email, vm.password, vm.username); 312 | } 313 | } 314 | })(); 315 | 316 | 317 | {x: angularjs_register_controller} 318 | Crea un controlador llamado `RegisterController` en `static/javascripts/authentication/controllers/register.controller.js` 319 | 320 | Como siempre, vamos a saltar las partes que ya conocemos y hablar de los nuevos conceptos 321 | 322 | .controller('RegisterController', RegisterController); 323 | 324 | Esto es similar a la forma en la que hemos registrado nuestro servicio. La diferencia es que, esta vez, estamos registrando un controlador. 325 | 326 | vm.register = register; 327 | 328 | `vm` permite al formulario que hemos creado, acceder al método `register` que hemos definido después en el controlador. 329 | 330 | Authentication.register(vm.email, vm.password, vm.username); 331 | 332 | Aqui llamamos al servicio que hemos creado antes. Pasamos los valores de username, password y email desde `vm`. 333 | 334 | ## Rutas y Modulos para registrarse 335 | Vamos a configurar algunas rutas del lado del cliente, así, los usuarios de la aplicación podran navegar hasta el formulario de registro. 336 | 337 | Crea un fichero en `static/javascripts` llamado `thinkster.routes.js` y añade lo siguiente: 338 | 339 | (function () { 340 | 'use strict'; 341 | 342 | angular 343 | .module('thinkster.routes') 344 | .config(config); 345 | 346 | config.$inject = ['$routeProvider']; 347 | 348 | /** 349 | * @name config 350 | * @desc Define valid application routes 351 | */ 352 | function config($routeProvider) { 353 | $routeProvider.when('/register', { 354 | controller: 'RegisterController', 355 | controllerAs: 'vm', 356 | templateUrl: '/static/templates/authentication/register.html' 357 | }).otherwise('/'); 358 | } 359 | })(); 360 | 361 | 362 | {x: angularjs_register_route} 363 | Define una ruta para el formulario de registro 364 | 365 | Hay algunos puntos que vamos a tratar. 366 | 367 | .config(config); 368 | 369 | Angular, como cualquier framework que puedas imaginar, te permite editar su configuración. Esto se hace con un bloque `.config`. 370 | 371 | function config($routeProvider) { 372 | 373 | Aquí estamos injectando `$routerProvider` como dependencia, la cual nos permitirá añadir la capacidad de erutar en el cliente. 374 | 375 | $routeProvider.when('/register', { 376 | 377 | `$routerProvider.when` toma dos argumentos: un camino y un objeto options. Aquí utilizamos /register como el camino, ya que es aquí donde queremos que se muestre el formulario de registro. 378 | 379 | controller: 'RegisterController', 380 | controllerAs: 'vm', 381 | 382 | En el objeto options podemos incluir varias claves, entre ellas `controller`. Esta opción mapeará un control para esta ruta. Aquí utilizamos el control `RegisterController` que hicimos antes. `controllerAs` es otra opción. Esta es necesaria para utilizar la variable `vm`. En resumen, estamos diciendo que nos queremos referir al controlador como `vm` en el template. 383 | 384 | templateUrl: '/static/templates/authentication/register.html' 385 | 386 | La otra clave que utilizamos es `templateUrl`. Esta opción toma una cadena de la URL donde esta el template que queremos utilizar para esta ruta. 387 | 388 | }).otherwise('/'); 389 | 390 | Vamos a añadir mas rutas según vayamos avanzando, pero es posible que un usuario entre una URL que no soportamos. Cuando esto pase, `$routerProvider.otherwise` redirigirá al usuario al camino especificado. En este caso '/'. 391 | 392 | ## Configurando los modulos de AngularJS 393 | Vamos a hablar un poco de los modelos de AngularJS. 394 | 395 | En Angular, debes definir los modulos anetes de utilizarlos. Por el momento tendremos que definir `thinkster.authentication.services`, `thinkster.authentication.controllers` y `thinkster.routes`. Como `thinkster.authentication.services` y `thinkster.authentication.controllers` son submódulos de `thinkster.authentication`, necesitaremos crear el modulo `thinkster.authentication` tambien. 396 | 397 | Crea un fichero en `static/javascripts/authentication` llamado `authentication.module.js` y añade lo siguiente: 398 | 399 | (function () { 400 | 'use strict'; 401 | 402 | angular 403 | .module('thinkster.authentication', [ 404 | 'thinkster.authentication.controllers', 405 | 'thinkster.authentication.services' 406 | ]); 407 | 408 | angular 409 | .module('thinkster.authentication.controllers', []); 410 | 411 | angular 412 | .module('thinkster.authentication.services', ['ngCookies']); 413 | })(); 414 | 415 | {x: angularjs_authentication_module} 416 | Define el modulo `thinkster.authentication` y sus dependencias 417 | 418 | Hay algunas cosas interesantes que comentar aquí. 419 | 420 | angula 421 | .module('thinkster.authentication', [ 422 | 'thinkster.authentication.controllers', 423 | 'thinkster,authentication.services' 424 | ]); 425 | 426 | Esta sintaxis define el modulo `thinkster.authentication` con `thinkster.authentication.controllers` y `thinkster.authentication.services` como dependencias. 427 | 428 | angular 429 | .module('thinkster.authentication.controllers', []); 430 | 431 | Esta sintaxis define el modulo `thinkster.authentication.controllers` sin dependencias. 432 | 433 | Ahora necesitaremos definir `thinkster.authentication` y `thinkster.routes` como dependencias de `thinkster`. 434 | 435 | Abre el fichero `static/javascript/thinkster.js`, define los módulos requeridos e incluyelos como dependencias en el modulo `thinkster`. Observa que `thinkster.routes` enlaza con `ngRoute`, que esta incluido en el proyecto de referencia. 436 | 437 | (function () { 438 | 'use strict'; 439 | 440 | angular 441 | .module('thinkster', [ 442 | 'thinkster.routes', 443 | 'thinkster.authentication' 444 | ]); 445 | 446 | angular 447 | .module('thinkster.routes', ['ngRoute']); 448 | })(); 449 | 450 | {x: angularjs_thinkster_module} 451 | Actualiza el modulo thinkster para incluir las nuevas dependencias 452 | 453 | ## Enrutamiento Hash 454 | Por defecto, Angular utiliza una característica llamada enrutamiento hash. Si alguna vez has visto alguna URL como esta `www.google.com/#/search` entonces sabes de lo que estamos hablando. Una vez mas, es una opinión personal, pero creo que esto es bastante feo. Para evitar este tipo de enrutamiento podemos activar `$locationProvider.html5Mode`. En navegadores que no soportan enrutamiento HTML5, Angular enrutará de forma inteligente con enrutamiento hash. 455 | 456 | Crea un fichero en `static/javascripts/` llamado `thinkster.config.js` y añade el siguiente contenido: 457 | 458 | (function () { 459 | 'use strict'; 460 | 461 | angular 462 | .module('thinkster.config') 463 | .config(config); 464 | 465 | config.$inject = ['$locationProvider']; 466 | 467 | /** 468 | * @name config 469 | * @desc Enable HTML5 routing 470 | */ 471 | function config($locationProvider) { 472 | $locationProvider.html5Mode(true); 473 | $locationProvider.hashPrefix('!'); 474 | } 475 | })(); 476 | 477 | 478 | {x: angularjs_html5mode_config} 479 | Activa el enrutamiento HTML para AngularJS. 480 | 481 | Como hemos mencionado, activando `$locationProvider.html5Mode` eliminamos el enrutamiento hash de URLs. La otra configuración que vemos aquí, `$locationProvider.hashPrefix`, convierte `#` en `#!`. Esto es para beneficiar los motores de búsqueda principalmente. 482 | 483 | Como estamos utilizando un nuevo modulo, necesitamos abrir `static/javascripts/thinkster.js`, define el modulo e incluye como dependencia en el modulo `thinkster`. 484 | 485 | angular 486 | .module(’thinkster', [ 487 | ’thinkster.config', 488 | // ... 489 | ]); 490 | 491 | angular 492 | .module('thinkster.config', []); 493 | 494 | {x: angularjs_config_module} 495 | Define el modulo `thinkster.config` 496 | 497 | ## Incluye los nuevos ficheros .js 498 | En este capitulo, hemos creado varios ficheros JavaScript. Necesitamos incluirlos en el cliente añadiéndolos en `template/javascripts.html` dentro del bloque `{% compress js %}`. 499 | 500 | Abre el fichero `templates/javascripts.html` y añade las siguientes lineas antes de la sentencia '{% endcompress %}` 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | {x: django_javascripts} 509 | Añade los nuevos ficheros javascript en `template/javascripts.html`. 510 | 511 | ## Manipulando la protección CSRF 512 | Como estamos utilizando autentificacion basada en sesiones, necesitamos ocuparnos de la protección CSRF. No entraremos en detalles en CSRF por que esta fuera del objetivo de este tutoría, solo basta con saber que CSRF es bastante malo. 513 | 514 | Django, por defecto, almacena un token CSRF en una cookie llamado `csrftoken` y espera una cabecera con el nombre `X-CSRFToken` para cualquier petición HTTP peligrosa (`POST`, `PUT`, `PATCH`, `DELETE`). Podemos configurar fácilmente Angular para gestionar esto. 515 | 516 | Abre `static/javascripts/thinkster.js` y añade lo siguiente, después de la definición de los módulos: 517 | 518 | angular 519 | .module('thinkster') 520 | .run(run) 521 | 522 | run.$inject = ['$http']; 523 | 524 | /** 525 | * @name run 526 | * @desc Update xsrf $http headers to align with Django's defaults 527 | */ 528 | function run($http) { 529 | $http.defaults.xsrfHeaderName = 'X-CSRFToken'; 530 | $http.defaults.xsrfCookieName = 'csrftoken'; 531 | } 532 | 533 | {x: angularjs_run_csrf} 534 | Configura CSRF en Angular 535 | 536 | ## Punto de control 537 | Intenta registrar un nuevo usuario ejecutando el servidor (`python manage.py runserver`), visita la url `http://localhost:8000/register` en tu navegador y rellena y envía el formulario. 538 | 539 | Si el proceso de registro funciona, podrás ver el nuevo objeto `Account` creado abriendo el terminal (`python manage.py shell`) y ejecutando los comando siguientes: 540 | 541 | >>> from authentication.models import Account 542 | >>> Account.objects.latest('created_at') 543 | 544 | El objeto `Account` devuelto debería ser el que acabas de crear. 545 | 546 | {x: checkpoint_register_account} 547 | Registra un nuevo usuario en `http://localhost:8000/register` y confirma que el objeto `Account` se ha creado. 548 | -------------------------------------------------------------------------------- /capitulo04.md: -------------------------------------------------------------------------------- 1 | # Conectando a los usuarios 2 | Ahora que los usuarios pueden registrarse, necesitan una forma de conectarse. Resulta que, esto es parte de lo que nos falta en nuestro sistema de registro. Una vez que un usuario se registra, debería estar conectado de forma automática. 3 | 4 | {video: log-user-in} 5 | 6 | Para empezar, crearemos las vistas para conectarse y desconectarse. Una vez hecho esto, progresaremos de la misma manera que para el sistema de registro: servicios, controladores, etc. 7 | 8 | ## Construyendo la vista login de la API 9 | Abre `authentication/views.py` y añade lo siguiente: 10 | 11 | import json 12 | 13 | from django.contrib.auth import authenticate, login 14 | 15 | from rest_framework improt status, views 16 | from rest_framework.response import Response 17 | 18 | class LoginView(views.APIView): 19 | def post(self, request, format=None): 20 | data = json.loads(request.body) 21 | 22 | email = data.get('email', None) 23 | password = data.get('password', None) 24 | 25 | account = authenticate(email=email, password=password) 26 | 27 | if account is not None: 28 | if account.is_active: 29 | login(request, account) 30 | 31 | serialized = AccountSerializer(account) 32 | 33 | return Response(serialized.data) 34 | else: 35 | return Response({ 36 | 'status': 'Unauthorized', 37 | 'message': 'This account has been disabled.' 38 | }, status=status.HTTP_401_UNAUTHORIZED) 39 | else: 40 | return Response({ 41 | 'status': 'Unauthorized', 42 | 'message': 'Username/password combination invalid.' 43 | }, status=status.HTTP_401_UNAUTHORIZED) 44 | 45 | {x: django_login_view} 46 | Crea una vista llamada `LoginView` en `authentication/views.py` 47 | 48 | Este es un trozo largo que hemos visto anteriormente, pero vamos a revisarlo de la misma manera: hablando de lo que es nuevo e ignorando lo que ya hemos visto. 49 | 50 | class LoginView(views.APIView): 51 | 52 | Notaras que no estamos utilizando vistas genéricas esta vez. Ya que esta vista no tiene una actividad perfecta genérica como crear o actualizar un objeto, debemos empezar con algo mas básico. Utilizaremos `views.APIView` de Django REST Framework. Mientras que `APIView` no hace todo por nosotros nos ofrece mas funcionalidades que un vista standard de Django. En particular, `views.APIView` esta hecha especialmente para gestionar peticiones AJAX. Esto nos va a permitir ahorrar tiempo. 53 | 54 | def post(self, request, format=None): 55 | 56 | Al contrario que las vistas genéricas, debemos gestionar cada verbo HTTP por nosotros mismos. Conectar debería ser una petición `POST` típica, así que sobreescribimos el metodo `self.post()`. 57 | 58 | account = authenticate(email=email, password=password) 59 | 60 | Django nos proporciona un buen conjunto de utilidades para autentificar usuarios. El método `authenticate()` es la primera herramienta que vamos a cubrir. `autenticase()` toma un email y una contraseña. Django comprueba en la base de datos que existe una cuenta `Account` con el email `email`. Si se encuentra uno, Django intentará verificar la contraseña dada. Si el nombre de usuario y la contraseña son correctos, se devuelve la cuenta `Account` encontrada por `authenticate()`. Si alguno de estos pasos falla, `authenticate()` devuelve `None`. 61 | 62 | if account is not None: 63 | # ... 64 | else: 65 | return Response({ 66 | 'status': 'Unauthorized', 67 | 'message': 'This account has been disabled.' 68 | }, status=status.HTTP_401_UNAUTHORIZED) 69 | 70 | En el caso en que `authenticate()` devuelva `None`, responderemos con un código de estado `401` y le diremos al usuario que la combinación email/password que ha proporcionado no es valida. 71 | 72 | if account.is_active: 73 | # ... 74 | else: 75 | return Response({ 76 | 'status': 'Unauthorized', 77 | 'message': 'This account has been disabled.' 78 | }, status=status.HTTP_401_UNAUTHORIZED) 79 | 80 | Si por cualquier razón, la cuenta del usuario esta inactiva, responderemos con un código de estado `401`. Simplemente diremos que la cuenta esta desactivada. 81 | 82 | login(request, account) 83 | 84 | Si `authenticate()` tiene éxito y el usuario esta activo, entonces utilizamos la herramienta `login()` de Django para crear una nueva sesión para este usuario. 85 | 86 | serialized = AccountSerializer(account) 87 | 88 | return Response(serialized.data) 89 | 90 | Queremos almacenar alguna informacion de este usuario en el navegador si la petición de conexión tiene éxito, asi que serializamos el objeto `Account` encontrado por `authenticate()` y devolvemos como respuesta el JSON resultante. 91 | 92 | ## Añadiendo un punto final a la API de conexion 93 | Como ya hicimos para `AccountViewSet`, necesitamos añadir una ruta para `LoginView`. 94 | 95 | Abre `thinkster_django_angular_boilerplate/urls.py` y añade la siguente URL entre `^/api/v1/` y `^`: 96 | 97 | from authentication.views import LoginView 98 | 99 | urlpatterns = patterns( 100 | # ... 101 | url(r'^api/v1/auth/login/$', LoginView.as_view(), name='login'), 102 | # ... 103 | ) 104 | 105 | {x: url_login} 106 | Añade el punto final para `LoginView` 107 | 108 | ## Servicio de autentificación 109 | Vamos a añadir algunos métodos a nuestro servicio `Authentication`. Lo vamos a hacer en dos pasos. Primero añadiremos el método `login()` y después añadiremos otros métodos útiles para almacenar datos de la sesión en el navegador. 110 | 111 | Abre el fichero `static/javascript/authentication/services/authentication.service.js` y añade el siguiente método al objeto `Authentication` que hemos creado antes: 112 | 113 | /** 114 | * @name login 115 | * @desc Try to log in with email `email` and password `password` 116 | * @param {string} email The email entered by the user 117 | * @param {string} password The password entered by the user 118 | * @ returns {Promise} 119 | * @memberOf thinkster.authentication.services.Authentication 120 | */ 121 | function login(email, password) { 122 | return $http.post('/api/v1/auth/login/', { 123 | email: email, password: password 124 | }); 125 | } 126 | 127 | Asegúrate de de exponerlo como parte del servicio. 128 | 129 | var Authentication = { 130 | login:login, 131 | register: register, 132 | }; 133 | 134 | {x: angularjs_authentication_service_login} 135 | Añade el metodo `login` al sevicio `Authentication` 136 | 137 | Así como el anterior método `register()`, la respuesta del método `login()` es una petición AJAX a nuestra API y devuelve una promesa(?). 138 | 139 | Ahora vamos a hablar sobre algunos métodos que necesitaremos para gestionar la información sobre la sesión en el cliente. 140 | 141 | Queremos mostrar información sobre el usuario que esta conectado actualmente en la barra de navegación y en la cabecera de la pagina. Esto significa que necesitaremos una manera de almacenar la respuesta devuelta por `login()`. También necesitaremos una forma de recuperar el usuario conectado. También necesitaremos una forma de desconectar al usuario en el navegador. Y por ultimo, estaría bien tener una forma sencilla de comprobar si el usuario actual esta autentificado. 142 | 143 | *NOTA: desautentificar es diferente a desconectar. cuando un usuario se desconecta, necesitamos una forma de suprimir todos los datos de la sesión que quedan en el cliente. 144 | 145 | Con esto requerimientos, propongo 4 métodos: `getAuthenticatedAccount`, `isAuthenticated`, `setAuthenticatedAccount`, y `unauthenticate`. 146 | 147 | Vamos a implementarlos ahora. Añade cada una de las funciones al servicio `Authenticate`: 148 | 149 | /** 150 | * @name getAuthenticatedAccount 151 | * @desc Return the currently authenticated account 152 | * @returns {object|undefined} Account if authenticated, else `undefined` 153 | * @memberOf thinkster.authentication.services.Authentication 154 | */ 155 | function getAuthenticatedAccount() { 156 | if (!$cookies.authenticatedAccount) { 157 | return; 158 | } 159 | 160 | return JSON.parse($cookies.authenticatedAccount); 161 | } 162 | 163 | Si no hay una cookie `authenticatedAccount` (creada en `setAuthenticatedAccount()`), entonces sale; si no devuelve el objeto parseado `usuario de la cookie. 164 | 165 | /** 166 | * @name isAuthenticated 167 | * @desc Check if the current user is authenticated 168 | * @returns {boolean} True is user is authenticated, else false. 169 | * @memberOf thinkster.authentication.services.Authentication 170 | */ 171 | function isAuthenticated() { 172 | return !!$cookies.authenticatedAccount; 173 | } 174 | 175 | Devuelve el valor de la cookie `authenticatedAccount`. 176 | 177 | /** 178 | * @name setAuthenticatedUser 179 | * @desc Stringify the account object and store it in a cookie 180 | * @param {Object} account The acount object to be stored 181 | * @returns {undefined} 182 | * @memberOf mapcake.authentication.services.Authentication 183 | */ 184 | function setAuthenticatedAccount(account) { 185 | $cookies.authenticatedAccount = JSON.stringify(account); 186 | } 187 | 188 | Selecciona la cookie `authenticatedAccount` en forma de cadena del objeto `account. 189 | 190 | /** 191 | * @name unauthenticate 192 | * @desc Delete the cookie where the account object is stored 193 | * @returns {undefined} 194 | * @memberOf mapcake.authentication.services.Authentication 195 | */ 196 | function unauthenticate() { 197 | delete $cookies.authenticatedAccount; 198 | } 199 | 200 | Suprime la cookie `authenticatedAccount. 201 | 202 | Una vez mas, no olvides exponer los métodos como parte del servicio: 203 | 204 | var Authentication = { 205 | getAuthenticatedAccount: getAuthenticatedAccount, 206 | isAuthenticated: isAuthenticated, 207 | login: login, 208 | register: register, 209 | setAuthenticatedAccount: setAuthenticatedAccount, 210 | unauthenticate: unauthenticate 211 | }; 212 | 213 | 214 | {x: angularjs_authentication_service_utilities} 215 | Añade los metodos `getAuthenticatedAccount`, `isAuthenticated`, `setAuthenticatedAccount` y `unauthenticate` al servicio `Authentication`. 216 | 217 | Antes de continuar con la interfaz, vamos a actualizar rapidamente el metodo `login` del servicio `Authentication` para que utilice uno de estos nuevos métodos. Reemplaza `Authentication.login` por lo siguiente: 218 | 219 | /** 220 | * @name login 221 | * @desc Try to log in with email `email` and password `password` 222 | * @param {string} email The email entered by the user 223 | * @param {string} password The password entered by the user 224 | * @returns {Promise} 225 | * @memberOf thinkster.authentication.services.Authentication 226 | */ 227 | function login(email, password) { 228 | return $http.post('/api/v1/auth/login/', { 229 | email: email, password: password 230 | }).then(loginSuccessFn, loginErrorFn); 231 | 232 | /** 233 | * @name loginSuccessFn 234 | * @desc Set the authenticated account and redirect to index 235 | */ 236 | function loginSuccessFn(data, status, headers, config) { 237 | Authentication.setAuthenticatedAccount(data.data); 238 | 239 | window.location = '/'; 240 | } 241 | 242 | /** 243 | * @name loginErrorFn 244 | * @desc Log "Epic failure!" to the console 245 | */ 246 | function loginErrorFn(data, status, headers, config) { 247 | console.error('Epic failure!'); 248 | } 249 | } 250 | 251 | 252 | {x: update_authentication_login} 253 | Actualiza el metodo `Authnetication.login` para utilizar los nuevos métodos. 254 | 255 | ## Creando una interfaz de conexión 256 | Ahora ya tenemos `Authentication.login()` para conectar a los usuarios, así que vamos a crear el formulario de conexión. Abre `static/templates/authentication/login.html` y añade el siguiente código HTML: 257 | 258 |
259 |
260 |

Login

261 | 262 |
263 | 264 |
265 | 266 |
267 | 268 | 269 |
270 | 271 |
272 | 273 | 274 |
275 | 276 |
277 | 278 |
279 | 280 |
281 |
282 |
283 | 284 | {x: angularjs_login_template} 285 | Crea la plantilla `login.html` 286 | 287 | ## Controlando la interfaz de conexión con LoginController 288 | Crea un nuevo fichero en `static/javascript/authentication/controllers/` llamado `login.controller.js` y añade el siguiente contenido: 289 | 290 | /** 291 | * LoginController 292 | * @namespace thinkster.authentication.controllers 293 | */ 294 | (function () { 295 | 'use strict'; 296 | 297 | angular 298 | .module('thinkster.authentication.controllers') 299 | .controller('LoginController', LoginController); 300 | 301 | LoginController.$inject = ['$location', '$scope', 'Authentication']; 302 | 303 | /** 304 | * @namespace LoginController 305 | */ 306 | function LoginController($location, $scope, Authentication) { 307 | var vm = this; 308 | 309 | vm.login = login; 310 | 311 | activate(); 312 | 313 | /** 314 | * @name activate 315 | * @desc Actions to be performed when this controller is instantiated 316 | * @memberOf thinkster.authentication.controllers.LoginController 317 | */ 318 | function activate() { 319 | // If the user is authenticated, they should not be here. 320 | if (Authentication.isAuthenticated()) { 321 | $location.url('/'); 322 | } 323 | } 324 | 325 | /** 326 | * @name login 327 | * @desc Log the user in 328 | * @memberOf thinkster.authentication.controllers.LoginController 329 | */ 330 | function login() { 331 | Authentication.login(vm.email, vm.password); 332 | } 333 | } 334 | })(); 335 | 336 | {x: angularjs_login_controller} 337 | Crea el controlador llamado `LoginController` en `static/javascripts/authentication/controllers/login.controller.js` 338 | 339 | Vamos a ver la función `activate`. 340 | 341 | function activate() { 342 | // If the user is authenticated, they should not be here. 343 | if (Authentication.isAuthenticated()) { 344 | $location.url('/'); 345 | } 346 | } 347 | 348 | Habras notado que utilizamos mucho una función llamada `activate` en este tutorial. No hay nada especialmente importante en este nombre; elegimos un nombre estándar para la función que se ejecutará cuando se instancia a un controlador en particular. 349 | 350 | Como sugiere el comentario, si un usuario esta ya autentificado, no tiene nada que hacer en la pagina login. Resolvemos esto, redireccionandolo a la pagina indice. 351 | 352 | Deberíamos hacer esto con la pagina de registro también. Cuando hemos escrito el controlador de registro, no teníamos `Authentication.isAuthenticated()`. Actualizaremos `RegisterController` en breve. 353 | 354 | ## De vuelta a RegisterController 355 | Volviendo un paso atras, vamos a añadir una prueba a `RegisterController` y redirigir al usuario si ya esta autentificado. 356 | 357 | Abre `static/javascripts/authentication/controllers/register.controller.js` y añade lo siguiente dentro de la definición del controlador: 358 | 359 | activate(); 360 | 361 | /** 362 | * @name activate 363 | * @desc Actions to be performed when this controller is instantiated 364 | * @memberOf thinkster.authentication.controllers.RegisterController 365 | */ 366 | function activate() { 367 | // If the user is authenticated, they should not be here. 368 | if (Authentication.isAuthenticated()) { 369 | $location.url('/'); 370 | } 371 | } 372 | 373 | {x: angularjs_register_controller_auth} 374 | Redirecciona a la pagina de indice a los usuarios que ya están autentificados en `RegisterController` 375 | 376 | Si recuerdas, también hemos hablado sobre conectar a un usuario de forma automática cuando se registra. Como ya hemos actualizado el contenido de registro, vamos a actualizar el método `register` del servicio `Authentication`. 377 | 378 | Reemplaza `Authentication.register` por lo siguiente: 379 | 380 | /** 381 | * @name register 382 | * @desc Try to register a new user 383 | * @param {string} email The email entered by the user 384 | * @param {string} password The password entered by the user 385 | * @param {string} username The username entered by the user 386 | * @returns {Promise} 387 | * @memberOf thinkster.authentication.services.Authentication 388 | */ 389 | function register(email, password, username) { 390 | return $http.post('/api/v1/accounts/', { 391 | username: username, 392 | password: password, 393 | email: email 394 | }).then(registerSuccessFn, registerErrorFn); 395 | 396 | /** 397 | * @name registerSuccessFn 398 | * @desc Log the new user in 399 | */ 400 | function registerSuccessFn(data, status, headers, config) { 401 | Authentication.login(email, password); 402 | } 403 | 404 | /** 405 | * @name registerErrorFn 406 | * @desc Log "Epic failure!" to the console 407 | */ 408 | function registerErrorFn(data, status, headers, config) { 409 | console.error('Epic failure!'); 410 | } 411 | } 412 | 413 | {x: angularjs_register_controller_login} 414 | Actualiza `Authentication.register` 415 | 416 | ## Creando una ruta para la interfaz de login 417 | El siguen paso es crear una ruta en el cliente para el formulario de conexión. 418 | 419 | Abre `static/javascripts/thinkster.routes.js` y añade la ruta para el formulario de conexión: 420 | 421 | $routeProvider.when('/register', { 422 | controller: 'RegisterController', 423 | controllerAs: 'vm', 424 | templateUrl: '/static/templates/authentication/register.html' 425 | }).when('/login', { 426 | controller: 'LoginController', 427 | controllerAs: 'vm', 428 | templateUrl: '/static/templates/authentication/login.html' 429 | }).otherwise('/'); 430 | 431 | {x: angularjs_login_route} 432 | Añade la ruta de `LoginContorller` 433 | 434 | {info} 435 | ¿Has visto como puedes encadenar peticiones a `$routeProvider.when()`? Mas adelante ignoraremos las rutas ya añadidas para abreviar. Solamente ten en mente que esta llamadas deben encadenarse y la primera ruta valida tomara el control. 436 | 437 | ## Incluye los nuevos ficheros .js 438 | Como podras imaginarte, solamente hemos creado un único fichero JavaScript desde la ultima vez: `login.controller.js`. Vamos a añadirlo a `javascritp.html` con los otros ficheros JavaScript: 439 | 440 | 441 | 442 | {x: angularjs_javascripts_login} 443 | Añade `login.controller.js` a `javascript.html` 444 | 445 | ## Punto de control 446 | Abre `http://localhost:8000/login` en el navegador y conéctate con el usuario que has creado anteriormente. Si esto funciona, la pagina debería redirigirte a `http://localhost:8000/` y la barra de navegación debería cambiar. 447 | 448 | {x: checkpoint_login} 449 | Conecta con uno de los usuarios creados anteriormente visitando `http://localhost:8000/login` -------------------------------------------------------------------------------- /capitulo05.md: -------------------------------------------------------------------------------- 1 | # Desconcetando a los usuarios 2 | Dado que los usuarios pueden registrarse y acceder, debemos imaginar que querran de alguna forma salir. La gente se vuelve loca si no puede desconectarse. 3 | 4 | {video: log-user-out} 5 | 6 | ## Creando una vista de desconexion en la API 7 | Vamos a implementar la ultima vista de la API relacionada con autenticación. 8 | 9 | Abre `authentication/views.py` y añade los siguientes imports y clases: 10 | 11 | from django.contrib.auth import logout 12 | 13 | from rest_framework import permissions 14 | 15 | class LogoutView(views.APIView): 16 | permissions_classes = (permissions.IsAuthenticated,) 17 | 18 | def post(self, request, format=None): 19 | logout(request) 20 | 21 | return Response({}, status=status.HTTP_204_NO_CONTENT) 22 | 23 | {x: django_logout_view} 24 | Haz una vista llamada `LogoutView` en `authentication/views.py` 25 | 26 | Solo hay unas pocas cosas nuevas de las que hablar esta vez. 27 | 28 | permissions_classes = (permissions.IsAuthenticated,) 29 | 30 | Solo usuarios autenticados son capaces de acceder a este punto final. `permissions.IsAuthenticated` de DjangoREST Framework gestiona esto por nosotros. Si el usuario no esta autenticado, tendrá un error `403`. 31 | 32 | logout(request) 33 | 34 | Si el usuario esta autenticado, lo único que debemos hacer es llamar al método `logout()` de Django. 35 | 36 | return Response({}, status=status.HTTP_204_NO_CONTENT) 37 | 38 | No hay nada razonable para devolver cuando nos desconectamos, así que devolvemos un respuesta vacía y código de estado `200`. 39 | 40 | Si vamos a las URL. 41 | 42 | Abre `thinkster_django_angular_boilerplate/urls.py` otra vez y añade el siguiente import y la siguiente URL: 43 | 44 | from authentication.views import LogoutView 45 | 46 | urlpatterns = patterns( 47 | # ... 48 | url(r'^api/v1/auth/logout/$', LogoutView.as_view(), name='logout'), 49 | #... 50 | ) 51 | 52 | {x: django_url_logout} 53 | Crea un punto final de la API para `LogoutView` 54 | 55 | ## Logout: Servicio AngularJS 56 | El último metodo que necesitaremos añadir a nuestro servicio `Authentication` es el método `logout()`. 57 | 58 | Añade el siguiente metodo al servicio `Authentication` en `authentication.service.js`: 59 | 60 | /** 61 | * @name logout 62 | * @desc Try to log the user out 63 | * @returns {Promise} 64 | * @memberOf thinkster.authentication.services.Authentication 65 | */ 66 | function logout() { 67 | return $http.post('/api/v1/auth/logout/') 68 | .then(logoutSuccessFn, logoutErrorFn); 69 | 70 | /** 71 | * @name logoutSuccessFn 72 | * @desc Unauthenticate and redirect to index with page reload 73 | */ 74 | function logoutSuccessFn(data, status, headers, config) { 75 | Authentication.unauthenticate(); 76 | 77 | window.location = '/'; 78 | } 79 | 80 | /** 81 | * @name logoutErrorFn 82 | * @desc Log "Epic failure!" to the console 83 | */ 84 | function logoutErrorFn(data, status, headers, config) { 85 | console.error('Epic failure!'); 86 | } 87 | } 88 | 89 | Y como siempre, acuérdate de exponer `logout` como parte del servicio `Authentication`: 90 | 91 | var Authentication = { 92 | getAuthenticatedUser: getAuthenticatedUser, 93 | isAuthenticated: isAuthenticated, 94 | login: login, 95 | logout: logout, 96 | register: register, 97 | setAuthenticatedUser: setAuthenticatedUser, 98 | unauthenticate: unauthenticate 99 | }; 100 | 101 | {x: angularjs_authentication_service_logout} 102 | Añade el metodo `logout()` a tu servicio `Authentication` 103 | 104 | ## Controlando la barra de navegación con NavbarController 105 | Por el moemnto no tenemos ni `LogoutController` ni `logout.html`. En lugar de esto, la barra de navegación ya contiene un enlace logout para usuarios registrados. Crearemos un `NavbarController` para gestionar la funcionalidad del botón logout cuando se selecciona y actualizaremos el enlace con un atributo `ng-click`. 106 | 107 | Crea un fichero en `static/javascripts/layout/controllers/` llamado `navbar.controller.js` y añade lo siguiente en el: 108 | 109 | /** 110 | * NavbarController 111 | * @namespace thinkster.layout.controllers 112 | */ 113 | (function () { 114 | 'use strict'; 115 | 116 | angular 117 | .module('thinkster.layout.controllers') 118 | .controller('NavbarController', NavbarController); 119 | 120 | NavbarController.$inject = ['$scope', 'Authentication']; 121 | 122 | /** 123 | * @namespace NavbarController 124 | */ 125 | function NavbarController($scope, Authentication) { 126 | var vm = this; 127 | 128 | vm.logout = logout; 129 | 130 | /** 131 | * @name logout 132 | * @desc Log the user out 133 | * @memberOf thinkster.layout.controllers.NavbarController 134 | */ 135 | function logout() { 136 | Authentication.logout(); 137 | } 138 | } 139 | })(); 140 | 141 | {x: angularjs_navbar_controller} 142 | Crea `NavbarController` en `static/javascripts/layout/controllers/navbar.controller.js` 143 | 144 | Abre `templates/navbar.html` y añade una directiva `ng-controller` con el valor` NavbarController as vm ' al tag '