28 сентябрь 2018 Python


#!/usr/bin/env python
# $ID$ #
import sys
import subprocess


try:

    IIDD = "$I"+"D: {}  $".format(
        subprocess.check_output(['git', 'log', '-1', '--format=%ae / %an / %cd']).decode("utf-8")[:-1]
    )

    if IIDD:
        for line in sys.stdin:
            sys.stdout.write( line.replace("$I"+"D$", IIDD ) )

except:
    pass

А вот настройки git для работы с этим скриптом

файл .git/config


[filter "idder"]
    smudge = python iidd.py
    clean = perl -pe \"s/\\\\\\$ID[^\\\\\\$]*\\\\\\$/\\\\\\$ID\\\\\\$/\"

файл .gitattributes


*.js filter=idder
*.py filter=idder
*.css filter=idder
*.html filter=idder


28 сентябрь 2018 29 сентябрь 2018 Nginx


Не все знают зачем к имени сайта добавляют префикс www., по мимо субъективных причин, про world wide web и удобство парсинга урлов есть совершенно объективная причина использовать алиас WWW.

Всё дело в структуре протокола DNS, когда происходит преобразование доменного имени обычного домена первого уровня, то происходит запрос к корневому серверу зоны, а корневые сервера зон обновляются достаточно редко, например в зоне .RU обновление происходит 4 раза в день, а при преобразовании домена третьего уровня, про выполняется запрос к DNS серверу контролирующему домен, а он обновляется быстрее чем корневые сервера зоны.

Таким образом, если у вас отключили сервер сайта, и вы меняете IP адрес парковки домена на IP адрес резервного, то вашим клиентам придётся ждать обновления корневого DNS, а если меняется IP адрес у алиаса WWW.yourdomain то обновление произойдёт гораздо быстрее, в некоторых случаях сразу


28 июнь 2018 29 сентябрь 2018 Linux


Есть таблица dispatch_dispatch_form в дублирующимися строками

чтобы быстро почистить таблицу от дублей необходимо:

delete from dispatch_dispatch_form  a using dispatch_dispatch_form b WHERE a.id < b.id AND a.email = b.email ;

вот так всё просто


28 май 2018 Nginx



Есть стереотип, что жители китая любят копировать готовое, а значит необходимо защищать от них контент, особенно медиа контент. Обычно это делается так:

Но в случае применения такого метода остаётся возможность получить доступ через Proxy/VPN либо воспользовать кешем любого поисковика, а они подтянут с сайта все защищаемые картинки

При  этом необходимо так же следить за актуальностью базы данных IP адресов или за состояние API сервиса

Если такие условия и ограничения не являются приелимыми, то можно воспользоваться возможностью веб-сервера Nginx фильтровать входящие запросы по HTTP-заголовкам. Суть этого метода в том, что у любого человека его родной язык включён в список языков его веб-браузера, таким образом у китайца среди всех языков, которые он использует есть китайский, а значит в его запросе на сайт будет присутвовать вот такой HTTP-заголовок

Accept-language: zh,ru;q=0.8,en-US;q=0.5,en;q=0.3

Значит нужно научить веб-сервер выделять таких клиентов и направлять их туда куда нужно, я сделал это вот так

map $http_accept_language $lang {
    default ru;
    ~by by;
    ~ru ru;
    ~zh zh;
}

location /media/ {
        # если китаец
        set $redir  0;
        if ( $lang ~* 'zh' ){
            set $redir  1;
        }
        # если запрос фотографии
        if ( $request_uri ~* \.(jpeg|jpg|png)$ ){
            set $redir  2$redir;
        }
        if ( $redir = 21 ){
            rewrite /media/(.*) /media_v/$1?lang=$lang&redir=$redir&$http_referer;
        }

        alias /webapps/centrsvet/media/;
    }

location /media_v/ {
            image_filter_jpeg_quality 10;
            image_filter resize 700 600;
            alias /webapps/centrsvet/media/;
    }

защита фотографий от китайцев

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


24 май 2018 11 июнь 2018 Bootstrap


ps: собираю ссылки на популярные сайты с использование Bootstrap


04 май 2018 Angular


Архитектура современного веб-приложения выглядит как показано на картинке

Архитектура современного веб-приложения


01 апрель 2018 Python


После миграции на более новую версию Django при изменении любой модели может вылезать вот такая ошибка

django.db.utils.IntegrityError: null value in column "name" violates not-null constraint
DETAIL:  Failing row contains (101, null, history, historymodel).

Это связано с тем что в старой версии для хранения имени модуля использовалось поле name а в новой app_label, при этом поле name является обязательным к заполнению и не учавствует в работе миграции, отсюда и происходит ошибка

Для решения проблемы достаточно удалить поле name из таблицы django_content_type


28 январь 2018 29 январь 2018 Angular | решать тесты


Для начала нужно установить этот модуль

bower install --save angular-switcher

затем подключаем в список зависимостей приложения вот так:

// app.js

angular.module('app', ['ngResource', 'ngRoute', 'ui.bootstrap', 'ui.date', 'ckeditor', 'ngSanitize', 'switcher']) 

используем вот так, заменяем

<input type="checkbox" ng-model="variant.correctly" />

на

<switcher ng-model="variant.correctly" true-label="верно" false-label="не верно"></switcher>

результат будет красив

angular-switcher


23 январь 2018 29 январь 2018 Python Nginx Angular | решать тесты


Чтобы сайт работал быстро, необходимо чтобы быстро загружалась статика сайта, для этого используется сжатие ccs и js файлов (Настройка расширения gulp-clean-css для сжати css), а так же кеширование на уровне веб-сервера nginx c помощью опции expires и gzip, например вот так:

    location /static/ {
        alias /home/python/static/;
        expires 30d;
        gzip on;
        gzip_types text/css application/x-javascript application/javascript;
    }

тут задаёт 30 дневный период хранения кеша статики и включается поточное сжатие статики в архив. Таким образом на продакшене образуется ситуация когда настроенный сайт очень быстро получает статику и при повторных запросах использует кеш на уровне веб-серера и веб-браузера. В случае когда присходит редкое обновление фронта требуется чтобы клиенты обновили кеши со статикой сайта. Принудить клиентов обновить кеш можно добавив номер коммита в урлы к статике. Я делаю это так: в моё конфиге я объявил переменную VERSION и инициализирую её при запуске Flask приложения следующим образом:

import os
import subprocess
import datetime
basedir = os.path.abspath(os.path.dirname(__file__))
 
 
class Config(object):
    PROJECT_DIR = basedir
    DEBUG = False
    TESTING = False
    CSRF_ENABLED = not True
    ENABLED_PRODUCTMARKUP = True
    VERSION = '{}.{}'.format(
            str(subprocess.check_output(['git', 'describe', '--tags']),'utf-8').strip(),
            int( subprocess.check_output(['git', 'rev-list', '--all', '--count']) )
        )

И подключить это вот так:

{% extends "base.html" %}

{% block container %}
    <span ng-include="'/static/js/vendor.min.js?v={ { config.VERSION } }'" ng-controller="ProductCtrl"></span>
{% endblock %}

это даст вот такую ссылку

<script src="/static/js/vendor.min.js?v=0.0-235-g1ced510.825" type="text/javascript"></script>

То-есть, после каждого обновления продакшена у клиентов будет обновляться статика, а значит и фронт

Но бывает необходимо чтобы статика закачивалась при каждом новом запросе, это необходимо бывает для верстальщикам. Для решения этой задачи можно подменить значение переменной VERSION текущим значением времени. Я делаю переопределив в DevelopmentConfig аттрибут VERSION следующим образом:

class classproperty(property):
    def __get__(self, cls, owner):
            return classmethod(self.fget).__get__(None, owner)()


class DevelopmentConfig(Config):
    DEVELOPMENT             = True
    DEBUG                   = True
    #~ SQLALCHEMY_ECHO         = True
    UPLOAD_FOLDER           = 'media/'
   
    @classproperty
    def VERSION(self):
        return str(datetime.datetime.now())

тут я создаю специальный класс, который декорирует функцию VERSION, которая возращает каждый раз новую дату и время и представляет эту функцию в виде статического аттрибута класса, таким образом в ссылках на статику при каждой генерации страницы будет новый, уникальный хешь и статика будет всегда самой свежей


19 январь 2018 Python datetime RFC1123 Last-Modified


Использование даты в этом формате необходимо для обработки HTTP заголовка Last-Modified. Это когда сервер отдаёт дату изменения страницы в заголовке Last-Modified а затем клиет отправляе HEAD запрос с этим заголовком и датой и если страница не менялась, то ваш сервер отвечает 200 и отправляет пустое тело страницы. Это экономит и трафик и ресурсы процессора, а так же ускоряет повторную индексацию сайта и просто более технологично чем просто отдават страницы на каждый запрос.

Итак настроить дату обновления страницы в таком формате очень легко и делается это вот так:

class BaseClass(SQLAModel):
    __abstract__ = True
...
    updated_on =    Column(DateTime, default=func.now(), onupdate=func.now(), doc=_('дата редактирования'))
...

    def get_last_modified(self):
        return self.updated_on.strftime("%a, %d %B %Y %H:%M:%S GMT")

И всё будет прекрасно работать до тех пор пока вам не потребуется локализовать даты (это когда месяцы на русском). Для этого вы сделаете вот так где в __init__.py

...
import locale
...

locale.setlocale(locale.LC_ALL, 'ru_RU.UTF-8')

Возможно, раз вы используете babel, то у вас будет сменная локаль, это всё круто. Но этот системный вызов изменит поведение вашей get_last_modified и все ваши усилия по обработке Last-Modified приведут к тому, что появится вот такая ошибка

    TypeError: http header must be encodable in latin1

По-этому, формировать дату в RFC формате формате необходимо правильным образом, а именно вот так

from time import mktime
from wsgiref.handlers import format_date_time


class BaseClass(SQLAModel):
    __abstract__ = True
....
    updated_on =    Column(DateTime, default=func.now(), onupdate=func.now(), doc=_('дата редактирования'))

    def get_last_modified(self):
        stamp = mktime(self.updated_on.timetuple())
        return format_date_time(stamp)

Вот так вот всё просто.


17 январь 2018 16 февраль 2018 Angular Gulp


Итак, сжатиt вендорных css исходников необходимо для уменьшения размера общего файла и делается это просто:

var gulp = require("gulp"),
rename = require("gulp-rename"),
sourcemaps = require("gulp-sourcemaps"),
cleancss = require('gulp-clean-css'), // подключаем сжималку
ngAnnotate = require("gulp-ng-annotate"),
gulpif = require("gulp-if"),
concat = require("gulp-concat"),
bower = require('gulp-bower');

/*
....
*/

gulp.task("vendor_css", function () {
    return gulp.src(paths.vendor_css)
        .pipe(cleancss({compatibility: 'ie8'}))  // настраиваем сжималку
        .pipe(concat("vendor.min.css"))
        .pipe(gulp.dest("static/css/"))
});

Вот так всё просто


15 январь 2018 Linux Ubuntu git


Для того чтобы смержить два проекта потребуется git начиная с версии >=2.9. Такого нет в репозитариях стабильной версии Ubuntu 16.04. Для установки более новой версии необходимо сделать вот так:

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

вуаля:

ffsdmad@ffsdmad:~/Project$ git --version
git version 2.15.1