martes, 14 de mayo de 2013

Montando un pequeno foro con Django [2]: Navegando polo foro

Tedes a primeira parte en Montando un pequeno foro con Django [I]: Preparación e modelos de dados

Da última vez quedaramos coa base da aplicación e o modelo de dados, ímos crear un Tema dende o panel de control para ir probando como manexar a base de dados e as plantillas.


Voltemos entón iniciar o servidor lanzando
python manage.py runserver
dende o directorio do proxecto, feito isto poderemos acceder o panel de control a través de http://127.0.0.1:8000/admin/.

Engadamos un par de temas...


Agora, para amosar todo-los temas temos que crear unha vista, ímos ata o arquivo `meu_foro/views.py' e engadimos un método para a pantaia principal:
from django.http import HttpResponse

def index(request):
    return HttpResponse("Funciona!")
Así cando se chame a vista `index' amosará unha resposta HTTP con esa cadea, para definir a  url da vista engadiremos a seguinte tupla ó `urlpatterns' no arquivo `urls.py', do directorio co nome do proxecto (neste caso `hl_django/urls.py')

urlpatterns = patterns('',
    # Regexp,  vista
    url(r'^$', 'meu_foro.views.index'),
    ...

A regexp define as URL's dende as que se accederá a vista (neste caso as que non especifiquen unha ruta), se accedemos a http://127.0.0.1:8000/ atoparemos a resposta que programaramos.

Pero preparar a resposta directamente na vista é excesivamente tedioso, para isto Django provee dun mecanismo de plantillas (templates), para tirar partido delas crearemos un directorio `templates' (plantillas) na ruta do app, por exemplo `meu_foro/templates' e o engadimos ó `TEMPLATES_DIRS' do arquivo de configuración (`settings.py')
TEMPLATE_DIRS = (
    # Engadide cadeas aquí, coma "/home/html/django_templates" ou "C:/www/django/templates".
    # Usade sempre barras normais '/', incluso en Windows.
    # Non olvidedes usar rutas absolutas, non relativas.Don't forget to use absolute paths, not relative paths.
    '/home/kenkeiras/hl_django/meu_foro/templates',
)

Crearemos enton unha plantilla sinxela para o índice `index.html':
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>MeuForo</title>
  </head>
  <body>
    <h1 class="banner">MeuForo</h1>
    {% if temas %}
      <ul class="topic_list">
        {% for tema in temas %}
          <a href="/{{ tema.id }}/">
            <li>{{ tema.nome }}</li>
          </a>
        {% endfor %}
      </ul>
    {% else %}
      <div id="error_msg">Sen temas aínda :/</div>
    {% endif %}
  </body>
</html>   

Pódese ver entre '{%' e '%}' o control de fluxo que soportan as plantillas, podendo evaluar unha variable e iterar sobre ela. Ademáis podemos introducir variables usando as secuencias '{{' e '}}'

Agora, para usar as plantillas dende a vista utilizaremos a función `render_to_response' deste xeito:
from django.shortcuts import render_to_response
from django.template import RequestContext

def index(request):
    return render_to_response('index.html',
                              context_instance=RequestContext(request))

Pero claro, a plantilla de algun lugar ten que tomar os datos, non?, podemos aforrar todo o proceso de manexar a base de dados utilizando as clases que definimos no modelo (Tema, Fio e Post):
from django.shortcuts import render_to_response
from django.template import RequestContext
from meu_foro.models import Tema, Fio, Post


def index(request):
    return render_to_response('index.html',
                              # Diccionario con 'Nome da variable': 'Valor'
                              {'temas': Tema.objects.all()},
                              context_instance=RequestContext(request))

E xa amosa a lista de temas sen SQL, sen nada máis que unha chamada a unha clase

Pero o estilo queda un pouco escaso, non? ímos ver como servir arquivos estáticos dende o propio servidor (neste caso unha folla de estilos CSS :) ), se houbera que ter en conta o rendemento probablemente sería mellor usar un servidor especializado (coma nginx) para iso.

Entón, no `settings.py' definiremos dúas variables:
# Cando fagamos python manage.py collectstatics
# os ficheiros irán a parar aquí
STATIC_ROOT = '/home/kenkeiras/hl_django/static/'

# Directorios onde se buscarán os arquivos estáticos
STATICFILES_DIRS = (
    '/home/kenkeiras/hl_django/meu_foro/static/', # Ollo a ',' do final
)
Cambiando as rutas ás adecuadas (lembrando que sexan sempre absolutas).

Para as probas creei un estilo que non entrarei a detaiar, iría en `meu_foro/static/css/style.css':
html, body{
    background-color: rgb(250, 250, 250);
    background-repeat: repeat;
    margin: 0px;
    padding: 0px 1px 0px 1px;

}

.banner{
    font-family: Courier;
    color: #FFF;

    text-shadow: 0px 0px 5px #000;
}

::-webkit-input-placeholder {
   color: #666;
}

:-moz-placeholder {
   color: #666;
}

.return_link{
    color: #222;
    text-decoration: none;

    font-size: 1.2em;
    font-family: Helvetica,sans-serif;
}
/***********************/
.post_user_info{
    text-align: right;
}

/***********************/
.side_thread_list{
    width: 25%;
    list-style: none;
    float: left;

    margin-left: 1px;
    padding: 0px;

    -moz-box-shadow:    0px 0px 3px 3px rgba(0, 0, 0, 0.5);
    -webkit-box-shadow: 0px 0px 3px 3px rgba(0, 0, 0, 0.5);
    box-shadow:         0px 0px 3px 3px rgba(0, 0, 0, 0.5);
}

.just_thread_list{
    width: 75%;
    list-style: none;
    text-align: center;

    margin: auto;
    padding: 0px;


    -moz-box-shadow:    0px 0px 3px 3px rgba(0, 0, 0, 0.5);
    -webkit-box-shadow: 0px 0px 3px 3px rgba(0, 0, 0, 0.5);
    box-shadow:         0px 0px 3px 3px rgba(0, 0, 0, 0.5);
}

.post_list {
    float: right;
    text-align: left;

    width: 60%;
}

li > form > textarea {
    width: 100%;
}

a:first-child > .thread_item {
    border: none !important;
}
/***********************/
ul {
    width: 25%;
    list-style: none;
    text-align: center;

    margin: auto;
    padding: 0px;

    -moz-box-shadow:    0px 0px 3px 3px rgba(0, 0, 0, 0.5);
    -webkit-box-shadow: 0px 0px 3px 3px rgba(0, 0, 0, 0.5);
    box-shadow:         0px 0px 3px 3px rgba(0, 0, 0, 0.5);
}

ul > :first-child {
    border: none !important;
}

ul > :first-child > li {
    border: none !important;
}

li {
    border-top: 1px dashed #444;
    padding: 1ex;

    background-color: rgb(241, 241, 241);
    background-repeat: repeat;
}

a {
    color: #222;
    text-decoration: none;
    font-size: 1.8em;
    font-family: Helvetica,sans-serif;
}

li:hover{
    background-color: rgb(230, 230, 230);
    background-repeat: repeat;
}

Engadamos enton o CSS a cabeceira da plantilla do índice:
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/style.css" />
Como se pode ver, a veriable '{{ STATIC_URL }}' garda a URL base dos arquivos estáticos.

 Ben, voltando o foro, se queremos ver os fíos, por exemplo, a través da ruta '/<id do tema>/', para iso engadiremos a seguinte liña o `urlpatterns' no `urls.py':
url(r'^(?P\d+)/?$', 'meu_foro.views.fios'),

Isto é, as url que teñan un ou máis díxitos, seguidos ou non dun '/', resultan na vista 'meu_foro.views.fios' co id do tema coma parámetro extra, hay un par de métodos que podemos usar para atopar os datos que nos interesen:

`get_object_or_404' busca un obxecto correspondente o modelo especificado co id que pidamos (e se faia fai que o servidor web produza un erro 404, non atopado)
tema = get_object_or_404(Tema, pk=id_tema)

Tamén podemos filtrar os objectos dun modelo dado un dos campos
fios = Fio.objects.filter(tema=tema)

E só con iso, xa temos para a vista `fios'
def fios(request, id_tema):
    tema = get_object_or_404(Tema, pk=id_tema)
    fios = Fio.objects.filter(tema=tema)
    return render_to_response('fios.html',
                              {'tema': tema,
                               'fios': fios},
                              context_instance=RequestContext(request))

Iso si, haberá que face-la plantilla `fios.html':
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>MeuForo</title>
    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/style.css" />
  </head>
  <body>
    <h3 class="banner">MeuForo-{{ tema.nome }}</h3>
    <ul class="just_thread_list">
      <a href="/" class="return_link"><li class="thread_item">← Atrás</li></a>
      {% if fios %}
        {% for fio in fios %}
          <a href="/{{ tema.id }}/{{ fio.id }}/"><li>{{ fio.nome }}</li></a>
        {% endfor %}
      {% else  %}
          <li>Sen fíos</li>
      {% endif %}
      <li>
        <form action="/new/{{ tema.id }}" method="POST">
          Nome: <input type="text" name="nome" placeholder="Introduce o nome do fío" /><br />
          <textarea name="texto" rows="5" cols="50" placeholder="O texto do primeiro post do fío"></textarea>
          <br />
          <input type="submit" value="Novo fío!"/>
          {% csrf_token %}
        </form>
      </li>
    </ul>
  </body>
</html>
É unha plantilla coma a primeira, apenas cun formulario engadido, pero ollo, Django integra un mecanismo de seguridade contra o CSRF "de paquete", por ese motivo hay que incluir '{% csrf_token %}' en tódolos formularios, do contrario o servidor rexeitará a petición por ser sospeitosa.

Agora, para respostar a esa petición e poder probar a vista dos fíos precisamos un xeito de manexar ese POST, repetindo o proceso de ate agora engadiremos un patrón as URLs:
url(r'^new/(?P\d+)/?$', 'meu_foro.views.novo_fio'),

E unha función no `views.py':
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User

def novo_fio(request, id_tema):
    tema = get_object_or_404(Tema, pk=id_tema)

    # Se non inclúe nome ou texto, retornar a lista de fíos
    try:
        nome = request.POST['nome']
        texto = request.POST['texto']
    except KeyError:
        return fios(request, id_tema)

    # "Espertamos" a variable do usuario, se o é
    if request.user.is_authenticated():
        user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
    # Se non o é, creamos un anónimo (pero da clase User)
    else:
        user = User(0)

    # Se todo vai ben, xera un novo fío
    fio = Fio.objects.create(nome=nome, op=user, tema=tema, data_creacion=datetime.now())

    # E nel o primeiro post
    post = Post.objects.create(fio=fio, usuario=user, texto=texto, data_creacion=datetime.now())

    # Cando esté todo feito, redireccionamos o usuario a vista de f
    return HttpResponseRedirect(reverse('meu_foro.views.fios', args=(id_tema)))



Aquí podemos ver varias cousas, que o obxecto `request' mantén a sesión do usuario e datos coma as variables que passan por POST, que dispomos dun método `reverse' que dado dunha vista e uns argumentos devolve unha url e un `HttpResponseRedirect' que permite voltar un código de redirección e cal é a sintaxe para crear obxectos.

E realmente pouco máis, a vista dos post de cada fío é máis do mesmo... podería ser unha boa ocasión para practicar ;), por non deixalo a medias deixo iso tamén, inda que sen explicar.


Lista dos post, patrón da url:
url(r'^(?P\d+)/(?P\d+)/?$', 'meu_foro.views.posts'),

Vista:
def posts(request, id_tema, id_fio):
    tema = get_object_or_404(Tema, pk=id_tema)
    fio = get_object_or_404(Fio, pk=id_fio)
    posts = Post.objects.filter(fio=fio)
    return render_to_response('posts.html',
                              {'tema': tema,
                               'fio': fio,
                               'id_fio': id_fio,
                               'posts': posts},
                              context_instance=RequestContext(request))

Plantilla:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>MeuForo</title>
    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/style.css" />
  </head>
  <body>
    <h3 class="banner">MeuForo-{{ tema.nome }}</h3>
    <ul class="side_thread_list">
      <a href="/" class="return_link"><li>← Atrás</li></a>
      {% if fios %}
        {% for fio_lateral in fios %}
          <a href="/{{ tema.id }}/{{ fio_lateral.id }}/"><li>{{ fio_lateral.nome }}</li></a>
        {% endfor %}
      {% endif %}
    </ul>
    {% if posts %}
    <ul class="post_list">
      {% for post in posts %}
        <li>{{ post.texto }}
          <br /><div class="post_user_info"><em class="username">{{ post.user.username }}</em></div>
        </li>
      {% endfor %}
      <li>
        <form action="/new/{{ tema.id }}/{{ id_fio }}" method="POST">
          <textarea name="texto" rows="5" placeholder="E ti que dis?"></textarea><br />
          {% csrf_token %}
          <input type="submit" value="Enviar" />
        </form>
      </li>
    </ul>
    {% endif %}
  </body>
</html>

O patrón de url do envío de posts:
url(r'^new/(?P\d+)/(?P\d+)/?$', 'meu_foro.views.novo_post'),

A función para crear os post:
def novo_post(request, id_tema, id_fio):
    fio = get_object_or_404(Fio, pk=id_fio)

    # Se non inclúe nome ou texto, retornar a lista de fíos
    try:
        texto = request.POST['texto']
    except KeyError:
        return posts(request, id_tema, id_fio)

    # "Espertamos" a variable do usuario, se o é
    if request.user.is_authenticated():
        user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
    # Se non o é, creamos un anónimo (pero da clase User)
    else:
        user = User(0)

    # E nel o primeiro post
    post = Post.objects.create(fio=fio, usuario=user, texto=texto, data_creacion=datetime.now())

    # Cando esté todo feito, redireccionamos o usuario a vista de f
    return HttpResponseRedirect(reverse('meu_foro.views.posts', args=(id_tema, id_fio)))


E xa está, temos un foro funcional... para a próxima como xestionar usuarios?

Saúdos

No hay comentarios:

Publicar un comentario