En produccion: nginx + uwsgi + django

Puesta en producción de django, utilizando nginx y uwsgi, que ambos me parecen muy eficientes, toda la vida utilizando apache y pasar a nginx fue una odicea total pero estoy muy a gusto con el rendimiento que ha mostrado nginx, en cuanto a uwsgi es el único wsgi(Web Server Gateway Interface) que e utilizado, incluyendo módulos para interprete de python o cualquier cosa con la que puede colocar en producción un proyecto de django.

Algunos conceptos básicos:

Nginx: Es un servidor proxy inverso de codigo abierto para HTTP, HTTPS, SMTP, POP3 e IMAP, asi como también un equilibrador de carga, cache HTTP y un servidor web. El proyecto ngnix comenzó con un fuerte enfoque en la alta concurrencia, alto rendimiento y bajo consumo de memoria.

WSGI: (Web Server Gateway Interface), es un interfaz simple y universal entre los servidor web y applicaciones web o marcos para el lenguaje de programación Python.

uWSGI: Es un software de código abierto BSD, para el modulo de integración de nginx, GPL para el modulo de integración de apache2. uWSGI Entrega una pila completa para aplicaciones WEB conectadas en red/cluster, implementacion mensajes/paso de objetos, cache, RPC y comunicacion entre procesos. Se puede ejecutar en los modos de preforking, threaded, asynchronous/eventos y soporta diversas formas de hils tales (uGreen, Greenlet, Fiber), Ofrece varios métodos de configuración: atraves de linea de comandos, variables de entorno, atra vez de XML, INI, archivos YAML, atravez de LDAP y muchos mas.

DJANGO: Es un framework web en python de alto nivel que fomenta el rápido desarrollo y el diseño limpio y pragmático.

La instalación y toda las configuración la voy a realizar con python 3, django 1.5, GNU/linux Debian 7, ngnix y virtualenv.

1. Instalamos todo lo necesario (Si el directorio www no existe, lo puedes crear o puede crear el entorno donde tu quieres)
# aptitude install nginx uwsgi uwsgi-plugin-python uwsgi-pluing-python3 python-virtualenv


2. Creamos un entorno nuevo de python para django.
# cd /var/www/

# virtualenv -p python3.2 pruebas_uwsgi


3. Ingresamos al entorno de pruebas_uwsgi e instalamos django
# source pruebas_uwsgi/bin/activate

# pip install django==1.5.5


4. Creamos un nuevo proyecto de django.
# cd pruebas_uwsgi 

# django-admin.py startproject pruebas


5. Configuramos uwsgi.
# vim /etc/uwsgi/apps-available/pruebas.ini

[uwsgi]

    plugins = python3

    virtualenv = /var/www/pruebas_uwsgi/

    chdir = /var/www/pruebas_uwsgi/pruebas/

    pythonpath = ..

    env = DJANGO_SETTINGS_MODULE=pruebas.settings

    module = django.core.handlers.wsgi:WSGIHandler()

    touch-reload = /var/www/pruebas_uwsgi/pruebas/pruebas/settings.py



    processes = 2

    threads = 1

    workers =1

    stats = 127.0.0.1:1717



# /etc/init.d/uwsgi restart


plugins = python3: Seleccionamos la version de python a ejecutar nuestro proyecto
virtualenv: Le decimos la dirección absoluta de nuestro entorno virual de python
chdir: Le decimos la dirección absoluta del proyecto.
env: ubicacion del archivo settings.py
module:
touch-reload: Archivos que si son tocados uwsgi se recargara, puedes tener varias lineas como desees.
processes: Cantidad de procesos. esta opción depende de la maquina en que se esta trabajando
threads: Cantidad de hilos se crearan con esta apps, esta opción depende de la maquina en que se este trabajando.
workers: Cantidad de trabajadores uwsgi
stats: Estadísticas de uwsgi

6. Luego creamos un enlace para uwsgi tenga presente que apps estan activas.
# cd /etc/uwsgi/apps-enabled/ 
 
# ln -s ../apps.available/pruebas.ini

# /etc/init.d/uwsgi restart


7. Configuramos nginx.
# vim /etc/nginx/sites-available/pruebas

server {

    listen 80;

    server_name 127.0.0.1



    access_log /var/log/nginx/local_access.log;

    error_log /var/log/nginx/local_error.log;



    location /static/ {

         gzip_static on;

         alias /var/www/pruebas_uwsgi/pruebas/static/;

     }



     location / {

          uwsgi_read_timeout 600;

          gzip_static on;

          include uwsgi_params;

          uwsgi_pass unix:///var/run/uwsgi/app/prueba/socket;

     }

} 
  
# cd /etc/nginx/sites-enabled/
# ln -s /etc/nginx/sites-available/pruebas
# /etc/init.d/nginx restart

8. Probamos en un navegador web preferido. en mi caso utilizo como principal iceweasel. 127.0.0.1, Debe aparecer el mensaje clásico "It worked!" y si ingresamos con 127.0.0.1:1717, podemos ver las estadísticas de funcionamiento de uwsgi.





nota: He utilizado un maquina virtual, KVM y manejador virt-manager. jejeje por eso se ve algo anti-estetico.

cualquier duda, comentario o corrección no duden en dejar el comentario, y alguna sugerencia sobre algún tema, sera bienvenida.

Convertir decimal a formato monetario python 3

Convertir un decimal a un formato monetario que se desee La siguiente función sacada de la documentación de python y utilizando python 3.3.
from decimal import *

def moneyfmt(value, places=2, curr='', sep=',', dp='.', pos='', neg='-', trailneg=''):

    q = Decimal(10) ** -places      # 2 places --> '0.01'
    sign, digits, exp = value.quantize(q).as_tuple()
    result = []
    digits = list(map(str, digits))
    build, next = result.append, digits.pop
    if sign:
        build(trailneg)
    for i in range(places):
        build(next() if digits else '0')
    if places:
        build(dp)
    if not digits:
        build('0')
    i = 0
    while digits:
        build(next())
        i += 1
        if i == 3 and digits:
            i = 0
            build(sep)
    build(curr)
    build(neg if sign else pos)
    return ''.join(reversed(result))
Con tan solo pasarle el decimal a converit, la fucnion devuelve un str, con el formato $1.000.00.
>>>moneyfmt(Decimal('1000000.00'))
'1,000,000.00'
Si le pasamos los datos como la cantidad de centavos por defecto es '2', el signo de pesos dolar o euro por defecto es '$', el tipo de separador para los ciento y miles '.'
>>> moneyfmt(Decimal('1000000.00'), 2, '$', ',')
'$1,000,000.00'
Otros parámetros como el separador de los centavos por defecto es ',' el carácter si el decimal es positivo por defecto '', el carácter si es negativo por defecto es '-' y el carácter final en tal caso que sea negativo.
>>> moneyfmt(Decimal('1000000.00'), 2, '$', ',', ',', '+', '(', ')' )
'+$1,000,000,00'
>>> moneyfmt(Decimal('-1000000.00'), 2, '$', ',', ',', '+', '(', ')' )
'($1,000,000,00)'
Fuente: http://docs.python.org/3.3/library/decimal.html#recipes

Primeros pasos con class-based view (vistas basas en clases) en django

Estoy muy acostumbrado a crear mis vistas con funciones eso fue lo que aprendes en cualquier tutorial que encuentras googleando (tambien utilizo mucho duckduckgo) y con funciones fue como escribí mi primer "Hola Mundo¡", pero leyendo las documentación de django 1.5, en esots dias encontré algo que me a gustado mucho y se ve muy practico que son las class-based views, y se ven muy interesante y practivo, además de darme otro punto de vista de como reutilizar código.

Ejemplo de function view, sacados de la pagina https://docs.djangoproject.com
# view.py
from django.http HttpResponse

def my_vista(request):
    if request.method == 'GET':
        return HttpResponse('result')

# urls.py
from django.conf.urls import patterns

urlpatterns = ('',
    url(r'^funcion_view/', 'miapp.views.mi_vista', name="vista_funcion"),
)
Ejemplo de Class-base view
#views.py
from django.http import HttpResponse
from django.views.generic.base import View

class MiVista(View):
    def get(self, request):
        return HttpResponse('result')
#urls.py
from django.conf.urls import patterns
from miapp.views import MiVista

urlpatterns = patterns('',
    url(r'^class_base_view/', MiVista.as_view(), name="vista_class"),
)
Tanto la función como la clase devuelven exactamente lo mismo. en la clase, también puede declarar otros metodos como post, dispatch, un ejemplo mas completo seria con un formulario
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.views.generic.base import View

from .forms import MyForm

class MiVistaFormulario(View):
    form_class = MiFormulario
    initial = {'llave': 'value'}
    template_name = 'formulario.html'

    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {'form': form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
            return HttpResponseRedirect(reverse('formulario_guardado'))

        return render(request, self.template_name, {'form': form})

Según la documentación de django, no pretende remplazar la funciones, pero si tiene algunas ventajas frente a las funciones, puede ser mas organizado, organiza el código con métodos para POST, GET, etc, en lugar de condiciones, utilizar la técnica de orientación a objetos, soporte mixes, herencia múltiple y puede ser mas reutilizable.

Fuente: https://docs.djangoproject.com/en/1.5/topics/class-based-views/intro/

Modelo de usuario personalizado en django 1.5

Esta publicación explica como configurar django 1.5, para usar modelos de usuario personalizados.

voy a suponer que ya tienen nociones de python y django, y voy a ir directo al grano.

ya creado nuestro proyecto. creamos una app.

python manage.py startapp usuarios

y ingresamos al directorio nuevo y creamos una archivo.

touch admin.py

nos quedaría un directorio de la siguiente manera.

apps/usuarios/
├── admin.py
├── form.py
├── __init__.py
├── models.py
├── tests.py
└── views.py

editamos el archivo models.py y creamos nuestro modelos para el usuario. en tal caso yo cree el mio con la siguiente informacion.


from django.utils import timezone
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.contrib.auth.models import UserManager
from django.db import models

class Usuarios(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(max_length=254, unique=True, db_index=True)
    first_name = models.CharField(max_length=30, blank=True)
    last_name = models.CharField(max_length=30, blank=True)
    email = models.EmailField(verbose_name='correo electronico', max_length=255, unique=True, db_index=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    is_staff = models.BooleanField()
    es_sub_admin = models.BooleanField()
    es_reseller = models.BooleanField()
    es_cliente = models.BooleanField()
    date_joined = models.DateTimeField()
    date_of_birth = models.DateField(blank=True, null=True)

    objects = UserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    def get_full_name(self):
        # The user is identified by their email address
        return self.email

    def get_short_name(self):
        # The user is identified by their email address
        return self.email

    def __unicode__(self):
        return self.email

una breve explicación del código anterior.

AbstractBaseUser:  Proporciona la implementación base de un modelo de usuario, incluyendo contraseñas hash y tokenized restablece la contraseña.  

BaseUserManager: Es un gestor personalizado para su modelo de usuario. Si su modelo de usuario define username, email, is_staff, is_active, is_superuser, last_login y campos date_joined lo mismo que el modelos User de Django, puede usar sólo UserManager de Django

PermissionsMixin: Para que sea más fácil incluir marco permiso de Django en su propia clase de usuario, Django proporciona PermissionsMixin. Este es un modelo abstracto que puede incluir en la jerarquía de clases para su modelo de usuario, que le da todos los métodos y campos de base de datos necesarios para apoyar modelo de permiso de Django. pero si quiere utilizar tus propios métodos para permisos lo puede crear sin ningún problema.

UserManager: También debe definir un gestor personalizado para su modelo de usuario. Si su modelo de usuario define nombre de usuario, correo electrónico, is_staff, is_active, is_superuser, last_login y campos date_joined lo mismo que de Django por defecto del usuario, puede instalar sólo UserManager de Django, sin embargo, si el modelo de usuario define los diferentes campos, tendrá que definir una gestor personalizado que se extiende BaseUserManage.

los campos, username, email, password, is_staff, is_superuser, is_active,
date_joined, first_name, last_name. Son necesarios para poder iniciar la sesión del administrador de django y no complicarnos la vida.

es_sub_admin, es_reseller, es_cliente, date_of_birth esto campos son agregamos a mi gusto, lo mismo pueden hacer ustedes.

USERNAME_FIELD = 'username' , definimos cual va ha ser el campo del username, podemos utilizar email, first_name o un campo nuevo como nombre_de_usuario, puede ser un campo de texto que se encuentre dentro del modelos que estamos creando.
REQUIRED_FIELDS = ['email'] , definimos los campos requeridos para la creación del usuario.

Guardamos nuestro modelo y abrimos admin.py e ingresamos el siguente codigo. este archivo fue copiado de django.contrib.auth, con unos pequeños retoques. para que funcione con nuestro modelo.

from django.db import transaction
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.forms import (UserCreationForm, 
    AdminPasswordChangeForm)
from django.contrib.auth.models import User, Group
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect, Http404
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.utils.html import escape
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext, ugettext_lazy as _
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django import forms
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from apps.usuarios.models import Usuarios
csrf_protect_m = method_decorator(csrf_protect)
sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())

class UserCreationForm(forms.ModelForm):
    """A form for creating new users. Includes all the required
    fields, plus a repeated password."""
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
        model = Usuarios
        fields = ('username', 'email', 'date_of_birth')
    def clean_password2(self):
        # Check that the two password entries match
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2
    def save(self, commit=True):
        # Save the provided password in hashed format
        user = super(UserCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

class UserChangeForm(forms.ModelForm):
    """A form for updating users. Includes all the fields on
    the user, but replaces the password field with admin's
    password hash display field.
    """
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = Usuarios

    def clean_password(self):
        # Regardless of what the user provides, return the initial value.
        # This is done here, rather than on the field, because the
        # field does not have access to the initial value
        return self.initial["password"]

class UserAdmin(admin.ModelAdmin):
    add_form_template = 'admin/auth/user/add_form.html'
    change_user_password_template = None
    fieldsets = (
        (None, {'fields': ('username', 'password')}),
        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'is_admin', 'es_sub_admin', 'es_reseller', 'es_cliente',
                                       'groups', 'user_permissions',)}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined', 'date_of_birth')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('username', 'password1', 'password2')}
        ),
    )
    form = UserChangeForm
    add_form = UserCreationForm
    change_password_form = AdminPasswordChangeForm
    list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff',)
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('username', 'first_name', 'last_name', 'email')
    ordering = ('username',)
    filter_horizontal = ('groups', 'user_permissions',)

    def get_fieldsets(self, request, obj=None):
        if not obj:
            return self.add_fieldsets
        return super(UserAdmin, self).get_fieldsets(request, obj)

    def get_form(self, request, obj=None, **kwargs):
        """
        Use special form during user creation
        """
        defaults = {}
        if obj is None:
            defaults.update({
                'form': self.add_form,
                'fields': admin.util.flatten_fieldsets(self.add_fieldsets),
            })
        defaults.update(kwargs)
        return super(UserAdmin, self).get_form(request, obj, **defaults)

    def get_urls(self):
        from django.conf.urls import patterns
        return patterns('',
            (r'^(\d+)/password/$',
             self.admin_site.admin_view(self.user_change_password))
        ) + super(UserAdmin, self).get_urls()

    def lookup_allowed(self, lookup, value):
        # See #20078: we don't want to allow any lookups involving passwords.
        if lookup.startswith('password'):
            return False

        return super(UserAdmin, self).lookup_allowed(lookup, value)
    @sensitive_post_parameters_m
    @csrf_protect_m
    @transaction.commit_on_success
    def add_view(self, request, form_url='', extra_context=None):
        # It's an error for a user to have add permission but NOT change
        # permission for users. If we allowed such users to add users, they
        # could create superusers, which would mean they would essentially have
        # the permission to change users. To avoid the problem entirely, we
        # disallow users from adding users if they don't have change
        # permission.
        if not self.has_change_permission(request):
            if self.has_add_permission(request) and settings.DEBUG:
                # Raise Http404 in debug mode so that the user gets a helpful
                # error message.
                raise Http404(
                    'Your user does not have the "Change user" permission. In '
                    'order to add users, Django requires that your user '

                    'account have both the "Add user" and "Change user" '
                    'permissions set.')
            raise PermissionDenied
        if extra_context is None:
            extra_context = {}
        username_field = self.model._meta.get_field(self.model.USERNAME_FIELD)
        defaults = {
            'auto_populated_fields': (),
            'username_help_text': username_field.help_text,
        }
        extra_context.update(defaults)
        return super(UserAdmin, self).add_view(request, form_url,
                                               extra_context)

    @sensitive_post_parameters_m
    def user_change_password(self, request, id, form_url=''):
        if not self.has_change_permission(request):
            raise PermissionDenied
        user = get_object_or_404(self.queryset(request), pk=id)
        if request.method == 'POST':

            form = self.change_password_form(user, request.POST)
            if form.is_valid():
                form.save()
                msg = ugettext('Password changed successfully.')
                messages.success(request, msg)
                return HttpResponseRedirect('..')
        else:
            form = self.change_password_form(user)

        fieldsets = [(None, {'fields': list(form.base_fields)})]
        adminForm = admin.helpers.AdminForm(form, fieldsets, {})

        context = {
            'title': _('Change password: %s') % escape(user.get_username()),
            'adminForm': adminForm,
            'form_url': form_url,
            'form': form,
            'is_popup': '_popup' in request.REQUEST,
            'add': True,
            'change': False,
            'has_delete_permission': False,
            'has_change_permission': True,
            'has_absolute_url': False,
            'opts': self.model._meta,
            'original': user,
            'save_as': False,
            'show_save': True,
        }
        return TemplateResponse(request,
            self.change_user_password_template or
            'admin/auth/user/change_password.html',
            context, current_app=self.admin_site.name)

    def response_add(self, request, obj, post_url_continue=None):
        """
        Determines the HttpResponse for the add_view stage. It mostly defers to
        its superclass implementation but is customized because the User model
        has a slightly different workflow.
        """
        # We should allow further modification of the user just added i.e. the
        # 'Save' button should behave like the 'Save and continue editing'
        # button except in two scenarios:
        # * The user has pressed the 'Save and add another' button
        # * We are adding a user in a popup
        if '_addanother' not in request.POST and '_popup' not in request.POST:
            request.POST['_continue'] = 1
        return super(UserAdmin, self).response_add(request, obj,
                                                   post_url_continue)

admin.site.register(Usuarios, UserAdmin)

Este le proporciona el form para crear y editar usuarios al que ya estamos acostumbrados con django, pueden agregar, quitar campos que quieran que se vean cuando se cree un usuario, cuando se modifique un usuario o los filtros necesario para los usuarios.

para terminar editarmos el setting.py de nuestro proyecto.

y agregamos la siguiente linea

AUTH_USER_MODEL = 'usuarios.Usuarios'

y queda todo listo para funcionar con el nuevo modelo personalizado para usuarios. con esto podemos tener nuestro perfil de usuario en el mismo modelos del usuario.

para asignar ForeignKey debes importa la configuración de nuestro proyecto.  seria de la siguente manera.

from django.db import models
from django.conf import settings

class Balance(models.Model):     """     Historial de cargas y deducciones del usuario     """     fecha = models.DateTimeField(auto_now_add=True, blank=True)     usuario = models.ForeignKey(settings.AUTH_USER_MODEL)     def __str__(self):         return self.usuario     class Meta:         db_table = 'balance'
 
Al terminar todas las modificaciones sincronizamos nuestra base de datos.
python manage.py syncdb

deja tu comentario si tienes alguna duda o sugerencia.
Hola soy Jorge.

Soy un entusiasta del open source. de la programación en python y algunos conocimientos de asterisk.

Abrí este blog para compartir algunos conocimientos y poder ayudar a los que están comenzando en el mundo de asterisk, python y Debian, y darle algunas herramientas para a realizar sus propios proyectos. Del mismo modo poder colaborarles a los que ya tienen proyectos desarrollados.