Теоретический минимум

Есть у айтишников серьёзная проблема — они слишком часто верят на слово. И готовы всерьёз обсуждать безумные домыслы.

Легендарный Теоретический минимум для программиста — одно из таких безумств.

Начнём с первой строчки — то есть с названия.

Теорминимум Ландау (кто сдавал, тот знает) — это не список книг и даже не список задач. И сдавать его никто не обязан. В госэкзамен по физике он не входит, на диплом не влияет. Некоторые из 11 экзаменов входят в учебную программу МФТИ. Он влияет только на возможность научной карьеры — скорее всего, хороший научрук не захочет брать к себе настолько нерадивого студента.

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

Ландау ввёл его для отбора аспирантов в свою лабораторию. Стояли 1930-е годы и надо было как-то защищать теоретическую физику от комсомольцев от станка, которые пошли учиться, потому что сказала партия.

Проходит он так — ты получаешь направление и едешь в МФТИ/Черноголовку/Дубну. Там находишь нужного преподавателя, он даёт тебе задачу. Идёшь решать. Возвращаешь с ответом. Тебе говорят “правильно” или “неправильно”. Пользоваться можно чем угодно — это не поможет. И так, пока не решишь или не заедешь в дурку.

Очень похоже проходило решение задач у Капицы. За аспирантами, которые получали хотя бы “уд”, гонялись все лучшие институты страны.

В этом же “Теоретическом минимуме” задач нет — есть просто список названий. Как установить, что ты понял всё, что там перечислено? Задач-то нет.

У тому же, составители подобных списков не учитывают очевидного факта — человек очень быстро забывает. Когда-то я помнил наизусть несколько самых знаменитых шахматных партий (целиком, вместе с вариантами). Сейчас помню в самых общих чертах — “Бессмертная” сыграна гамбитом слона, Вечнозелёная — гамбитом Эванса, а Оперная — защитой Филидора и т.п.

Теперь по самому списку.

До 70% — взято из оглавления книг Таненбаума. Таненбаум, конечно, хорош, но он очень старый. Например, последний рассмотренный процессор — Pen­tium II (его ещё по телевизору рекламировали). Остальные 30%…

А остальные вы успеете забыть прежде, чем представится повод их применить.

Ежели же мы посмотрим в комментарии, то вдруг выяснится, что аргументы автора и его адептов сводятся к “Попробуйте сделать поиск по этой ссылке хотя бы в русскоязычных блогах и поискать отзывы” и “Практика показывает, что разработчики новых фундаментальных технологий либо делают их на C++, либо переписывают, либо уходят в забвенье.”.

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

Между тем, как раз в нелюбимом им С++ QFA есть превосходный совет по оценке компетентности:

[6.14] What are some “inter­view ques­tions” I could ask that would let me know if can­di­dates real­ly know their stuff? FAQ: If you are a non-tech­ni­cal per­son (manager/HR), ask a tech­ni­cal per­son to help you judge the tech­ni­cal com­pe­tence of a can­di­date. If you are a tech­ni­cal per­son, the FAQ is one source of good ques­tions, sep­a­rat­ing the tru­ly com­pe­tent peo­ple from the posers.

FQA: The good inter­view ques­tions prob­a­bly don’t men­tion any­thing unique to C++.

Ulti­mate­ly, you are look­ing for peo­ple with good will (some call them “coop­er­a­tive”), who will do things, not just talk about them (some call them “prac­ti­cal”), and who will think, not just do (some call them “intel­li­gent”). So the best ques­tions, rel­e­vant for all can­di­dates, are about their largest last projects. The answers give you lots of infor­ma­tion and good answers are almost impos­si­ble to fake.

You may also need peo­ple to have some pri­or knowl­edge rel­e­vant to their work since you don’t have time to have them trained and gain expe­ri­ence. If you are sure that’s the case (despite the fact that the peo­ple you are look­ing for are good learn­ers), ask spe­cif­ic ques­tions. Ques­tions about high-lev­el soft­ware orga­ni­za­tion issues (like OO) may be use­ful. Ques­tions about low-lev­el soft­ware con­struc­tion issues (like point­ers) may be use­ful. These issues are not spe­cif­ic to C++.

Ask­ing about things spe­cif­ic to C++ is not very use­ful.

First, many of these things are use­less for any prac­ti­cal pur­pose and are best avoid­ed. Whether some­one knows these things is cor­re­lat­ed quite loose­ly with pro­fi­cien­cy, and there are many excel­lent devel­op­ers out there who weren’t con­front­ed with a par­tic­u­lar obscure C++ fea­ture yet, or suc­cess­ful­ly for­got it. So chances are that you are going to reject a good can­di­date.

Sec­ond, a good can­di­date actu­al­ly know­ing the answer may pre­fer an employ­er ask­ing more rel­e­vant and prac­ti­cal ques­tions. So chances are that a good can­di­date is going to reject you.

And third, there are peo­ple who look for the most com­pli­cat­ed way to solve a prob­lem to show off their intel­li­gence. These tend to stum­ble into the dark areas of the tools they use all the time, so they will know answers to many C++-specific ques­tions (they won’t know answers to many more, because almost nobody does). Your ques­tions will rank these peo­ple as the best pos­si­ble can­di­dates. Lat­er you will find out that these peo­ple are poor prac­ti­tion­ers.

Загадка enum-а в C++

Ещё детстве я недоумевал, почему enum в C++ не считается name­space-ом. Вот в этом примере мы явно засорили публичное пространство непрошенными глобальными перемеными.

enum Color { red, green, blue };
Color r = red;
switch(r)
{
    case red  : std::cout >> "red\n";   break;
    case green: std::cout >> "green\n"; break;
    case blue : std::cout >> "blue\n";  break;
}

Теперь понимаю, что это пошло из C и переделывать было поздно. К счастью, есть enum class:

enum class Color { red, green = 20, blue };
Color r = Color::blue;
switch(r)
{
    case Color::red  : std::cout >> "red\n";   break;
    case Color::green: std::cout >> "green\n"; break;
    case Color::blue : std::cout >> "blue\n";  break;
}

Доступ к элементу name­space через . (как в Java, Python, C#) мне нравится больше. Но :: до сих пор жив в Ruby. Так что “оператор четыре точки” подарит нам ещё немало весёлых часов, убитых на отладку казалось бы правильно написанного кода.

(Мета) программируем Redux

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

Рассмотрим классическую пару action/reduce в redux. action хронически пишут свой для каждого объекта приложения. Хотя как раз действия для большинства объектов в приложении очень стандарны: CRUD (Cre­ate, Read, Update, Delete) и дождаться загрузки. reduc­ers хронически пишут через switch{}.

При всей наглядности и простоте, конструкция не очень — все переменные лежат в одном пространстве, даже если они и не нужны обработчику. К тому же, JavaScript очень хорош для метапрограммирования. А значит, если постараться, можно добиться, чтобы код писал сам себя.

Пишем универсальный обработчик

Для начала напишем и положем в корневую директорию проекта файл DataStatus.jsx, где будут храниться статусы. Это простой аналог enum, каким мы его видели в Java или C#:

const DataStatus = {
    NotStarted: "DataStatus.NotStarted",
    Loading : "DataStatus.Loading",
    Updating : "DataStatus.Updating",
    Deleting : "DataStatus.Deleting",
    Completed : "DataStatus.Completed",
    Failure: "DataStatus.Failure" 
};

export default DataStatus;

Текстовые значения добавлены исключительно для удобства отладки. Если вы напишите вместе них числа — я не против. Теперь создадим action. Я назвал файл actions/crud.js:

const generateHeaders = (getState) => {
    const headers = {"Content-Type": "application/json"};
    const {token} = getState().auth;

    if (token) {
        headers["Authorization"] = `Token ${token}`;
    }
    return headers;
}

const act = actionName => actionName.toUpperCase();

const fetchDispatch = (typeName, url, dispatchFunc, errorText, method = "get", body = {}) => {
  const bodylessMethods = ["get", "head", "delete"],
        returnlessMethods = ["delete"];

  let   fetchOptions = { method };

  if(!bodylessMethods.includes(method)){
    fetchOptions = { ...fetchOptions, body: JSON.stringify(body) };
  }
  return (dispatch, getState) => {
    dispatch(itemLoading(typeName));
    return fetch(url, {
      ...fetchOptions,
      headers: generateHeaders(getState)
    })
    .then(
        response => returnlessMethods.includes(method) ? {results : { ok:response.ok, id:body.id }} : response.json(),
        error => {
          console.error(errorText);
          itemFailure(typeName, errorText);
        }
      )
    .catch(error => {
        console.error(`Wrong response from ${url}. Error message: ${errorText}`);
        itemFailure(typeName, errorText);
        }
     )
    .then(data => data && dispatch(dispatchFunc(typeName, data.results || data)));
  }
}

const itemsAll = (typeName, items) => ({
    type: act(`ALL_${typeName}`),
    items: items
  });

const itemAdd = (typeName, item) => ({
    type: act(`ADD_${typeName}`),
    item
  });

const itemGet = (typeName, item) => ({
    type: act(`GET_${typeName}`),
    item
  });

const itemUpdate = (typeName, item) => ({
    type: act(`UPDATE_${typeName}`),
    item
  });

const itemDrop = (typeName, item) => ({
    type: act(`DROP_${typeName}`),
    item
  });

const itemLoading = (typeName) => ({
    type: act(`LOADING_${typeName}`)
  });

const itemFailure = (typeName, errorText) => ({
    type: act(`FAILURE_${typeName}`),
    error: errorText
  });

const add = (data, typeName, url = null) =>
    fetchDispatch(typeName, url || `/api/${typeName}/`, itemAdd,
      `Не удаётся добавить ${typeName}`,
      'post', data);

const update = (id, data, typeName, url = null) =>
    fetchDispatch(typeName, url || `/api/${typeName}/${id}/`, itemUpdate,
      `Не удаётся обновить ${typeName} c id=${id}`,
      'put', data);

const drop = (id, typeName, url = null) => 
    fetchDispatch(typeName, url || `/api/${typeName}/${id}/`, itemDrop,
      `Не удаётся удалить ${typeName} c id=${id}`,
      'delete', {id: id});

const all = (typeName, filter = {}, url = null) => {
  const filterParams = Object.keys(filter).map(key => `${key}=${filter[key]}`).join('&');
  return fetchDispatch(typeName, `/api/${typeName}/?${filterParams}`, itemsAll,
    `Не удаётся загрузить ${typeName}`);
}

const get = (id, typeName, url = null) =>
  fetchDispatch(typeName, url || `/api/${typeName}/${id}`, itemGet,
    `Не удаётся загрузить ${typeName} c id=${id}`);

export {add, update, drop, all, get};

generateHeaders нужен для аутенфикации. Предполагается, что в глобальном state есть свойство auth, в котором и лежат имя пользователя и его Token.

Потом добавляем его экспорт в actions/index.js.

Теперь создадим reducers/crud.js. Чтобы узнать множественное число от называния типа, он использует библиотеку plu­ral­ize. Поэтому сначала нужно выполнить:

npm i pluralize --save

А уже потом писать в reducers/crud.js:

import pluralize from "pluralize"

import DataStatus from '../DataStatus'

export default function generateReducer(typeName, initialState = []) {
    const   typeNameUpperCase = typeName.toUpperCase(),
            typeNamePlural = pluralize(typeName),
            generateState = (status) => {
                const newState = {};
                newState[`${typeNamePlural}Status`] = status;
                return newState;
            },
            actionFor = (command) => `${command}_${typeNameUpperCase}`;
    return (state=initialState, action) => {
        const actions = {};
        actions[actionFor("ALL")] = (state, action, newState) => {
            newState[typeNamePlural] = action.items;
            return {...state, ...newState};
        }
        actions[actionFor("GET")] = (state, action, newState) => {
            newState[typeName] = action.item;
            return {...state, ...newState};
        }
        actions[actionFor("ADD")] = (state, action, newState) => {
            newState[typeNamePlural] = [
                ...state[typeNamePlural],
                action.item
            ];
            return {...state, ...newState};
        }
        actions[actionFor("UPDATE")] = (state, action, newState) => {
            const   newData = state[typeNamePlural].slice(0),
                    indexToUpdate = newData.findIndex(e => e.id === action.item.id);
            newData[indexToUpdate] = action.item;
            newState[typeNamePlural] = newData;
            return {...state, ...newState};
        }
        actions[actionFor("DROP")] = (state, action, newState) => {
            newState[typeNamePlural] = state[typeNamePlural].filter(e => e.id !== action.item.id);
            return {...state, ...newState};
        }
        actions[actionFor("FAILURE")] = (state, action, newState) => ({
                ...state,
                ...{ ...generateState(DataStatus.Failure), error: action.error }
            });
        actions[actionFor("LOADING")] = (state, action, newState) => ({
                ...state,
                ...generateState(DataStatus.Loading)
            });
        const actionToExec = actions[action.type];
        return actionToExec ? actionToExec(state, action, generateState(DataStatus.Completed)) : state;
    }
}

Переменные успешно изолированы. Конечно, функции не очень чистые — иногда new­State изменяется. Если критично, можно клонировать new­State перед использованием. Как ни странно, это всё.

Применяем к компоненту

Пусть теперь у нас есть некий тип, доступный по адресу /api/element. Чтобы создать reduc­er, дописываем в reducers/index.js

import generateReducer from "./crud";

import DataStatus from '../DataStatus'
//...
const elementReducer = generateReducer("element", {
    status: DataStatus.NotStarted,
    elements: []
});

const appReducer = combineReducers({
    //....
    elementReducer
});

И потом в самом компоненте:

import { crud } from '../../actions';

//.....
class ElementPage extends React.Component {
//....
  render() {
    const { elementsStatus, clinicalresearches, drop } = this.props;
    const { formVisible, currentItem } = this.state;
    const isLoadingCompleted = this.props.status != DataStatus.Completed;

    return(
      <div>
        // ...
        { (isLoadingCompleted) ? (
          // Тут какой-нибудь <Spinner />, пока загружается
        ) : (
          <ElementList items={elements} />
        )}
      //...
      </div>
    );
  }
}


//......
const typeName = "element";

const mapDispatchToProps = dispatch => {
    return {
        all: () => dispatch(crud.all(typeName)),
        add: (data) => dispatch(crud.add(data, typeName)),
        update: (id, data) => dispatch(crud.update(id, data, typeName)),
        drop: (id) => dispatch(crud.drop(id, typeName))
    }
}

Конечно, elementsStatus выглядит не очень изящно. Но это лучше, чем statuselements.

Всё, ничего больше писать не нужно.

Можно так подгружать несколько типов в одном компоненте и т.п.

Не думаю, что я первый, кто это придумал. Но эксперимент интересный. Думаю, это надо выпустить в виде небольшой библиотечки.

В таких вещах — как в шахматах, самый красивый вариант часто находят уже потом, при анализе.

Markdown

Если вы любите GitHub так же, как люблю его я, то вам тоже не хватает в редакторе Word­Press благородной простоты Mark­down. Чтобы не мучаться больше с выделением, а просто пометить — полужирный текст идёт от сих и до сих.

Поставил плагин Mark­down, начал писать — и обнаружил, кто код изуродован. А изуродовала его моя бывшая тема — там зачем-то прописали стиль для тега <code>, который делал его неимоверно уродливым.

Чтобы работать с Mark­down, достаточно поставить WP-Mark­down и отключить Simple Code Highlighter, которым я подсвечивал код раньше. Markdown Editor не ставьте, он ужасен и не умеет работать с кодом.

Блог ожил

Сегодня наконец-то дошли руки. В блоге появилась возможность комментировать и появилась информация об авторе.

В перспективе — будет много интересного. Давно пора написать, что узнал про React, Redux и прочую компашку. Только надо код до ума довести.

Пределы дискуссии

Сергей Волков — человек спорный, но спорить с ним не стоит. А вот прочитать некоторые его заметки — стоит.

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

Наблюдение над реальностью – единственное основание для выводов о возможном (как настоящем, так и в будущем), потому что бывает только так, как может быть, а может быть только так, как бывало. Человек, исходящий из идеальных схем, теряет связь с реальностью. Он рисует себе картинку «как должно быть» и потом начинает воображать, что где-то когда-то так оно и было, а потом вот почему-то испортилось (и вот бы восстановить). При этом о том, почему испортилось, приходится выдумывать самую нелепую ерунду — потому что на самом деле просто не было той благости, которая ему представлялась или эта благость вовсе таковой не являлась, а в лучшем случае была обречена собственными пороками.

***

Проведение виртуальных границ – между людьми, явлениями, позициями, различными сторонами деятельности одного и того же человека и даже разными составляющими его натуры, я вообще считаю самым важным. На неумении (а тем более нежелании) их проводить замешано большинство неприятностей и недоразумений. 

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

Тут важные и нужные мысли идут буквально плотным строем.

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

Но это контрпродуктивно. Если знаешь позицию собеседника — уже знаешь достаточно. Разубеждать волхва в подлинности очередных Славяно-Греко-Латинских Вед бесполезно, он слышал эти аргументы миллион раз.

Тем более контрпродуктивно спорить с заведомо бредовой позицией на публике. Спорить с чем-то — означает признавать это потенциально равным.

Прапорщик с солдатом не спорит, прапорщик (не говоря о вышестоящих офицерах) солдату приказывает. Потому что солдат не равен прапорщику. Неравенство даже в Уставе прописано.

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

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

Кацнельсон очень верно советует учёному бороться с лженаукой только на одном, небольшом участке — в своих собственных работах.

Бессмысленно спорить, если причина занятой позиции лежит за пределами спора. И тем более бессмысленно спорить с обитателями удивительной “планеты чудес и загадок”.

***

В известном анекдоте коварный профессор валил заочников на экзамене сложнейшим вопросом “Как называется предмет, который вы сдаёте?”.

Интернет — не заочка, обязательных лекций нет и экзамены сдавать не надо. Поэтому тут скорее был бы уместен более лёгкий вопрос — “На какой планете мы сейчас находимся?”.

Если человек уверен, что на Юпитере — спорить с уважаемым гуманоидом уже заранее не о чем.

Конечно, и среди людей инопланетного происхождения попадаются адекватные ксеноморфы, которые в курсе, что они на Земле, а не на исторической родине.

Опытный космолётчик даже может рассказать про планету такое, чего ты не знаешь. Со стороны ему виднее то, чего ты не замечаешь.

Но не надо зацикливаться на странных выводах, которые инопланетянин иногда выдаёт. Для марсианина вполне нормально верить в непременное наступление национал-феминизма в отдельно взятом Земшарном Евросоюзе или считать, что у всех нормальных планет должно быть по два спутника — как на родном Марсе. Поэтому лун на самом деле две — только одну из них НАСА скрывает.

Ни в какие масштабные подлоги и судьбоносные фальсификации я не верю. Это как вечный двигатель – «не рассматривается». Сие есть отрасль конспирологии, которая, как и фоменковщина – ПАРАЛЛЕЛЬНЫЙ МИР, «планета чудес и загадок». Жить на ней гораздо интереснее и приятнее (даже жаль, что в силу пошлости мышления я там не прижился), но это примерно то же, что мир фэнтэзи (видел у нас в парке эльфов с луками; вроде и при переписи сколько-то их насчитали), в котором тоже можно с успехом абстрагироваться от реальности […]

Но в мире, где «все подделано», пронизанном сетями двойных, тройных и далее агентов, которым управляют из тайных убежищ гениальные умы, — действуют своя логика и свои законы, которые распространяются на ВСЕХ его обитателей и которые, так сказать, «обоюдоостры». Как-то я сказал доставшему меня со своими «докажи» однокашнику примерно следующее: «А попробуй доказать, что ты действительно Андрюша Ш., а не натурализовавшийся марсианин №0123456789. Паспорт у тебя, конечно, фальшивый, диплом в переходе купил. Копию лицевого счета из ДЭЗа принесешь? Так у них там, известно, зарплаты маленькие… Что, мать жива? Так старушка за молочишко и Тони Блэра сыном признает. Ах, в Бауманском ЗАГСе метрическая запись есть? Так ведь марсиане для своего агента не поленятся всю книгу за 56-й год переписать. И о чем нам с тобой после этого говорить?» 

Если можно объявить фальшивым один документ – то и всякий другой, на обвинение в «работе на» всегда возможно ответное и т.д. Поэтому параллельный мир населен общностями (как бы сектами), каждая из которых верует в подлинность одного, отрицая подлинность другого и наоборот и находя в этом удовлетворение. В рамках своего мира всем хорошо, на реальный — не влияет. Придет человек в Лувр: «Ну Ван Дейк – ладно, но Рубенс-то – явная фальшивка. Многовато его, ох многовато. Ну не мог один человек столько написать. Скорее всего, и не было его вовсе, это в XVIII веке подмастерья забавлялись». Люди пожмут плечами, цены не дрогнут. 

Фальсификация же глобального порядка, предполагающая желание человека подурачить потомков, которым предстоит жить через несколько столетий – за пределами здравого смысла. Никто в ХХ-Х1Х в. не писал «историй» о существовании в это время вымышленных государств и происходящих там событиях, и просто потому что «все знали». Но «все знали» и 400, и 800 лет назад, поэтому про современную им жизнь «по крупному» не фантазировали и тогда. Конечно, всегда был тот мотив, по которому подделывают антиквариат, но он предполагает максимальное приближение к известным образцам и исключает оригинальничанье. Поэтому такие тексты и документы известны, но сенсаций обычно не содержат. Наконец, никакой текст или документ, как правило, не является единственным источником по вопросу, всегда есть множество других, с которыми он может быть сопоставлен. Если же вдруг таковым является (как платоновское известие об Атлантиде), то в этом случае вопрос обычно вовсе не рассматривается (кстати сказать, когда сомнительным источником заполняют «черную дыру», это «впредь до прояснения» более терпимо, чем отрицание хорошо известных фактов).

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

Для меня, в абсолютном большинстве случаев работающим с массовым материалом, который «подделать» заведомо нельзя, этот вопрос не актуален, сюжетами, когда решение вопроса зависит от подлинности или толкования какого-то одного документа, я предпочитаю не заниматься. Но на самом деле никакой такой бумажки, которая бы стоила миллиардов других хранящихся в архивах листов, просто не существует. Если документ противоречит всем остальным известным фактам, то есть все основания посчитать недостоверным именно его. Точно так же нормальный историк не станет перекраивать хронологию из-за одного труднообъяснимого события, если тем самым он сделает необъяснимыми 30 других.

Когда же вопрос о подлинности встает, для меня решающим является мнение специалистов — профессиональной среды. Критика источников преподается на младших курсах всем. Специалист же есть тот, кто реально работал именно с такого рода материалом и писал не «ваще», а «конкретно». Человек, выдавший на гора «Историю Востока», не написав предварительно десятки статей типа «Административные фунции уделов в Китае в нач. ХУ в.» или «Социальная мобильность землевладельческого слоя общины в Гуджарате в Х1Х в.» – это шарлатан. Вообще в историю, лежащую за пределами «новейшей» и частично «новой», с 50-х годов по комсомольским путевкам не направляли, и публика там в профессиональном отношении приличная, мне знакомая. Несерьезные и жулики встречаются, но их все знают. Так вот если относительно какого-то источника мнения разделяются – можно вникать в аргументы и выбирать «сторону»; если никто из серьезных специалистов подозрение в «фальсификации» не разделяет – вопроса просто нет. Мнение же по столь «специальным» вопросам людей, профессионально по теме не работавших, можно в расчет не принимать, это – «параллельный мир», в котором Ксения Собчак может основать историческую школу с еще большим успехом, чем Фоменко. 

Точно так же я не склонен принимать за результат действия «тайных сил» то, что по большей части бывает порождено глупостью, человеческими слабостями и случаем. Предпочтение экзотических объяснений наиболее простым всегда казалось мне чем-то типа ковыряния правой ногой в левом ухе, а излюбленное занятие жителей параллельного мира по конструированию «истинной» политической принадлежности человека – чрезвычайно занимательной игрой. Большинству интеллигентных людей годам к 30–35 довелось учитья, общаться, работать, пить с таким количеством разных лиц (не говоря о наличии близких и дальних родственников), где-то что-то при разных обстоятельствах писать и говорить, что возможности интерпретации весьма широки, а с подключением «логических умозаключений» — поистине безграничны. Меня самого, например, можно при желании смело отнести к ЛЮБОЙ из существующих политических общностей. Но и мне, в свою очередь, не составит труда проделать то же самое с кем угодно. 

Эта игра «чистого разума» гораздо увлекательней пошлых систематических штудий, поэтому, например, масоноборческие изыскания «пьяных советских самоделкиных» известны всем, а про словарь Серкова не знают даже многие историки. Поскольку же логика конспирологического мышления предполагает поиски враждебной агентуры прежде всего в ближайшем окружении и среди наиболее заметных соратников, картина их взаимоотношений обычно доставляет массу удовольствия менее озабоченным гражданам. Если на «планете чудес и загадок» невозможно доказать существовавшее, то опровергнуть несуществующее тем более невозможно. Если скажут, что это я организовал дефолт-98, не представляю, как бы я мог оправдаться. 

Загрузка файла в Django REST Framework

Учимся загружать файл через стандартное API Djan­go REST Frame­work.

Загруженный файл приходит в request.FILES, класс Upload­ed­File. Для HTML-формы должно быть установлено enctype=“multipart/form-data” и она должна заливаться через POST. Идут годы, а формы в HTML не меняются…

В Djan­go Mod­el есть специальный поля для хранения загруженных файлов — File­Field и Image­Field. Они хранят, разумеется, не в базе, а на жёстком диске.

Куда класть закаченный файлы — прописывается в settings.py:

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

И можно разрешить смотреть их прямо по URL, если у нас DEBUG:

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # Project url patterns...
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Можно специально указать поддиректорию, в которую будут скидываться файлы, указанные для File­Field:

from django.db import models

class Document(models.Model):
    description = models.CharField(max_length=255, blank=True)
    document = models.FileField(upload_to='documents/%Y/%m/%d/')
    uploaded_at = models.DateTimeField(auto_now_add=True)

Загруженные в Document.document файлы будут лежать в папке media/documents/2018/05/04.

Удобней всего загружать через формы.

Создаём форму:

from django import forms
from uploads.core.models import Document

class DocumentForm(forms.ModelForm):
    class Meta:
        model = Document
        fields = ('description', 'document', )

Создаём view:

def model_form_upload(request):
    if request.method == 'POST':
        form = DocumentForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
            return redirect('home')
    else:
        form = DocumentForm()
    return render(request, 'core/model_form_upload.html', {
        'form': form
    })

 

jQuery UI control в React на примере FormBuilder

jQuery UI — симпатичная библиотека. У неё есть недостатки — она тяжелее Boot­strap и довольно старая. Но бывает, что нужный компонент есть только под неё. А у тебя проект на одном из React-фреймворков…

Для примера попробуем подключить старый jQuery Form­Builder. Это просто отличный редактор форм.

Конечно, есть другие. Есть даже редактор форм под React… но он использует доисторические библиотеки и отныне не собирается. А их тех, которые собираются, Form­Builder — самый наглядный. В него не страшно пустить даже неопытного пользователя.

Но увы… большинство React-фреймворков давным-давно jQuery-free. Как же нам подключить компонент?

Для начала идём в package.json и добавляем туда form­Builder. К счастью, он уже собран в виде npm-пакета, так что нам не нужно упоминать про jQuery и прочие зависимости. NPM их и так подтянет.

  "dependencies": {
    // ...
    "formBuilder": "^2.9.8",
    // ...
}

А потом создаём новый компонент и пишем:

import React, { Component } from 'react';
import * as $ from 'jquery'

class FormBuilder extends Component {
  state = { };

  componentDidMount() {
    window.jQuery = $;
    window.$ = $;
    require('jquery-ui-sortable');
    require('formBuilder');
    this.$formBuilderContainer = $(this.refs.formBuilderEditor);
    this.$formBuilderEditor = this.$formBuilderContainer.formBuilder({
        i18n: {
          locale: 'ru-RU'
        }
      });
  }
  render() {
    return (
             <div ref="formBuilderEditor"></div>
            );
    }
}

export default FormBuilder;

Добавляем на нужную страницу, запускаем… и видим, что форма на месте и даже кнопки нажимаются!

Динамически создаём React-компоненты

Иногда нужно создавать React-компонент динамически и даже в цикле. Но ren­der(), само собой, не желает выполнять циклы и начинает ругаться.

Чтобы создавать компоненты динамически, пишите по этому образцу. В зависимости от фреймворка можно заменить вызов на создание класса и отформатировать arrow-функциями.

let Block = React.createClass({
  createImage: function (image) {
    return <Image source={image} key={image} />;
  },

  createImages: function (images) {
    return images.map(this.createImage);
  },

  render: function () {
    return (
      <div className="container">
        <div className="row">
          <div className="col-sm-12 text-center">

            {this.createImages(data.images)}

          </div>
        </div>
      </div>
    );
  }
});

 

Вышел ya-api-direct 0.2.6

Как оказалось, от моего пробного gem-а ya-api-direct сделали 4 форка. В каждом добавилось что-то полезное.

Причём pull request сделал только один.

Сделал merge в проект и выпустил новую версию. Всего его скачало порядка 3000 человек. Кто все эти люди — для меня загадка.