Django VS Sysadmin

PyConES, Septiembre 2017, Cáceres

Pruébalo

https://github.com/klashxx/PyConES2017

  • Estructurada en ramas
  • Incremental
  • Git + Docker

Autobombo

  • Sysadmin since 2004
  • Python desde 2012
  • Primer programa Amstrad CPC6128

https://klashxx.github.io/about

Web Experience = 0

¿Por qué Django?

  • Python

  • Fiable

  • ORM

  • Extensible

  • DRF y otras hierbas

  • Admin Site

Win or Win

First Fail

CMS

Empezar por los basics

Desarrollo

  • virtualenv

  • Git + Docker

  • docker-compose

  • Kubernetes - Docker-Swarm

Docker es amor!

Databases

  • PostgreSQL
  • MySQL
  • SQLite
  • Oracle

Go for PostgreSQL !

settings.py & .env

  • Configuración

  • Ocultar los secretos

  • Variables globales: django.conf.settings

Decouple

.env
							
							SECRET_KEY=#^p^js0g91jhtarws6bp7s@r&988%%n_ok6#b)q=3s1v8-(si$
							
						
settings.py
							
							from decouple import config
							SECRET_KEY = config('SECRET_KEY')
							
						

DJ DATABASE URL

.env
							
								DB_URL=postgresql://postgres:postgres@postgres:5432/postgres
							
						
settings.py
							
								DATABASES = {
								'default': dj_database_url.config(default=config('DB_URL')),
								}
							
						

Second Fail

La estructura del proyecto

DOCS

							
							mysite/
							    manage.py
							    mysite/
							    	__init__.py
							    	settings.py
							    	urls.py
							    	wsgi.py
							    polls/
							    	__init__.py
							    	admin.py
							    	migrations/
							    		__init__.py
							    		0001_initial.py
							    	models.py
							    	static/
							    		polls/
							    			images/
							    				background.gif
							    			style.css
							    	templates/
							    		polls/
							    			detail.html
							    			index.html
							    			results.html
							    	tests.py
							    	urls.py
							    	views.py
							    templates/
							    	admin/
							    		base_site.html
							
						

First try

							
							sysgate/
							    manage.py
							    sysgate/
							    	__init__.py
							    	settings.py
							    	urls.py
							    	wsgi.py
							    account/
							    	__init__.py
							    	…
							    metrics/
							    	__init__.py
							    	…
							    templates/
							    	account/
							    		login.html
							    	metrics/
							    		home.html
							
						

F*CK YEAH

							
                            sysgate/
                                manage.py
                                sysgate/
                                    __init__.py
                                    settings.py
                                    urls.py
                                    wsgi.py
                                    routers.py
                                    account/
                                        __init__.py
                                        ...
                                    apps/
                                        __init__.py
                                        core/
                                            templatetags/
                                                __init__.py
                                                core_tags.py
                                            templates/
                                                core/
                                                    home.html
                                                    ...
                                            __init__.py
                                            ...
                                        metrics/
                                            templates/
                                                metrics/
                                                    home.html
                                                    ...
                                            __init__.py
                                            ...
                                        manager/
                                            templates/
                                                manager/
                                                    home.html
                                                    ...
                                            __init__.py
                                            ...
                                    static/
                                        ...
                                    fixtures/
                                        ...
                                    templates/
                                        account/
                                            login.html
                                            ...
                                        base.html
							
						

PRODUCTIVITY BOOST

urls.py

  • Regexp

  • Modular
root
							
							urlpatterns = [
								url(r'^', include('apps.core.urls', namespace='core')),
								url(r'^account/', include('account.urls'), name='Autenticación'),
								url(r'^metrics/', include('apps.metrics.urls'), name='Métricas'),
								url(r'^manager/', include('apps.manager.urls'), name='Manager'),
								url(r'^admin/', admin.site.urls),
								]
							
						
core
							
							urlpatterns = [
								url(r'^$', views.Home.as_view(), name='home'),
								]
							
						

BE MODULAR MY FRIEND

Modelos y vistas …¿Dónde está el controlador?

  • Framework «MTV». «Model», «Template» y «View»

models.py

  • Clase cuyos objetos instanciados están dotados de persistencia en BD

TIPS

  • managed = False

  • Primary Key única

views.py

  • Extraer datos de nuestros Modelos
  • NO necesitamos SQL

TIPS

  • Class Based Views

template.html

  • Representa los datos extraidos por la vista
						    
								

Metrics App

Bienvenid@ {{ request.user }}

Disponibles:

{% for metrica in metricas %} {% endfor %}
{{ metrica.nombre }} {{ metrica.get_tipo_display }}

Autenticación y registro

  • AbstractUser

  • Auth views

DRF

First try

							
							class Home(View):
								def get(self, request, *args, **kwargs):
								  metricas = Metrica.objects.all().order_by('tipo')
								  return render(
									  request,
									  'metrics/home.html',
									  context={'metricas': metricas})
							
						
                        	
							   {% for metrica in metricas %}
							   
							      {{ metrica.nombre }}
							      {{ metrica.get_tipo_display }}
							   
					    	   {% endfor %}
							
						

Serializando ...

							
							class SerializerMetricas(serializers.ModelSerializer):
							    class Meta:
							        model = Metrica
							        fields = '__all__'
							
						
							
							class MetricasViewSet(viewsets.ModelViewSet):
							  serializer_class = SerializerMetricas
							  http_method_names = ['get']
							  filter_backends = (OrderingFilter, SearchFilter,)
							  search_fields = ('tipo',)
							  def get_queryset(self):
							  	queryset = Metrica.objects.all().order_by('tipo')
							  	tipo = self.request.query_params.get('tipo', None)
							  	if tipo is not None:
							  		queryset = queryset.filter(tipo=tipo)
							  	return queryset
							
						

ENDPOINT

                            
                            router = routers.SimpleRouter()
                            router.register(r'metricas', views.MetricasViewSet, 'metricas')

                            app_name = 'metrics'

                            urlpatterns = [
                                url(r'^$', views.Home.as_view(), name='home'),
                                url(r'^api/v1/', include(router.urls)),
                            ]
                            
                        
                            
                                http://0.0.0.0/metrics/api/v1/metricas/
                            
                        
						
					

REST API: Resultado

							
							{
								"nombre": "USERS",
								"long_tipo": "Custom",
								"tipo": "c",
								"alta": "2017-08-13T10:21:19.015000Z",
								"descripcion": "Return users ..."
							},
							{
								"nombre": "READ",
								"long_tipo": "Disk",
								"tipo": "d",
								"alta": "2017-08-13T10:15:09.516000Z",
								"descripcion": "Number of reads"
							}
							
						

PRODUCTIVITY BOOST INCREASED!

Frontend (Templating)

							
							+-- account
							+-- apps
							¦   +-- core
							¦   ¦   +-- templates
							¦   ¦   ¦   +-- core
							¦   ¦   ¦       +-- home.html
							¦   ¦   +-- templatetags
							¦   ¦   ¦   +-- core_tags.py
							¦   +-- metrics
							¦       +-- templates
							¦       ¦   +-- metrics
							¦       ¦       +-- home.html
							+-- templates
							¦   +-- account
							¦   ¦   +-- login.html
							¦   +-- base.html
							
						

Conceptos

  • Herencia

  • Lenguaje

  • Custom Tags y Filtros
							
							from django import template
							register = template.Library()

							@register.filter('en_grupo')
							def en_grupo(user, group_name):
								groups = user.groups.all().values_list('name', flat=True)
								return True if group_name in groups else False
							
						
							
							{% extends 'base.html' %}
							{% load core_tags %}
							{% block content %}

							{% if request.user|en_grupo:"pycones" or user.is_superuser %}
								

Metrics

Acceder {% endif %} {% if not user.is_authenticated %} Longin {% endif %} {% endblock %}

CUTE!

Y esto ...

¿cómo se ve en el móvil?

¿OLA K ASE?

Mejorando el FRONTEND

  • Bootstrap

  • Javascript
							
							var v = new Vue({
							  delimiters: ['[[', ']]'],
							  el: '#container',
							  data: {
								metricas: []
							  },
							  mounted: function(){
								$.get('/metrics/api/v1/metricas/', function(data){
								  v.metricas = data;
								})}
							})
							
						
							
							
[[ metrica.nombre ]] [[ metrica.long_tipo ]]

Permisos

  • Templates

  • Views

  • Serializers

Vistas

							
							@method_decorator(login_required, name='dispatch')
							class Home(UserPassesTestMixin, View):
								def test_func(self):
									return super_o_esta_en_grupo(self.request.user,
																 grupo='pycones')
								def get(self, request, *args, **kwargs):
									return render(request, 'metrics/home.html')
							
						
							
                            def super_o_esta_en_grupo(user, grupo):
                                if user.is_superuser:
                                    return True

                                if user.groups.filter(name=grupo).exists():
                                    return True

                                return False
							
						

Rest

							
                            from rest_framework.permissions import IsAuthenticated

                            class MetricasViewSet(viewsets.ModelViewSet):
                                grupos_autorizados = settings.GR_AUTH_METRICS
                                permission_classes = (IsAuthenticated, EsSuperOTienePermisosPorGrupo,)
							
						
							
							class EsSuperOTienePermisosPorGrupo(BasePermission):
							    def has_permission(self, request, view):
							    	gr_auth_map = getattr(view, 'grupos_autorizados', {})
							    	gr_auth = gr_auth_map.get(request.method, [])
							    	return any([super_o_esta_en_grupo(request.user, grupo)
							    	            for grupo in gr_auth])
							
						

Deploy

  • Webserver: NGINX

  • WSGI: Gunicorn

  • Distribución: Docker

¡Muchas Gracias!