Показаны сообщения с ярлыком jinja. Показать все сообщения
Показаны сообщения с ярлыком jinja. Показать все сообщения

28 февраля 2013

Объявление флага в цикле Jinja2

Понадобилось мне тут создать флаг в цикле, который можно использовать где нибудь потом в шаблоне. По логике все должно выглядеть примерно так:
{% set exists = 0 %}
{% for i in range(5) %}
      {% if True %}
          {% set exists = 1 %}
      {% endif %}
{% endfor %}
{% if exists %}
    
{% endif %}
Но такой код не фурычит! exist всегда будет 0. Это особенность области видимости переменных в Jinja при присваивании.
Поэтому есть небольшой хак как это поправить:
{% set exists = [] %}
{% for i in range(5) %}
      {% if True %}
          {% do exists.append(1) %}
      {% endif %}
{% endfor %}
{% if exists %}
    
{% endif %}
Решение взято от сюда

19 февраля 2013

Jinja2 Lorem ipsum dolor sit amet

Иногда в шаблоне нужно зафигачить какую-нибудь рыбу типа "Lorem ipsum dolor sit amet", часто в цикле итд. Для этого существует функция lipsum()
Вот пример:
{% for x in range(5) %}
  {{ lipsum()|truncate(150)|safe }}
  
{% endfor %}
И результат:

Justo aliquam faucibus lacus pulvinar commodo nisl, quisque est fusce venenatis mattis magnis arcu, hac felis parturient suspendisse a. Vitae ...


Porta tellus turpis leo suspendisse rutrum metus blandit, montes dis lacinia felis non, vehicula vivamus condimentum luctus massa, vehicula ...


Convallis molestie blandit viverra imperdiet eros dolor nam, ridiculus tortor duis blandit duis enim, cursus bibendum lobortis faucibus dui ...


Diam placerat risus porta litora consequat vel, accumsan tempus ligula laoreet a mollis rutrum, aptent est tortor pulvinar senectus, litora etiam. ...


Cursus non morbi non proin, porttitor consequat eget ac eget netus penatibus, egestas primis suspendisse ad, iaculis eu cursus urna blandit, leo. ...


28 сентября 2012

Перевод шаблонов Jinja в Pyramid

Основная документация как это делать здесь. Но как обычно есть нюансы.

Рабочий пример можно посмотреть установив шаблон. Устанавливаем pyramid_jinja2 и пишем:
pcreate -t pyramid_jinja2_starter yoyoyoyo
Если лень устанавливать пример, то нужно сделать следующее:
Добавить файл message-extraction.ini
[python: **.py]
[jinja2: **.jinja2]
encoding = utf-8
Добавить в setup.cfg
[extract_messages]
add_comments = TRANSLATORS:
output_file = myprojectname/locale/myprojectname.pot
width = 80
mapping_file = message-extraction.ini
далее в __init__.py
    settings = dict(settings)
    settings.setdefault('jinja2.i18n.domain', 'myprojectname')
    ...
    config.add_translation_dirs("myprojectname:locale/")
    config.include('pyramid_jinja2')
    config.add_jinja2_search_path("myprojectname:templates")
В шаблоне:
{{ gettext('Logo') }}
{% trans %}Home{% endtrans %}
все остальное по документации... Я для упрощения использую скрипт i18n.sh
#!/usr/bin/env bash

py=python

$py setup.py extract_messages
$py setup.py update_catalog
$py setup.py compile_catalog
# vim:set et sts=4 ts=4 tw=80:

27 сентября 2012

Перевод шаблонов Jinja в Pylons

Все точно также как и с mako, "Но есть нюансы"©
Добавьте в setup.py
    package_data={'myproject': ['i18n/*/LC_MESSAGES/*.mo']},
    message_extractors={'myproject': [
            ('**.py', 'python', None),
            ('templates/**.html', 'jinja2', None),
            ('public/**', 'ignore', None)]},
Добавьте в lib/base.py
from pylons.i18n.translation import _, ungettext
И что то типа того в config/environment.py
    # Create the Jinja2 Environment
    config['pylons.app_globals'].jinja2_env = Environment(loader=ChoiceLoader(
            [FileSystemLoader(path) for path in paths['templates']]),
            autoescape=True,
            extensions=['jinja2.ext.do', 'jinja2.ext.i18n'])
    config['pylons.app_globals'].jinja2_env.install_gettext_translations(pylons.i18n)
    # Jinja2's unable to request c's attributes without strict_c
    config['pylons.strict_c'] = True
Теперь можно переводить {{ _('Translate me!') }}

10 февраля 2012

Jinja замена None Null итд на пустую строку

В питоне пустые значения возвращаются как None. Поэтому в шаблонах Jinja вместо пустых значений отображаются None. Что бы поправить это нужно изменить метод finalize. Пример из google groups

def silent_none(value):
     if value is None:
         return ''
     return value

from jinja2 import Environment
env = Environment()
env.finalize = silent_none 
Теперь вместо None будет писаться пустая строка ''. В pylons нужно править файл environment.py
def silent_none(value):
    """ Jinja fix output None
    For more details:
    http://groups.google.com/group/pocoo-libs/browse_thread/thread/490f6e6e8fca6a6c
    """
    if value is None:
        return ''
    return value

def load_environment(global_conf, app_conf):
    """Configure the Pylons environment via the ``pylons.config``
    object
    """
    bla bla bla...
    # Create the Jinja2 Environment
    config['pylons.app_globals'].jinja2_env = Environment(loader=ChoiceLoader(
            [FileSystemLoader(path) for path in paths['templates']]))
    # replace None output to ''
    config['pylons.app_globals'].jinja2_env.finalize = silent_none
    # Jinja2's unable to request c's attributes without strict_c
    config['pylons.strict_c'] = True
    bla bla bla...

OpenStreetMap, Геокодирование и автодополнение адреса в строке поиска (как у гугла) с помощью Яндекс API :)

Геокоди́рование — процесс назначения географических идентификаторов (таких как географические координаты, выраженные в виде широты и долготы) объектам карты и записям данных.
Например, геокодированием является назначение координат записям, описывающим адрес (улица/дом) или фотографиям (где было сделано фото) или IP-адресам, или любой другой информации, имеющей географический компонент. Геокодированные объекты могут быть использованы в геоинформационных системах.

Создадим карту и строку поиска объекта по адресу, через API Яндекс Карты. Служба Яндекс.Карт предлагает своим пользователям сервис геокодирования. Он позволяет определять координаты и получать сведения о географическом объекте по его названию или адресу и наоборот, определять адрес объекта на карте по его координатам (обратное геокодирование).
Например, по запросу «Москва, ул. Малая Грузинская, д. 27/13» геокодер возвратит географические координаты этого дома: "37.571309, 55.767190" (долгота, широта). И, наоборот, если в запросе указать географические коордианты дома "37.571309, 55.767190", то геокодер вернет его адрес.
К геокодеру можно обращаться как по HTTP-протоколу, так и с помощью JavaScript API. При обращении к геокодеру по HTTP-протоколу ответ может быть сформирован либо в виде XML-документа формата YMapsML, либо в формате JSON.

При обращении к геокодеру по HTTP-протоколу в параметрах запроса требуется указывать API-ключ. Ключ можно получить, заполнив соответствующую форму.
В данном документе описаны параметры HTTP-запроса к геокодеру, его ответ, а также приведены примеры использования.

Как добавить карту на сайт можно посмотреть здесь

Я приведу пример использования в окружении Pylons и шаблонизатора Jinja. При желании переделать скрипты под другие фреймворки не составит труда. Главное понять принципы.

Добавим поле для поиска
input id="searchAddress" name="address" size="73" type="text" />

Напишем AJAX функцию для выпадающего списка при автодополнении (http://www.devbridge.com/projects/autocomplete/jquery/





Создадим контроллер который будет отдавать данные по AJAX запросу
common.py
# coding=utf8
import logging
import pylons

from pyandexmap import geotagging
from pylons import request, response, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect

from myapp.lib.base import BaseController, render

log = logging.getLogger(__name__)

class CommonController(BaseController):

    def geocodding_ajax(self):
        """ for autocomplete search in Yandex API
        """
        # Ключ для Яндекс карт из development.ini
        key = pylons.config['yandexKey']
        address = ''
        for addr in geotagging.listGeoObject(request.GET['query'], key):
            if address:
                address = address +", '"+addr+"'"
            else:
                address = "['"+addr+"'"

        resp = '''{
         query:'%s',
         suggestions: %s,
         data:[]
        }''' % (request.GET['query'], address+']')
        return resp


Формат ответа выглядит так
{
 query:'Li',
 suggestions:['Liberia','Libyan Arab Jamahiriya','Liechtenstein','Lithuania'],
 data:['LR','LY','LI','LT']
}

pyandexmap устанавливаем из pypi (про этот модуль я напишу позже)
pip install pyandexmap

Все должно заработать, остались только стили


Результат
Яндекс Карты автодополнение через геокодинг

22 ноября 2011

готовим Pylons + WTForms

WTForm простая, но довольно удобная библиотека для создания форм. И еще WTForm очень похожа на формы в Django - одно из немногово что в джанге сделано хорошо. Посмотрим как это работает с Pylons. Для удобства будем хранить формы отдельно
|+config/
|+controllers/
|~forms/
| |~mycontroller/
| | |-__init__.py
| | `-equipments.py
| |+validators/
| `-__init__.py
Создаем форму для редактирования оборудования equipments.py
from myapp.model.meta import Session as s
from myapp.model.mymodel import EquipmentType
from wtforms import Form, TextField, validators
from wtforms.ext.sqlalchemy.fields import QuerySelectField

# Выбор всех разновидностей оборудования для списка type в форме
def all_equipment_types():
    return s.query(EquipmentType).all()

class EditForm(Form):
    ip = TextField('ip address')
    netmask = TextField('network mask')
    mac = TextField('mac address')
    type = QuerySelectField('type of equipment',
                            query_factory=all_equipment_types)
QuerySelectField это поле из расширения WTForm для SQLAlchemy, которое позволяет создавать списки на основе выборок. Также есть расширения для Django и GAE. Инициализируем нашу форму в контроллере и передадим шаблону
from myapp.forms.mycontroller.equipments import EditForm

class EquipmentsController(BaseController):
    ...
    def edit(self, id, format='html'):
        """GET /myapp/equipments/id/edit: Form to edit an existing item"""
        # url('myapp_edit_equipment', id=ID)
        c.equipment = s.query(Equipment).filter(id==id)
        c.form = EditForm(ip=c.equipment.ip, mac=c.equipment.mac,
                        type=c.equipment.equipmenttype,
                        netmask=c.equipment.netmask)

        return render('/myapp/equipments/edit.html')
После передачи одноименных полям параметров в форму, значения этих параметров будут по умолчанию выведены в форме. Напишем шаблон на Jinja
{% extends "base.html" %}

{% block content %}
<form action="{{ url(controller='myapp/equipments', action='update', id=c.equipment.id) }}" method="POST">
<table class="simpletable">
<caption>Edit equipment of {{ c.equipment.equipmenttype }}
    ({{ c.equipment.ip }})</caption>
<tbody>
{% for field in c.form.data %}
<tr><td>{{ c.form[field].label }}</td>
    <td>{{ c.form[field] }}</td>
</tr>
{% endfor %}
<tr><td colspan="5">
<button><img src="/img/common/save.png" />Save</button>
<input type="hidden" name="_method" value="PUT" />
</td></tr>
</tbody>
</table>
</form>
{% endblock %}
В примере поля выводятся в цикле из списка form.data. Я использую REST контроллеры поэтому обновление происходит в методе update
from myapp.forms.mycontroller.equipments import EditForm

class EquipmentsController(BaseController):
    ...
    def update(self, id):
        """PUT /myapp/equipments/id: Update an existing item"""
        # Forms posted to this method should contain a hidden field:
        #    
        # Or using helpers:
        #    h.form(url('myapp_equipment', id=ID),
        #           method='put')
        # url('myapp_equipment', id=ID)
        equipment = s.query(Equipment).filter(id=id)
        if request.POST['ip']:
            equipment.ip = request.POST['ip']
        if request.POST['netmask']:
            equipment.netmask = request.POST['netmask']
        if request.POST['mac']:
            equipment.mac = request.POST['mac']
        type = EquipmentType.by_id(request.POST['type'])
        equipment.equipmenttype = type
        s.commit()

        came_from = str(request.environ.get('HTTP_REFERER', '')) or url('/')

        redirect(came_from)
Последние две строки возвращают пользователя обратно на страницу редактирования. Выглядит это как-то так:
Пример WTForm и Pylons

27 октября 2011

Админка FormAlchemy для Pylons и Jinja

Для SQLAlchemy существуют интерфейсы управления моделями(что-то типа админки). Как минимум их 2, это Rum и formalchemy.ext.pylons.

Рассмотрим FormAlchemy. Вообще как обычно можно прочитать документацию, но я опишу еще как это все состыковать с шаблонами на Jinja и поддержкой полей Postgres таких как mac, cidr, net и т.д..

Создаем контроллер admin
paster controller admin

Далее редактируем его controllers/admin.py
# coding=utf-8
import logging

from pylons import request, response, session, tmpl_context as c, url

from formalchemy.ext.pylons.controller import ModelsController

from webhelpers.paginate import Page

from repoze.what.predicates import has_permission
from repoze.what.plugins.pylonshq import ControllerProtector

from myapp.lib.base import BaseController
from myapp import model
from myapp import forms
from myapp.model import meta

log = logging.getLogger(__name__)

class AdminControllerBase(BaseController):
    model = model # where your SQLAlchemy mappers are
    forms = forms # module containing FormAlchemy fieldsets definitions
    def Session(self): # Session factory
        return meta.Session

    ## customize the query for a model listing
    # def get_page(self):
    #     if self.model_name == 'Foo':
    #         return Page(meta.Session.query(model.Foo).order_by(model.Foo.bar)
    #     return super(AdminControllerBase, self).get_page()

AdminController = ModelsController(AdminControllerBase,
                                     prefix_name='admin',
                                     member_name='model',
                                     collection_name='models',)


Дальше добавляем пути в config/routing.py
# ADMIN
# Map the /admin url to FA's AdminController
# Map static files
map.connect('fa_static', '/admin/_static/{path_info:.*}',
            controller='admin', action='static')
# Index page
map.connect('admin', '/admin', controller='admin', action='models')
map.connect('formatted_admin', '/admin.json', controller='admin',
            action='models', format='json')
# Models
map.resource('model', 'models', path_prefix='/admin/{model_name}',
             controller='admin')

И добавим в шаблоны папку myapp/templates/forms можно взять с github.
Правим следующим образом myapp/forms/__init__.py
from pylons.templating import render_mako
from pylons import config
from myapp import model
from myapp.lib.base import render
from formalchemy import config as fa_config
from formalchemy import templates
from formalchemy import validators
from formalchemy import fields
from formalchemy import forms
from formalchemy import tables
from formalchemy.ext.fsblob import FileFieldRenderer
from formalchemy.ext.fsblob import ImageFieldRenderer

fa_config.encoding = 'utf-8'

class TemplateEngine(templates.TemplateEngine):
    def render(self, name, **kwargs):
        return render_mako('/forms/%s.mako' % name, extra_vars=kwargs)
fa_config.engine = TemplateEngine()

class FieldSet(forms.FieldSet):
    pass

class Grid(tables.Grid):
    pass

## Initialize fieldsets

#Foo = FieldSet(model.Foo)
#Reflected = FieldSet(Reflected)

## Initialize grids

#FooGrid = Grid(model.Foo)
#ReflectedGrid = Grid(Reflected)

Сейчас как и пишут в документации вы действительно по адресу http://localhost:5000/admin попадете в админку! Но только если вы не используете особенностей БД, например sqlalchemy.databases.postgresql. Вторая проблема в том что шаблоны админки не встроены в ваши шаблоны.

Для поддержки Postgres полей в FormAlchemy необходимо добавить в файле lib/base.py
from formalchemy.fields import FieldRenderer
from formalchemy.tests import FieldSet

from sqlalchemy.databases import postgresql

class PostgresFieldRenderer(FieldRenderer):
    """render a field as a postgres field"""
    def render(self, **kwargs):
        return h.text_field(self.name, value=self.value, **kwargs)

# fix postgres field in FormAlchemy
# tnx for http://code.google.com/p/formalchemy/issues/detail?id=167
FieldSet.default_renderers[postgresql.CIDR] = PostgresFieldRenderer
FieldSet.default_renderers[postgresql.MACADDR] = PostgresFieldRenderer
FieldSet.default_renderers[postgresql.INET] = PostgresFieldRenderer
Здесь мы создали 3 правила для FormAlchemy как рендерить поля postgresql.CIDR, postgresql.MACADDR и INET. Этот трюк можно проделать со всем чем угодно :) Более подробно здесь http://code.google.com/p/formalchemy/issues/detail?id=167

И наконец встраиваем админку в ваш шаблон.
Для того что бы это сделать придется переписать все шаблоны на jinja и возможно поменять некоторые файлы(как минимум forms/__init__.py). Я приведу более ленивый и простой метод. У меня в шаблонах есть header.html который я инклудом добавляю в base.html в самом начале. Так же и здесь добавим его в начало админки FormAlchemy. В файле lib/base.py
class BaseController(WSGIController):

    def __call__(self, environ, start_response):
        """Invoke the Controller"""
        # WSGIController.__call__ dispatches to the Controller method
        # the request is routed to. This routing information is
        # available in environ['pylons.routes_dict']

        # Костыль с mako шаблонами в админке FormAlchemy
        # FIXME: для чистоты кода, переписать шаблоны с mako на jinja в папке forms
        c.jinja_menu = render('/common/header.html')
И в шаблоне templates/form/restfieldset.mako
  <body>
${c.jinja_menu}
%if isinstance(models, dict): ...
Сразу после <body> добавляем ${c.jinja_menu}. Тем самым в админку уже отдается отрендеренное меню.

В документации показан интерфейс fa.jquery на самом деле будет обычный. Для jquery его еще надо дополнительно пилить, добавлять в controller/admin
from fa.jquery.pylons import ModelsController as ModelsControllerJQ и дальше как сказанно в исходниках https://github.com/FormAlchemy/fa.jquery/blob/master/fa/jquery/pylons.py обернуть наш контроллер. Что то вроде этого:
AdminController = ModelsController(AdminControllerBase,
                                     prefix_name='admin',
                                     member_name='model',
                                     collection_name='models',)
AdminController = ModelsControllerJQ(AdminController,
                                     prefix_name='admin',
                                     member_name='model',
                                     collection_name='models',)

В целом FormAlchemy довольно мощная штука, но очень сильно привязана к mako, поэтому пользоваться ей не удобно и ксожалению придется искать что то другое.

26 октября 2011

Авторизация в Pylons за 5 мин при помощи repoze.what

Статья по сути вольный перевод PylonsTemplates: extra Paster templates for Pylons apps с моими дополнениями.

PylonsTemplates дает вам дополнительные шаблоны в paster для приложений на Pylons. После установки PylonsTemplates можно создать новый проект на Pylons примерно так:
paster create -t [templatename] [projectname]

pylons_repoze_what


Шаблон pylons_repoze_what добавляет систему авторизации основанную на repoze.what и repoze.what-quickstart. (При этом аутентификация на repoze.who устанавливается автоматически.)
Шаблон включает в себя:
* Модели User, Group и Permission для SQLALchemy
* Контроллер login (& logout)
* Простой шаблон для входа
* Зависимость от repoze.what-pylons, включающая декораторы которые можно использовать в контроллерах и действиях(action).
* Закоментированный код в websetup.py который создает user, group и permission.

Список всех шаблонов:
paster create --list-templates

Создание проекта:
paster create -t pylons_repoze_what myapp

Пример с repoze.what-pylons


После создания проекта используя paster create -t pylons_repoze_what, вам нужно защитить контроллеры и их действия от несанкционированного доступа. Ниже простой пример:
from repoze.what.predicates import has_permission
from repoze.what.plugins.pylonshq import ActionProtector

class HelloWorldController(BaseController):
    @ActionProtector(has_permission('be_cool'))
    def index(self):
        return 'Hello World'
Здесь запрещен доступ к действию index всем у кого нет прав 'be_cool'. Что бы запретить весь контроллер есть декоратор ControllerProtector. Более подробно об этом можно почитать в документации на repoze.what-pylons.

У меня в шаблонах отображается постоянно кто сейчас залогирован. Что бы это сделать, создадим lib/auth.py
from pylons import request

def get_user(default):
    """Return the user object from the `repoze.who` Metadata Plugin

    :param default: default item to send back if user not logged in

    Since we might not be logged in and template choke on trying to output
    None/empty data we can pass in a blank User object to get back as a default
    and the templates should work ok with default empty values on that

    """
    if 'repoze.who.identity' in request.environ:
        return request.environ['repoze.who.identity']['user']
    else:
        return default
И добавим контекстную переменную во все контроллеры при помощи lib/base.py
from gottlieb.model.auth import User
from gottlieb.lib import auth

class BaseController(WSGIController):

    def __call__(self, environ, start_response):
        """Invoke the Controller"""
        # WSGIController.__call__ dispatches to the Controller method
        # the request is routed to. This routing information is
        # available in environ['pylons.routes_dict']

        # if there's no user set, just setup a blank instance
        c.current_user = auth.get_user(User())

        try:
            return WSGIController.__call__(self, environ, start_response)
        finally:
            meta.Session.remove()

Теперь можно в шаблоны вставлять что то вроде того(примеры на Jinja):
{% if c.current_user.user_name %} {{ c.current_user.user_name }} (logout) {% else %} login {% endif %}
или
{% for group in c.current_user.groups %}
    {% if group.group_name == 'admin' %}
        

  • Admin
  • {% endif %} {% endfor %}

    отображение залогированного пользователя

    форма авторизации