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

 


08 январь 2018 30 август 2021 Базы данных, PSQL, MySQL Postgresql Mysql | решать тесты


Нет простого способа перекинуть данные из разных СУБД, даже в OpenSource. Но всё же можно и сделать это можно через промежуточный формат CSV следующим образом в терминале Postgresql выгружаем нужную таблицу в файл /tmp/test.csv

Copy (Select * from region) To '/tmp/test.csv' With CSV DELIMITER '|';

Затем, необходимо разрешить Mysql загружать данные из внешних файлов, для этого нужно временно добавить в файл /etc/mysql/my.cnf в секцию добавить параметр secure-file-priv

[mysqld]
#
# * Basic Settings
#
secure-file-priv = ""

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

mv /tmp/test.csv /var/lib/mysql-files/

после это заходим в терминал mysql и загружаем данные из нашего файла

LOAD DATA INFILE '/var/lib/mysql-files/test.csv' INTO TABLE region FIELDS TERMINATED BY '|' ;

всё, теперь можно удалить файл и вернуть настройки Mysql сервера на место


08 январь 2018 29 январь 2018 Python | решать тесты


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

# начальная версия кода
def вычисление_значения(a, b):
    return a+b

# код после добавления новых требований
def вычисление_значения(a, b):
    if проверка_пользователя:
        учёт_результатов
    результат = a+b
    учёт_результатов(результат)
    печать_результатов(результат)

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

В программировании декоратор это обёртка функционального кода применяемая относительно другого функционального кода

Можно декорировать любой функциональный код

Можно использовать цепочки декораторов, подобно конфетам которые завёрнуты в фантики, упакованны в коробке и лежат в сумке

Декораторам можно передавать параметры для использования текущего контекста

Использование декораторов позволяет создавать более абстрактный и менее зависящий от контекста код.

Используя декораторы вы можете разделять ваш основной абстрактный код от кода контекста в разных файлах и даже модулях.

Вот так будет выглядеть необходимые нам декораторы

def проверка_пользователя(декорируемая_функция):
    if пользователь.авторизован == True:
        декорируемая_функция()
    генерация исключения авторизации

def печать_результатов(декорируемая_функция):
   печать( "шапка бланка печати" )
   печать( декорируемая_функция() )
   печать( "подвал бланка печати" )

def учёт_результатов(декорируемая_функция):
   try:
      декорируемая_функция()
      журнал("положительное вычисление")
   except:
      журнал("неудачное вычисление")

Схематично использование декораторов можно представить в виде:

@проверка_пользователя
@печать_результатов_вычисление_значения
@учёт_результатов_вычисление_значения
def вычисление_значения(a, b):
    return a+b

Функционально декораторы это простой функциональный код который запускается на этапе интерпретации и в качестве входных аргументов получив имя декорируемой функции (в даннам случае указатель на функцию) манипулирует аргументами и результатами декорируемой функции


07 январь 2018 Python variables


Переменная в программе это объект с именем в оперативной памяти компьютера.

Оперативная память компьютера это множество пронумерованных ячеек в которых хранится информация в виде 0 и 1.

Объект в оперативной памяти это участок памяти который интерпретируется процессором в зависимости от типа объекта.

Самый простой тип объекта это бит, бит может принимать только два значений 0(ноль) или 1(единица).

Бит может использоваться в качестве индикаторов состояния включено или выключено.

Если объединить несколько бит то можно интерпретировать состояние бит в виде числа, так например в с помощью 8 бит можно преставить последовательность и 256 цифр (2 значения в 8 степени).

С объединением нескольких значений одного или нескольких типов объектов создаёт составной тип данных, самый простой составной тип данных это байт.

С помощью 1 байта из 8 бит можно представить последовательность из цифр 256 либо последовательность из 256 символов алфавита и технических символов.

Последовательность символов составляет ещё один тип данных — строка символов. Строки могут объединяться в массивы строк.

С помощью последовательности бит можно сформировать активный код выполняеымй процессором, это называет код функции. Код функций это последовательность бит, который выполняется процессором в командном режиме, что приводит к изменению состояния процессора, а следовательно и к изменению состояния объектов в оперативной памяти.

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

Для обращения к объекту необходимо знать адерес объекта в оперативной компьютера, так как работать с адресам для человека не удобно, существую таблицы имён созданых человеком и связанных с именами адресов объектов в оперативной памяти. При этом адрес объекта в оперативной памяти может постоянно меняться но связь с именем всегда позволяет обратиться к объекту.

Так как объекты бывают составными, то существуют иерархически составные названия объектов по аналогии с реальным миром, например: дом, улица, квартира, стул номер 3, ножка стула номер 2, длинна ножки стула.

Таким образом:

Переменная это объект созданный в оперативной памяти компьютера, имещий имя которое используется для обращения к объекту, а так же информацию о типе объекта, а от типа объекта зависит интерпретация процессором оперативной памяти занимаемой объектом.

Типы объектов крайне важны в программировании. Так например, можно изготовить из пластилина молоток на 100% похожий на настоящий молоток, только забивать гвозди им не получится.