рдЕрдкрдиреЗ Django рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд▓рд┐рдП рдмрд╡рдВрдбрд░ рд╡реЗрдмрд╕реНрдХреЗрдЯ рдЪреИрдЯ

рдмрд╡рдВрдбрд░ рдореИрдВрдиреЗ рд╣рд╛рд▓ рд╣реА рдореЗрдВ рд╡реЗрдмрд╕рд╛рдЗрдЯ backgrounddating.com рд▓реЙрдиреНрдЪ рдХрд┐рдпрд╛ рдФрд░ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдпрд╣рд╛рдВ рд╣рдмрд░рд╛рдмреНрд░ рдкрд░ рд▓рд┐рдЦрд╛ ред рдмреЗрд╢рдХ, рдореИрдВрдиреЗ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдЗрд╕ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдХреЗ рдХреБрдЫ рддрдХрдиреАрдХреА рд╡рд┐рд╡рд░рдгреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХреА рдереА, рд▓реЗрдХрд┐рди рдореИрдВ рд╕рд╛рдЗрдЯ рдХреА рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рдореЗрдВ рд╕реЗ рдПрдХ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрд▓рдЧ рд╕реЗ рд▓рд┐рдЦрдирд╛ рдЪрд╛рд╣реВрдВрдЧрд╛, рдЦрд╛рд╕рдХрд░ рдХреНрдпреЛрдВрдХрд┐ рдЗрдВрдЯрд░рдиреЗрдЯ рдкрд░ рдЗрд╕ рд╡рд┐рд╖рдп рдкрд░ рдЕрднреА рднреА рдмрд╣реБрдд рдХрдо рдкреНрд░рд▓реЗрдЦрди (рджреЛрдиреЛрдВ рд░реВрд╕реА рдФрд░ рдЕрдВрдЧреНрд░реЗрдЬреА рдореЗрдВ) рд╣реИрдВред ред рддреЛ, рд╣рдо рджреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рдмреАрдЪ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕рдордп рдЪреИрдЯ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╛рдд рдХрд░реЗрдВрдЧреЗред рдХрд╛рд░реНрдп рдпрд╣ рд╣реИ рдХрд┐ рдХреЛрдИ рднреА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЕрдиреНрдп рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рд╕рдВрджреЗрд╢ рднреЗрдЬ рд╕рдХрддрд╛ рд╣реИ, рдФрд░ рдпрджрд┐ рд╕рдВрджреЗрд╢ рдХреЗ рдкреНрд░рд╛рдкреНрддрдХрд░реНрддрд╛ рдХреЗ рдкрд╛рд╕ рдЗрди рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд╕рд╛рде рдЪреИрдЯ рд╣реИ, рддреЛ рдЙрд╕рдиреЗ рддреБрд░рдВрдд рдЖрдиреЗ рд╡рд╛рд▓реЗ рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рджреЗрдЦрд╛ (рдЕрдиреНрдпрдерд╛, рд╡рд╣ рд╕рдВрджреЗрд╢реЛрдВ рдХреЛ рдмрд╛рдж рдореЗрдВ рдкрдврд╝ рд╕рдХрддрд╛ рд╣реИ: рдЕрд░реНрдерд╛рдд, рдЬрдм рдЪреИрдЯ рдЦреЛрд▓рд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рдпрд╣ рд▓реЛрдб рд╣реЛрддрд╛ рд╣реИ рд╣рд╛рд▓ рдХреЗ рдкреЛрд╕реНрдЯ рдХрд╛ рдЗрддрд┐рд╣рд╛рд╕)ред

рдпрджрд┐ рдЖрдкрдХреЛ рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рддрд╛рдХрд┐ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рди рдХреЗрд╡рд▓ рдПрдХ рд╕рд╛рде, рдмрд▓реНрдХрд┐ рдХрд┐рд╕реА рднреА рд╕рдВрдЦреНрдпрд╛ рдореЗрдВ рд▓реЛрдЧреЛрдВ рдХреЗ рд╕рдореВрд╣реЛрдВ рдореЗрдВ рд╕рдВрд╡рд╛рдж рдХрд░ рд╕рдХреЗрдВ, рддреЛ рдпрд╣ рд▓рдЧрднрдЧ рддрддреНрд╡ рд░реВрдк рд╕реЗ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ: рд╡рд░реНрдгрд┐рдд рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди, рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреЗ рдРрд╕реЗ рд╡рд┐рд╕реНрддрд╛рд░ рдХреЗ рд▓рд┐рдП рдбрд┐рдЬрд╝рд╛рдЗрди рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред

рдореИрдВ рддреБрд░рдВрдд рд╕реНрдкрд╖реНрдЯ рдХрд░ рджреВрдВрдЧрд╛ рдХрд┐ рдЗрд╕реЗ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХрд╛ рдПрдХрдорд╛рддреНрд░ рддрд░реАрдХрд╛ рдирд╣реАрдВ рд╣реИред рдЖрдк рдПрдХ рдЕрдиреНрдп рдПрд╕рд┐рдВрдХреНрд░реЛрдирд╕ рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдиреЛрдб.рдЬреЗрдПрд╕), рдЖрдк рдПрдХ рдЕрд▓рдЧ рд╕рдВрджреЗрд╢ рдХрддрд╛рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (рдпрд╛ рдЗрд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рдмрд┐рд▓реНрдХреБрд▓ рднреА рди рдХрд░реЗрдВ рдпрджрд┐ рдЗрд╕ рд╡рд┐рдХрд▓реНрдк рдХреА рд╡рд┐рд╢реЗрд╖рддрд╛рдПрдВ рдЖрдкрдХреЛ рд╕реВрдЯ рдХрд░рддреА рд╣реИрдВ: рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХрд╛ рдПрдХ рд╣реА рдХрд╛рд░реНрдпрдХрд░реНрддрд╛ рд╣рдореЗрд╢рд╛ рдПрдХ рд╣реА рдЪреИрдирд▓ рдХреЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд╕рд╛рде рд╕рдВрдЪрд╛рд░ рдХрд░рддрд╛ рд╣реИ)ред рдореИрдВ рдпрд╣ рднреА рджрд╛рд╡рд╛ рдирд╣реАрдВ рдХрд░рддрд╛ рдХрд┐ рдпрд╣ рд╡рд┐рдХрд▓реНрдк рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рд╣реИ (рд▓реЗрдХрд┐рди рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ рдЗрд╕рдиреЗ рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рдХрд╛рдо рдХрд┐рдпрд╛ рд╣реИ)ред рдЕрдВрдд рдореЗрдВ, рд╣рдо рдкреБрд░рд╛рдиреЗ рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдХреЗ рд▓рд┐рдП рдмреИрд╕рд╛рдЦреА (рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдорддрджрд╛рди, рдлреНрд▓реИрд╢) рдкрд░ рд╡рд┐рдЪрд╛рд░ рдирд╣реАрдВ рдХрд░реЗрдВрдЧреЗ (рдФрд░ рдпреЗ IE рдХреЗ рд▓рдЧрднрдЧ рд╕рднреА рд╕рдВрд╕реНрдХрд░рдг рд╣реИрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП) рдЬреЛ рд╡реЗрдм рд╕реЙрдХреЗрдЯ рдХрд╛ рд╕рдорд░реНрдерди рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░ рд╣рдо рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рд╕реЗ рдХрдиреЗрдХреНрдЯ рд╣реЛрдиреЗ рдкрд░ рднреА рд╡рд┐рдЪрд╛рд░ рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВред WebSocket рдкреНрд░реЛрдЯреЛрдХреЙрд▓ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░реЗрдВ, рд▓реЗрдХрд┐рди рдПрдХ рдорд╛рдирдХреАрдХреГрдд рд╕рдВрд╕реНрдХрд░рдг ( RFC 6455 ) рдирд╣реАрдВ, рдмрд▓реНрдХрд┐ рдЕрдкреНрд░рдЪрд▓рд┐рдд рдореЗрдВ рд╕реЗ рдПрдХред рдбреНрд░рд╛рдлреНрдЯ 76 (рдЙрд░реНрдл рд╣рд┐рдХреНрд╕реА -76) рдХреЗ рдкреБрд░рд╛рдиреЗ рд╕рдВрд╕реНрдХрд░рдг рдХреЗ рд▓рд┐рдП рд╕рдорд░реНрдерди рдХреЛ рд╕рдХреНрд╖рдо рдХрд░рдиреЗ рдХреЗ рддрд░реАрдХреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдХреЗ рд▓рд┐рдП, рдмрд╡рдВрдбрд░ рдкреНрд░рд▓реЗрдЦрди рджреЗрдЦреЗрдВ ред

рдлрд┐рд░ рднреА, рдХреЛрдИ рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рдХрд╣ рд╕рдХрддрд╛ рд╣реИ - рдпрд╣ рд╡рд┐рдзрд┐ рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рдХрд╛рдо рдХрд░рддреА рд╣реИ, рдФрд░ рдПрдХ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ рдирд╣реАрдВ, рд▓реЗрдХрд┐рди рдХрдИ рдореЗрдВ (рд╡рд░реНрдгрд┐рдд рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╡рд┐рдзрд┐ рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдЙрдкрдпреЛрдЧ рдХреА рдЧрдИ рд╣реИ, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдмрд╣реБрдд рдЕрдзрд┐рдХ рдЬрд╛рдирдХрд╛рд░реА рдирд╣реАрдВ рд╣реИ)ред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдмреИрдХрдЧреНрд░рд╛рдЙрдВрдб рдбреЗрдЯрд┐рдВрдЧ рдЪрд▓рд╛рдиреЗ рд╡рд╛рд▓рд╛ рд╕рд░реНрд╡рд░ рд╡рд░реНрддрдорд╛рди рдореЗрдВ рд▓рд┐рдиреЛрдб (512 MiB рдореЗрдореЛрд░реА) рд╕реЗ рд╕рдмрд╕реЗ рдХрдо рдЙрдореНрд░ рдХрд╛ VPS рд╣реИ, рд▓реЗрдХрд┐рди рдкреНрд░реЛрд╕реЗрд╕рд░ рд▓реЛрдб 20-40 рдкреНрд░рддрд┐рд╢рдд рд╕реЗ рдЕрдзрд┐рдХ рдирд╣реАрдВ рдмрдврд╝рд╛ рд╣реИ, рдФрд░ рд░реИрдо рдХрд╛ рдЙрдкрдпреЛрдЧ рд▓рдЧрднрдЧ 30% рдерд╛ред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╕рдВрд╕рд╛рдзрди рдореБрдЦреНрдп рд░реВрдк рд╕реЗ gunicorn (Django рдкрд░ рдЪрд▓рдиреЗ рд╡рд╛рд▓реЗ рд╡реЗрдм рд╕рд░реНрд╡рд░) рдФрд░ PostgreSQL рджреНрд╡рд╛рд░рд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛рддреЗ рд╣реИрдВред рд▓реЗрдХрд┐рди рдЗрд╕рдореЗрдВ рдХреБрдЫ рднреА рдЖрд╢реНрдЪрд░реНрдп рдХреА рдмрд╛рдд рдирд╣реАрдВ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдХреЛрдИ рд░рд╣рд╕реНрдп рдирд╣реАрдВ рд╣реИ рдХрд┐ рдЯреЙрд░реНрдиреЗрдбреЛ рди рдХреЗрд╡рд▓ рдЬрд▓реНрджреА рд╕реЗ рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкрд░реНрдпрд╛рдкреНрдд рдкреНрд░рдХрд╛рд╢ рд╣реИ, рдмрд▓реНрдХрд┐ рд╕реА 10k (рдЬреЛ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рд╣рдмреНрд░рд╛рд╣рдмреНрд░ рдкрд░ рдерд╛) рдХреЗ рд╕рд╛рде рд╕рд╛рдордирд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдПред

рддреЛ, рд╣рдо Django 1.4.2, Tornado 2.4, Redis 2.6.5 рдФрд░ PostgreSQL 9.2.1 рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ (рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рдЖрдк рдПрдХ рдФрд░ рд░рд┐рд▓реЗрд╢рдирд▓ DBMS рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ - Django рдХреЗ рдХрдИ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдмреИрдХреЗрдВрдб рд╣реИрдВ)ред рд░реЗрдбрд┐рд╕ рд╕реЗ рдЬреБрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдкрд╛рдпрдерди рдФрд░ рдмреНрд░рд┐рдВрдбрд╕рд╡рд╛ рдХреЗ рд▓рд┐рдП рд░реЗрдбрд┐рд╕-рдкреА рдХреНрд▓рд╛рдЗрдВрдЯ рдорд╛рдирдХ (рдЯреЙрд░реНрдиреЗрдбреЛ рдХреЗ рд╕рд╛рде рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рд░реЗрдбрд┐рд╕ рдХреНрд▓рд╛рдЗрдВрдЯ) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рд╕рд░реНрд╡рд░ рдкрд░ рдпрд╣ рд╕рдм рддреИрдирд╛рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рд╣рд╛рдЗрдкреЛрдХреНрд╕реА рдФрд░ рдиреЗрдЧреНрдиреЗрдХреНрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ, рд╣рдо рдЙрддреНрдкрд╛рджрди рдореЗрдВ Django рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЛ рд▓реЙрдиреНрдЪ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП gunicorn рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ, рдФрд░ рдкрд░реНрдпрд╡реЗрдХреНрд╖рдХ Django рдФрд░ Tadoado рдХреЗ рд▓рд┐рдП рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХреЗ рд▓реЙрдиреНрдЪ рдХреЛ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░реЗрдЧрд╛ред

рдпрджрд┐ рдЖрдкрдХреЛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ рд╕реИрджреНрдзрд╛рдВрддрд┐рдХ рдЬреНрдЮрд╛рди рдХреА рдХрдореА рд╣реИ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЖрдк рдХрд╛рдлреА рд╕рдордЭ рдореЗрдВ рдирд╣реАрдВ рдЖрддреЗ рд╣реИрдВ рдХрд┐ рдПрдХ рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдЧреИрд░-рдЕрд╡рд░реБрджреНрдз рд╡реЗрдм рд╕рд░реНрд╡рд░, рд╕рдВрджреЗрд╢ рдХрддрд╛рд░ рдФрд░ рдЗрддрдиреЗ рдкрд░ рдХреНрдпрд╛ рд╣реИ), рддреЛ рдореЗрд░рд╛ рд╕реБрдЭрд╛рд╡ рд╣реИ рдХрд┐ рдЖрдк рдкрд╣рд▓реЗ рд╕рдВрдмрдВрдзрд┐рдд рджрд╕реНрддрд╛рд╡реЗрдЬ рдкрдврд╝реЗрдВ, рдФрд░ рдпрджрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ рдкреНрд░рд╢реНрди рд╣реИрдВ, рддреЛ рд▓рд┐рдЦреЗрдВ рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ (рдпрд╛ рдореБрдЭреЗ рдореЗрд▓ рдХрд░реЗрдВ)ред рдореИрдВ рдЧреБрд░реБ рдЗрд╡рд╛рди рд╕рдЧрд▓рд╛рдПрд╡ рджреНрд╡рд╛рд░рд╛ рдЙрддреНрдХреГрд╖реНрдЯ рдкрд╛рдпрдерди рдлреЛрд░рдо рдкрд░ ( 1 , 2 , 3 , 4 , 5 , 6 , 7 ) рд╡рд┐рд╖рдп рдкрд░ рдХрдИ рдЪрд░реНрдЪрд╛рдУрдВ рдкрд░ рдзреНрдпрд╛рди рджреЗрдиреЗ рдХреА рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВ, рд╕рд╛рде рд╣реА рд░рд┐рдкреЛрд░реНрдЯ рдХреА рд╕реНрд▓рд╛рдЗрдбреНрд╕ рдкрд░ "рдПрдХ рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдПрдХ рдХреЗ рд╕рд╛рде рдПрдХ рддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рд░реВрдкрд░реЗрдЦрд╛ рдХреЛ рдЬреЛрдбрд╝рдирд╛ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд░реВрдк рдореЗрдВ django рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ)" рдПрдХ рдФрд░ рдкрд╛рдпрдерди рдЧреБрд░реБ, рдорд┐рдЦрд╛рдЗрд▓ рдХреЛрд░реЛрдмреЛрд╡ , рджреЗрд╡рдХреЛрдирдл 2011 рдореЗрдВ рдкрдврд╝рд╛ рдЧрдпрд╛ред

рд╡рд┐рдЪрд╛рд░рд╛рдзреАрди рд╕рдорд╛рдзрд╛рди рдХреЗ рд╕реНрд░реЛрдд рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдФрд░ рдЧрд┐рдЯрд╣рдм рджреЛрдиреЛрдВ рдкрд░ рдореМрдЬреВрдж рд╣реИрдВред

Django рд╕реЗрдЯрдЕрдк


рдПрдХ Django рдкрд░рд┐рдпреЛрдЬрдирд╛ рдмрдирд╛рдПрдВ рдФрд░ рджрд┐рдЦрд╛рдИ рджреЗрдиреЗ рд╡рд╛рд▓реА рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдкрд░ рдЬрд╛рдПрдВ:

 django-admin.py startproject myproject
 рд╕реАрдбреА myproject /

рдЕрдм myproject / settings.py рдлрд╝рд╛рдЗрд▓ рдХреЛ рдПрдбрд┐рдЯ рдХрд░реЗрдВред

рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд╕рддреНрд░реЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд░реЗрдбрд┐рд╕ рдмреИрдХрдПрдВрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рддреБрд░рдВрдд рд╕реНрд╡рд┐рдЪ рдХрд░рдирд╛ рдмрд╣реБрдд рдЕрдЪреНрдЫрд╛ рд╡рд┐рдЪрд╛рд░ рд╣реИред рдпрд╣ рд╕рднреА рдкрд░рд┐рдпреЛрдЬрдирд╛рдУрдВ рдореЗрдВ рдХрд░рдиреЗ рдХреЗ рд▓рд╛рдпрдХ рд╣реИ (рдЙрди рд▓реЛрдЧреЛрдВ рдХреЛ рдЫреЛрдбрд╝рдХрд░, рдЬрд╣рд╛рдВ рдХрд┐рд╕реА рдХрд╛рд░рдг рд╕реЗ рдЖрдк рд░реЗрдбрд┐рд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдирд╣реАрдВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рд╕рд╛рде рд╣реА рд╕рд╛рде рдЬрд╣рд╛рдВ рдЙрди рд▓реЛрдЧреЛрдВ рдХреА рдпреЛрдЬрдирд╛ рдирд╣реАрдВ рд╣реИ)ред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, django-redis-session (pip install django-redis-session) рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ рдФрд░ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ:

SESSION_ENGINE = 'redis_sessions.session' 

рдмрдзрд╛рдИ, рдЖрдкрдиреЗ рдХреЗрд╡рд▓ 1 рд╕реЗ рдХрдо рдХреА рдЧрдИ рд╕рд╛рдЗрдЯ рдкрд░ рдкреНрд░рд╢реНрди рднреЗрдЬрддреЗ рд╕рдордп рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдкреНрд░рд╢реНрдиреЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рдмрдирд╛ рджреА (Redis рддреБрд░рдВрдд рдкреНрд░рд╢реНрдиреЛрдВ рдХрд╛ рдЬрд╡рд╛рдм рджреЗрддреЗ рд╣реИрдВ)ред :)

рд╡реИрд╕реЗ, рдРрд╕реА рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдПрдХ рдЕрд▓рдЧ рдлрд╝рд╛рдЗрд▓ рдореЗрдВ рд╕рдмрд╕реЗ рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рд╕рд╣реЗрдЬреА рдЬрд╛рддреА рд╣реИрдВ рдЬрд┐рд╕реЗ рдЖрдк gitignore рдореЗрдВ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ - рдЖрдорддреМрд░ рдкрд░ local_settings.py

рд╡рд╣рд╛рдВ рдЖрдк рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреА рдЧреБрдкреНрдд рдХреБрдВрдЬреА рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП (SECRET_KEY), рд╕рд╛рде рд╣реА рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ред рдпрд╣рд╛рдБ рдореБрджреНрджрд╛ рдпрд╣ рд╣реИ рдХрд┐, рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдЖрдк рдПрдХ рд╡рд┐рд╢рд┐рд╖реНрдЯ рд╕рд░реНрд╡рд░ (рд╕реНрдерд╛рдиреАрдп рдбреЗрдЯрд╛рдмреЗрд╕, рдХреИрд╢рд┐рдВрдЧ рдмреИрдХрдПрдВрдб, рд╕рддреНрд░ рдмреИрдХрдПрдВрдб, рдбрд┐рдмрдЧ рдореЛрдб рд╕реЗрдЯрд┐рдВрдЧреНрд╕) рдореЗрдВ local_settings.py, рдФрд░ рджреВрд╕рд░реА рддрд░рд╣ Git (рдпрд╛ рдХрд┐рд╕реА рдЕрдиреНрдп рд╕рдВрд╕реНрдХрд░рдг рдирд┐рдпрдВрддреНрд░рдг рдкреНрд░рдгрд╛рд▓реА) рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢рд┐рд╖реНрдЯ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдХреЛ рд╕рд╣реЗрдЬ рд╕рдХрддреЗ рд╣реИрдВред рдХреБрдВрдЬреА рдФрд░ рдкрд╛рд╕рд╡рд░реНрдб рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рд╕рдВрдЧреНрд░рд╣реАрдд рдирд╣реАрдВ рдХрд░реЗрдЧрд╛ред

рддрджрдиреБрд╕рд╛рд░, рдРрд╕реА рд╕реЗрдЯрд┐рдВрдЧ рдХреЛ local_settings.py рдореЗрдВ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рд╕реЗрдЯрд┐рдВрдЧ рдХреЗ рдЕрдВрдд рдореЗрдВ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдЬреЛрдбрд╝рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:

 try: from local_settings import * except ImportError: pass 

рдЗрд╕рдХреЗ рдмрд╛рдж, рдбреЗрдЯрд╛рдмреЗрд╕ (DATABASES) рдХреЛ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рдирд╛ рди рднреВрд▓реЗрдВ, рдЯреЗрдореНрдкреНрд▓реЗрдЯ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░реЗрдВ, рд╕реНрдЯреЗрдЯрд┐рдХ рдлрд╛рдЗрд▓реНрд╕ рдХреА рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА рдФрд░ рдПрдкреАрдЖрдИ рдХреА (Tornado, рдПрд╕рд┐рдВрдХреНрд░реЛрдирд╕ рд░реВрдк рд╕реЗ Django рдХреЛ рд░рд┐рдХреНрд╡реЗрд╕реНрдЯ рднреЗрдЬреЗрдЧрд╛ рдФрд░ Django рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдЗрд╕ рдореИрд╕реЗрдЬ рдХреЛ рдПрдХ рдирдпрд╛ рдореИрд╕реЗрдЬ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рд╕реЗ рдмрдЪрд╛рдПрдЧрд╛), рд╕рд╛рде рд╣реА URL рднреАред рдЬрд┐рд╕ рдкрд░ рдЖрдкрдХреЛ рдЕрдиреБрд░реЛрдз рднреЗрдЬрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред

рддрд╛рдХрд┐ рдЬрдм рдЖрдк рд╕рд╛рдЗрдЯ рдХреЛ рдХрд┐рд╕реА рдЕрдиреНрдп рд╕рд░реНрд╡рд░ рдкрд░ рддреИрдирд╛рдд рдХрд░реЗрдВ (рдпрд╛ рдПрдлрдПрд╕ рдореЗрдВ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХреЛ рдХрд┐рд╕реА рдЕрдиреНрдп рд╕реНрдерд╛рди рдкрд░ рд░рдЦрддреЗ рд╕рдордп) рддреЛ рдЖрдкрдХреЛ рдЯреЗрдореНрдкрд▓реЗрдЯ рдФрд░ рд╕реНрдерд┐рд░ рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреА рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ рдкрде рдмрджрд▓рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдирд╣реАрдВ рд╣реИ, рдКрдкрд░ рджрд┐рдП рдЧрдП рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдореЗрдВ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдЬреЛрдбрд╝рдХрд░ рд╕реНрдЯрд╛рд░реНрдЯрдЕрдк рдкрд░ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХрд╛ рд╕реНрдерд╛рди рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рдирд╛ рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рд╣реИ:

 import os PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) 

рдФрд░ рдлрд┐рд░, рддрджрдиреБрд╕рд╛рд░, PROJECT_ROOT рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдЕрдиреНрдп рдкрде рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВред

рд╕реНрдереИрддрд┐рдХ рдлрд╝рд╛рдЗрд▓реЗрдВ:

 STATICFILES_DIRS = ( os.path.join(PROJECT_ROOT, "static"), ) 

рдЯреЗрдореНрдкрд▓реЗрдЯреНрд╕:

 TEMPLATE_DIRS = ( os.path.join(PROJECT_ROOT, "templates"), ) 

рдПрдкреАрдЖрдИ рдХреБрдВрдЬреА рдФрд░ рдкрддрд╛:

 API_KEY = '$0m3-U/\/1qu3-K3Y' SEND_MESSAGE_API_URL = 'http://127.0.0.1:8000/messages/send_message_api' 

рдПрдХ рдХреБрдВрдЬреА рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдк рдХрдВрд╕реЛрд▓ рдореЗрдВ рдЗрд╕ рд╕рд░рд▓ рд░реЗрдЦрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (рд╡реИрд╕реЗ, рдЗрдореЛрдЯрд┐рдХреЙрди рдЖрдкрдХреЛ рд╡рд╣рд╛рдВ рд╕реЗ рджрд┐рдЦрддрд╛ рд╣реИ):

 </ dev / urandom trdc _A-Zaz-0-9 |  рд╕рд┐рд░ -c $ {1: -32}; рдЧреВрдВрдЬ;

рдЕрдм рдпрд╣ рдорд╛рдпрдкреНрд░реЛрдЬреЗрдХреНрдЯ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА (рдЬрд╣рд╛рдВ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдУрд░реЛрдореЛ рдФрд░ рд▓реЛрдХрд▓_рд╕реЗрдЯрд╕реЗрдЯреНрд╕рд╣реЛрдо рд╕реНрдерд┐рдд рд╣реИрдВ) рдореЗрдВ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА рдХреЛ рд╕реНрдЯреИрдЯрд┐рдХ рдФрд░ рдЯреЗрдореНрдкреНрд▓реЗрдЯ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдирд╛ рд╣реБрдЖ рд╣реИ рдФрд░ рдЪреИрдЯ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд▓рд┐рдЦрдирд╛ рд╢реБрд░реВ рдХрд░реЗрдВред

рдЪреИрдЯ (Django)


рдПрдХ рдирдпрд╛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдЬреЛрдбрд╝реЗрдВ:

 python рдирд┐рдЬреАрдХреГрдд рд╕реНрдЯрд╛рд░реНрдЯрдЕрдк рдХреЛ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░рддрд╛ рд╣реИ

рдФрд░ рдореЙрдбрд▓ рд▓рд┐рдЦреЗрдВ (Privatemessages / model.py):

 from django.db import models from django.db.models.signals import post_save from django.contrib.auth.models import User # Create your models here. class Thread(models.Model): participants = models.ManyToManyField(User) last_message = models.DateTimeField(null=True, blank=True, db_index=True) class Message(models.Model): text = models.TextField() sender = models.ForeignKey(User) thread = models.ForeignKey(Thread) datetime = models.DateTimeField(auto_now_add=True, db_index=True) def update_last_message_datetime(sender, instance, created, **kwargs): """ Update Thread's last_message field when a new message is sent. """ if not created: return Thread.objects.filter(id=instance.thread.id).update( last_message=instance.datetime ) post_save.connect(update_last_message_datetime, sender=Message) 

рдХреБрдЫ рднреА рдЬрдЯрд┐рд▓ рдирд╣реАрдВ рд╣реИ - рд╕рдВрджреЗрд╢ рдФрд░ рдзрд╛рдЧреЗ (рдереНрд░реЗрдб) рд╣реИрдВред рдкреНрд░рддреНрдпреЗрдХ рджреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдПрдХ рдереНрд░реЗрдб рдмрдирд╛рдпрд╛ рдЬрд╛рдПрдЧрд╛, рдФрд░ рдЙрдирдХреЗ рд╕рднреА рд╕рдВрджреЗрд╢ рдЗрд╕ рдереНрд░реЗрдб рд╕реЗ рд╕рдВрдмрдВрдзрд┐рдд рд╣реЛрдВрдЧреЗред рдЬрдм рдПрдХ рдирдпрд╛ рд╕рдВрджреЗрд╢ рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рддреЛ рд╕рдВрдмрдВрдзрд┐рдд рдереНрд░реЗрдб рдХреЗ рд▓рд┐рдП рдЕрдВрддрд┐рдо рд╕рдВрджреЗрд╢ рдХреА рддрд╛рд░реАрдЦ рдФрд░ рд╕рдордп рдЕрдкрдбреЗрдЯ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред

рдЕрдм рд╕реЗрдЯрд┐рдВрдЧ рдореЗрдВ INSTALLED_APPS рдореЗрдВ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдЬреЛрдбрд╝рдиреЗ рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд╕рд╛рде рдореЙрдбрд▓ рдХреЛ рд╕рд┐рдВрдХреНрд░рдирд╛рдЗрдЬрд╝ рдХрд░рдиреЗ рдХрд╛ рд╕рдордп рд╣реИ:

 python manage.py syncdb

рдЖрдЗрдП, Privatemessages / views.py рдЦреЛрд▓реЗрдВ рдФрд░ 4 рд╡рд┐рдЪрд╛рд░ рд▓рд┐рдЦреЗрдВ:

 # Create your views here. import json import redis from django.shortcuts import render_to_response, get_object_or_404 from django.http import HttpResponse, HttpResponseRedirect from django.template import RequestContext from django.core.urlresolvers import reverse from django.utils import timezone from django.views.decorators.csrf import csrf_exempt from django.conf import settings from django.contrib.auth.models import User from privatemessages.models import Thread, Message from privatemessages.utils import json_response, send_message def send_message_view(request): if not request.method == "POST": return HttpResponse("Please use POST.") if not request.user.is_authenticated(): return HttpResponse("Please sign in.") message_text = request.POST.get("message") if not message_text: return HttpResponse("No message found.") if len(message_text) > 10000: return HttpResponse("The message is too long.") recipient_name = request.POST.get("recipient_name") try: recipient = User.objects.get(username=recipient_name) except User.DoesNotExist: return HttpResponse("No such user.") if recipient == request.user: return HttpResponse("You cannot send messages to yourself.") thread_queryset = Thread.objects.filter( participants=recipient ).filter( participants=request.user ) if thread_queryset.exists(): thread = thread_queryset[0] else: thread = Thread.objects.create() thread.participants.add(request.user, recipient) send_message( thread.id, request.user.id, message_text, request.user.username ) return HttpResponseRedirect( reverse('privatemessages.views.messages_view') ) @csrf_exempt def send_message_api_view(request, thread_id): if not request.method == "POST": return json_response({"error": "Please use POST."}) api_key = request.POST.get("api_key") if api_key != settings.API_KEY: return json_response({"error": "Please pass a correct API key."}) try: thread = Thread.objects.get(id=thread_id) except Thread.DoesNotExist: return json_response({"error": "No such thread."}) try: sender = User.objects.get(id=request.POST.get("sender_id")) except User.DoesNotExist: return json_response({"error": "No such user."}) message_text = request.POST.get("message") if not message_text: return json_response({"error": "No message found."}) if len(message_text) > 10000: return json_response({"error": "The message is too long."}) send_message( thread.id, sender.id, message_text ) return json_response({"status": "ok"}) def messages_view(request): if not request.user.is_authenticated(): return HttpResponse("Please sign in.") threads = Thread.objects.filter( participants=request.user ).order_by("-last_message") if not threads: return render_to_response('private_messages.html', {}, context_instance=RequestContext(request)) r = redis.StrictRedis() user_id = str(request.user.id) for thread in threads: thread.partner = thread.participants.exclude(id=request.user.id)[0] thread.total_messages = r.hget( "".join(["thread_", str(thread.id), "_messages"]), "total_messages" ) return render_to_response('private_messages.html', { "threads": threads, }, context_instance=RequestContext(request)) def chat_view(request, thread_id): if not request.user.is_authenticated(): return HttpResponse("Please sign in.") thread = get_object_or_404( Thread, id=thread_id, participants__id=request.user.id ) messages = thread.message_set.order_by("-datetime")[:100] user_id = str(request.user.id) r = redis.StrictRedis() messages_total = r.hget( "".join(["thread_", thread_id, "_messages"]), "total_messages" ) messages_sent = r.hget( "".join(["thread_", thread_id, "_messages"]), "".join(["from_", user_id]) ) if messages_total: messages_total = int(messages_total) else: messages_total = 0 if messages_sent: messages_sent = int(messages_sent) else: messages_sent = 0 messages_received = messages_total-messages_sent partner = thread.participants.exclude(id=request.user.id)[0] tz = request.COOKIES.get("timezone") if tz: timezone.activate(tz) return render_to_response('chat.html', { "thread_id": thread_id, "thread_messages": messages, "messages_total": messages_total, "messages_sent": messages_sent, "messages_received": messages_received, "partner": partner, }, context_instance=RequestContext(request)) 

рдФрд░ рдпрд╣рд╛рдБ рдкреНрд░рд╛рдЗрд╡реЗрдЯрдореИрд╕реЗрдЬ / utils.py рд╣реИ:

 import json import redis from django.utils import dateformat from privatemessages.models import Message def json_response(obj): """ This function takes a Python object (a dictionary or a list) as an argument and returns an HttpResponse object containing the data from the object exported into the JSON format. """ return HttpResponse(json.dumps(obj), content_type="application/json") def send_message(thread_id, sender_id, message_text, sender_name=None): """ This function takes Thread object id (first argument), sender id (second argument), message text (third argument) and can also take sender's name. It creates a new Message object and increases the values stored in Redis that represent the total number of messages for the thread and the number of this thread's messages sent from this specific user. If a sender's name is passed, it also publishes the message in the thread's channel in Redis (otherwise it is assumed that the message was already published in the channel). """ message = Message() message.text = message_text message.thread_id = thread_id message.sender_id = sender_id message.save() thread_id = str(thread_id) sender_id = str(sender_id) r = redis.StrictRedis() if sender_name: r.publish("".join(["thread_", thread_id, "_messages"]), json.dumps({ "timestamp": dateformat.format(message.datetime, 'U'), "sender": sender_name, "text": message_text, })) for key in ("total_messages", "".join(["from_", sender_id])): r.hincrby( "".join(["thread_", thread_id, "_messages"]), key, 1 ) 

рдЖрдк рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдкрд░ Django рдкреНрд░рд▓реЗрдЦрди рдореЗрдВ tz.activate () рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВред рдЪреВрдВрдХрд┐ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рджрд╛рди рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ (рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рднрд╛рд╖рд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА рдХреЗ рд╡рд┐рдкрд░реАрдд), рд╣рдореЗрдВ рдЯрд╛рдЗрдордЬрд╝реЛрди рдирд╛рдордХ рдЧреНрд░рд╛рд╣рдХ рдкрдХреНрд╖ рдкрд░ рдПрдХ рдХреБрдХреА рдмрдирд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ - рдЗрд╕ рддрд░рд╣ рд╕реЗ Django рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдореЗрдВ рдкреНрд░рддреНрдпреЗрдХ рд╕рдВрджреЗрд╢ рдХреА рддрд╛рд░реАрдЦ рдФрд░ рд╕рдордп рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛрдЧрд╛ рдЬрд┐рд╕рдореЗрдВ рдкрд▓ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд╣реИред

рдХреНрд▓рд╛рдЗрдВрдЯ рдкрдХреНрд╖ рдкрд░ рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдХреА рдЧрдгрдирд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо jstz рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ (myproject / static directory рдореЗрдВ jstz.min.js рд░рдЦрдирд╛ рди рднреВрд▓реЗрдВ)ред рдРрд╕реЗ рдЕрдиреНрдп рд╕рдорд╛рдзрд╛рди рд╣реИрдВ рдЬреЛ рдЖрдкрдХреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдХреЛ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддреЗ рд╣реИрдВ: рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдЖрдк рдЬрд┐рдпреЛрдЖрдИрдкреА рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рдЖрдзрд╛рд░ рдкрд░ рд╕рдмрд╕реЗ рдЕрдзрд┐рдХ рд╕рдВрднрд╛рд╡рдирд╛ рд╕рдордп рдХреНрд╖реЗрддреНрд░ рд▓реЗ рд╕рдХрддреЗ рд╣реИрдВ рдпрд╛ рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рд╕рд░реНрд╡рд░ рдкрд░ рд╕реНрдерд╛рдиреАрдп рд╕рдордп рдХреЗ рд╕рд╛рде рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдХрдВрдкреНрдпреВрдЯрд░ рдкрд░ рд╕реНрдерд╛рдиреАрдп рд╕рдордп рдХреА рддреБрд▓рдирд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (рдХреНрдпреЛрдВрдХрд┐ рд╣рдо рдЬрд╛рдирддреЗ рд╣реИрдВ рдХрд┐ рд╕рдордп рд╕рд░реНрд╡рд░ рдкрд░ рд╕рд╣реА рдврдВрдЧ рд╕реЗ рд╕реЗрдЯ рд╣реИ) - рдРрд╕рд╛ рд╕рдорд╛рдзрд╛рди рдЖрдкрдХреЛ рдЗрд╕ рдШрдЯрдирд╛ рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдХрд╛ рдкрддрд╛ рд▓рдЧрд╛рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ рдХрд┐ рдХреБрдЫ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдЕрд▓рдЧ рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдЙрд╕рдХреЗ рд╕рд┐рд╕реНрдЯрдо рдореЗрдВ рдЪреБрдирд╛ рдЧрдпрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╕рдордп рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдХреЗ рдЕрдиреБрд░реВрдк рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред

рд▓реЗрдХрд┐рди рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ, рдорд╛рди рд▓реЗрдВ рдХрд┐ рдпрджрд┐ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдкрд╛рд╕ рдЧрд▓рдд рд╕рдордп рдХреНрд╖реЗрддреНрд░ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рд╣реИ, рддреЛ рд╕рдордп рдХреА рдЕрд╢реБрджреНрдзрд┐ рдХреА рд╕рдмрд╕реЗ рдЕрдзрд┐рдХ рд╕рдВрднрд╛рд╡рдирд╛ рдЙрд╕реЗ рд╕реВрдЯ рдХрд░рддреА рд╣реИ (рдЕрдиреНрдпрдерд╛ рдпрд╣ рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рдмрджрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрддрд┐рд░рд┐рдХреНрдд рдЕрдиреБрд╕реНрдорд╛рд░рдХ рд╣реЛрдЧрд╛)ред

рдпрд╣ рднреА рдзреНрдпрд╛рди рджреЗрдВ рдХрд┐ рдЖрдкрдХреЛ pytz (pip install pytz) рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреА - рдСрд▓рд╕рди рдбреЗрдЯрд╛рдмреЗрд╕ ( IANA рдЯрд╛рдЗрдо рдЬрд╝реЛрди рдбреЗрдЯрд╛рдмреЗрд╕ ) рдХреЗ рдкреНрд░рд╛рд░реВрдк рдореЗрдВ рдПрдХ рд╕реНрдЯреНрд░рд┐рдВрдЧ рд╕реЗ рдЯрд╛рдЗрдо рдЬрд╝реЛрди рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдпрд╣ рдЖрд╡рд╢реНрдпрдХ рд╣реИред

рдЬрдм send_messages рдлрд╝рдВрдХреНрд╢рди рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдПрдХ рдирдпрд╛ рд╕рдВрджреЗрд╢ рдЬреЛрдбрд╝рддрд╛ рд╣реИ, рддреЛ рдпрд╣ Redis рдореЗрдВ рдЗрд╕ рдереНрд░реЗрдб рдХреЗ рд▓рд┐рдП рд╕рдВрджреЗрд╢реЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рдХреЗ рд╕рд╛рде рд╣реИрд╢ рдХреЛ рднреА рдЕрдкрдбреЗрдЯ рдХрд░рддрд╛ рд╣реИред рдЗрд╕ рд╣реИрд╢ рдореЗрдВ, рджреЛ рдХреБрдВрдЬрд┐рдпрд╛рдБ рдмрдврд╝ рдЬрд╛рддреА рд╣реИрдВ - рдЙрдирдореЗрдВ рд╕реЗ рдПрдХ рдереНрд░реЗрдб рдореЗрдВ рд╕рдВрджреЗрд╢реЛрдВ рдХреА рдХреБрд▓ рд╕рдВрдЦреНрдпрд╛ рдХрд╛ рдкреНрд░рддрд┐рдирд┐рдзрд┐рддреНрд╡ рдХрд░рддрд╛ рд╣реИ, рдФрд░ рджреВрд╕рд░рд╛ рдЗрд╕ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рд╕рдВрджреЗрд╢реЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рдХрд╛ рдкреНрд░рддрд┐рдирд┐рдзрд┐рддреНрд╡ рдХрд░рддрд╛ рд╣реИред рдЗрди рдХреБрдВрдЬрд┐рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рд╕рдВрджреЗрд╢реЛрдВ рдХреА рдХреБрд▓ рд╕рдВрдЦреНрдпрд╛ рдХреЛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд╕рд╛рде-рд╕рд╛рде рдкреНрд░рд╛рдкреНрдд рдФрд░ рднреЗрдЬреЗ рдЧрдП рд╕рдВрджреЗрд╢реЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ (рдкреНрд░рд╛рдкреНрдд рдХреА рдЧрдИ рд╕рдВрдЦреНрдпрд╛ рдХреБрд▓ рднреЗрдЬреЗ рдЧрдП рд╕рдВрджреЗрд╢реЛрдВ рдХреА рд╕рдВрдЦреНрдпрд╛ рдХреЛ рдШрдЯрд╛рдХрд░) рдХреЗ рд░реВрдк рдореЗрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред

рдЬрдм рдХреЛрдИ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдПрдХ рдЪреИрдЯ рдЦреЛрд▓рддрд╛ рд╣реИ рдФрд░ рдЙрд╕рдХрд╛ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдПрдХ Tornado рд╕рд░реНрд╡рд░ рдХреЗ рд╕рд╛рде рдПрдХ WS-рдХрдиреЗрдХреНрд╢рди рдЦреЛрд▓рддрд╛ рд╣реИ, рддреЛ Tornado рд╕рд░реНрд╡рд░ Redis рдореЗрдВ рдереНрд░реЗрдб рдЪреИрдирд▓ рдХреА рд╕рджрд╕реНрдпрддрд╛ рд▓реЗрддрд╛ рд╣реИ, рдФрд░ рдЬрдм рдирдП рд╕рдВрджреЗрд╢ рджрд┐рдЦрд╛рдИ рджреЗрддреЗ рд╣реИрдВ, рддреЛ рдпрд╣ рддреБрд░рдВрдд рдЙрдиреНрд╣реЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рднреЗрдЬрддрд╛ рд╣реИред

рдпрджрд┐ рдЖрдк Redis (SUBSCRIBE, UNSUBSCRIBE, рдФрд░ PUBLISH рдХрдорд╛рдВрдбреНрд╕) рдореЗрдВ Pub / Sub рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рдирд╣реАрдВ рд╣реИрдВ, рддреЛ рдЖрдк рдЗрд╕рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ Redis рдкреНрд░рд▓реЗрдЦрди рдореЗрдВ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВред

рдЬрдм send_messages рдлрд╝рдВрдХреНрд╢рди рдХреЙрд▓ send_message_view рдФрд░ рдЧреБрдЬрд░рддрд╛ рд╣реИ, рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ, рдкреНрд░реЗрд╖рдХ рдХрд╛ рдирд╛рдо, рдлрд╝рдВрдХреНрд╢рди Redis рдореЗрдВ рдЗрд╕ рдереНрд░реЗрдб рдХреЗ рдЪреИрдирд▓ рдкрд░ рдПрдХ рд╕рдВрджреЗрд╢ рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░рддрд╛ рд╣реИред Send_message_api_view рдХреЗ рдорд╛рдорд▓реЗ рдореЗрдВ, рдкреНрд░реЗрд╖рдХ рдХрд╛ рдирд╛рдо рдкреНрд░реЗрд╖рд┐рдд рдирд╣реАрдВ рд╣реЛрддрд╛ рд╣реИ, рдФрд░ рдпрд╣ рдорд╛рдирд╛ рдЬрд╛рддрд╛ рд╣реИ рдХрд┐ рд╕рдВрджреЗрд╢ рдЗрд╕ рдзрд╛рдЧреЗ рдХреЗ рдЪреИрдирд▓ рдХреЛ рдкрд╣рд▓реЗ рд╣реА рднреЗрдЬрд╛ рдЬрд╛ рдЪреБрдХрд╛ рд╣реИ, send_message_api_view (рдпрд╣ рдХрд░рдиреЗ рд╕реЗ рдкрд╣рд▓реЗ рд╣реА рд╕рдВрджреЗрд╢ рдкреНрд░реЗрд╖рд┐рдд рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ рддрд╛рдХрд┐ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рд╕реЗ рдЪреИрдЯрд┐рдВрдЧ рдХреЗ рдмреАрдЪ рдЬрд┐рддрдиреА рдЬрд▓реНрджреА рд╣реЛ рд╕рдХреЗ рд╕рдВрдЪрд╛рд░ рд╣реЛ рдЬрд╛рдП - рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рд╕рд╣реЗрдЬрдирд╛ рдЕрд╕рдВрдЧрдд рд░реВрдк рд╕реЗ рд╣реЛрддрд╛ рд╣реИред , рдЪреИрдирд▓ рдкрд░ рдкреНрд░рдХрд╛рд╢рди рдХреЗ рдмрд╛рдж)ред

рдЕрдм Privatemessages / urls.py рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдПрдВ рдФрд░ Django рд╡рд┐рдзрд┐рдпреЛрдВ рдХреЗ рд▓рд┐рдП urlpatterns рд╕реЗрдЯ рдХрд░реЗрдВ:

 from django.conf.urls import patterns, url urlpatterns = patterns('privatemessages.views', url(r'^send_message/$', 'send_message_view'), url(r'^send_message_api/(?P<thread_id>\d+)/$', 'send_message_api_view'), url(r'^chat/(?P<thread_id>\d+)/$', 'chat_view'), url(r'^$', 'messages_view'), ) 

рдФрд░ рд╣рд╛рдВ, рдЙрдиреНрд╣реЗрдВ рд░реВрдЯ URLconf (myproject / urls.py) рдореЗрдВ рд╢рд╛рдорд┐рд▓ рдХрд░реЗрдВ:

 from django.conf.urls import patterns, include, url # something else urlpatterns = patterns('', # something else url(r'^messages/', include('privatemessages.urls')), # something else ) 

рдЯреЗрдореНрдкрд▓реЗрдЯ рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рдореЗрдВ рдЖрдкрдХреЛ chat.html рдФрд░ priv_messages.html рд░рдЦрдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рдЖрдзрд╛рд░ рдЯреЗрдореНрдкрд▓реЗрдЯ base.html рднреА рдЬреЛрдбрд╝реЗрдВред

base.html

 <!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="/static/privatemessages.css"> <script type="text/javascript" src="http://yandex.st/jquery/1.8.3/jquery.min.js"></script> <script type="text/javascript" src="/static/jstz.min.js"></script> <script type="text/javascript" src="/static/privatemessages.js"></script> {% block head %}{% endblock %} <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>{% block title %} {% endblock title %}</title> </head> <body> {% block content %}{% endblock content %} </body> </html> 

chat.html

 {% extends "base.html" %} {% block title %}{{ partner.username }}{% endblock %} {% block head %} <script type="text/javascript"> $(document).ready(function() { activate_chat({{ thread_id }}, "{{ user.username }}", { "total": {{ messages_total }}, "sent": {{ messages_sent }}, "received": {{ messages_received }} }); }); </script> {% endblock %} {% block content %} {% load pluralize %} <div class="chat"> <div class="partner"> <p class="name">{{ partner.username }}</p> <p class="messages"><span class="total">{{ messages_total }}</span> {{ messages_total|rupluralize:",," }} (<span class="received">{{ messages_received }}</span> , <span class="sent">{{ messages_sent }}</span> )</p> </div> <div class="conversation"> {% for message in thread_messages reversed %} <div class="message"> {% if message.sender == user %}<p class="author we"><span class="datetime">{{ message.datetime|date:"dmY H:i:s" }}</span> {{ user.username }}:</p>{% else %}<p class="author partner"><span class="datetime">{{ message.datetime|date:"dmY H:i:s" }}</span> {{ partner.username }}:</p>{% endif %} <p class="message">{{ message.text|linebreaksbr }}</p> </div> {% endfor %} </div> <form class="message_form"> <div class="compose"> <textarea rows="1" cols="30" id="message_textarea"></textarea> </div> <div class="send"> <button class="btn" type="button"></button> <p>        Ctrl + Enter.</p> </div> </form> </div> {% endblock content %} 

private_messages.html

 {% extends "base.html" %} {% block content %} {% load pluralize %} <div class="private_messages"> <h1></h1> <div class="partners"> {% for thread in threads %} <p><a href="{% url privatemessages.views.chat_view thread.id %}">{{ thread.partner.username }} ({{ thread.total_messages|default_if_none:"0" }} {{ thread.total_messages|rupluralize:",," }})</a></p> {% empty %} <p>   .</p> {% endfor %} </div> <h1> </h1> <form action="{% url privatemessages.views.send_message_view %}" method="post" class="new_message"> {% csrf_token %} <p class="name"><input name="recipient_name" placeholder=" "></p> <p><textarea name="message" placeholder=""></textarea></p> <p><input type="submit" value=""></p> </form> </div> {% endblock content %} 

рдФрд░ __init__.py рдФрд░ pluralize.py рдлрд╛рдЗрд▓реЛрдВ рдХреЗ рд╕рд╛рде рдкреНрд░рд╛рдЗрд╡реЗрдЯрдореИрд╕реЗрдЬ / рдЯреЗрдореНрдкреНрд▓реЗрдЯреИрдЯреНрд╕ рдбрд╛рдпрд░реЗрдХреНрдЯрд░реА рдмрдирд╛рдПрдВред

Privatemessages / templatetags / pluralize.py ( рдлрд╝рд┐рд▓реНрдЯрд░ рд▓реЗрдЦрдХ - V @ s3K):

 from django import template register = template.Library() @register.filter def rupluralize(value, arg): args = arg.split(",") try: number = abs(int(value)) except TypeError: number = 0 a = number % 10 b = number % 100 if (a == 1) and (b != 11): return args[0] elif (a >= 2) and (a <= 4) and ((b < 10) or (b >= 20)): return args[1] else: return args[2] 

рд╢реИрд▓рд┐рдпрд╛рдБ рдЬреЛрдбрд╝реЗрдВ (myproject / static / privmessages.css):

 html, body { height: 100%; margin: 0; } body { font-family: Geneva, Arial, Helvetica, sans-serif; font-size: 14px; color: #000; background: #fff; } textarea, form p.name input { border: 1px #d4d4d4 solid; } form.new_message p { margin: 4px 0; } form.new_message p.name input, form.new_message textarea { width: 300px; } form.new_message textarea { height: 100px; } textarea:focus, input.name:focus { border-color: #cacaca; -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 3px rgba(150, 150, 150, 0.5); -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 3px rgba(150, 150, 150, 0.5); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 3px rgba(150, 150, 150, 0.5); } div.private_messages { padding: 10px; } div.private_messages h1 { margin-top: 0; } div.private_messages div.partners { margin-bottom: 30px; } div.chat { height: 100%; min-height: 400px; min-width: 600px; position: relative; background-color: #e0e0e0; } div.chat div.partner { height: 50px; padding: 5px; } div.chat div.partner p { margin: 0; } div.chat div.partner p.name { font-weight: bold; margin-bottom: 3px; } div.chat div.conversation { position: absolute; top: 50px; left: 5px; right: 5px; bottom: 140px; overflow: auto; padding: 0 5px; border-style: solid; border-color: #eee; border-width: 10px 0; background-color: #fff; -webkit-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; } div.chat div.conversation div.message { padding: 5px 0; } div.chat div.conversation div.message p { margin: 0; } div.chat div.conversation div.message p.author.partner { color: #002c64; } div.chat div.conversation div.message p.author.we { color: #216300; } div.chat div.conversation div.message p.author span.datetime { font-size: 12px; } div.chat form.message_form { position: absolute; margin: 0; left: 0; right: 0; bottom: 5px; height: 130px; } div.chat form.message_form div.outdated_browser_message { margin: 10px 5px; } div.chat form.message_form div.compose { float: left; height: 100%; width: 80%; padding: 0 5px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } div.chat form.message_form div.compose textarea { width: 100%; height: 100%; margin: 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; resize: none; } div.chat form.message_form div.send { float: left; height: 100%; width: 20%; min-width: 100px; padding-right: 5px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } div.chat form.message_form div.send p { margin-top: 5px; color: #333; } div.chat form.message_form div.send button { width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } 

рдФрд░ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ (myproject / static / privatemessages.js):

 function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } function setCookie(key, value) { document.cookie = escape(key) + '=' + escape(value); } function getNumEnding(iNumber, aEndings) { var sEnding, i; iNumber = iNumber % 100; if (iNumber>=11 && iNumber<=19) { sEnding=aEndings[2]; } else { i = iNumber % 10; switch (i) { case (1): sEnding = aEndings[0]; break; case (2): case (3): case (4): sEnding = aEndings[1]; break; default: sEnding = aEndings[2]; } } return sEnding; } var timezone = getCookie('timezone'); if (timezone == null) { setCookie("timezone", jstz.determine().name()); } function activate_chat(thread_id, user_name, number_of_messages) { $("div.chat form.message_form div.compose textarea").focus(); function scroll_chat_window() { $("div.chat div.conversation").scrollTop($("div.chat div.conversation")[0].scrollHeight); } scroll_chat_window(); var ws; function start_chat_ws() { ws = new WebSocket("ws://127.0.0.1:8888/" + thread_id + "/"); ws.onmessage = function(event) { var message_data = JSON.parse(event.data); var date = new Date(message_data.timestamp*1000); var time = $.map([date.getHours(), date.getMinutes(), date.getSeconds()], function(val, i) { return (val < 10) ? '0' + val : val; }); $("div.chat div.conversation").append('<div class="message"><p class="author ' + ((message_data.sender == user_name) ? 'we' : 'partner') + '"><span class="datetime">' + time[0] + ':' + time[1] + ':' + time[2] + '</span> ' + message_data.sender + ':</p><p class="message">' + message_data.text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g, '<br />') + '</p></div>'); scroll_chat_window(); number_of_messages["total"]++; if (message_data.sender == user_name) { number_of_messages["sent"]++; } else { number_of_messages["received"]++; } $("div.chat p.messages").html('<span class="total">' + number_of_messages["total"] + '</span> ' + getNumEnding(number_of_messages["total"], ["", "", ""]) + ' (<span class="received">' + number_of_messages["received"] + '</span> , <span class="sent">' + number_of_messages["sent"] + '</span> )'); } ws.onclose = function(){ // Try to reconnect in 5 seconds setTimeout(function() {start_chat_ws()}, 5000); }; } if ("WebSocket" in window) { start_chat_ws(); } else { $("form.message_form").html('<div class="outdated_browser_message"><p><em>!</em>    . ,    :</p><ul><li> <em>Android</em>: <a href="http://www.mozilla.org/ru/mobile/">Firefox</a>, <a href="http://www.google.com/intl/en/chrome/browser/mobile/android.html">Google Chrome</a>, <a href="https://play.google.com/store/apps/details?id=com.opera.browser">Opera Mobile</a></li><li> <em>Linux</em>, <em>Mac OS X</em>  <em>Windows</em>: <a href="http://www.mozilla.org/ru/firefox/fx/">Firefox</a>, <a href="https://www.google.com/intl/ru/chrome/browser/">Google Chrome</a>, <a href="http://ru.opera.com/browser/download/">Opera</a></li></ul></div>'); return false; } function send_message() { var textarea = $("textarea#message_textarea"); if (textarea.val() == "") { return false; } if (ws.readyState != WebSocket.OPEN) { return false; } ws.send(textarea.val()); textarea.val(""); } $("form.message_form div.send button").click(send_message); $("textarea#message_textarea").keydown(function (e) { // Ctrl + Enter if (e.ctrlKey && e.keyCode == 13) { send_message(); } }); } 

рдПрдХ рд╕рд░реНрд╡рд░ рдкрд░ рддреИрдирд╛рддреА рдХрд░рддреЗ рд╕рдордп, рдЙрд╕ рдкрддреЗ рдХреЛ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдирд╛ рди рднреВрд▓реЗрдВ рдЬрд╣рд╛рдВ рдЯреЙрд░рдиреЗрдбреЛ рд╕рд░реНрд╡рд░ рдЙрдкрд▓рдмреНрдз рд╣реЛрдЧрд╛ (рдпрд╛ рдЗрд╕реЗ рдПрдХ рдЕрд▓рдЧ рдЪрд░ рдореЗрдВ рднреА рдбрд╛рд▓ рджрд┐рдпрд╛ рдЬрд╛рдП)ред

рдЪреИрдЯ (рдмрд╡рдВрдбрд░)


рд╣рдо рдПрдХ рдирдпрд╛ рдмрд╡рдВрдбрд░ рдЖрд╡реЗрджрди рд▓рд┐рдЦреЗрдВрдЧреЗ рдФрд░ рдЗрд╕реЗ рдирд┐рдЬреАрдХреГрдд / рдмрд╡рдВрдбрд░рдПрдкреАрдбреАрдУ рдореЗрдВ рд╕рд╣реЗрдЬреЗрдВрдЧреЗ:

 import datetime import json import time import urllib import brukva import tornado.web import tornado.websocket import tornado.ioloop import tornado.httpclient from django.conf import settings from django.utils.importlib import import_module session_engine = import_module(settings.SESSION_ENGINE) from django.contrib.auth.models import User from privatemessages.models import Thread c = brukva.Client() c.connect() class MainHandler(tornado.web.RequestHandler): def get(self): self.set_header('Content-Type', 'text/plain') self.write('Hello. :)') class MessagesHandler(tornado.websocket.WebSocketHandler): def __init__(self, *args, **kwargs): super(MessagesHandler, self).__init__(*args, **kwargs) self.client = brukva.Client() self.client.connect() def open(self, thread_id): session_key = self.get_cookie(settings.SESSION_COOKIE_NAME) session = session_engine.SessionStore(session_key) try: self.user_id = session["_auth_user_id"] self.sender_name = User.objects.get(id=self.user_id).username except (KeyError, User.DoesNotExist): self.close() return if not Thread.objects.filter( id=thread_id, participants__id=self.user_id ).exists(): self.close() return self.channel = "".join(['thread_', thread_id,'_messages']) self.client.subscribe(self.channel) self.thread_id = thread_id self.client.listen(self.show_new_message) def handle_request(self, response): pass def on_message(self, message): if not message: return if len(message) > 10000: return c.publish(self.channel, json.dumps({ "timestamp": int(time.time()), "sender": self.sender_name, "text": message, })) http_client = tornado.httpclient.AsyncHTTPClient() request = tornado.httpclient.HTTPRequest( "".join([ settings.SEND_MESSAGE_API_URL, "/", self.thread_id, "/" ]), method="POST", body=urllib.urlencode({ "message": message.encode("utf-8"), "api_key": settings.API_KEY, "sender_id": self.user_id, }) ) http_client.fetch(request, self.handle_request) def show_new_message(self, result): self.write_message(str(result.body)) def on_close(self): try: self.client.unsubscribe(self.channel) except AttributeError: pass def check(): if self.client.connection.in_progress: tornado.ioloop.IOLoop.instance().add_timeout( datetime.timedelta(0.00001), check ) else: self.client.disconnect() tornado.ioloop.IOLoop.instance().add_timeout( datetime.timedelta(0.00001), check ) application = tornado.web.Application([ (r"/", MainHandler), (r'/(?P<thread_id>\d+)/', MessagesHandler), ]) 

рдЗрд╕ рдЖрд╡реЗрджрди рдореЗрдВ рд▓рдЧрднрдЧ рд╕рдм рдХреБрдЫ tornado.ioloop рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рд░реВрдк рд╕реЗ рдЪрд▓рддрд╛ рд╣реИред рдПрдХ рдЕрдкрд╡рд╛рдж рд╕рддреНрд░ рдкрд╣рдЪрд╛рдирдХрд░реНрддрд╛ рджреНрд╡рд╛рд░рд╛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХрд╛ рдкреНрд░рд╛рдзрд┐рдХрд░рдг рд╣реИ (рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗ рдЙрд╕рдХрд╛ рдирд╛рдо рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛)ред рдпрд╣ рдПрдХ рдмреНрд▓реЙрдХрд┐рдВрдЧ рдСрдкрд░реЗрд╢рди рд╣реИ, рдЕрд░реНрдерд╛рдд, рдЗрд╕ рд╕рдордп, рдЗрд╕ рдЯреЛрд░рдиреЗрдбреЛ рд╕рд░реНрд╡рд░ рд╕реЗ рдЬреБрдбрд╝реЗ рдЕрдиреНрдп рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреНрд░рддреАрдХреНрд╖рд╛ рдХрд░реЗрдВрдЧреЗред рдпрджрд┐ рдЖрдкрдХреЛ рдЗрд╕рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рддреЛ рдЗрд╕реЗ рдмрджрд▓рдирд╛ рдЖрд╕рд╛рди рд╣реИ, рд▓реЗрдХрд┐рди, рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдЗрд╕ рддрдереНрдп рдХреЛ рдирд╣реАрдВ рдХрд┐ рдпрд╣ рдЖрд╡рд╢реНрдпрдХ рд╣реЛрдЧрд╛, рдФрд░ рджреВрд╕рд░реА рдмрд╛рдд, рдЗрд╕ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪреЗрдВ рдХрд┐ рдХреНрдпрд╛ рдпрд╣ рд╕рд╣реА рдирд┐рд░реНрдгрдп рд╣реИред рдЗрд╕ рдЕрд╡рд╕рд░ рдкрд░, рдлреНрд░реЗрдВрдбрдлреАрдб (рдЯреЛрд░рдиреЗрдбреЛ рдбреЗрд╡рд▓рдкрд░реНрд╕) рдХреЗ рд▓реЛрдЧреЛрдВ рдиреЗ рдПрдХ рд╕реЗ рдЕрдзрд┐рдХ рдмрд╛рд░ рдмрд╛рдд рдХреА рд╣реИред рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдмреЗрди рдбрд░реНрдиреЗрд▓ рдХреА рдПрдХ рдкреЛрд╕реНрдЯ рдХрд╛ рдПрдХ рдЙрджреНрдзрд░рдг рд╣реИ:

Friendfeed рдорд╛рдирдХ рддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ MySQLdb рдореЙрдбреНрдпреВрд▓ (http://bret.appspot.com/entry/how-friendfeed-use-mysql) рдХреЗ рд╕рд╛рде mysql рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред рдлреНрд░реЗрдВрдбрдлреАрдб рдХрд╛ рджрд░реНрд╢рди рдпрд╣ рд╣реИ рдХрд┐ рдЙрди рдЪреАрдЬреЛрдВ рдХреЗ рд▓рд┐рдП рдЬреЛ рддреЗрдЬрд╝ рд╣реИрдВ рдФрд░ рдЖрдкрдХреЗ рдирд┐рдпрдВрддреНрд░рдг рдореЗрдВ рд╣реИрдВ (рдпрд╛рдиреА рдореЗрдореНрдЪреЗ рдФрд░ рдбреЗрдЯрд╛рдмреЗрд╕ - рдФрд░ рдпрджрд┐ рдЖрдкрдХрд╛ рдбреЗрдЯрд╛рдмреЗрд╕ рддреЗрдЬрд╝ рдирд╣реАрдВ рд╣реИ, рддреЛ рдЖрдк рдЗрд╕реЗ рдХреИрд╕реЗ рдХреНрд╡реЗрд░реА рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рдЗрд╕ рдкрд░ рдкреБрдирд░реНрд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВ), рдЖрдкрдХреЛ рдмрд╕ рдЙрдиреНрд╣реЗрдВ рд╕рдордХрд╛рд▓рд┐рдХ рд░реВрдк рд╕реЗ рдХреЙрд▓ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдЬрдЯрд┐рд▓рддрд╛ рдХреЗ рд▓рд╛рдпрдХ рдирд╣реАрдВ рд╣реИ рдХреЙрд▓рдмреИрдХ рдХрд╛ рдкреНрд░рдмрдВрдзрди рдХрд░рдирд╛ред рдПрд╕рд┐рдВрдХреНрд░реЛрдирд╕ рдХреЛрдб рдЙрди рдЪреАрдЬреЛрдВ рдХреЗ рд▓рд┐рдП рд╣реИ, рдЬреЛ рдЙрди рддрд░реАрдХреЛрдВ рд╕реЗ рдзреАрдорд╛ рд╣реЛрдиреЗ рдХреА рд╕рдВрднрд╛рд╡рдирд╛ рд╣реИ рдЬрд┐рдирдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЖрдк рдХреБрдЫ рднреА рдирд╣реАрдВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ (рдЬреИрд╕реЗ рдмрд╛рд╣рд░реА рдПрдкреАрдЖрдИ) рдпрд╛ рдЬрдм рдЖрдк рдЕрдирд┐рд╢реНрдЪрд┐рдд рд╕рдордп рдХреЗ рд▓рд┐рдП рдЕрдиреБрд░реЛрдз рдХреЛ рд╕реНрдердЧрд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ (рд▓рдВрдмреЗ рд╕рдордп рддрдХ рдорддрджрд╛рди)ред рдПрдХ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдЬреИрд╕рд╛ рдХрд┐ рдХреБрдЫ рд╣рдж рддрдХ рд╕рдВрддреБрд▓рди рдХреЛ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рдХрд░ рджреЗрддрд╛ рд╣реИ рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ async рдЗрдВрдЯрд░рдлреЗрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрд╣реБрдд рд╕рд░рд▓ рдмрдирд╛рддрд╛ рд╣реИ, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдЗрд╕ рддрдереНрдп рдХреА рддрд░рд╣ рдЕрднреА рднреА рдореБрджреНрджреЗ рд╣реИрдВ рдХрд┐ рдЬрдирд░реЗрдЯрд░ рдХреЛрд░рд╛рдЙрдЯ рд╕рд╛рдорд╛рдиреНрдп рдлрд╝рдВрдХреНрд╢рди рдХреЙрд▓ рдХреА рддрд░рд╣ рд░рдЪрдирд╛ рдпреЛрдЧреНрдп рдирд╣реАрдВ рд╣реИрдВред

рдпрд╣реА рд╣реИ, рдореЛрдЯреЗ рддреМрд░ рдкрд░ рдмреЛрд▓рдирд╛, рдЕрдЧрд░ рдЖрдкрдХрд╛ рдбреЗрдЯрд╛рдмреЗрд╕ рдзреАрдорд╛ рд╣реИ, рддреЛ рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рдХрд┐рд╕реА рднреА рдорд╛рдорд▓реЗ рдореЗрдВ рд╕рд░реНрд╡рд░ рд▓реЛрдб рдХрд╛ рд╕рд╛рдордирд╛ рдирд╣реАрдВ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдФрд░ рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рд╕реНрдЯрд╛рд░реНрдЯрдЕрдк рдпрд╣рд╛рдВ рдмрд╣реБрдд рдорджрдж рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВред , , тАФ , , .

Tornado- management-. Django ORM .

management- . privatemessages management, commands.

privatemessages/management privatemessages/management/commands __init__.py ( - , , ).

privatemessages/management/commands/starttornadoapp.py:

 import signal import time import tornado.httpserver import tornado.ioloop from django.core.management.base import BaseCommand, CommandError from privatemessages.tornadoapp import application class Command(BaseCommand): args = '[port_number]' help = 'Starts the Tornado application for message handling.' def sig_handler(self, sig, frame): """Catch signal and init callback""" tornado.ioloop.IOLoop.instance().add_callback(self.shutdown) def shutdown(self): """Stop server and add callback to stop i/o loop""" self.http_server.stop() io_loop = tornado.ioloop.IOLoop.instance() io_loop.add_timeout(time.time() + 2, io_loop.stop) def handle(self, *args, **options): if len(args) == 1: try: port = int(args[0]) except ValueError: raise CommandError('Invalid port number specified') else: port = 8888 self.http_server = tornado.httpserver.HTTPServer(application) self.http_server.listen(port, address="127.0.0.1") # Init signals handler signal.signal(signal.SIGTERM, self.sig_handler) # This will also catch KeyboardInterrupt exception signal.signal(signal.SIGINT, self.sig_handler) tornado.ioloop.IOLoop.instance().start() 

, (, SIGINT, Ctrl + C) - , . .

рдЪреЗрдХ


( myproject privatemessages, manage.py) development- Django Tornado.

python manage.py runserver
python manage.py starttornadoapp

, screen (, , SSH SSH-).

8000 Django, 8888 Tornado.

:

http://127.0.0.1:8000/messages/

( ).

, Django-. , , . тАФ рдкреНрд░рд╢рд╛рд╕рдирд┐рдХ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рд╕рдХреНрд╖рдо рдХрд░реЗрдВ рдФрд░ рдЗрд╕рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рд▓реЙрдЧ рдЗрди рдХрд░реЗрдВред

рддреИрдирд╛рддреА


рд╕рд░реНрд╡рд░ рдкрд░ рдЕрдкрдирд╛ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╕реЙрдлрд╝реНрдЯрд╡реЗрдпрд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:


Supervisor. ( Supervisor Ubuntu) /etc/supervisor/conf.d django.conf tornadoapp.conf. , Supervisor ( include files = /etc/supervisor/conf.d/*.conf).

django.conf

[program:django]
command=gunicorn_django --workers 4 -b 127.0.0.1:8150
directory=/home/yourusername/myproject
user=yourusername
autostart=true
autorestart=true

tornadoapp.conf

process_name = tornado-%(process_num)s
user = yourusername
directory = /home/yourusername/myproject
command = python manage.py starttornadoapp %(process_num)s
# Increase numprocs to run multiple processes on different ports.
# Note that the chat demo won't actually work in that configuration
# рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдорд╛рдирддрд╛ рд╣реИ рдХрд┐ рд╕рднреА рд╢реНрд░реЛрддрд╛ рдПрдХ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдореЗрдВ рд╣реИрдВред
рд╕рдВрдЦреНрдпрд╛ = 4
numprocs_start = 8000
рдСрдЯреЛрд╕реНрдЯрд╛рд░реНрдЯ = рд╕рддреНрдп
autorestart = true

рдЕрдм рд╕реБрдкрд░рд╡рд╛рдЗрдЬрд╝рд░ рдЪрд▓рд╛рдПрдВ рдФрд░ рд╕реБрдкрд░рд╡рд╛рдЗрдЬрд╝рд░рд▓ рд╕реНрдЯреЗрдЯрд╕ рдХрдорд╛рдВрдб рдХрд╛ рдЖрдЙрдЯрдкреБрдЯ рджреЗрдЦреЗрдВред

рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ рдХрд┐ рд╕рднреА 5 рдкреНрд░рдХреНрд░рд┐рдпрд╛рдУрдВ (рдПрдХ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХреЗ рд░реВрдк рдореЗрдВ рдЕрдВрдЧрд░рдЦрд╛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛) рдПрдХ рд░рдирд┐рдВрдЧ рд╕реНрдерд┐рддрд┐ рдХреЛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддрд╛ рд╣реИ рдФрд░ рдкрд░реНрдпрд╡реЗрдХреНрд╖рдХ рдХреЗ рд╢реБрд░реВ рд╣реЛрдиреЗ рдХреЗ рдмрд╛рдж рдХрд┐рддрдирд╛ рд╕рдордп рдмреАрдд рдЪреБрдХрд╛ рд╣реИ, рдпрд╣ рдореЗрд▓ рдЦрд╛рддрд╛ рд╣реИред

рдореИрдВ рдпрд╣ рднреА рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдиреЗ рдХреА рд╕рд▓рд╛рд╣ рджреЗрддрд╛ рд╣реВрдВ рдХрд┐ Django рд╕рд░реНрд╡рд░ рдФрд░ рдЯреЙрд░рдиреЗрдбреЛ рд╕рд░реНрд╡рд░ рджреЛрдиреЛрдВ рдХрд╛рдо рдХрд░ рд░рд╣реЗ рд╣реИрдВред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рд╕рд░реНрд╡рд░ рдкрд░ SSH рд╕реБрд░рдВрдЧ рдЦреЛрд▓ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рд╕реНрдерд╛рдиреАрдп рдкрддреЗ рдкрд░ рдЪрдпрдирд┐рдд рдкреЛрд░реНрдЯ рддрдХ рдкрд╣реБрдБрдЪрддреЗ рд╣реБрдП, рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдЕрдкрдиреА рд╕рд╛рдЗрдЯ рдЦреЛрд▓рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдпрд╣ рд╣реИ:

ssh -L 8000: рд▓реЛрдХрд▓рд╣реЛрд╕реНрдЯ: 8150 someverycoolserver.com

127.0.0.1:8000 (SSH- 127.0.0.1:8150 тАФ -, ).

Tornado-. 8000тАФ8003.

Django Tornado , nginx haproxy.

nginx:

 рд╕рд░реНрд╡рд░ {
    listen 127.0.0.1:8100;
    server_name someverycoolserver.com;

    # no security problem here, since / is always passed to upstream
    root /home/yourusername/myproject/myproject/static/;

    ## Compression
    # src: http://www.ruby-forum.com/topic/141251
    # src: http://wiki.brightbox.co.uk/docs:nginx

     рдкрд░ gzip;
    gzip_http_version 1.0;
    gzip_comp_level 2;
     gzip_proxied рдХреЛрдИ рднреА;
    gzip_min_length 1100;
    gzip_buffers 16 8k;
    gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    
    # Some version of IE 6 don't handle compression well on some mime-types, so just disable for them
    gzip_disable "MSIE [1-6].(?!.*SV1)";
    
    # Set a vary header so downstream proxies don't send cached gzipped content to IE6
     gzip_vary рдкрд░;
    ## /Compression

    location /static/admin/ {
        # this changes depending on your python version
        root /usr/local/lib/python2.7/dist-packages/django/contrib/admin/;
     }

    location /robots.txt {
        alias /home/yourusername/myproject/myproject/robots.txt;
     }

    location /favicon.ico {
        alias /home/yourusername/myproject/myproject/img/favicon.ico;
        expires 3d;
     }

    location /static/ {
        root /home/yourusername/myproject/myproject/;
        expires 3d;
     }

    location / {
        proxy_pass_header Server;
        proxy_set_header Host $http_host;
        proxy_redirect off;
         рдкреНрд░реЙрдХреНрд╕реА_рд╕реЗрдЯ_рд╣реЗрдбрд░ рдПрдХреНрд╕-рд░рд┐рдпрд▓-рдЖрдИрдкреА $ рд░рд┐рдореЛрдЯ_рдбреНрд░реЗрдб;
        proxy_set_header X-Scheme $scheme;
        proxy_connect_timeout 10;
        proxy_read_timeout 10;
        proxy_pass http://localhost:8150/;
     }
 }

, . , , ( - ), static files versioning ( , query string, , , query string ). , , () CSS JS тАФ , django-assets .

haproxy:

global
    maxconn 10000 # Total Max Connections. This is dependent on ulimit
    nbproc 2

 рдЪреВрдХ
    mode http
    option redispatch
    maxconn 2000
    contimeout 5000
    clitimeout 50000
    srvtimeout 50000
    option httpclose

frontend all 0.0.0.0:80
    timeout client 86400000

    acl is_chat hdr_beg(host) -i chat
    use_backend socket_backend if is_chat

    default_backend www_backend

backend www_backend
    option forwardfor # This sets X-Forwarded-For
    timeout server 30000
    timeout connect 4000
    server server1 localhost:8100

backend socket_backend
    balance roundrobin
    option forwardfor # This sets X-Forwarded-For
    no option httpclose # To match the `Connection` header for the websocket protocol rev.  76
    option http-server-close
    option http-pretend-keepalive
    timeout queue 5000
    timeout server 86400000
    timeout connect 86400000
    server server1 localhost:8000 weight 1 maxconn 5000 check
    server server2 localhost:8001 weight 1 maxconn 5000 check
    server server2 localhost:8002 weight 1 maxconn 5000 check
    server server2 localhost:8003 weight 1 maxconn 5000 check

, , Host chat, localhost:8000, localhost:8001, localhost:8002 localhost:8003, localhost:8100.

haproxy 80- IPv4-. IPv6 ( , /), frontend bind IPv6- 80- .

, IPv6- (, , AAAA- DNS), . , . тАФ IPv6, DNS whitelisting . , , тАФ IPv6, 1тАФ2 ( ).

, Tornado- , тАФ , ( 80 443).

haproxy , backend Host, Upgrade тАФ , Django, Tornado ( ), WS- Tornado-, тАФ Django-.

, . , , , ( Django Tornado ), , . ! .

Source: https://habr.com/ru/post/In160123/


All Articles