Commits

ronald martinez committed 6b4d5d3

admin

  • Participants
  • Parent commits ad59672

Comments (0)

Files changed (15)

controllers/admin/__init__.py

Empty file added.

controllers/admin/auth.py

+import logging
+
+from datetime import datetime
+from controllers import BaseHandler
+from models import SystemUser
+
+
+class Login(BaseHandler):
+
+    def get(self, status_code=None):
+
+        if self.current_user:
+            self.redirect(self.reverse_url('admin_product'))
+            return
+
+        self.render('admin/login.html', status_code=status_code)
+
+    def post(self):
+        username = self.get_argument('username')
+        password = self.get_argument('password')
+        status_code = 0
+
+        if not username or not password:
+            status_code = 1
+        else:
+            _user = SystemUser.auth(username, password)
+            if _user:
+                _user.last_login_at = datetime.now()
+                try:
+                    _user.save()
+                except Exception as exc:
+                    logging.error(exc)
+                    status_code = 3
+                else:
+                    self.set_secure_cookie('user', str(_user.id))
+                    self.redirect(
+                        self.get_argument(
+                            'next',
+                            self.reverse_url('admin_product')
+                        )
+                    )
+                return
+            else:
+                status_code = 2
+
+        self.get(status_code)
+
+
+class Logout(BaseHandler):
+
+    def get(self):
+        if self.current_user:
+            self.clear_all_cookies()
+        self.redirect(self.reverse_url('admin_login'))

controllers/admin/product.py

+import logging
+
+from tornado.web import HTTPError, authenticated
+
+from controllers import BaseHandler, ListMixin
+from models import Product, Category, Match
+
+from boto.s3.connection import S3Connection
+
+import datetime
+import settings
+
+
+class List(BaseHandler, ListMixin):
+
+    #@authenticated
+    def get(self):
+
+        filters = {'created_at__gte': datetime.date(2012, 01, 01)}
+        status = self.get_argument('status', None)
+
+        if status:
+            filters['status'] = status
+
+        #queryset = Product.objects.order_by('-id')
+        queryset = Product.objects.filter(**filters).order_by('-created_at')
+
+        #created_at__gte=datetime.date(2012, 01, 01)
+        #.order_by('-created_at')
+
+        self.render(
+            'admin/product/list.html',
+            status=status,
+            **self.get_pagination(count=len(queryset), query=queryset)
+        )
+
+
+class Edit(BaseHandler):
+
+    @authenticated
+    def get(self, id, **kwargs):
+        user = self.get_current_user()
+
+        try:
+            product = Product.objects.get(id)
+        except:
+            self.send_error(404)
+            return
+
+        if not product.title:
+            product.title = 'product'
+
+        if not product.description:
+            product.description = 'This is a brief description..'
+
+        if not product.cost:
+            product.cost = 50
+
+        if not product.will_travel:
+            product.will_travel = 1000
+
+        if product.published_at:
+            try:
+                product.published_at = product.published_at.date()
+            except Exception as exc:
+                logging.error(exc)
+        else:
+            product.published_at = datetime.datetime.now().strftime('%Y-%m-%d')
+
+        if product.expires_at:
+            try:
+                product.expires_at = product.expires_at.date()
+            except Exception as exc:
+                logging.error(exc)
+        else:
+            product.expires_at = (datetime.datetime.now() + \
+                datetime.timedelta(days=5)).strftime('%Y-%m-%d')
+
+        if not product.location:
+            product.location = 'WC 12 avenue'
+
+        categories = Category.objects.all()
+
+        if not product.categories:
+            product.categories = [u'metarials', u'sporting']
+
+        if product.categories:
+            for x, y in enumerate(categories):
+                if y.title.lower() in product.categories:
+                    categories[x].checked = True
+                else:
+                    categories[x].checked = False
+
+        logging.info('image: %s' % product.image)
+        logging.info('tyny_image: %s' % product.tiny_url)
+        logging.info('small_image: %s' % product.small_url)
+        logging.info('large_image: %s' % product.large_url)
+        logging.info('medium_image: %s' % product.medium_url)
+        logging.info('is_upload_thumbs: %s' % product.is_upload_thumbs)
+
+        kwargs['product'] = product
+        kwargs['categories'] = categories
+        kwargs['audiences'] = Product.AUDIENCES_TYPES
+        kwargs['user'] = user
+        kwargs['action'] = 'edit'
+        self.render('product.html', **kwargs)
+
+    @authenticated
+    def post(self, id):
+        status_code = 0
+
+        categories = self.get_arguments('categories', None)
+        title = self.get_argument('title', '')
+        description = self.get_argument('description', '')
+        cost = self.get_argument('cost', '')
+        will_travel = self.get_argument('will_travel', '')
+        allow_retailers = self.get_argument('allow_retailers', None)
+        allow_charities = self.get_argument('allow_charities', None)
+        donate_now = self.get_argument('donate_now', None)
+        published_at = self.get_argument('published_at', '')
+        expires_at = self.get_argument('expires_at', '')
+        type = self.get_argument('type', None)
+        location = self.get_argument('location', '')
+        audience = self.get_argument('audience', '')
+
+        try:
+            will_travel = int(will_travel)
+        except Exception as exc:
+            logging.error(exc)
+            will_travel = 0
+
+        try:
+            cost = int(cost)
+        except Exception as exc:
+            logging.error(exc)
+            cost = 0
+
+        user = self.get_current_user()
+
+        if not user:
+            self.send_error(404)
+
+        try:
+            product = Product.objects.get(id)
+        except Exception as exc:
+            self.send_error(404)
+
+        if len(title) < 1 or len(description) < 1 or len(published_at) < 1:
+            status_code = 1
+        elif not type in ('have', 'want'):
+            status_code = 1
+
+        if status_code == 0:
+            try:
+                published_at = datetime.datetime.strptime(
+                    published_at, '%Y-%m-%d')
+            except ValueError as exc:
+                logging.error(exc)
+                status_code = 2
+
+            logging.info(expires_at)
+
+            try:
+                expires_at = datetime.datetime.strptime(
+                    expires_at, '%Y-%m-%d')
+            except ValueError as exc:
+                logging.error(exc)
+                status_code = 2
+
+        if status_code == 0:
+            if not categories:
+                status_code = 4
+
+        if status_code == 0:
+            try:
+                _raw_image = self.request.files['filename'][0]
+            except (KeyError, IndexError) as exc:
+                _raw_image = None
+
+            product.title = title
+            product.description = description
+            product.cost = cost
+            product.will_travel = will_travel
+            product.allow_charities = True if allow_charities else False
+            product.allow_retailers = True if allow_retailers else False
+            product.donate_now = True if donate_now else False
+            product.published_at = published_at
+            product.expires_at = expires_at
+            product.type = type
+            product.user = user.email
+            product.categories = categories
+            product.location = location
+            product.audience = audience
+            product.status = 'active'
+            product.set_slug(title)
+
+            try:
+                product.update()
+            except Exception as exc:
+                logging.error(exc)
+                status_code = 5
+
+            if status_code == 0:
+                if _raw_image:
+
+                    if not product.upload_img(_raw_image.get('body')):
+                        status_code = 4
+                    else:
+                        if not product.send_message_to_generate_thumbs():
+                        #if not product.generate_thumbs():
+                            logging.error('no generate slug')
+                            #status_code = 4
+                        #generate_thumbs.delay(product.id)
+
+                    if status_code == 0:
+                        self.redirect(self.reverse_url('product_edit',
+                            product.id))
+                        return
+
+        self.get(id, status_code=status_code)
+
+
+class Delete(BaseHandler):
+
+    #@authenticated
+    #def post(self):
+    def get(self):
+
+        #if self.request.headers.get('X-Requested-With') != 'XMLHttpRequest':
+            #raise HTTPError(403)
+
+        status_code = 0
+
+        try:
+            product = Product.objects.get(self.get_argument('id'))
+        except Exception as exc:
+            status_code = 1
+            logging.error(exc)
+            raise HTTPError(404)
+        else:
+
+            for conversation in product.conversations:
+                print conversation
+                conversation.delete()
+
+            for match in Match.objects.filter(source=product.id):
+                print match
+                match.delete()
+
+            for match in Match.objects.filter(target=product.id):
+                print match
+                match.delete()
+
+            try:
+                product.delete()
+            except Exception as exc:
+                logging.error(exc)
+                status_code = 2
+            else:
+                cn = S3Connection(
+                    settings.AWS_KEY, settings.AWS_SECRET
+                )
+
+                for suffix in ('', '_large', '_medium', '_small', '_tiny'):
+
+                    print '%s/%s%s' % (settings.S3_UPLOAD_PATH_PRODUCT,
+                        product.id, suffix)
+
+                    try:
+                        key = cn.get_bucket(settings.S3_BUCKET_NAME).get_key(
+                            '%s/%s%s' % (settings.S3_UPLOAD_PATH_PRODUCT,
+                                product.id, suffix))
+                        key.delete()
+                    except Exception as exc:
+                        logging.error(exc)
+
+        self.finish({'status_code': status_code})
+
+
+class DeleteImage(BaseHandler):
+
+    #@authenticated
+    def post(self):
+        #if self.request.headers.get('X-Requested-With') != 'XMLHttpRequest':
+            #raise HTTPError(403)
+
+        status_code = 0
+
+        try:
+            product = Product.objects.get(self.get_argument('id'))
+        except Exception as exc:
+            logging.error(exc)
+        else:
+
+            cn = S3Connection(
+                settings.AWS_KEY, settings.AWS_SECRET
+            )
+
+            key = cn.get_bucket(settings.S3_BUCKET_NAME).get_key(
+                '%s/%s' % (settings.S3_UPLOAD_PATH_PRODUCT, product.id))
+            key.delete()
+
+        self.finish({'status_code': status_code})

crontab_generate_thumbs.py

+from boto.sqs.connection import SQSConnection
+import settings
+import logging
+
+from models import Product
+
+from boto.s3.connection import S3Connection
+from boto.s3.key import Key
+from cStringIO import StringIO
+
+import Image as Image_
+
+from utils import thumbnail
+
+
+conn = SQSConnection(settings.AWS_KEY, settings.AWS_SECRET)
+q = conn.create_queue('generate_thumbs')
+messages = q.get_messages(10)
+#visibility_timeout=60
+
+
+print len(messages)
+
+if len(messages) > 0:
+    for m in messages:
+
+        try:
+            product = Product.objects.get(m.get_body())
+        except Exception as exc:
+            print exc
+            logging.error(exc)
+        else:
+
+            print "product: %s" % product.id
+
+            cn = S3Connection(
+                settings.AWS_KEY, settings.AWS_SECRET
+            )
+
+            bucket = cn.get_bucket(settings.S3_BUCKET_NAME)
+            key = Key(bucket)
+            key.key = '%s/%s' % (settings.S3_UPLOAD_PATH_PRODUCT, product.id)
+
+            try:
+                image = Image_.open(StringIO(key.get_contents_as_string()))
+            except Exception as exc:
+                print exc
+            else:
+
+                try:
+                    for width, height, suffix in  Product.THUMB_SIZES_IMAGES:
+                        #print width, height
+                        output_image = StringIO()
+                        thumb = thumbnail(image, width, height, force=True,
+                                crop=False, adjust_to_width=True)
+                        thumb.save(output_image, 'jpeg')
+
+                        key.key = '%s/%s_%s' % (
+                            settings.S3_UPLOAD_PATH_PRODUCT,
+                            product.id, suffix)
+
+                        key.set_contents_from_string(
+                            output_image.getvalue(),
+                            headers={'Content-Type': 'image/jpeg'}
+                        )
+                        key.set_acl('public-read')
+                except Exception as exc:
+                    print exc
+                    logging.error(exc)
+                    #generate_thumbs.retry(exc=exc)
+                else:
+                    product.is_upload_thumbs = True
+
+                    try:
+                        product.update()
+                    except Exception as exc:
+                        logging.error()
+
+                    q.delete_message(m)

libs/__init__.py

Empty file added.

libs/indexable.py

+import boto
+
+
+class Indexable(object):
+
+    def __init__(self):
+
+        self.cn = boto.connect_cloudsearch()
+        domain = conn.create_domain('products')
+
+        #policy = domain.get_access_policies()
+        #policy.allow_search_ip(our_ip)
+        #policy.allow_doc_ip(our_ip)
+
+    def save_document(self):
+        title = domain.create_index_field('title', 'text')
+        description = domain.create_index_field('description', 'text')

libs/pagination.py

+from math import ceil
+
+
+class Paginator(object):
+
+    def __init__(self, page, total_items, adjacents=2, per_page=10):
+        self.page = page
+        self.per_page = per_page
+        self.total_items = total_items
+        self.adjacents = adjacents
+
+    @property
+    def total_pages(self):
+        return int(ceil(self.total_items / (self.per_page * 1.0)))
+
+    @property
+    def pages(self):
+        if self.page > self.total_pages:
+            pages = []
+        elif self.total_pages <= 2 * self.adjacents:
+            pages = range(1, self.total_pages + 1)
+        else:
+            if self.page <= self.adjacents:
+                frm = 1
+            else:
+                if self.total_pages - self.page < self.adjacents:
+                    frm = self.total_pages - 2 * self.adjacents
+                else:
+                    frm = self.page - self.adjacents
+
+            inc = 2 * self.adjacents + 1
+            pages = range(frm, frm + inc)
+
+        return pages

templates/admin/form_macro.html

+{% macro input_multiple(name, id, type, label, value, end_label, cls, attrs, input) -%}
+<div class="group">
+  {% if label %}
+  <label for="{{ id|default(name) }}">{{ label }}</label>
+  {% endif %}
+
+  <div class="input-multiple-content input-block {{- cls -}}">
+    <div id="{{ id|default(name) }}">
+      <div class="input-multiple-item">
+        <input style=" margin: 0 10px 0 0" class="input-file" type="{{ type|default('text') }}" name="{{ name }}" value="{{- value -}}" {{ attrs }} />
+        <a href="#" style="display: none" class="input-remove">Quitar campo</a>
+      </div>
+    </div>
+
+    {% if end_label %}
+    <label for="{{ id|default(name) }}" style="clear: both; display: block;">{{ end_label }}</label>
+    {% endif %}
+
+    <a href="#" rel="{{ id|default(name) }}" style="margin: 10px 0 0" class="input-add ">
+      Agregar nuevo campo
+    </a>
+  </div>
+</div>
+{%- endmacro %}

templates/admin/index.html

+{% extends 'admin/layout.html' %}
+{% set title = 'Inicio' %}
+

templates/admin/layout.html

+<!DOCTYPE html>
+{% set title = title|default('')%}
+{% set page_active = page_active|default('')%}
+
+<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
+<!--[if lt IE 7]> <html id="html" class="no-js lt-ie9 lt-ie8 lt-ie7" lang="es"> <![endif]-->
+<!--[if IE 7]>    <html id="html" class="no-js lt-ie9 lt-ie8" lang="es"> <![endif]-->
+<!--[if IE 8]>    <html id="html" class="no-js lt-ie9" lang="es"> <![endif]-->
+<!--[if gt IE 8]><!--> <html id="html" lang="es"> <!--<![endif]-->
+<head>
+	<meta charset="utf-8" />
+	
+	<!-- Set the viewport width to device width for mobile -->
+	<meta name="viewport" content="width=device-width" />
+	
+  <title>{% if title %}{{ title }} - {% endif %}Gestor de contenidos - BCP Hipotecas</title>
+  
+	<!-- Included CSS Files -->
+  <link rel="stylesheet" href="{{ handler.static_url('css/admin.foundation.css') }}">
+  <link rel="stylesheet" href="{{ handler.static_url('css/admin.css') }}">
+
+	<!--[if lt IE 9]>
+  <link rel="stylesheet" href="{{ handler.static_url('css/admin.ie.css') }}">
+	<![endif]-->
+
+	
+	<!-- IE Fix for HTML5 Tags -->
+	<!--[if lt IE 9]>
+		<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
+	<![endif]-->
+
+  {% block head %}{% endblock %}
+
+</head>
+<body id="body">
+  <div id="bcp">
+    <span id="logo_top">BCP</span>
+  </div>
+
+
+	<div class="container">
+		
+		<div id="header" class="row">
+      <h1 id="logo">Gestor de contenidos - BCP Hipotecas</h1>
+
+		</div>
+
+		<div id="content" class="row">
+      {% block menu %}
+
+      {% endblock %}
+      
+      {% block content %}{% endblock %}
+		</div>
+
+	</div>
+
+
+  <script src="{{ handler.static_url('js/admin.foundation.js') }}"></script>
+  <script src="{{ handler.static_url('js/admin.js') }}"></script>
+  {% block javascripts %}{% endblock %}
+</body>
+</html>

templates/admin/list_macro.html

+{% macro pagination(pagination, base_url, page_url='?page=', query='') %}
+{% if query %}
+  {% set p = 'page=%s' % pagination.current_page %}
+  {% set q = query|replace('%s&' % p,'') %}
+  {% set q = q|replace(p,'') %}
+  {% if q %}
+    {% set _query = '&%s' % q %}
+  {% endif %}
+{% endif %}
+
+<ul class="pagination">
+  {% if pagination.current_page > 1 %}
+  <li>
+    <a href="{{ base_url }}{{ page_url }}{{ pagination.current_page - 1 }}{{ _query }}" class="prev">&laquo; anterior</a>
+  </li>
+  {% else %}
+  <li class="unavailable">
+    <a>&laquo; anterior</a>
+  </li>
+  {% endif %}
+
+  {% for page in pagination.pages %}
+    {% if page == pagination.current_page %}
+    <li class="current">
+      <a>{{ page }}</a>
+    </li>
+    {% else %}
+    <li>
+      <a href="{{ base_url }}{{ page_url }}{{ page }}{{ _query }}">{{ page }}</a>
+    </li>
+    {% endif %}
+  </li>
+  {% endfor %}
+
+  {% if pagination.current_page < pagination.pages[pagination.pages|length - 1] %}
+  <li>
+    <a class="next" href="{{ base_url }}{{ page_url }}{{ pagination.current_page + 1 }}{{ _query }}">siguiente &raquo;</a>
+  </li>
+  {% else %}
+  <li class="unavailable">
+    <a>siguiente &raquo;</a>
+  </li>
+  {% endif %}
+  </li>
+</ul>
+{% endmacro %}
+

templates/admin/login.html

+  <div id="main" class="row">
+
+    <div class="five columns">
+      <h2>{{ title }}</h2>
+      <p>Todos los campos marcados con (*) son requeridos</p>
+    </div>
+
+    <form action="" class="thre columns" method="post">
+      {% if status_code == 1 %}
+        El nombre de usuario y/o la contrase&ntilde;a no son v&aacute;lidos
+      {% endif %}
+
+      {{ handler.xsrf_form_html() }}
+
+      <label for="username">Usuario</label>
+      <input type="text" class="input-text" id="username" name="username" value="" />
+
+      <label for="password">Contrase&ntilde;a</label>
+      <input type="password" class="input-text" id="password" name="password" value="" />
+
+        <input type="submit">
+
+    </form>
+  </div>

templates/admin/product/create.html

+{% extends 'admin/layout.html' %}
+{% if externo %}
+{% set page_active = 'otro' %}
+{% else %}
+{% set page_active = 'bcp' %}
+{% endif %}
+{% import 'admin/form_macro.html' as form %}
+
+{% set title = title|default('Crear proyecto') %}
+{% set data = data|default({}) %}
+{% set data_contact = data.contact|default({}) %}
+{% set data_specs = data.specs|default({}) %}
+{% set data_characteristics = data.characteristics|default({}) %}
+{% set data_ubigeo = data.ubigeo|default({}) %}
+{% set action = action|default('create') %}
+
+{% block javascripts %}
+  <script src="{{ handler.static_url('js/admin.projects.create.js') }}"></script>
+{% endblock %}
+
+{% block content %}
+  <div id="main">
+    <h2>{{ title }}</h2>
+
+    {% if status_code == 0 %}
+      <div class="alert-box success">Se guardaron los datos con &eacute;xito</div>
+    {% elif status_code == 1 %}
+      <div class="alert-box error">Ocurri&oacute; un error inesperado</div>
+    {% endif %}
+
+    <form id="form" action="" method="post" accept-charset="utf-8" enctype="multipart/form-data">
+      {{ handler.xsrf_form_html() }}
+
+      <div class="alert-box">Todos los campos marcados con (*) son requeridos</div>
+
+      <div class="buttons buttons-set">
+        {% if externo %}
+          <a href="{{ handler.reverse_url('admin_project_main_externos') }}" class="nice small black button">Cancelar</a>
+        {% else %}
+          <a href="{{ handler.reverse_url('admin_project_main') }}" class="nice small black button">Cancelar</a>
+        {% endif %}
+        <button type="submit" class="nice small black button">Guardar</button>
+      </div>
+
+      <fieldset>
+
+        <h6 class="legend">Ubigeo</h6>
+
+        <div class="row">
+
+           <div class="four columns">
+
+              <label for="department">* Departamento</label>
+              <select name="department" id="department" class="large">
+                <option value="">Elija una opci&oacute;n</option>
+                {% for item in departments %}
+                <option value="{{ item.department_code }}" {% if data_ubigeo.department_code == item.department_code|string %}} selected=selected {% else %} {%endif%}>{{ item.name }}</option>
+                {% endfor %}
+              </select>
+
+           </div>
+           <div class="four columns">
+
+              <label for="province">* Provincia</label>
+              <select name="province" id="province" class="large">
+                {% if provinces %}
+                  <option value="">Elija una opci&oacute;n</option>
+                  {% for item in provinces %}
+                  <option value="{{ item.id }}" {% if data_ubigeo.province_code == item.province_code|string %}} selected=selected {% else %} {%endif%}>{{ item.name }}</option>
+                  {% endfor %}
+                {% else %}
+                  <option value="">Elija un departamento</option>
+                {% endif %}
+              </select>
+
+           </div>
+           <div class="four columns">
+
+              <label for="district">* Distrito</label>
+              <select name="ubigeo" id="district" class="large">
+                {% if districts %}
+                  <option value="">Elija una opci&oacute;n</option>
+                  {% for item in districts %}
+                  <option value="{{ item.id }}" {% if data_ubigeo.district_code == item.district_code|string %}} selected=selected {% else %} {%endif%}>{{ item.name }}</option>
+                  {% endfor %}
+                {% else %}
+                  <option value="">Elija una provincia</option>
+                {% endif %}
+              </select>
+
+           </div>
+
+        </div>
+        {% if project_form and project_form.ubigeo.errors %}
+        <small class="error">{% for error in project_form.ubigeo.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+        {% endif %}
+        
+
+      </fieldset>
+
+
+      <fieldset>
+        <h6 class="legend">Datos generales</h6>
+
+        <div class="row">
+
+          <div class="nine columns">
+
+
+            <label for="title">* Nombre</label>
+            <input type="text" name="title" id="title" class="input-text large" value="{{ project_form.title.data }}" />
+            {% if project_form and project_form.title.errors %}
+            <small class="error">{% for error in project_form.title.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+            <label for="address">* Direcci&oacute;n</label>
+            <input type="text" name="address" id="address" class="input-text large" value="{{ project_form.address.data }}" />
+            {% if project_form and project_form.address.errors %}
+            <small class="error">{% for error in project_form.address.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+          <div class="three columns">
+
+            <label for="type">* Tipo</label>
+            <select name="type" id="type">
+              <option value="">Elija una opci&oacute;n</option>
+              {% for item in types %}
+              <option value="{{ item[0] }}" {% if project_form.type.data == item[0] %}selected="selected"{% endif %}>{{ item[1] }}</option>
+              {% endfor %}
+            </select>
+            {% if project_form and project_form.type.errors %}
+            <small class="error">{% for error in project_form.type.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+            
+          </div>
+        </div>
+
+
+
+        <div class="row">
+
+          <div class="two columns">
+
+            <label for="bedrooms">Dormitorios</label>
+            <input type="text" name="bedrooms" id="bedrooms" class="input-text small number" value="{{ project_form.bedrooms.data }}" />
+            {% if project_form and project_form.bedrooms.errors %}
+            <small class="error">{% for error in project_form.bedrooms.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+            
+
+          </div>
+
+          <div class="two columns">
+
+            <label for="bathrooms">Ba&ntilde;os</label>
+            <input type="text" name="bathrooms" id="bathrooms" class="input-text small number" value="{{ project_form.bathrooms.data }}" />
+            {% if project_form and project_form.bathrooms.errors %}
+            <small class="error">{% for error in project_form.bathrooms.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+            
+          </div>
+
+          <div class="two columns">
+
+            <label for="garages">Cocheras</label>
+            <input type="text" name="garages" id="garages" class="input-text small number" value="{{ project_form.garages.data }}" />
+            {% if project_form and project_form.garages.errors %}
+            <small class="error">{% for error in project_form.garages.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+        </div>
+
+
+        {#
+        <label for="description">* Descripci&oacute;n</label>
+        <textarea name="description" id="description" class="large" rows="7">{{- project_form.description.data -}}</textarea>
+        {% if project_form and project_form.description.errors %}
+        <small class="error">{% for error in project_form.description.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+        {% endif %}
+        #}
+        
+
+        <div class="row" style="padding-bottom: 20px">
+          <label for="status">Estado</label>
+          <label for="status_0" class="inline">
+            <input name="status" id="status_0" type="radio" {% if project_form.status.data == True %}checked=checked{% endif %} value='true' />
+            Publicado
+          </label>
+
+          <label for="status_1" class="inline">
+            <input name="status" id="status_1" type="radio" {% if project_form.status.data == False %}checked=checked{% endif %} value='' />
+            Pendiente
+          </label>
+          {% if project_form and project_form.status.errors %}
+          <small class="error">{% for error in project_form.status.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+          {% endif %}
+
+          <br/>
+          <br/>
+          <br/>
+
+          <label for="externo_0">
+            &iquest;Inmueble externo al BCP?
+          </label>
+          <label for="externo_0" class="inline">
+            <input name="externo" id="externo_0" type="checkbox" {% if externo == True %}checked=checked{% endif %} value='true' />
+            S&iacute;, el inmueble es externo
+          </label>
+        </div>
+      </fieldset>
+
+      <fieldset>
+        <h6 class="legend">Precios</h6>
+
+        <div class="row">
+          <div class="two columns">
+            <label for="price_pen">* Precio S/.</label>
+            <input type="text" name="price_pen" id="price_pen" class="input-text small number" value="{{ project_form.price_pen.data }}" />
+            {% if project_form and project_form.price_pen.errors %}
+            <small class="error">{% for error in project_form.price_pen.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+          </div>
+
+          <div class="two columns">
+
+            <label for="price_min_pen">Precio desde S/.</label>
+            <input type="text" name="specs_price_min_pen" id="price_min_pen" class="input-text small number" value="{{ specs_form.price_min_pen.data }}" />
+            {% if specs_form and specs_form.price_min_pen.errors %}
+            <small class="error">{% for error in specs_form.price_min_pen.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+          <div class="two columns">
+
+            <label for="price_max_pen">Precio hasta S/.</label>
+            <input type="text" name="specs_price_max_pen" id="price_max_pen" class="input-text small number" value="{{ specs_form.price_max_pen.data }}" />
+            {% if specs_form and specs_form.price_max_pen.errors %}
+            <small class="error">{% for error in specs_form.price_max_pen.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+        </div>
+
+        <div class="row">
+          <div class="two columns">
+            <label for="price">Precio US$</label>
+            <input type="text" name="price" id="price" class="input-text small number" value="{{ project_form.price.data }}" />
+            {% if project_form and project_form.price.errors %}
+            <small class="error">{% for error in project_form.price.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+          </div>
+
+          <div class="two columns">
+
+            <label for="price_min">Precio desde US$</label>
+            <input type="text" name="specs_price_min" id="price_min" class="input-text small number" value="{{ specs_form.price_min.data }}" />
+            {% if specs_form and specs_form.price_min.errors %}
+            <small class="error">{% for error in project_form.price_min.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+          <div class="two columns">
+
+            <label for="price_max">Precio hasta US$</label>
+            <input type="text" name="specs_price_max" id="price_max" class="input-text small number" value="{{ specs_form.price_max.data }}" />
+            {% if specs_form and specs_form.price_max.errors %}
+            <small class="error">{% for error in project_form.price_max.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+        </div>
+
+
+      </fieldset>
+
+      <fieldset>
+        <h6 class="legend">Contacto</h6>
+
+        <div class="row">
+
+          <div class="six columns">
+
+            <label for="contact_name">* Nombre</label>
+            <input type="text" name="contact_name" id="contact_name" class="input-text large" value="{{ contact_form.name.data }}" />
+            {% if contact_form and contact_form.name.errors %}
+            <small class="error">{% for error in contact_form.name.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+            
+
+            <label for="contact_email">* Correo electr&oacute;nico</label>
+            <input type="text" name="contact_email" id="contact_email" class="input-text large" value="{{ contact_form.email.data }}" />
+            {% if contact_form and contact_form.email.errors %}
+            <small class="error">{% for error in contact_form.email.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+          <div class="six columns">
+
+            <label for="contact_phone">* Tel&eacute;fono</label>
+            <input type="text" name="contact_phone" id="contact_phone" class="input-text large" value="{{ contact_form.phone.data }}" />
+            {% if contact_form and contact_form.phone.errors %}
+            <small class="error">{% for error in contact_form.phone.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+            <label for="contact_address">* Direcci&oacute;n</label>
+            <input type="text" name="contact_address" id="contact_address" class="input-text large" value="{{ contact_form.address.data }}" />
+            {% if contact_form and contact_form.address.errors %}
+            <small class="error">{% for error in contact_form.address.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+        </div>
+
+      </fieldset>
+
+      <fieldset>
+        <h6 class="legend">Especificaciones</h6>
+
+        <div class="row">
+
+          <div class="six columns">
+
+            <label for="builder">Construye</label>
+            <input type="text" name="specs_builder" id="builder" class="input-text large" value="{{ specs_form.builder.data }}" />            
+            {% if specs_form and specs_form.builder.errors %}
+            <small class="error">{% for error in project_form.builder.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+            <label for="floor">Decoraci&oacute;n</label>
+            <input type="text" name="specs_floor" id="floor" class="input-text large" value="{{ specs_form.floor.data }}" />            
+            {% if specs_form and specs_form.floor.errors %}
+            <small class="error">{% for error in project_form.floor.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+          <div class="six columns">
+
+            <label for="web">Web</label>
+            <input type="text" name="specs_web" id="web" class="input-text large" value="{{ specs_form.web.data }}" />            
+            {% if specs_form and specs_form.web.errors %}
+            <small class="error">{% for error in project_form.web.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+        </div>
+
+        <div class="row">
+
+          <div class="two columns">
+
+            <label for="num_available">Unid. disponibles</label>
+            <input type="text" name="specs_num_available" id="num_available" class="input-text small number" value="{{ specs_form.num_available.data }}" />            
+            {% if specs_form and specs_form.num_available.errors %}
+            <small class="error">{% for error in project_form.num_available.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+          <div class="two columns">
+
+            <label for="area_min">&Aacute;rea desde</label>
+            <input type="text" name="specs_area_min" id="area_min" class="input-text small number" value="{{ specs_form.area_min.data }}" />            
+            {% if specs_form and specs_form.area_min.errors %}
+            <small class="error">{% for error in project_form.area_min.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+
+          <div class="two columns">
+
+            <label for="area_max">&Aacute;rea hasta</label>
+            <input type="text" name="specs_area_max" id="area_max" class="input-text small number" value="{{ specs_form.area_max.data }}" />            
+            {% if specs_form and specs_form.area_max.errors %}
+            <small class="error">{% for error in project_form.area_max.errors %}{% if not loop.first %}<br/>{% endif %}{{ error }}{% endfor %}</small>
+            {% endif %}
+
+          </div>
+        </div>
+
+      </fieldset>
+
+      <fieldset>
+        <h6 class="legend">Caracter&iacute;sticas</h6>
+
+        <div class="row">
+
+          <div class="six columns">
+
+            <label for="serviceroom">
+              <input name="chars_serviceroom" id="serviceroom" type="checkbox" {% if characteristics_form.serviceroom.data == True %}checked="checked"{% endif %}  />
+              Cuarto de servicio
+            </label>
+
+            <label for="gas">
+              <input name="chars_gas" id="gas" type="checkbox" {% if characteristics_form.gas.data == True %}checked="checked"{% endif %} />
+              Instalaci&oacute;n de gas
+            </label>
+              
+            <label for="swimming_pool">
+              <input name="chars_swimming_pool" id="swimming_pool" type="checkbox" {% if characteristics_form.swimming_pool.data == True %}checked="checked"{% endif %} />
+              Piscina
+            </label>
+
+            <label for="closet">
+              <input name="chars_closet" id="closet" type="checkbox" {% if characteristics_form.closet.data == True %}checked="checked"{% endif %} />
+              Closets en dormitorio
+            </label>
+              
+            <label for="laundry">
+              <input name="chars_laundry" id="laundry" type="checkbox" {% if characteristics_form.laundry.data == True %}checked="checked"{% endif %} />
+              Lavander&iacute;a
+            </label>
+
+          </div>
+
+          <div class="six columns">
+
+            <label for="park_view">
+              <input name="chars_park_view" id="park_view" type="checkbox" {% if characteristics_form.park_view.data == True %}checked="checked"{% endif %} />
+              Vista parque
+            </label>
+
+            <label for="terrace">
+              <input name="chars_terrace" id="terrace" type="checkbox" {% if characteristics_form.terrace.data == True %}checked="checked"{% endif %} />
+              Terraza
+            </label>
+              
+            <label for="garden">
+              <input name="chars_garden" id="garden" type="checkbox" {% if characteristics_form.garden.data == True %}checked="checked"{% endif %} />
+              Jard&iacute;n
+            </label>
+
+            <label for="furnished">
+              <input name="chars_furnished" id="furnished" type="checkbox" {% if characteristics_form.furnished.data == True %}checked="checked"{% endif %} />
+              Amoblado
+            </label>
+              
+            <label for="kitchen_cabinet">
+              <input name="chars_kitchen_cabinet" id="kitchen_cabinet" type="checkbox" {% if characteristics_form.kitchen_cabinet.data == True %}checked="checked"{% endif %} />
+              Reposteros en cocina
+            </label>
+
+          </div>
+        </div>
+      </fieldset>
+
+
+      {% if action == 'edit' %}
+      <fieldset>
+        <h6 class="legend">Im&aacute;genes</h6>
+
+        {{ form.input_multiple(label='Agregar im&aacute;genes', end_label='Medidas recomendadas 277 x 186 pixeles', name='imagen', type='file', attrs='multiple') }}
+
+        {% for item in data.images %}
+        {% set url =  (handler.static_url('uploads') + '/' + item.id|string) + '/' + ('small%s' % item.fext) | default('') %}
+        {{ loop.cycle('<div class="row">','') }}
+            <div id="image_{{ loop.index }}" class="six columns image_content">
+              <div class="alert-box">
+                <label for="image_selected_{{ loop.index }}">
+                  <input name="image_selected" id="image_selected_{{ loop.index }}" class="image-selected" type="radio" value="{{ item.id|string }}" data-delete-image="delete_image_{{ loop.index }}" {% if data.selected_image.id == item.id %}checked="checked"{% endif %} />
+                  Resaltar imagen
+                </label>
+                <img class="image_" src="{{ url }}" />
+                <a id="delete_image_{{ loop.index }}" href="{{ handler.reverse_url('admin_project_delete_image') }}" class="delete-image nice small red button" 
+                  data-modal="imageRemoveModal" 
+                  data-id="{{ item.id }}" 
+                  data-project_id="{{ data.id }}" 
+                  data-index="image_{{ loop.index }}" 
+                  >Eliminar imagen</a>
+              </div>
+            </div>
+          {% if loop.last %}
+            </div>
+          {% else %}
+            {{ loop.cycle('', '</div>') }}
+          {% endif %}
+        {% endfor %}
+
+      </fieldset>
+      {% endif %}
+
+      <div class="buttons buttons-set">
+
+        {% if externo %}
+          <a href="{{ handler.reverse_url('admin_project_main_externos') }}" class="nice small black button">Cancelar</a>
+        {% else %}
+          <a href="{{ handler.reverse_url('admin_project_main') }}" class="nice small black button">Cancelar</a>
+        {% endif %}
+
+        <button type="submit" class="nice small black button">Guardar</button>
+
+      </div>
+    </form>
+  </div>
+
+  <div id="imageRemoveModal" class="reveal-modal">
+    <h3>&iquest;Eliminar imagen?</h3>
+     
+     <div class="buttons">
+       <a href="#" id="modal_no" class="nice normal black button close-reveal-modal ">No</a>
+       <a href="#" id="modal_go" class="nice normal red button">S&iacute;, eliminar imagen</a>
+     </div>
+     <a class="close-reveal-modal">&#215;</a>
+  </div>
+
+{% endblock %}

templates/admin/product/edit.html

+{% extends 'admin/projects/create.html' %}
+
+{% set title = 'Editar proyecto' %}
+{% set data = data|default({}) %}
+{% set action = action|default('edit') %}
+

templates/admin/product/list.html

+<form>
+
+    {{ status }}
+Status:<select name="status">
+    <option value="">[All]</option>
+    <option value="active" {% if status == 'active' %}selected{% endif %} >active</option>
+    <option value="inactive" {% if status == 'inactive' %}selected{% endif %} >inactive</option>
+</select>
+
+<input type="submit">
+</form>
+
+<br / >
+<br / >
+
+<table width="100%" border="1">
+    <tr>
+        <td><strong></strong></td>
+        <td><strong></strong></td>
+        <td><strong>id</strong></td>
+        <td><strong>slug</strong></td>
+        <td><strong>title</strong></td>
+        <td><strong>description</strong></td>
+        <td><strong>cost</strong></td>
+        <td><strong>will_travel</strong></td>
+        <td><strong>allow_retailers</strong></td>
+        <td><strong>allow_charities</strong></td>
+        <td><strong>donate_now</strong></td>
+        <td><strong>status</strong></td>
+        <td><strong>created_at</strong></td>
+        <td><strong>last_modified_at</strong></td>
+        <td><strong>published_at</strong></td>
+        <td><strong>expires_at</strong></td>
+        <td><strong>type</strong></td>
+        <td><strong>user</strong></td>
+        <td><strong>categories</strong></td>
+        <td><strong>location</strong></td>
+        <td><strong>audience</strong></td>
+        <td><strong>is_upload_thumbs</strong></td>
+    </tr>
+
+    {% for x in items %}
+
+    <tr>
+        <td><a href="product/delete?id={{ x.id }}">[Delete]</a></td>
+        <td><a href="product/edit/{{ x.id }}">[Edit]</a></td>
+        <td>{{ x.id }}</td>
+        <td>{{ x.slug }}</td>
+        <td>{{ x.title }}</td>
+        <td>{{ x.description }}</td>
+        <td>{{ x.cost }}</td>
+        <td>{{ x.will_travel }}</td>
+        <td>{{ x.allow_retailers }}</td>
+        <td>{{ x.allow_charities }}</td>
+        <td>{{ x.donate_now }}</td>
+        <td>{{ x.status }}</td>
+        <td>{{ x.created_at_f }}</td>
+        <td>{{ x.last_modified_at }}</td>
+        <td>{{ x.published_at }}</td>
+        <td>{{ x.expires_at }}</td>
+        <td>{{ x.type }}</td>
+        <td>{{ x.user }}</td>
+        <td>{{ x.categories }}</td>
+        <td>{{ x.location }}</td>
+        <td>{{ x.audience }}</td>
+        <td>{{ x.is_upload_thumbs }}</td>
+    </tr>
+
+    {% endfor %}
+</table>
+
+{% for x in pages %}
+<a href="?page={{ x }}&status={{ status }}"> {{ x }}</a> |
+{% endfor %}