Quem sou eu

Minha foto

Formado em Computação, desenvolvedor web, interessado em tecnologia, metaleiro e um gamer inveterado.

Pesquisar

quinta-feira, 14 de setembro de 2017

Django + Masonry + Imagesloaded + Twitter Style Pagination

O que é o Masonry?

Como o proprio site do plugin explica:
Masonry is a JavaScript grid layout library. It works by placing elements in optimal position based on available vertical space, sort of like a mason fitting stones in a wall. You’ve probably seen it in use all over the Internet.

 O objetivo no final deste post é ter um layout organizado pelo plugin, com registros paginados e requisições via ajax para obter os registros das páginas seguintes, utilizando uma Class Based View no Django.

Requisitos:




Bom, primeiramente faremos a View:

class PostListView(View):
	def get(self, request, *args, **kwargs):

		posts = Post.objects.all()

		RPP = 18
		PAGINA = 1
		TOTAL = posts.count()

		if request.GET.get('page'):
			PAGINA = int(request.GET.get('page'))

		page_template = 'blog/paginacao.html'

		template = 'blog/list.html'
		if request.is_ajax():
			template = page_template

		tem_mais = posts[PAGINA*RPP+1:]
		tem_mais = tem_mais.count()
		
		posts = posts[(PAGINA-1)*RPP:PAGINA*RPP]

		if posts.count() == 0:
			return HttpResponse('')

		VARS = {
			'posts':posts,
			'pag':PAGINA,
			'tem_mais': tem_mais,
			'total': TOTAL,
		}
		return render(request, template, VARS)


Serão necessários dois templates:

blog/list.html
{% extends "base.html" %}

{% block scripts %}
    <script src="{% static "site/vendor/masonry/jquery.masonry.pkgd.min.js" %}" type="text/javascript"></script>
    <script src="{% static "site/vendor/masonry/imagesloaded.pkgd.min.js" %}" type="text/javascript"></script>
    <script src="{% static "site/src/js/layout.js" %}" type="text/javascript"></script>
    <script src="{% static "site/src/js/blog.list.js" %}" type="text/javascript"></script>
{% endblock scripts %}

{% block conteudo %}
	<!-- Masonry Grid -->
	<div class="masonry-grid">
	    <div class="masonry-grid-sizer col-xs-6 col-sm-4 col-md-3 col-lg-2"></div>

	    {% for p in posts %}
	        <!-- Post Item -->
	        <div class="masonry-grid-item col-xs-6 col-sm-4 col-md-3 col-lg-2">
	            
	        </div>
	        <!-- End Post Item -->
	    {% endfor %}

	</div>
	<!-- End Masonry Grid -->
	<div class='loading-bg'>
	    <div class='loading'></div>
	</div>
{% endblock conteudo %}

blog/paginacao.html
<!-- Masonry Grid -->
	<div class="masonry-grid">
	    <div class="masonry-grid-sizer col-xs-6 col-sm-4 col-md-3 col-lg-2"></div>

	    {% for p in posts %}
	        <!-- Post Item -->
	        <div class="masonry-grid-item col-xs-6 col-sm-4 col-md-3 col-lg-2">
	            
	        </div>
	        <!-- End Post Item -->
	    {% endfor %}

	</div>
	<!-- End Masonry Grid -->
	<div class='loading-bg'>
	    <div class='loading'></div>
	</div>

layout.js:
var Layout = function () {
    'use strict';

    var mobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));

    // handle on page scroll
    var handleHeaderOnScroll = function() {
        if ($(window).scrollTop() > 60) {
            $('body').addClass('page-on-scroll');
        } else {
            $('body').removeClass('page-on-scroll');
        }
    }

    // handle carousel
    var handleCarousel = function() {
        var $item = $('.carousel .item');
        var $wHeight = $(window).height();
        $item.eq(0).addClass('active');
        $item.height($wHeight);
        $item.addClass('full-screen');

        $('.carousel img').each(function() {
            var $src = $(this).attr('src');
            var $color = $(this).attr('data-color');
            $(this).parent().css({
                'background-image' : 'url(' + $src + ')',
                'background-color' : $color
            });
            $(this).remove();
        });

        $(window).on('resize', function (){
            $wHeight = $(window).height();
            $item.height($wHeight);
        });
    }

    if($(window).width() > 992) {
        $('.screen__height').height($(window).height()/1.3);

        $(window).resize(function(){
            $('.screen__height').height($(window).height()/1.3);
        });
    }



    // handle group element heights
    var handleHeight = function() {
       $('[data-auto-height]').each(function() {
            var parent = $(this);
            var items = $('[data-height]', parent);
            var height = 0;
            var mode = parent.attr('data-mode');
            var offset = parseInt(parent.attr('data-offset') ? parent.attr('data-offset') : 0);

            items.each(function() {
                if ($(this).attr('data-height') == "height") {
                    $(this).css('height', '');
                } else {
                    $(this).css('min-height', '');
                }

                var height_ = (mode == 'base-height' ? $(this).outerHeight() : $(this).outerHeight(true));
                if (height_ > height) {
                    height = height_;
                }
            });

            height = height + offset;

            items.each(function() {
                if ($(this).attr('data-height') == "height") {
                    $(this).css('height', height);
                } else {
                    $(this).css('min-height', height);
                }
            });

            if(parent.attr('data-related')) {
                $(parent.attr('data-related')).css('height', parent.height());
            }
       });
    }

    return {
        init: function () {
            handleHeaderOnScroll(); // initial setup for fixed header
            handleCarousel(); // initial setup for carousel
            handleHeight(); // initial setup for group element height

            // handle minimized header on page scroll
            $(window).scroll(function() {
                handleHeaderOnScroll();
            });
        }
    };
}();

$(document).ready(function() {
    Layout.init();
});

blog.list.js
jQuery(document).ready(function($) {

    var carregando = false;
    $(window).on('scroll', function(event) {
        event.preventDefault();
        /* Act on the event */
        if($(window).scrollTop() + $(window).height() > $(document).height() - 100 && carregando == false && parseInt($('#tem_mais').val()) > 0) {
            // console.log("near bottom!");
            carregando = true
            retrive_posts();
        }
    });


    $('body').on('click', '.next', function(event) {
        event.preventDefault();
        carregando = true;
        /* Act on the event */
        retrive_posts();
    });

    function retrive_posts(){
        var pag = parseInt($('#pag').val())+1;
        $('.loading-bg').show();

        $.get('?page='+pag, function(data) {
            /*optional stuff to do after success */
            // console.log(data);
            // console.log(data == '');

            // console.log($('#tem_mais').length);
            console.log($('#tem_mais').val());

            if(data){
                $('#pag').val(parseInt(pag));
                // console.log(data);

                var aux = $('<div/>');
                $(aux).html(data);

                var tem_mais = $(aux).find('.tem_mais')[0];
                $('#tem_mais').val($(tem_mais).val());

                if (parseInt($('#tem_mais').val()) == 0) {
                    $('button.next').hide();
                }

                var $container = $('.masonry-grid');
                $container.imagesLoaded( function() {
                    $container.masonry({
                        itemSelector: '.masonry-grid-item', // use a separate class for itemSelector, other than .col-
                        columnWidth: '.masonry-grid-sizer',
                        percentPosition: true,
                        transitionDuration: 0,
                    });
                });

                // console.log($(aux).find('.masonry-grid-item.appended'));
                $(aux).find('.masonry-grid-item.appended').each(function(index, el) {
                    $(el).css({ opacity: 0 });
                    // console.log($(el));
                    $(el).imagesLoaded(function(){
                      // show elems now they're ready
                      $(el).css({ opacity: 1 });
                      $container.append( $(el) );
                      $container.masonry( 'appended', $(el), true );
                      $(el).removeClass('.appended')
                    });
                });
                carregando = false;
                $('.loading-bg').hide();
            }else{
                $('button.next').hide();
            }

        });
    }
});

sexta-feira, 9 de junho de 2017

Como pegar o ID do último commit no GIT para forçar atualização de arquivos

Normalmente os navegadores modernos fazem cache dos arquivos para diminuir banda utilizada e aumentar o desempenho.

Isso é sempre uma boa, a não ser quando vc precisa forçá-lo a atualizar, principalmente quando alguma mudança é feita em CSS e JS.

No mobile ainda é mais custoso, haja visto que muitos usuários não sabem como limpar o cache e forçar o recarregamento.

Para isso, podemos "mudar" a url do arquivo com cache, para forçá-lo a atualizar e com isso obtermos o resultado desejado.

No seu arquivo de tags, inclua a simple_tag abaixo:

@register.simple_tag
def git_ver():
    '''
    Retrieve and return the latest git commit hash ID and tag as a dict.
    '''
    import os, subprocess
    from django.conf import settings

    git_dir = os.path.dirname(settings.PROJECT_PATH)

    try:
        # Date and hash ID
        head = subprocess.Popen(
            "git --git-dir={dir}/.git rev-parse HEAD".format(dir=git_dir),
            shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        version = head.stdout.readline().strip().decode('utf-8')
        git_string = "{v}".format(v=version)
    except:
        git_string = u'unknown'

    return git_string


E no seu template:

{% load site_tags %}
<link href="styles.css?v={% git_ver %}" rel="stylesheet">

hasta!

sexta-feira, 5 de maio de 2017

'Settings' object has no attribute 'TEMPLATE_DEBUG'

Para quem atualizou a versão do Django para 1.11 e utiliza a sorl-thumbnail, a versão disponível desse plugin no PyPI ainda não contempla a setting TEMPLATE como um dicionário, ocasionando o erro do título deste post pela falta da setting TEMPLATE_DEBUG.

Caso opte por colocar no arquivo settings.py a setting obsoleta, será gerado o warning abaixo:


?: (1_8.W001) The standalone TEMPLATE_* settings were deprecated in Django 1.8 and the TEMPLATES dictionary takes precedence. You must put the values of the following settings into your default TEMPLATES dict: TEMPLATE_DEBUG.

A solução (provisória) é instalar a Sorl direto do github onde este problema foi resolvido até que ela seja atualizada no PyPI.


Execute comando abaixo para a instalação:

pip install -e git+https://github.com/mariocesar/sorl-thumbnail.git#egg=sorl-thumbnail

O comando acima irá desinstalar a versão que ainda apresenta este problema e baixará a versão presente no git.

Atualize também seu arquivo requirements.txt com o comando acima (sem o pip install) para usar em produção.

hasta!