├── .gitignore ├── .travis.yml ├── CONTRIBUTORS.txt ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── assets ├── css │ ├── bootstrap │ │ ├── alerts.less │ │ ├── badges.less │ │ ├── bootstrap.less │ │ ├── breadcrumbs.less │ │ ├── button-groups.less │ │ ├── buttons.less │ │ ├── carousel.less │ │ ├── close.less │ │ ├── code.less │ │ ├── component-animations.less │ │ ├── dropdowns.less │ │ ├── forms.less │ │ ├── glyphicons.less │ │ ├── grid.less │ │ ├── input-groups.less │ │ ├── jumbotron.less │ │ ├── labels.less │ │ ├── list-group.less │ │ ├── media.less │ │ ├── mixins.less │ │ ├── mixins │ │ │ ├── alerts.less │ │ │ ├── background-variant.less │ │ │ ├── border-radius.less │ │ │ ├── buttons.less │ │ │ ├── center-block.less │ │ │ ├── clearfix.less │ │ │ ├── forms.less │ │ │ ├── gradients.less │ │ │ ├── grid-framework.less │ │ │ ├── grid.less │ │ │ ├── hide-text.less │ │ │ ├── image.less │ │ │ ├── labels.less │ │ │ ├── list-group.less │ │ │ ├── nav-divider.less │ │ │ ├── nav-vertical-align.less │ │ │ ├── opacity.less │ │ │ ├── pagination.less │ │ │ ├── panels.less │ │ │ ├── progress-bar.less │ │ │ ├── reset-filter.less │ │ │ ├── resize.less │ │ │ ├── responsive-visibility.less │ │ │ ├── size.less │ │ │ ├── tab-focus.less │ │ │ ├── table-row.less │ │ │ ├── text-emphasis.less │ │ │ ├── text-overflow.less │ │ │ └── vendor-prefixes.less │ │ ├── modals.less │ │ ├── navbar.less │ │ ├── navs.less │ │ ├── normalize.less │ │ ├── pager.less │ │ ├── pagination.less │ │ ├── panels.less │ │ ├── popovers.less │ │ ├── print.less │ │ ├── progress-bars.less │ │ ├── responsive-embed.less │ │ ├── responsive-utilities.less │ │ ├── scaffolding.less │ │ ├── tables.less │ │ ├── theme.less │ │ ├── thumbnails.less │ │ ├── tooltip.less │ │ ├── type.less │ │ ├── utilities.less │ │ ├── variables.less │ │ └── wells.less │ ├── flexslider.css │ ├── rynda-theme.less │ ├── rynda.less │ └── rynda │ │ ├── buttons.less │ │ ├── scaffolding.less │ │ ├── site.less │ │ ├── sprites.less │ │ └── variables.less ├── fonts │ ├── flexslider-icon.eot │ ├── flexslider-icon.svg │ ├── flexslider-icon.ttf │ └── flexslider-icon.woff └── javascript │ ├── libs │ ├── bootstrap.min.js │ ├── jquery-1.11.0.min.js │ ├── jquery.flexslider-min.js │ ├── leaflet.markercluster.js │ └── modernizr-2.5.3.min.js │ ├── plugins.js │ ├── rynda.js │ └── script.js ├── createdb.sh ├── docs ├── Makefile └── source │ ├── conf.py │ ├── index.rst │ └── message.rst ├── locale ├── en │ └── LC_MESSAGES │ │ └── django.po └── ru │ └── LC_MESSAGES │ └── django.po ├── manage.py ├── mysettings.py.example ├── package.json ├── requirements.txt ├── requirements ├── _base.txt ├── _test.txt ├── develop.txt ├── production.txt ├── stage.txt ├── test.txt └── travis.txt └── rynda ├── .coveragerc ├── Rynda ├── __init__.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── dev_sarutobi.py │ ├── local.py │ ├── stage.py │ ├── test.py │ └── travis.py ├── urls.py └── wsgi.py ├── __init__.py ├── api ├── __init__.py ├── serializers.py ├── urls.py └── views.py ├── core ├── __init__.py ├── admin.py ├── backends.py ├── context_processors.py ├── factories.py ├── fixtures │ └── initial_data.json ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20141022_2110.py │ ├── 0003_sitemapoptions.py │ └── __init__.py ├── mixins.py ├── models.py ├── serializers.py ├── tests │ ├── __init__.py │ └── xUnit │ │ ├── __init__.py │ │ ├── test_backends.py │ │ └── test_models.py ├── utils.py └── views.py ├── geozones ├── __init__.py ├── admin.py ├── factories.py ├── fixtures │ └── russian_regions.json ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20141022_2110.py │ ├── 0003_auto_20141215_2111.py │ └── __init__.py ├── models.py ├── static │ └── geozones │ │ ├── geolocation.css │ │ ├── geolocation.js │ │ ├── l.control.geosearch.js │ │ ├── l.geosearch.css │ │ └── l.geosearch.provider.google.js ├── templates │ └── geozones │ │ ├── olwidget │ │ └── admin_olwidget.html │ │ └── widgets │ │ └── geolocation.html └── tests │ ├── __init__.py │ └── xUnit │ ├── __init__.py │ └── test_models.py ├── message ├── __init__.py ├── admin.py ├── factories.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20141022_2110.py │ ├── 0003_auto_20141214_1751.py │ ├── 0004_remove_message_linked_location.py │ └── __init__.py ├── models.py ├── templates │ ├── message_details.html │ ├── message_form.html │ ├── message_success.html │ ├── messages_list.html │ ├── offer_form.html │ └── request_form.html ├── tests │ ├── __init__.py │ ├── functional │ │ ├── __init__.py │ │ ├── test_message_views.py │ │ └── test_send_message.py │ └── xUnit │ │ ├── __init__.py │ │ ├── test_forms.py │ │ ├── test_models.py │ │ └── test_views.py ├── urls.py └── views.py ├── newsline ├── __init__.py ├── admin.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20141022_2110.py │ └── __init__.py ├── models.py ├── templates │ └── post_list.html ├── urls.py └── views.py ├── static ├── crossdomain.xml ├── css │ ├── bootstrap-theme.css │ ├── default.css │ ├── flexslider.css │ └── fonts │ │ ├── flexslider-icon.eot │ │ ├── flexslider-icon.svg │ │ ├── flexslider-icon.ttf │ │ └── flexslider-icon.woff ├── favicon.ico ├── humans.txt ├── img │ ├── actual_rec_bg.png │ ├── anonymous.png │ ├── auth_bg.png │ ├── bg.jpg │ ├── favicon.ico │ ├── logo.png │ ├── navigation.png │ ├── nh_icon.png │ ├── social.png │ ├── top_nav_brdr.png │ ├── triple_li_bg.png │ └── wh_icon.png ├── js │ ├── libs │ │ ├── bootstrap.min.js │ │ ├── jquery-1.11.0.min.js │ │ ├── jquery.flexslider-min.js │ │ ├── leaflet.markercluster.js │ │ └── modernizr-2.5.3.min.js │ ├── plugins.js │ ├── rynda.js │ └── script.js ├── readme.md └── robots.txt ├── templates ├── admin │ ├── base_site.html │ └── message │ │ └── message │ │ └── line_field.html ├── base.html ├── base_list.html ├── base_singlepane.html ├── external │ └── vk_comments.html ├── flatpages │ └── default.html ├── floppyforms │ ├── attrs.html │ ├── checkbox_select.html │ ├── errors.html │ ├── layouts │ │ └── default.html │ └── rows │ │ └── default.html ├── index.html ├── infopages_list.html ├── login.html ├── not_implemented.html ├── registration │ ├── logged_out.html │ ├── password_change_done.html │ ├── password_change_form.html │ ├── password_reset_complete.html │ ├── password_reset_confirm.html │ ├── password_reset_done.html │ └── password_reset_form.html └── widgets │ ├── base_hub.html │ ├── filter.html │ ├── message_pane.html │ └── messages.html ├── test ├── __init__.py ├── factories.py └── test_user_registration.py └── users ├── __init__.py ├── admin.py ├── forms.py ├── management └── __init__.py ├── migrations ├── 0001_initial.py ├── 0002_profile_is_public.py ├── 0003_auto_20140911_1613.py ├── 0004_remove_profile_flags.py ├── 0005_auto_20140911_1634.py └── __init__.py ├── models.py ├── templates ├── activation_failed.html ├── edit_profile_form.html ├── registration_form.html ├── registration_success.html ├── user_details.html └── user_list.html ├── tests ├── __init__.py └── xUnit │ ├── __init__.py │ ├── test_forms.py │ └── test_models.py ├── urls.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.py[co] 3 | *.vim 4 | *.db 5 | *.mo 6 | *.log 7 | local_* 8 | mysettings.py 9 | build/ 10 | htmlcov/ 11 | mailbox/ 12 | node_modules/ 13 | docs/build/ 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 5 | install: "pip install -r requirements/travis.txt" 6 | # # command to run tests, e.g. python setup.py test 7 | before_script: 8 | - psql -c 'create database rynda;' -U postgres 9 | - psql -c 'create extension postgis' -U postgres -d rynda 10 | # - python manage.py migrate --settings=Rynda.settings.travis 11 | 12 | script: 13 | - python manage.py test --settings=rynda.Rynda.settings.travis 14 | 15 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | Valery "tengu sarutobi" Ilychev 2 | Gleb Suvorov 3 | Deniss Kulandin 4 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt){ 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON("package.json"), 4 | concat: { 5 | }, 6 | 7 | // Build css from less styles 8 | less: { 9 | dev: { 10 | files: { 11 | "rynda/static/css/bootstrap-theme.css": "assets/css/rynda-theme.less", 12 | "rynda/static/css/default.css": "assets/css/rynda.less" 13 | } 14 | } 15 | }, 16 | 17 | // Check javascripts 18 | jshint: { 19 | all: ["Gruntfile.js", "assets/javascripts/*.js"] 20 | }, 21 | 22 | // Copying files 23 | copy: { 24 | // Development version 25 | dev: { 26 | files: [ 27 | // Libraries 28 | {expand: true, cwd:"assets/javascript/", src:["libs/*.js"], dest: "rynda/static/js/"}, 29 | 30 | // Development javascripts 31 | {expand: true, cwd:"assets/javascript/", src:["*.js"], dest: "rynda/static/js/", filter: "isFile"}, 32 | 33 | // Css files 34 | {expand: true, cwd:"assets/css/", src:["*.css"], dest: "rynda/static/css/", filter: "isFile"} 35 | ]}, 36 | 37 | //Root files 38 | prod: { 39 | files:[ 40 | {src:["*.txt", "README.md", "manage.py"], dest: "build/"}, 41 | {src:["rynda/**/*.html", "rynda/**/*.json", "rynda/**/*.py" , "!**/tests/**", "!**/test/**"], dest:"build/"}, 42 | {src:"locale/**/*.po", dest:"build/"} 43 | ]} 44 | }, 45 | 46 | // Clean destination dirs 47 | clean: ["build", "rynda/static/js", "rynda/static/css"] 48 | }); 49 | 50 | grunt.loadNpmTasks("grunt-contrib-concat"); 51 | grunt.loadNpmTasks("grunt-contrib-jshint"); 52 | grunt.loadNpmTasks("grunt-contrib-less"); 53 | grunt.loadNpmTasks("grunt-contrib-copy"); 54 | grunt.loadNpmTasks("grunt-contrib-clean"); 55 | grunt.registerTask("default", ["jshint", "clean", "less:dev", "copy:dev"]); 56 | grunt.registerTask("package", ["jshint", "clean", "less:development", "copy:root", "copy:libs", "copy:sourcejs", "copy:css"]); 57 | }; 58 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Valery A. Ilychev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rynda 2 | ===== 3 | [![Build Status](https://travis-ci.org/sarutobi/Rynda.svg?branch=master)](https://travis-ci.org/sarutobi/Rynda) 4 | 5 | http://rynda.org 6 | http://openrynda.te-st.ru/ 7 | 8 | Технические требования 9 | ------------ 10 | - Python 2.7+ 11 | - spatialite 12 | - GEOS 13 | - PROJ.4 14 | - Virtualenv (опционально, но строго рекомендуется) 15 | 16 | Быстрый старт 17 | ------------ 18 | 19 | (Опционально) Создаем и активируем virtualenv: 20 | 21 | ``` 22 | $ virtualenv rynda 23 | $ source rynda/bin/activate 24 | ``` 25 | 26 | 1. Клонируем репозиторий: 27 | 28 | ``` 29 | $ git clone https://github.com/sarutobi/Rynda.git 30 | ``` 31 | 32 | 2. переходим в клонированный репозиторий: 33 | 34 | ``` 35 | $ cd Rynda 36 | ``` 37 | 38 | 3. Устанавливаем все зависимости: 39 | 40 | ``` 41 | $ pip install -r requirements/test.txt 42 | ``` 43 | 44 | 4. Копируем mysettings.py.example в mysettings.py 45 | 46 | 5. Задаем структуру базы данных и пароль суперпользователя: 47 | 48 | ``` 49 | $ chmod +x createdb.sh 50 | $ bash createdb.sh 51 | ``` 52 | 53 | Дважды вводим пароль суперпользователя. 54 | 55 | Имя суперпользователя по умолчанию: admin 56 | 57 | 6. Запускаем локальный сервер: 58 | 59 | ``` 60 | $ python manage.py runserver 61 | ``` 62 | 63 | 7. Открываем в браузере [http://localhost:8000](http://localhost:8000) 64 | 65 | 8. Чтобы протестировать систему запускаем: 66 | 67 | ``` 68 | $ python manage.py test 69 | ``` 70 | 71 | [Документация на русском языке](http://rynda.readthedocs.org/ru/latest/production.html) 72 | 73 | 74 | Если вам нужна помощь волонтеров в установке и настройке - создайте задачу на https://itv.te-st.ru 75 | 76 | 77 | ------- 78 | http://rynda.org 79 | http://openrynda.te-st.ru/ 80 | 81 | Requirements 82 | ------------ 83 | - Python 2.7+ 84 | - spatialite 85 | - GEOS 86 | - PROJ.4 87 | - Virtualenv (optional, but strongly recommended) 88 | 89 | Quickstart 90 | ------------ 91 | 92 | (Optional) Create and activate virtualenv: 93 | 94 | ``` 95 | $ virtualenv rynda 96 | $ source rynda/bin/activate 97 | ``` 98 | 99 | 1. Clone the repository: 100 | 101 | ``` 102 | $ git clone https://github.com/sarutobi/Rynda.git 103 | ``` 104 | 105 | 2. cd to cloned repository: 106 | 107 | ``` 108 | $ cd Rynda 109 | ``` 110 | 111 | 3. Install all requirements: 112 | 113 | ``` 114 | $ pip install -r requirements/test.txt 115 | ``` 116 | 117 | 4. Copy mysettings.py.example to mysettings.py 118 | 119 | 5. Create database structure and superuser password: 120 | ``` 121 | a) edit createdb.sh to set proper admin username and email address 122 | b) Set execution mode for createdb.sh 123 | $ chmod +x createdb.sh 124 | c) Execute the script. Enter superuser password twice. 125 | $ bash createdb.sh 126 | ``` 127 | 128 | 6. Run the local server: 129 | 130 | ``` 131 | $ python manage.py runserver 132 | ``` 133 | 134 | 7. Point your browser to [http://localhost:8000](http://localhost:8000) 135 | 136 | 8. To make tests, type: 137 | 138 | ``` 139 | $ python manage.py test 140 | ``` 141 | [Russian documentation](http://rynda.readthedocs.org/ru/latest/production.html) 142 | -------------------------------------------------------------------------------- /assets/css/bootstrap/alerts.less: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base styles 7 | // ------------------------- 8 | 9 | .alert { 10 | padding: @alert-padding; 11 | margin-bottom: @line-height-computed; 12 | border: 1px solid transparent; 13 | border-radius: @alert-border-radius; 14 | 15 | // Headings for larger alerts 16 | h4 { 17 | margin-top: 0; 18 | // Specified for the h4 to prevent conflicts of changing @headings-color 19 | color: inherit; 20 | } 21 | // Provide class for links that match alerts 22 | .alert-link { 23 | font-weight: @alert-link-font-weight; 24 | } 25 | 26 | // Improve alignment and spacing of inner content 27 | > p, 28 | > ul { 29 | margin-bottom: 0; 30 | } 31 | > p + p { 32 | margin-top: 5px; 33 | } 34 | } 35 | 36 | // Dismissible alerts 37 | // 38 | // Expand the right padding and account for the close button's positioning. 39 | 40 | .alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0. 41 | .alert-dismissible { 42 | padding-right: (@alert-padding + 20); 43 | 44 | // Adjust close link position 45 | .close { 46 | position: relative; 47 | top: -2px; 48 | right: -21px; 49 | color: inherit; 50 | } 51 | } 52 | 53 | // Alternate styles 54 | // 55 | // Generate contextual modifier classes for colorizing the alert. 56 | 57 | .alert-success { 58 | .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); 59 | } 60 | .alert-info { 61 | .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); 62 | } 63 | .alert-warning { 64 | .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); 65 | } 66 | .alert-danger { 67 | .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); 68 | } 69 | -------------------------------------------------------------------------------- /assets/css/bootstrap/badges.less: -------------------------------------------------------------------------------- 1 | // 2 | // Badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .badge { 8 | display: inline-block; 9 | min-width: 10px; 10 | padding: 3px 7px; 11 | font-size: @font-size-small; 12 | font-weight: @badge-font-weight; 13 | color: @badge-color; 14 | line-height: @badge-line-height; 15 | vertical-align: baseline; 16 | white-space: nowrap; 17 | text-align: center; 18 | background-color: @badge-bg; 19 | border-radius: @badge-border-radius; 20 | 21 | // Empty badges collapse automatically (not available in IE8) 22 | &:empty { 23 | display: none; 24 | } 25 | 26 | // Quick fix for badges in buttons 27 | .btn & { 28 | position: relative; 29 | top: -1px; 30 | } 31 | .btn-xs & { 32 | top: 0; 33 | padding: 1px 5px; 34 | } 35 | 36 | // Hover state, but only for links 37 | a& { 38 | &:hover, 39 | &:focus { 40 | color: @badge-link-hover-color; 41 | text-decoration: none; 42 | cursor: pointer; 43 | } 44 | } 45 | 46 | // Account for badges in navs 47 | a.list-group-item.active > &, 48 | .nav-pills > .active > a > & { 49 | color: @badge-active-color; 50 | background-color: @badge-active-bg; 51 | } 52 | .nav-pills > li > a > & { 53 | margin-left: 3px; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /assets/css/bootstrap/bootstrap.less: -------------------------------------------------------------------------------- 1 | // Core variables and mixins 2 | @import "variables.less"; 3 | @import "mixins.less"; 4 | 5 | // Reset and dependencies 6 | @import "normalize.less"; 7 | @import "print.less"; 8 | @import "glyphicons.less"; 9 | 10 | // Core CSS 11 | @import "scaffolding.less"; 12 | @import "type.less"; 13 | @import "code.less"; 14 | @import "grid.less"; 15 | @import "tables.less"; 16 | @import "forms.less"; 17 | @import "buttons.less"; 18 | 19 | // Components 20 | @import "component-animations.less"; 21 | @import "dropdowns.less"; 22 | @import "button-groups.less"; 23 | @import "input-groups.less"; 24 | @import "navs.less"; 25 | @import "navbar.less"; 26 | @import "breadcrumbs.less"; 27 | @import "pagination.less"; 28 | @import "pager.less"; 29 | @import "labels.less"; 30 | @import "badges.less"; 31 | @import "jumbotron.less"; 32 | @import "thumbnails.less"; 33 | @import "alerts.less"; 34 | @import "progress-bars.less"; 35 | @import "media.less"; 36 | @import "list-group.less"; 37 | @import "panels.less"; 38 | @import "responsive-embed.less"; 39 | @import "wells.less"; 40 | @import "close.less"; 41 | 42 | // Components w/ JavaScript 43 | @import "modals.less"; 44 | @import "tooltip.less"; 45 | @import "popovers.less"; 46 | @import "carousel.less"; 47 | 48 | // Utility classes 49 | @import "utilities.less"; 50 | @import "responsive-utilities.less"; 51 | -------------------------------------------------------------------------------- /assets/css/bootstrap/breadcrumbs.less: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal; 8 | margin-bottom: @line-height-computed; 9 | list-style: none; 10 | background-color: @breadcrumb-bg; 11 | border-radius: @border-radius-base; 12 | 13 | > li { 14 | display: inline-block; 15 | 16 | + li:before { 17 | content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space 18 | padding: 0 5px; 19 | color: @breadcrumb-color; 20 | } 21 | } 22 | 23 | > .active { 24 | color: @breadcrumb-active-color; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/css/bootstrap/close.less: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: (@font-size-base * 1.5); 9 | font-weight: @close-font-weight; 10 | line-height: 1; 11 | color: @close-color; 12 | text-shadow: @close-text-shadow; 13 | .opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: @close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | .opacity(.5); 21 | } 22 | 23 | // Additional properties for button version 24 | // iOS requires the button element instead of an anchor tag. 25 | // If you want the anchor version, it requires `href="#"`. 26 | button& { 27 | padding: 0; 28 | cursor: pointer; 29 | background: transparent; 30 | border: 0; 31 | -webkit-appearance: none; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/css/bootstrap/code.less: -------------------------------------------------------------------------------- 1 | // 2 | // Code (inline and block) 3 | // -------------------------------------------------- 4 | 5 | 6 | // Inline and block code styles 7 | code, 8 | kbd, 9 | pre, 10 | samp { 11 | font-family: @font-family-monospace; 12 | } 13 | 14 | // Inline code 15 | code { 16 | padding: 2px 4px; 17 | font-size: 90%; 18 | color: @code-color; 19 | background-color: @code-bg; 20 | border-radius: @border-radius-base; 21 | } 22 | 23 | // User input typically entered via keyboard 24 | kbd { 25 | padding: 2px 4px; 26 | font-size: 90%; 27 | color: @kbd-color; 28 | background-color: @kbd-bg; 29 | border-radius: @border-radius-small; 30 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); 31 | 32 | kbd { 33 | padding: 0; 34 | font-size: 100%; 35 | box-shadow: none; 36 | } 37 | } 38 | 39 | // Blocks of code 40 | pre { 41 | display: block; 42 | padding: ((@line-height-computed - 1) / 2); 43 | margin: 0 0 (@line-height-computed / 2); 44 | font-size: (@font-size-base - 1); // 14px to 13px 45 | line-height: @line-height-base; 46 | word-break: break-all; 47 | word-wrap: break-word; 48 | color: @pre-color; 49 | background-color: @pre-bg; 50 | border: 1px solid @pre-border-color; 51 | border-radius: @border-radius-base; 52 | 53 | // Account for some code outputs that place code tags in pre tags 54 | code { 55 | padding: 0; 56 | font-size: inherit; 57 | color: inherit; 58 | white-space: pre-wrap; 59 | background-color: transparent; 60 | border-radius: 0; 61 | } 62 | } 63 | 64 | // Enable scrollable blocks of code 65 | .pre-scrollable { 66 | max-height: @pre-scrollable-max-height; 67 | overflow-y: scroll; 68 | } 69 | -------------------------------------------------------------------------------- /assets/css/bootstrap/component-animations.less: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | // Heads up! 6 | // 7 | // We don't use the `.opacity()` mixin here since it causes a bug with text 8 | // fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. 9 | 10 | .fade { 11 | opacity: 0; 12 | .transition(opacity .15s linear); 13 | &.in { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | .collapse { 19 | display: none; 20 | 21 | &.in { display: block; } 22 | tr&.in { display: table-row; } 23 | tbody&.in { display: table-row-group; } 24 | } 25 | 26 | .collapsing { 27 | position: relative; 28 | height: 0; 29 | overflow: hidden; 30 | .transition(height .35s ease); 31 | } 32 | -------------------------------------------------------------------------------- /assets/css/bootstrap/grid.less: -------------------------------------------------------------------------------- 1 | // 2 | // Grid system 3 | // -------------------------------------------------- 4 | 5 | 6 | // Container widths 7 | // 8 | // Set the container width, and override it for fixed navbars in media queries. 9 | 10 | .container { 11 | .container-fixed(); 12 | 13 | @media (min-width: @screen-sm-min) { 14 | width: @container-sm; 15 | } 16 | @media (min-width: @screen-md-min) { 17 | width: @container-md; 18 | } 19 | @media (min-width: @screen-lg-min) { 20 | width: @container-lg; 21 | } 22 | } 23 | 24 | 25 | // Fluid container 26 | // 27 | // Utilizes the mixin meant for fixed width containers, but without any defined 28 | // width for fluid, full width layouts. 29 | 30 | .container-fluid { 31 | .container-fixed(); 32 | } 33 | 34 | 35 | // Row 36 | // 37 | // Rows contain and clear the floats of your columns. 38 | 39 | .row { 40 | .make-row(); 41 | } 42 | 43 | 44 | // Columns 45 | // 46 | // Common styles for small and large grid columns 47 | 48 | .make-grid-columns(); 49 | 50 | 51 | // Extra small grid 52 | // 53 | // Columns, offsets, pushes, and pulls for extra small devices like 54 | // smartphones. 55 | 56 | .make-grid(xs); 57 | 58 | 59 | // Small grid 60 | // 61 | // Columns, offsets, pushes, and pulls for the small device range, from phones 62 | // to tablets. 63 | 64 | @media (min-width: @screen-sm-min) { 65 | .make-grid(sm); 66 | } 67 | 68 | 69 | // Medium grid 70 | // 71 | // Columns, offsets, pushes, and pulls for the desktop device range. 72 | 73 | @media (min-width: @screen-md-min) { 74 | .make-grid(md); 75 | } 76 | 77 | 78 | // Large grid 79 | // 80 | // Columns, offsets, pushes, and pulls for the large desktop device range. 81 | 82 | @media (min-width: @screen-lg-min) { 83 | .make-grid(lg); 84 | } 85 | -------------------------------------------------------------------------------- /assets/css/bootstrap/jumbotron.less: -------------------------------------------------------------------------------- 1 | // 2 | // Jumbotron 3 | // -------------------------------------------------- 4 | 5 | 6 | .jumbotron { 7 | padding: @jumbotron-padding; 8 | margin-bottom: @jumbotron-padding; 9 | color: @jumbotron-color; 10 | background-color: @jumbotron-bg; 11 | 12 | h1, 13 | .h1 { 14 | color: @jumbotron-heading-color; 15 | } 16 | p { 17 | margin-bottom: (@jumbotron-padding / 2); 18 | font-size: @jumbotron-font-size; 19 | font-weight: 200; 20 | } 21 | 22 | > hr { 23 | border-top-color: darken(@jumbotron-bg, 10%); 24 | } 25 | 26 | .container & { 27 | border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container 28 | } 29 | 30 | .container { 31 | max-width: 100%; 32 | } 33 | 34 | @media screen and (min-width: @screen-sm-min) { 35 | padding-top: (@jumbotron-padding * 1.6); 36 | padding-bottom: (@jumbotron-padding * 1.6); 37 | 38 | .container & { 39 | padding-left: (@jumbotron-padding * 2); 40 | padding-right: (@jumbotron-padding * 2); 41 | } 42 | 43 | h1, 44 | .h1 { 45 | font-size: (@font-size-base * 4.5); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /assets/css/bootstrap/labels.less: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // -------------------------------------------------- 4 | 5 | .label { 6 | display: inline; 7 | padding: .2em .6em .3em; 8 | font-size: 75%; 9 | font-weight: bold; 10 | line-height: 1; 11 | color: @label-color; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | border-radius: .25em; 16 | 17 | // Add hover effects, but only for links 18 | a& { 19 | &:hover, 20 | &:focus { 21 | color: @label-link-hover-color; 22 | text-decoration: none; 23 | cursor: pointer; 24 | } 25 | } 26 | 27 | // Empty labels collapse automatically (not available in IE8) 28 | &:empty { 29 | display: none; 30 | } 31 | 32 | // Quick fix for labels in buttons 33 | .btn & { 34 | position: relative; 35 | top: -1px; 36 | } 37 | } 38 | 39 | // Colors 40 | // Contextual variations (linked labels get darker on :hover) 41 | 42 | .label-default { 43 | .label-variant(@label-default-bg); 44 | } 45 | 46 | .label-primary { 47 | .label-variant(@label-primary-bg); 48 | } 49 | 50 | .label-success { 51 | .label-variant(@label-success-bg); 52 | } 53 | 54 | .label-info { 55 | .label-variant(@label-info-bg); 56 | } 57 | 58 | .label-warning { 59 | .label-variant(@label-warning-bg); 60 | } 61 | 62 | .label-danger { 63 | .label-variant(@label-danger-bg); 64 | } 65 | -------------------------------------------------------------------------------- /assets/css/bootstrap/list-group.less: -------------------------------------------------------------------------------- 1 | // 2 | // List groups 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | // 8 | // Easily usable on 4 | -------------------------------------------------------------------------------- /rynda/templates/floppyforms/errors.html: -------------------------------------------------------------------------------- 1 | {% if errors %}{% for error in errors %} {{ error }} {% if not forloop.last %}
{% endif %}{% endfor %}
{% endif %} 2 | -------------------------------------------------------------------------------- /rynda/templates/floppyforms/layouts/default.html: -------------------------------------------------------------------------------- 1 | {% load floppyforms %} 2 | 3 | {% block formconfig %} 4 | {% endblock %} 5 | 6 | {% block forms %}{% for form in forms %} 7 | {% block errors %} 8 | {% for error in form.non_field_errors %} 9 |
10 | × 11 | {{ error }} 12 |
13 | {% endfor %} 14 | {% for error in form|hidden_field_errors %} 15 |
16 | × 17 | {{ error }} 18 |
19 | {% endfor %} 20 | {% endblock errors %} 21 | 22 | {% block rows %} 23 | {% for field in form.visible_fields %} 24 | {% if forloop.last %}{% formconfig row with hidden_fields=form.hidden_fields %}{% endif %} 25 | {% block row %}{% formrow field %}{% endblock %} 26 | {% endfor %} 27 | {% if not form.visible_fields %}{% for field in form.hidden_fields %}{% formfield field %}{% endfor %}{% endif %} 28 | {% endblock %} 29 | {% endfor %}{% endblock %} 30 | -------------------------------------------------------------------------------- /rynda/templates/floppyforms/rows/default.html: -------------------------------------------------------------------------------- 1 | {% load floppyforms %}{% block row %} 2 | {% for field in fields %} 3 |
4 | {% with classes=field.css_classes label=label|default:field.label help_text=help_text|default:field.help_text %} 5 | {% block label %}{% if field|id %}{% endif %}{% endblock %} 6 | {% block field %} 7 |
8 | {% block widget %}{% formfield field %}{% endblock %} 9 | {% block errors %}{% include "floppyforms/errors.html" with errors=field.errors %}{% endblock %} 10 | {% block help_text %}{% if field.help_text %} 11 |

{{ field.help_text }}

12 | {% endif %}{% endblock %} 13 | {% block hidden_fields %}{% for field in hidden_fields %}{{ field.as_hidden }}{% endfor %}{% endblock %} 14 |
15 | {% endblock %} 16 | {% endwith %} 17 |
18 | {% endfor %}{% endblock %} 19 | -------------------------------------------------------------------------------- /rynda/templates/infopages_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n flatpages %} 3 | 4 | {% block hubs %} 5 | {% include "widgets/base_hub.html" with part='about' %} 6 | {% endblock %} 7 | 8 | {% block navigation %} 9 | 13 | {% endblock %} 14 | 15 | {% block maincontent %} 16 |
17 |
18 |
19 |

{% trans "Information pages" %}

20 |
21 |
22 | {% get_flatpages as flatpages %} 23 |
    24 | {% for page in flatpages %} 25 |
  • {{ page.title }}
  • 26 | {% empty %} 27 |
  • {% trans "Information pages not found" %}
  • 28 | {% endfor %} 29 |
30 |
31 |
32 |
33 | {% endblock %} 34 | 35 | -------------------------------------------------------------------------------- /rynda/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base_singlepane.html' %}{# login form template #} 2 | {% load url from future %} 3 | {% load i18n widget_tweaks %} 4 | 5 | {% block pane-title %}{% trans "Login" %}{% endblock %} 6 | 7 | {% block pane-body %} 8 | 9 | {% if form.non_field_errors %} 10 |
11 | {% for e in form.non_field_errors %} 12 | {{ e }} 13 | {% endfor %} 14 |
15 | {% endif %} 16 | 17 |
18 | {% csrf_token %} 19 |
20 | {{ form.username.label_tag }} 21 | {% for e in form.username.errors %}{{ e }}{% endfor %} 22 | {{ form.username|add_class:"form-control" }} 23 |
24 | 25 |
26 | {{ form.password.label_tag }} 27 | {% for e in form.password.errors %}{{ e }}{% endfor %} 28 |
29 | {{ form.password|add_class:"form-control" }} 30 | 31 | 34 | 35 |
36 |

{% trans "Password reminder" %}

37 |
38 | 39 |
40 | 41 |
42 |
43 | 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /rynda/templates/not_implemented.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load i18n %} 3 | 4 | {% block hubs %} 5 | {% include "widgets/base_hub.html" with part='base' %} 6 | {% endblock %} 7 | 8 | {% block maincontent %} 9 |
10 |
11 |
12 |

{% blocktrans %}Not implemented{% endblocktrans %}

13 |
14 |
15 |

16 | {% blocktrans %}Unfortunately, this page is under construction. Please use another site parts.{% endblocktrans %} 17 |

18 |
19 |
20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /rynda/templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% extends "base_singlepane.html" %} 2 | {% load i18n %} 3 | 4 | {% block page_title %}{{ title }}{% endblock %} 5 | 6 | {% block message-panel-title %}{{ title }}{% endblock %} 7 | 8 | {% block message-pane-body %} 9 |

{% trans "Thanks for spending some quality time with the Web site today." %}

10 | {% trans 'Log in again' %} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /rynda/templates/registration/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base_singlepane.html" %} 2 | {% load i18n %} 3 | {% trans 'Password change successful' as title %} 4 | 5 | {% block page_title %}{{ title }}{% endblock %} 6 | 7 | {% block pane-title %}{{ title }}{% endblock %} 8 | 9 | {% block pane-body %} 10 |

{% trans 'Your password was changed.' %}

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /rynda/templates/registration/password_change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base_site.html" %} 2 | {% load i18n %} 3 | 4 | {% block hubs %} 5 | {% include "widgets/base_hub.html" with part='base' %} 6 | {% endblock %} 7 | 8 | {% block breadcrumbs %} 9 | 13 | {% endblock %} 14 | {% block title %}{% trans 'Password change' %}{% endblock %} 15 | {% block content-class %}{% endblock %} 16 | 17 | {% block content %} 18 | 19 |

{% trans 'Password change' %}

20 |
21 |
22 |
23 |

{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}

24 |
25 |
{% csrf_token %} 26 |
27 |
28 |
29 |
30 | {{ form.old_password }} 31 | {{ form.old_password.errors }} 32 |
33 |
34 |
35 |
36 |
37 | {{ form.new_password1 }} 38 | {{ form.new_password1.errors }} 39 |
40 |
41 |
42 |
43 |
44 | {{ form.new_password2 }} 45 | {{ form.new_password2.errors }} 46 |
47 |
48 |
49 |
50 |
    51 |
  • 52 |
53 |
54 |
55 |
56 |
57 | 58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /rynda/templates/registration/password_reset_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "base_singlepane.html" %} 2 | {% load i18n %} 3 | {% trans 'Password reset complete' as title %} 4 | 5 | {% block page_title %}{{ title }}{% endblock %} 6 | 7 | {% block pane-title %}{{ title }}{% endblock %} 8 | 9 | {% block panel-body %} 10 |

{% trans "Your password has been set. You may go ahead and log in now." %}

11 |

{% trans 'Log in' %}

12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /rynda/templates/registration/password_reset_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base_singlepane.html" %} 2 | {% load i18n widget_tweaks %} 3 | 4 | {% block page_title %}{% if validlink %}{% trans 'Enter new password' %}{% else %}{% trans 'Password reset unsuccessful' %}{% endif %}{% endblock %} 5 | 6 | {% if validlink %} 7 | {% block pane-title %}{% trans 'Enter new password' %}{% endblock %} 8 | {% block panel-body %} 9 |

{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}

10 |
{% csrf_token %} 11 |
12 | 13 | {{ form.new_password1|add_class:"form-control" }} 14 | {{ form.new_password1.errors }} 15 |
16 |
17 | 18 | {{ form.new_password2|add_class:"form-control" }} 19 | {{ form.new_password2.errors }} 20 |

{% trans 'Enter the same password as above, for verification.' %}

21 |
22 |
23 | 24 |
25 |
26 | {% else %} 27 | {% block pane-type %}panel-warning{% endblock %} 28 | 29 | {% block pane-title %}{% trans 'Password reset unsuccessful' %}{% endblock %} 30 | 31 | {% block pane-body %} 32 |

{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}

33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /rynda/templates/registration/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base_singlepane.html" %}{# Successfully reset password #} 2 | {% load i18n %} 3 | 4 | {% block page_title %}{% trans 'Password reset successful' %}{% endblock %} 5 | 6 | {% block pane-title %}{% trans 'Password reset successful' %}{% endblock %} 7 | 8 | {% block pane-body %} 9 |

{% trans "We've emailed you instructions for setting your password. You should be receiving them shortly." %}

10 |

{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /rynda/templates/registration/password_reset_form.html: -------------------------------------------------------------------------------- 1 | {% extends "base_singlepane.html" %} 2 | {% load i18n widget_tweaks %} {# grp_csrf #} 3 | 4 | {% block page_title %}{% trans "Password reset" %}{% endblock %} 5 | 6 | {% block pane-title %}{% trans "Password reset" %}{% endblock %} 7 | 8 | {% block pane-body %} 9 |

{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}

10 |
{% csrf_token %} 11 |
12 | 13 | {{ form.email|add_class:"form-control" }} 14 | {% if form.email.errors %}{{ form.email.errors }}{% endif %} 15 |
16 |
17 | 18 |
19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /rynda/templates/widgets/base_hub.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% trans "Messages" as msgs %} 3 | {% trans "Users" as users %} 4 | {% trans "About" as about %} 5 |
6 | 31 |
32 | -------------------------------------------------------------------------------- /rynda/templates/widgets/filter.html: -------------------------------------------------------------------------------- 1 | {% load i18n floppyforms %} 2 | 3 |
4 | {% form filter.form %} 5 | 6 |
7 | -------------------------------------------------------------------------------- /rynda/templates/widgets/message_pane.html: -------------------------------------------------------------------------------- 1 | {% load i18n %}
2 |
3 |
4 |

{{ helper_title }}

5 |
6 |
7 | {% if messages %} 8 | 29 | {% if has_more %} 30 | {% trans "All messages" %} 31 | {% endif %} 32 | {% else %} 33 |
    34 |
  • {% trans "No messages found" %}
  • 35 |
36 | {% endif %} 37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /rynda/templates/widgets/messages.html: -------------------------------------------------------------------------------- 1 | {% if messages %} 2 |
3 | {% for message in messages %} 4 |
5 | 6 | {{ message }} 7 |
8 | {% endfor %} 9 |
10 | {% endif %} 11 | -------------------------------------------------------------------------------- /rynda/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarutobi/Rynda/aa10d3ffdd7b3f7eaa0a1470a8541b8d0519e1f5/rynda/test/__init__.py -------------------------------------------------------------------------------- /rynda/test/factories.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import factory 4 | from factory import django 5 | 6 | from django.contrib.auth.models import User 7 | 8 | from rynda.users.models import Profile 9 | 10 | 11 | class ProfileFactory(django.DjangoModelFactory): 12 | class Meta: 13 | model = Profile 14 | 15 | user = factory.SubFactory('test.factories.UserFactory', profile=None) 16 | 17 | 18 | class UserFactory(django.DjangoModelFactory): 19 | FACTORY_FOR = User 20 | 21 | first_name = "Boy" 22 | last_name = factory.Sequence(lambda n: "Factory_%s" % n) 23 | email = factory.LazyAttribute( 24 | lambda a: 25 | "{0}_{1}@mail.ru".format(a.first_name, a.last_name).lower()) 26 | username = factory.Sequence(lambda n: "username_%s" % n) 27 | password = factory.PostGenerationMethodCall('set_password', '123') 28 | is_active = True 29 | is_staff = False 30 | is_superuser = False 31 | -------------------------------------------------------------------------------- /rynda/test/test_user_registration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.models import User 5 | from django.contrib.sites.models import Site 6 | from django.core.urlresolvers import reverse 7 | 8 | from django_webtest import WebTest 9 | from post_office.models import Email, EmailTemplate 10 | 11 | from rynda.test.factories import UserFactory 12 | from rynda.users.models import UserAuthCode 13 | 14 | 15 | class TestUserRegistration(WebTest): 16 | """ Checks that the user can successfully register and activate account. """ 17 | 18 | def setUp(self): 19 | self.site = Site.objects.get() 20 | self.site.domain = "example.com" 21 | self.site.name = "Example site" 22 | self.site.save() 23 | confirm = EmailTemplate( 24 | name='registration confirmation', 25 | subject='Account activation', 26 | content='http://{{site.domain}}/user/activate/{{user.id}}/{{activation_code}}', 27 | html_content='http://{{site.domain}}/user/activate/{{user.id}}/{{activation_code}}', 28 | ) 29 | confirm.save() 30 | complete = EmailTemplate( 31 | name='registration complete', 32 | subject='Welcome to team !', 33 | ) 34 | complete.save() 35 | 36 | def action_registration(self): 37 | """ User fills registration form """ 38 | page = self.app.get(reverse("user-creation")) 39 | form = page.forms["registration_form"] 40 | user = UserFactory.attributes(create=False) 41 | form["first_name"] = user['first_name'] 42 | form["last_name"] = user['last_name'] 43 | form["email"] = user['email'] 44 | form["password1"] = "123" 45 | form["password2"] = "123" 46 | response = form.submit() 47 | return response 48 | 49 | def test_registration_page(self): 50 | """ User create account """ 51 | users = User.objects.count() 52 | page = self.action_registration() 53 | self.assertEqual(200, page.status_code) 54 | self.assertTemplateUsed("registration_success.html") 55 | self.assertEqual(users+1, User.objects.count()) 56 | 57 | def test_registration_email(self): 58 | """ Tests for account activation email """ 59 | activation_string = "http://{}/user".format(self.site.domain) 60 | self.action_registration() 61 | mail = Email.objects.get() 62 | self.assertEqual("Account activation", mail.subject) 63 | self.assertIn( 64 | activation_string, mail.message, mail.message.encode('utf-8')) 65 | self.assertIn(activation_string, mail.html_message) 66 | 67 | def test_activation_link(self): 68 | """ Click on auto-activation link """ 69 | self.action_registration() 70 | user = User.objects.all().order_by('-id')[0] 71 | self.assertFalse(user.is_active) 72 | activation_code = UserAuthCode(settings.SECRET_KEY).auth_code(user) 73 | activation_url = "/user/activate/{0}/{1}/".format(user.id, activation_code) 74 | page = self.app.get(activation_url).follow() 75 | self.assertEqual(200, page.status_code) 76 | self.assertTrue(User.objects.get(id=user.id).is_active) 77 | self.assertTemplateUsed("login.html") 78 | email = Email.objects.all().order_by("-id")[0] 79 | self.assertEqual(user.email, email.to[0]) 80 | self.assertEqual(u"Welcome to team !", email.subject) 81 | 82 | def test_logged_in(self): 83 | """ Logged in user attempts to get registration form """ 84 | user = UserFactory.create(is_active=True) 85 | page = self.app.get(reverse("user-creation"), user=user) 86 | self.assertRedirects( 87 | page, reverse("user-details", kwargs={'pk': user.id, })) 88 | -------------------------------------------------------------------------------- /rynda/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarutobi/Rynda/aa10d3ffdd7b3f7eaa0a1470a8541b8d0519e1f5/rynda/users/__init__.py -------------------------------------------------------------------------------- /rynda/users/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.contrib import admin 4 | from django.contrib.auth.admin import UserAdmin 5 | from django.contrib.auth.models import User 6 | 7 | from .models import Profile 8 | 9 | admin.site.unregister(User) 10 | 11 | 12 | class ProfileInline(admin.StackedInline): 13 | model = Profile 14 | 15 | 16 | class UserProfile(UserAdmin): 17 | inlines = [ProfileInline, ] 18 | 19 | admin.site.register(User, UserProfile) 20 | -------------------------------------------------------------------------------- /rynda/users/management/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf import settings 4 | from django.contrib.auth import get_user_model 5 | from django.db.models import signals 6 | 7 | from rynda.users import models as users_app 8 | 9 | 10 | def create_anonymous_user(sender, **kwargs): 11 | User = get_user_model() 12 | ANONYMOUS_USER_ID = settings.ANONYMOUS_USER_ID 13 | ANONYMOUS_DEFAULT_USERNAME = getattr( 14 | settings, "ANONYMOUS_DEFAULT_USERNAME", "anonymous") 15 | try: 16 | User.objects.get(pk=ANONYMOUS_USER_ID) 17 | except User.DoesNotExist: 18 | User.objects.create( 19 | pk=ANONYMOUS_USER_ID, 20 | **{User.USERNAME_FIELD: ANONYMOUS_DEFAULT_USERNAME}) 21 | 22 | if hasattr(settings, "ANONYMOUS_USER_ID"): 23 | signals.post_syncdb.connect( 24 | create_anonymous_user, sender=users_app, 25 | dispatch_uid="users.management.create_anonymous_user") 26 | -------------------------------------------------------------------------------- /rynda/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Profile', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('forgotCode', models.CharField(max_length=40, null=True, editable=False, db_column=b'forgotten_password_code')), 20 | ('rememberCode', models.CharField(max_length=40, null=True, editable=False, db_column=b'remember_code')), 21 | ('forgotten_time', models.DateTimeField(null=True, editable=False, db_column=b'forgotten_password_time')), 22 | ('flags', models.IntegerField(default=0, editable=False, db_column=b'flags')), 23 | ('phones', models.CharField(max_length=255, blank=True)), 24 | ('about_me', models.TextField(default=b'', blank=True)), 25 | ('birthday', models.DateField(null=True, blank=True)), 26 | ('gender', models.IntegerField(default=0, verbose_name='Gender', choices=[(0, 'Unknown'), (1, 'Male'), (2, 'Female')])), 27 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), 28 | ], 29 | options={ 30 | 'ordering': ['user'], 31 | 'verbose_name': 'Profile', 32 | 'verbose_name_plural': 'Profiles', 33 | }, 34 | bases=(models.Model,), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /rynda/users/migrations/0002_profile_is_public.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='profile', 16 | name='is_public', 17 | field=models.BooleanField(default=False, verbose_name='Public'), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /rynda/users/migrations/0003_auto_20140911_1613.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | def set_public_flags(apps, schema_editor): 8 | Profile = apps.get_model("users", "Profile") 9 | for profile in Profile.objects.all(): 10 | profile.is_public = 1 == (profile.flags & 1) 11 | profile.save() 12 | 13 | 14 | class Migration(migrations.Migration): 15 | 16 | dependencies = [ 17 | ('users', '0002_profile_is_public'), 18 | ] 19 | 20 | operations = [ 21 | migrations.RunPython(set_public_flags) 22 | ] 23 | -------------------------------------------------------------------------------- /rynda/users/migrations/0004_remove_profile_flags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0003_auto_20140911_1613'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='profile', 16 | name='flags', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /rynda/users/migrations/0005_auto_20140911_1634.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0004_remove_profile_flags'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='profile', 16 | name='forgotCode', 17 | ), 18 | migrations.RemoveField( 19 | model_name='profile', 20 | name='forgotten_time', 21 | ), 22 | migrations.RemoveField( 23 | model_name='profile', 24 | name='rememberCode', 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /rynda/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarutobi/Rynda/aa10d3ffdd7b3f7eaa0a1470a8541b8d0519e1f5/rynda/users/migrations/__init__.py -------------------------------------------------------------------------------- /rynda/users/templates/activation_failed.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarutobi/Rynda/aa10d3ffdd7b3f7eaa0a1470a8541b8d0519e1f5/rynda/users/templates/activation_failed.html -------------------------------------------------------------------------------- /rynda/users/templates/edit_profile_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html'%} 2 | {% load i18n floppyforms %} 3 | 4 | {% block hubs %} 5 | {% include "widgets/base_hub.html" with part='user' %} 6 | {% endblock %} 7 | 8 | {% block maincontent %} 9 |
10 |
11 |
12 |

{% trans "Edit profile" %}

13 |
14 | 15 |
16 |
17 | {% csrf_token%} 18 | {% form form %} 19 | 22 |
23 |
24 |
25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /rynda/users/templates/registration_form.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html'%} 2 | 3 | {% load i18n floppyforms %} 4 | 5 | {% block hubs %} 6 | {% include "widgets/base_hub.html" with part='base' %} 7 | {% endblock %} 8 | 9 | {% block maincontent %} 10 |
11 |
12 |
13 |

{% trans "Registration" %}

14 |
15 | 16 |
17 |
18 | {% csrf_token%} 19 | {% form form %} 20 | 23 |
24 |
25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /rynda/users/templates/registration_success.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load i18n %} 3 | 4 | {% block page_title %}{% trans "Registration success" %}{% endblock %} 5 | 6 | {% block hubs %} 7 | {% include "widgets/base_hub.html" with part='main' %} 8 | {% endblock %} 9 | 10 | {% block maincontent %} 11 |
12 |
13 |
14 |

{% trans 'You successfully registered!' %}

15 |
16 |
17 |

{% trans "Congratulations!" %}

18 |

{% blocktrans %} 19 | Your registration is almost done. Please, check your emails for a mail from us with a confirmation link.{% endblocktrans %} 20 |

21 |
22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /rynda/users/templates/user_list.html: -------------------------------------------------------------------------------- 1 | {% extends 'base_list.html' %} 2 | {% load i18n %} 3 | 4 | {% block listfilter %} 5 | {% include "widgets/filter.html" %} 6 | {% endblock %} 7 | 8 | {% block hubs %} 9 | {% include "widgets/base_hub.html" with part='user' %} 10 | {% endblock %} 11 | 12 | {% block listcontent %} 13 | {% for u in filter %} 14 |
  • 15 |
    16 | {{ u.get_full_name }} 17 |
    18 | {% trans "More info" %} 19 |
    20 | {% trans "Registered at:" %} {{ u.date_joined|date:"DATETIME_FORMAT" }} | {% trans "Last login:" %} {{ u.last_login|date:"SHORT_DATE_FORMAT" }} 21 |
    22 |
    23 |
  • 24 | {% endfor%} 25 | {% endblock %} 26 | 27 | {% block paginator%} 28 |
    {% trans "Users on page:" %} 29 | 10 30 |
    31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /rynda/users/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarutobi/Rynda/aa10d3ffdd7b3f7eaa0a1470a8541b8d0519e1f5/rynda/users/tests/__init__.py -------------------------------------------------------------------------------- /rynda/users/tests/xUnit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sarutobi/Rynda/aa10d3ffdd7b3f7eaa0a1470a8541b8d0519e1f5/rynda/users/tests/xUnit/__init__.py -------------------------------------------------------------------------------- /rynda/users/tests/xUnit/test_forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.test import TestCase 4 | from django.utils.translation import ugettext as _ 5 | 6 | from rynda.users.forms import SimpleRegistrationForm 7 | from rynda.users.models import Profile 8 | from rynda.test.factories import UserFactory 9 | 10 | 11 | class SimpleRegistrationFormTest(TestCase): 12 | ''' Registration form test''' 13 | 14 | def setUp(self): 15 | self.data = { 16 | 'first_name': 'test', 17 | 'last_name': 'user', 18 | 'email': 'test_user@mail.ru', 19 | 'password1': '123', 20 | 'password2': '123' 21 | } 22 | 23 | def test_user_creation(self): 24 | form = SimpleRegistrationForm(self.data) 25 | self.assertTrue(form.is_bound) 26 | self.assertTrue(form.is_valid()) 27 | 28 | def test_lost_firstname(self): 29 | self.data['first_name'] = None 30 | form = SimpleRegistrationForm(self.data) 31 | self.assertTrue(form.is_bound) 32 | self.assertFalse(form.is_valid()) 33 | self.assertEqual([_(u'This field is required.'), ], 34 | form['first_name'].errors) 35 | 36 | def test_empty_firstname(self): 37 | self.data['first_name'] = ' ' 38 | form = SimpleRegistrationForm(self.data) 39 | self.assertTrue(form.is_bound) 40 | self.assertFalse(form.is_valid()) 41 | self.assertEqual([_(u'You must provide a first name!'), ], 42 | form['first_name'].errors) 43 | 44 | def test_lost_lastname(self): 45 | self.data['last_name'] = None 46 | form = SimpleRegistrationForm(self.data) 47 | self.assertTrue(form.is_bound) 48 | self.assertFalse(form.is_valid()) 49 | self.assertEqual([_(u'This field is required.'), ], 50 | form['last_name'].errors) 51 | 52 | def test_empty_lastname(self): 53 | self.data['last_name'] = ' ' 54 | form = SimpleRegistrationForm(self.data) 55 | self.assertTrue(form.is_bound) 56 | self.assertFalse(form.is_valid()) 57 | self.assertEqual([_(u'You must provide a last name!'), ], 58 | form['last_name'].errors) 59 | 60 | def test_existing_email(self): 61 | user = UserFactory() 62 | self.data['email'] = user.email 63 | form = SimpleRegistrationForm(self.data) 64 | self.assertTrue(form.is_bound) 65 | self.assertFalse(form.is_valid()) 66 | self.assertEqual([_(u'This email already registered'), ], 67 | form['email'].errors) 68 | 69 | def test_passwords_diff(self): 70 | self.data['password1'] = 'qwe' 71 | form = SimpleRegistrationForm(self.data) 72 | self.assertTrue(form.is_bound) 73 | self.assertFalse(form.is_valid()) 74 | self.assertEqual([_(u'The password fields did not match'), ], 75 | form.errors['__all__']) 76 | -------------------------------------------------------------------------------- /rynda/users/urls.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | from django.conf.urls import patterns, url 4 | 5 | from rynda.users.views import UserDetail, UserList, EditProfile 6 | 7 | 8 | urlpatterns = patterns('rynda.users.views', 9 | url(r'^$', UserList.as_view(), name="user-list"), 10 | url(r'^page/(?P\d+)/$', UserList.as_view(), name='user-list'), 11 | url(r'^(?P\d+)$', UserDetail.as_view(), name='user-details'), 12 | url(r'^activate/(?P\d+)/(?P[a-zA-Z0-9_-]+)/$', 13 | 'activate_profile', name='user-activate-profile'), 14 | url(r'^edit/$', EditProfile.as_view(), name='user-profile-edit'), 15 | #url(r'^requestpassword$', ''), 16 | ) 17 | --------------------------------------------------------------------------------