├── .gitignore ├── LICENCE ├── Makefile ├── README.md ├── client ├── Gruntfile.js ├── app │ ├── app.js │ ├── common │ │ ├── auth │ │ │ ├── authorization.js │ │ │ └── login │ │ │ │ ├── login-form.html │ │ │ │ ├── logout-form.html │ │ │ │ ├── register-form.html │ │ │ │ ├── toolbar.html │ │ │ │ └── toolbar.js │ │ ├── comment │ │ │ ├── comment.js │ │ │ └── templates │ │ │ │ ├── comment.html │ │ │ │ └── comments.html │ │ ├── common.js │ │ ├── gallery │ │ │ ├── gallery.js │ │ │ └── templates │ │ │ │ ├── gallery.html │ │ │ │ └── image.html │ │ ├── modal │ │ │ ├── modal.js │ │ │ └── templates │ │ │ │ ├── dialog.html │ │ │ │ └── progress.html │ │ ├── other │ │ │ ├── directives.js │ │ │ ├── filters.js │ │ │ └── services.js │ │ ├── print │ │ │ ├── print-table.html │ │ │ └── print.js │ │ └── toolbar │ │ │ ├── templates │ │ │ └── toolbar.html │ │ │ └── toolbarDirective.js │ ├── entries │ │ ├── detail │ │ │ ├── detail.js │ │ │ └── templates │ │ │ │ ├── detail.html │ │ │ │ ├── form-widget │ │ │ │ ├── addr-input-auto-code.html │ │ │ │ ├── addr-input-auto-regioncode.html │ │ │ │ └── addr-input-autocomplete.html │ │ │ │ └── form │ │ │ │ ├── fields │ │ │ │ ├── check-box-input.html │ │ │ │ ├── date-input.html │ │ │ │ ├── geom.html │ │ │ │ ├── input.html │ │ │ │ ├── latlng.html │ │ │ │ ├── time-input.html │ │ │ │ └── typeahead-input.html │ │ │ │ └── form.html │ │ ├── entries.js │ │ ├── index.js │ │ ├── list │ │ │ ├── list.js │ │ │ └── templates │ │ │ │ ├── entries-list.html │ │ │ │ ├── entries-table.html │ │ │ │ └── entry-in-list.html │ │ ├── manager │ │ │ ├── dynamit.js │ │ │ ├── editor │ │ │ │ ├── base-form.html │ │ │ │ ├── dynamit-field.html │ │ │ │ ├── dynamit-fields.html │ │ │ │ ├── dynamit-options.html │ │ │ │ ├── editor-modal.html │ │ │ │ └── editor.js │ │ │ ├── manager.js │ │ │ └── templates │ │ │ │ ├── dynamit-in-list.html │ │ │ │ ├── dynamit-list.html │ │ │ │ └── sidebar.html │ │ └── sidebar.html │ └── map │ │ ├── mapster.js │ │ └── services │ │ ├── emap.js │ │ ├── mapDefaults.js │ │ └── mapHelpers.js ├── assets │ ├── css │ │ └── .gitkeep │ ├── favicon.ico │ ├── humans.txt │ ├── img │ │ ├── cluster.svg │ │ ├── cluster_current.svg │ │ ├── cluster_selected.svg │ │ ├── cluster_stroke.svg │ │ ├── glyphicons-halflings-white.png │ │ ├── glyphicons-halflings.png │ │ ├── shadow.svg │ │ ├── single.svg │ │ ├── single_current.svg │ │ ├── single_selected.svg │ │ ├── single_stroke.png │ │ └── single_stroke.svg │ ├── js │ │ ├── main.js │ │ ├── map.js │ │ └── plugins.js │ └── robots.txt ├── dist │ ├── css │ │ ├── geonote.css │ │ └── styles.css │ ├── favicon.ico │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── humans.txt │ ├── img │ │ ├── cluster.svg │ │ ├── cluster_current.svg │ │ ├── cluster_selected.svg │ │ ├── cluster_stroke.svg │ │ ├── glyphicons-halflings-white.png │ │ ├── glyphicons-halflings.png │ │ ├── shadow.svg │ │ ├── single.svg │ │ ├── single_current.svg │ │ ├── single_selected.svg │ │ ├── single_stroke.png │ │ └── single_stroke.svg │ ├── js │ │ ├── geonote.js │ │ ├── main.js │ │ ├── map.js │ │ └── plugins.js │ ├── robots.txt │ └── templates │ │ └── app.js ├── less │ ├── app.less │ ├── app │ │ ├── animate.less │ │ ├── base.less │ │ ├── bootstrap_customizing.less │ │ ├── comments.less │ │ ├── dynamit.less │ │ ├── entry-detail.less │ │ ├── entry-list.less │ │ ├── gallery.less │ │ ├── helpers.less │ │ ├── leaflet_customizing.less │ │ └── print_modal.less │ ├── styles.less │ └── variables.less └── package.json ├── comments ├── __init__.py ├── admin.py ├── models.py ├── tests.py └── views.py ├── dynamit ├── __init__.py ├── actions.py ├── admin.py ├── api │ ├── __init__.py │ ├── metadata.py │ ├── serializers.py │ └── views.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests.py ├── utils.py └── views.py ├── entry ├── __init__.py ├── admin.py ├── api │ ├── __init__.py │ ├── authenticators.py │ ├── filters.py │ ├── metadata.py │ ├── permissions.py │ ├── serializers.py │ └── views.py ├── forms.py ├── models.py ├── templatetags │ ├── __init__.py │ └── json_filters.py ├── tests.py ├── urls.py ├── utils.py └── views.py ├── manage.py ├── prj ├── __init__.py ├── local_settings.py.template ├── middleware.py ├── settings.py ├── urls.py ├── utils.py ├── wsgi.py ├── wsgi.py.bak └── wsgi.py.def ├── requirements.txt ├── templates ├── 403.html ├── 404.html ├── 500.html ├── _layouts │ ├── base.html │ └── modal │ │ ├── aboutModal.html │ │ ├── attributionModal.html │ │ ├── modals.html │ │ └── printModal.html └── page │ └── index.html └── uploader ├── __init__.py ├── admin.py ├── api ├── __init__.py ├── utils.py └── views.py ├── forms.py ├── migrations ├── 0001_initial.py └── __init__.py ├── models.py ├── tests.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.py[co] 3 | *.sw[po] 4 | *.egg* 5 | .coverage 6 | *.pyc 7 | *.swp 8 | *~ 9 | *.mo 10 | static/* 11 | 12 | prj/local_settings.py 13 | env 14 | .env 15 | node_modules 16 | .DS_Store 17 | npm-debug.log 18 | 19 | .vscode 20 | /client/node_modules/ 21 | .idea/ 22 | media/ 23 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Artemiy 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SETTINGS = project.settings 2 | 3 | run: 4 | python manage.py runserver 0.0.0.0:8000 5 | 6 | shell: 7 | python manage.py shell 8 | 9 | superuser: 10 | ./manage.py createsuperuser --username=admin --email=admin@example.com 11 | 12 | mailserver: 13 | python -m smtpd -n -c DebuggingServer 0.0.0.0:1025 14 | 15 | collect: 16 | python manage.py collectstatic --noinput 17 | 18 | manage: 19 | python manage.py $(CMD) 20 | 21 | graphviz: 22 | python manage.py graph_models -a -o logic_models.png -e -g 23 | 24 | pull: 25 | git pull origin master && ./manage.py collectstatic --noinput 26 | 27 | install: 28 | pip install -r requirements.txt 29 | npm install 30 | bower install install 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GEONOTE v1.5.0 (alpha) 2 | ====================== 3 | 4 | __THIS PROJECT IS DEPRECATED AND NO LONGER SUPPORTED__ 5 | 6 | Web-based application for storage geodata 7 | 8 | [Live demo](http://geonote.ru) 9 | 10 | [Youtube #1](https://www.youtube.com/watch?v=SoMN1hQXKnw), 11 | [Youtube #2](https://www.youtube.com/watch?v=WPfIXxpSHeU) 12 | 13 | ## Server-side 14 | 15 | Requirements: 16 | 17 | - Django => 1.10.0 18 | - PostgresSQL/PostGIS 19 | 20 | Installation: 21 | 22 | $ git clone https://github.com/rendrom/geonote.git 23 | $ cd ./geonote 24 | $ virtualenv ./.env 25 | $ . ./.env/bin/activate 26 | $ pip install -r requirements.txt 27 | $ cp ./prj/local_settings.py.template ./prj/local_settings.py 28 | $ vim ./prj/local_settings.py # edit database connection 29 | $ ./manage.py migrate 30 | $ ./manage.py runserver # go to http://localhost:8000 31 | 32 | ### Client-side 33 | 34 | Requirements: 35 | - [Nodejs](http://nodejs.org/) 36 | - [npm](https://www.npmjs.org/) 37 | - [grunt](http://gruntjs.com/) 38 | 39 | Major library versions: 40 | - Angular 1.5.x 41 | - Leaflet 0.7.7 42 | 43 | Development: 44 | 45 | $ cd ./client 46 | $ npm install 47 | & grunt 48 | 49 | ## ToDo: 50 | 51 | General: 52 | 53 | - i18n 54 | - documentations 55 | - tests, tests everywhere 56 | - increase stability 57 | - error handling 58 | 59 | Server-side: 60 | 61 | - refactoring 62 | - simplify dynamic URLs 63 | - extract Dynamit REST classes and methods 64 | - support all spatial format import 65 | - export data to various format 66 | - plugin system (calculator) 67 | - database tools (merge, clone) 68 | - optimization for large dataset 69 | - test memory with many dynamit-tables 70 | - Python 3.x compatibility 71 | 72 | Client-side: 73 | 74 | - switch to angular2 and typescript, webpack 75 | - upgrade leaflet to 1.0.1 version 76 | - make independent from the server-side 77 | - create fake REST-service 78 | 79 | ## Changelog: 80 | * 19.10.2016 - django 1.8 to 1.10; angular 1.3 to 1.5 81 | 82 | ## Authors 83 | 84 | Artemiy Doroshkov rendrom@gmail.com 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /client/app/app.js: -------------------------------------------------------------------------------- 1 | angular.module('app', [ 2 | 'ngCookies', 3 | 'ngTouch', 4 | 'ngRoute', 5 | 'ngAnimate', 6 | 'ui.bootstrap', 7 | 'templates.app', 8 | 'ui-notification', 9 | 'ui.sortable', 10 | 'angularFileUpload', 11 | 'angular-carousel', 12 | 13 | 'app-common', 14 | 'entries' 15 | ]); 16 | 17 | angular.module('app').controller('AppController', 18 | ['$window', '$scope', '$location', 'auth', 'Entries', 'NAMES', 'appConf', 'emap', 'modalService', 19 | function ($window, $scope, $location, auth, Entries, NAMES, appConf, emap, modalService) { 20 | $scope.auth = auth; 21 | $scope.Entries = Entries; 22 | 23 | $scope.NAMES = NAMES; 24 | 25 | $scope.showOnTheMap = function (id) { 26 | emap.sidebarClick(id); 27 | }; 28 | $scope.bboxSearchMode = function () { 29 | if (emap.bboxSearchMode.enabled()) { 30 | emap.bboxSearchMode.disable(); 31 | } 32 | else { 33 | emap.bboxSearchMode.enable(); 34 | } 35 | }; 36 | $scope.goToMainPage = function () { 37 | appConf.goToMainPage(); 38 | }; 39 | $scope.backToList = function () { 40 | appConf.backToList(Entries.username, Entries.model); 41 | }; 42 | $scope.cleanSelection = function () { 43 | appConf.cleanSelection(Entries.username, Entries.model); 44 | }; 45 | $scope.goToDetail = function (id) { 46 | appConf.goToDetail(Entries.username, Entries.model, id); 47 | }; 48 | $scope.goToCreatePage = function () { 49 | $location.path(Entries.username + '/layer/' + Entries.model + '/create'); 50 | }; 51 | $scope.showSidebar = function () { 52 | emap.showSidebar(); 53 | }; 54 | $scope.hideSidebar = function () { 55 | emap.hideSidebar(); 56 | }; 57 | $scope.toggleSidebar = function () { 58 | emap.toggleSidebar(); 59 | }; 60 | $scope.showFullExtent = function () { 61 | emap.showFullExtent(); 62 | }; 63 | $scope.is_filter = false; 64 | //TODO: перенести в сервис. Избавиться от $watchCollection 65 | $scope.$watchCollection('Entries.queryParam', function (newVal, oldVal) { 66 | var has_filter = false; 67 | for (var fry in Entries.queryParam) { 68 | if (Entries.queryParam.hasOwnProperty(fry)) { 69 | if (Entries.queryParam[fry]) { 70 | $scope.is_filter = has_filter = true; 71 | break; 72 | } 73 | } 74 | } 75 | $scope.is_filter = has_filter; 76 | }); 77 | /** 78 | is_narrow - показывает статус боковой панели. Может быть широкой(false) или узкой(true) 79 | нелинейно зависит ош ширины окна браузера 80 | */ 81 | $scope.emap = emap; 82 | angular.element($window).bind('resize', function () { 83 | $scope.$apply( 84 | $scope.is_narrow = emap.checkSidebarIsNarrow() 85 | ); 86 | }); 87 | $scope.entryForExport = null; 88 | }]) 89 | .run(['auth', function (auth) { 90 | auth.initialize('/auth', false); 91 | }]); 92 | 93 | angular.module('app').config(['$httpProvider', function ($httpProvider) { 94 | $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; 95 | $httpProvider.defaults.xsrfCookieName = 'csrftoken'; 96 | }]); 97 | 98 | angular.module('app').config(['$animateProvider', function ($animateProvider) { 99 | $animateProvider.classNameFilter(/animate/); 100 | }]); 101 | 102 | angular.module('app').config(['$interpolateProvider', function ($interpolateProvider) { 103 | $interpolateProvider.startSymbol('{$'); 104 | $interpolateProvider.endSymbol('$}'); 105 | }]); 106 | 107 | // angular.module('app').config(['$controllerProvider', function ($controllerProvider) { 108 | // $controllerProvider.allowGlobals(); 109 | // }]); -------------------------------------------------------------------------------- /client/app/common/auth/login/login-form.html: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /client/app/common/auth/login/logout-form.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/app/common/auth/login/register-form.html: -------------------------------------------------------------------------------- 1 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /client/app/common/auth/login/toolbar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/common/auth/login/toolbar.js: -------------------------------------------------------------------------------- 1 | angular.module('authster') 2 | 3 | .directive('loginToolbar', ['auth', function (auth) { 4 | return { 5 | templateUrl: 'common/auth/login/toolbar.html', 6 | restrict: 'A', 7 | replace: true, 8 | scope: true, 9 | link: function ($scope, $element, $attrs, $controller) { 10 | $scope.isAuthenticated = auth.isAuthenticated; 11 | $scope.login = auth.showLogin; 12 | $scope.logout = auth.showLogout; 13 | $scope.register = auth.showRegister; 14 | } 15 | }; 16 | }]) 17 | 18 | .controller('AuthFormController', ['$scope', '$modalInstance', 'auth','Notification', 19 | function ($scope, $modalInstance, auth, Notification) { 20 | 21 | $scope.auth_user = {}; 22 | 23 | $scope.login = function () { 24 | $modalInstance.dismiss('cancel'); 25 | auth.login($scope.auth_user.username, $scope.auth_user.password); 26 | }; 27 | $scope.register = function () { 28 | var u = $scope.auth_user; 29 | auth.register(u.username, u.password, u.password2, u.email).catch(function (error) { 30 | returnError(error); 31 | }); 32 | }; 33 | $scope.logout = function () { 34 | $modalInstance.dismiss('cancel'); 35 | auth.logout(); 36 | }; 37 | $scope.clearForm = function () { 38 | $scope.auth_user = {}; 39 | }; 40 | $scope.cancel = function () { 41 | $modalInstance.dismiss('cancel'); 42 | }; 43 | function returnError(error) { 44 | angular.forEach(error, function (errors, field) { 45 | Notification.error("" + field + ": " + errors); 46 | if ($scope.registerForm) { 47 | $scope.registerForm[field].$setValidity('server', false); 48 | } 49 | }); 50 | } 51 | }]); 52 | -------------------------------------------------------------------------------- /client/app/common/comment/templates/comment.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{$ c.comment |limitTo: c.textLength $} 4 | 5 | {$ c.readMoreText $} 6 | 7 |

8 | {$ c.submit_date | date:'yyyy-MM-dd HH:mm'$} | 9 |  {$ c.user $}  10 | |  11 |    12 | 13 | |  14 |    15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 | 27 | 28 |
29 |
30 |
-------------------------------------------------------------------------------- /client/app/common/comment/templates/comments.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | Показать комментарии ({$ comments.comments.length $})  6 | 7 |
8 | 9 |
10 |
11 |
12 | 13 | Скрыть комментарии  14 | 15 |
16 | 17 |
18 |
19 |
20 | 25 |
-------------------------------------------------------------------------------- /client/app/common/common.js: -------------------------------------------------------------------------------- 1 | angular.module('app-common', [ 2 | 'appDirectives', 3 | 'appServices', 4 | 'appFilters', 5 | 'modal-work', 6 | 'authster', 7 | 'mapster', 8 | 'toolbar' 9 | ]); -------------------------------------------------------------------------------- /client/app/common/gallery/templates/gallery.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/common/gallery/templates/image.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Responsive image 4 | 5 |
-------------------------------------------------------------------------------- /client/app/common/modal/templates/dialog.html: -------------------------------------------------------------------------------- 1 | 4 | 7 | 13 | -------------------------------------------------------------------------------- /client/app/common/modal/templates/progress.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/common/other/directives.js: -------------------------------------------------------------------------------- 1 | angular.module('appDirectives', []) 2 | 3 | .directive('endlessScroll', [function () { 4 | return { 5 | restrict: 'A', 6 | link: function (scope, element, attrs) { 7 | var raw = element[0]; 8 | element.bind('scroll', function () { 9 | if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) { 10 | scope.$apply(attrs.endlessScroll); 11 | } 12 | }); 13 | } 14 | }; 15 | }]) 16 | 17 | .directive('serverError', function() { 18 | return { 19 | restrict: 'A', 20 | require: '?ngModel', 21 | link: function(scope, element, attrs, ctrl) { 22 | return element.on('input', function() { 23 | if (ctrl.$invalid) { 24 | return scope.$apply(function () { 25 | return ctrl.$setValidity('server', true); 26 | }); 27 | } 28 | }); 29 | } 30 | }; 31 | }); -------------------------------------------------------------------------------- /client/app/common/other/filters.js: -------------------------------------------------------------------------------- 1 | angular.module('appFilters', []) 2 | 3 | .filter('truncate', function () { 4 | return function (text, length, end) { 5 | if (text!==undefined) { 6 | if (isNaN(length)) { 7 | length = 10; 8 | } 9 | 10 | if (end === undefined) { 11 | end = "..."; 12 | } 13 | text = text.toString(); 14 | if (text.length <= length || text.length - end.length <= length) { 15 | 16 | return text; 17 | } 18 | else { 19 | return String(text).substring(0, length - end.length) + end; 20 | } 21 | } 22 | }; 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /client/app/common/print/print-table.html: -------------------------------------------------------------------------------- 1 | {$ entryForExport.meta.verbose_name[field] $}{$ entryForExport[field] $} -------------------------------------------------------------------------------- /client/app/common/print/print.js: -------------------------------------------------------------------------------- 1 | angular.module('entries.print', []) 2 | 3 | .directive("printTable", ["api", function(api) { 4 | return { 5 | restrict: 'A', 6 | replace: true, 7 | // scope: { entry: '=' }, 8 | templateUrl: 'common/print/print-table.html', 9 | link: function (scope, element, attrs) { 10 | scope.fields = ['entryid', 'entry_model', 'entry_software_name','entry_software_vendor','web_address','description', 11 | 'organization', 'inn', 'organization_kpp', 'organization_okved', 'organization_tel','address', 12 | 'postalcode','regioncode','region','city','locality','street','housenum','buildnum', 'strucnum', 13 | 'flatnum','okato','latlng']; 14 | } 15 | }; 16 | }]); -------------------------------------------------------------------------------- /client/app/common/toolbar/templates/toolbar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/common/toolbar/toolbarDirective.js: -------------------------------------------------------------------------------- 1 | angular.module('toolbar', []) 2 | .directive('toolBar', [function () { 3 | return { 4 | restrict: 'A', 5 | replace: true, 6 | templateUrl: 'common/toolbar/templates/toolbar.html', 7 | }; 8 | }]); -------------------------------------------------------------------------------- /client/app/entries/detail/templates/form-widget/addr-input-auto-code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
{$ match.label.full_name $}
4 | {$ match.label.rel_name $} 5 |
6 | -------------------------------------------------------------------------------- /client/app/entries/detail/templates/form-widget/addr-input-auto-regioncode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/app/entries/detail/templates/form-widget/addr-input-autocomplete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
{$ match.label.rel_name $}
4 |
-------------------------------------------------------------------------------- /client/app/entries/detail/templates/form/fields/check-box-input.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /client/app/entries/detail/templates/form/fields/date-input.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 8 | 9 | 11 | 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /client/app/entries/detail/templates/form/fields/geom.html: -------------------------------------------------------------------------------- 1 | 5 |
6 |
7 | 11 |
12 |
-------------------------------------------------------------------------------- /client/app/entries/detail/templates/form/fields/input.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /client/app/entries/detail/templates/form/fields/latlng.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/entries/detail/templates/form/fields/time-input.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | Hours step is: 7 | 8 |
9 |
10 | Minutes step is: 11 | 12 |
13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /client/app/entries/detail/templates/form/fields/typeahead-input.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 13 | 14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /client/app/entries/detail/templates/form/form.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 14 |
16 |
17 | 18 | 21 | 22 | 23 | 26 | 27 |
{$ entryMeta[field]['label'] $}{$ entry[field] $} 24 |
25 |
28 |
29 |
30 |
31 |
32 |
-------------------------------------------------------------------------------- /client/app/entries/index.js: -------------------------------------------------------------------------------- 1 | angular.module('entries', ['entries.list', 'entries.detail', 'entries.factory','entries.dynamit']) 2 | 3 | .config(['$routeProvider', function ($routeProvider) { 4 | $routeProvider 5 | .when('/', { 6 | templateUrl: 'entries/manager/templates/sidebar.html', 7 | controller: 'DynamitListCtrl' 8 | }) 9 | .when('/:username/layer/:model', { 10 | templateUrl: 'entries/sidebar.html', 11 | controller: 'EntryListCtrl' 12 | }) 13 | .when('/:username/layer/:model/create', { 14 | templateUrl: 'entries/detail/templates/detail.html', 15 | controller: 'EntryDetailCtrl' 16 | }) 17 | .when('/:username/layer/:model/:id', { 18 | templateUrl: 'entries/detail/templates/detail.html', 19 | controller: 'EntryDetailCtrl' 20 | }); 21 | }]) 22 | 23 | .run(['$rootScope', '$routeParams','appConf','Entries', 'emap', 'mapDefaults', 24 | function ($rootScope, $routeParams, appConf, Entries, emap, mapDefaults) { 25 | $rootScope.$on("$locationChangeSuccess", function (event, next, current) { 26 | emap.disableDraw(); 27 | emap.disableEdit(); 28 | if (Entries.entryToUpdate && Entries.entries.length !== 0) { 29 | var data = Entries.entryToUpdate; 30 | for (var fry = 0; fry < Entries.entries.length; fry++) { 31 | if (Entries.entries[fry].id === data.id) { 32 | data.entryListName = Entries.entryListName(data); 33 | Entries.entries[fry] = data; 34 | break; 35 | } 36 | } 37 | } 38 | if (Entries.entriesLayerToReturn) { 39 | emap.returnLayerBack(Entries.entriesLayerToReturn); 40 | Entries.entriesLayerToReturn = null; 41 | } 42 | if (Entries.entryIcoToReturn) { 43 | Entries.entryIcoToReturn.setIcon(mapDefaults.singleIcon); 44 | } 45 | //addressExampleItems.clearLayers(); 46 | emap.clearDrawItems(); 47 | //highlight.clearLayers(); 48 | var queryType = null; 49 | var _getParamInQuery = function (from_query_str) { 50 | for (var fry in Entries.queryParam) { 51 | if (Entries.queryParam.hasOwnProperty(fry)) { 52 | var urlParam = appConf.getUrlParameters(fry, from_query_str, true); 53 | if (urlParam) { 54 | queryType = fry; 55 | return urlParam; 56 | } 57 | } 58 | } 59 | return null; 60 | }; 61 | var currentQuery = _getParamInQuery(current); 62 | var nextQuery = _getParamInQuery(next); 63 | for (var q in Entries.queryParam) { 64 | if (Entries.queryParam.hasOwnProperty(q)) { 65 | if (currentQuery && nextQuery == null) { 66 | Entries.queryParam[q] = null; 67 | } 68 | else if (q === queryType) { 69 | Entries.queryParam[q] = nextQuery ? nextQuery : currentQuery; 70 | } 71 | else { 72 | Entries.queryParam[q] = null; 73 | } 74 | } 75 | } 76 | if ((nextQuery || currentQuery) && (nextQuery !== currentQuery)) { 77 | Entries.entries = []; 78 | if (!emap.map.hasLayer(emap.markerClusters)) { 79 | emap.map.addLayer(emap.markerClusters); 80 | } 81 | //angular.element("#loading").show(); 82 | emap.clearRegionLayer(); 83 | emap.markerClusters._unspiderfy(); 84 | //emap.redrawMap(Entries.model); 85 | } 86 | }); 87 | }]); 88 | -------------------------------------------------------------------------------- /client/app/entries/list/list.js: -------------------------------------------------------------------------------- 1 | angular.module('entries.list', []) 2 | 3 | .controller('EntryListCtrl', ["$scope","$routeParams","$http","Entries", 4 | function( $scope , $routeParams , $http , Entries ) { 5 | Entries.setModel($routeParams.username, $routeParams.model); 6 | $scope.loadingBtn = { 7 | btn: angular.element('.show-more-btn'), 8 | loading: function() { 9 | this.btn.button('loading'); 10 | }, 11 | reset: function() { 12 | this.btn.button('reset'); 13 | } 14 | }; 15 | $scope.init = function () { 16 | $scope.loadingBtn.loading(); 17 | Entries.getEntries().then(function(){ 18 | $scope.entries = Entries.entries; 19 | $scope.nextPage = Entries.nextPage; 20 | }); 21 | if (Entries.previousEntry) { 22 | setTimeout(function () { 23 | var $sidebarBody = angular.element('.sidebar-body'); 24 | var $elem = angular.element('#entry-' + Entries.previousEntry.id); 25 | if ($elem.length > 0) { 26 | $sidebarBody.scrollTop($sidebarBody.scrollTop() + $elem.position().top - 5); 27 | $elem.addClass('previous-entry'); 28 | Entries.previousEntry = null; 29 | } 30 | }, 1); 31 | } 32 | $scope.loadingBtn.reset(); 33 | }; 34 | $scope.init(); 35 | $scope.nextPage = Entries.nextPage ? Entries.nextPage: null; 36 | $scope.showMore = function() { 37 | if ($scope.nextPage) { 38 | $scope.loadingBtn.loading(); 39 | $http.get($scope.nextPage) 40 | .then(function (res) { 41 | var data = res.data; 42 | Entries.nextPage = $scope.nextPage = data.next; 43 | angular.forEach(data.results, function (item) { 44 | item.entryListName = Entries.entryListName(item); 45 | $scope.entries.push(item); 46 | }); 47 | Entries.entries = $scope.entries; 48 | $scope.loadingBtn.reset(); 49 | }); 50 | } 51 | }; 52 | }]) 53 | 54 | .directive("entriesList", [function() { 55 | return { 56 | restrict: 'A', 57 | replace: true, 58 | templateUrl: 'entries/list/templates/entries-list.html' 59 | }; 60 | }]) 61 | 62 | .directive("entryInList", ["Entries","appConf", function(Entries, appConf) { 63 | return { 64 | restrict: 'A', 65 | replace: true, 66 | templateUrl: 'entries/list/templates/entry-in-list.html', 67 | link: function(scope, element) { 68 | scope.deleteEntry = function(id, entryid){ 69 | Entries.deleteEntry(id, entryid); 70 | }; 71 | scope.startEdit = function(id) { 72 | Entries.editmode = true; 73 | appConf.goToDetail(Entries.username, Entries.model, id); 74 | }; 75 | } 76 | }; 77 | }]); 78 | -------------------------------------------------------------------------------- /client/app/entries/list/templates/entries-list.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 |
6 | 7 |
14 |
15 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /client/app/entries/list/templates/entries-table.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /client/app/entries/list/templates/entry-in-list.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | {$ ::e.entryListName $} 5 |
6 |
7 | 26 |
27 | 28 |
29 |
30 | 32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /client/app/entries/manager/editor/base-form.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 | 8 |
9 | 12 | 13 |

{$ meta.actions['name'].help_text $}

15 |
16 |
17 | 18 |
19 | 20 | 21 |
22 | 25 | 26 |

{$ meta.actions['slug'].help_text $}

28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 |
36 | 38 | 39 |

{$ meta.actions['description'].help_text $}

41 |
42 |
43 |
44 |
-------------------------------------------------------------------------------- /client/app/entries/manager/editor/dynamit-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /client/app/entries/manager/editor/dynamit-fields.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |  Выключить сортировку полей 4 |  Включить сортировку полей 5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 |
{$ meta.actions['fields']['verbose_name'].label $}{$ meta.actions['fields']['name'].label $}{$ meta.actions['fields']['field_type'].label $}
25 |
26 |
27 | Добавить поле 28 |
29 |
-------------------------------------------------------------------------------- /client/app/entries/manager/editor/dynamit-options.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 | 8 |
9 | 14 | 19 |
20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 | 36 | 37 |

{$ meta.actions['entryname'].help_text $}

39 |
40 |
41 |
42 | 43 |
44 |
-------------------------------------------------------------------------------- /client/app/entries/manager/editor/editor-modal.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | 34 | 35 | 54 |
55 | 56 | -------------------------------------------------------------------------------- /client/app/entries/manager/editor/editor.js: -------------------------------------------------------------------------------- 1 | angular.module('dynamit.editor', []) 2 | 3 | .directive('dynamitBaseForm', [function () { 4 | return { 5 | templateUrl: 'entries/manager/editor/base-form.html', 6 | restrict: 'A', 7 | transclude: false, 8 | replace: true, 9 | scope: true 10 | }; 11 | }]) 12 | .directive('dynamitFields', ['appConf', function (appConf) { 13 | return { 14 | templateUrl: 'entries/manager/editor/dynamit-fields.html', 15 | restrict: 'A', 16 | transclude: false, 17 | replace: true, 18 | scope: true, 19 | controller: (['$scope', function ($scope) { 20 | $scope.candrag = true; 21 | $scope.removeDynamitField = function (field) { 22 | $scope.dynamit.fields.splice(appConf.getElementIndex(field.id, $scope.dynamit.fields), 1); 23 | reorder(); 24 | }; 25 | $scope.sortableOptions = { 26 | stop: function (e, ui) { 27 | reorder(); 28 | 29 | } 30 | }; 31 | function reorder () { 32 | for (var index in $scope.dynamit.fields) { 33 | $scope.dynamit.fields[index].order = parseInt(index)+1; 34 | } 35 | } 36 | }]) 37 | }; 38 | }]) 39 | .directive('dynamitField', ['Dynamit', 'appConf', function (Dynamit, appConf) { 40 | return { 41 | templateUrl: 'entries/manager/editor/dynamit-field.html', 42 | restrict: 'A', 43 | replace: true, 44 | scope: {field: '=', meta: '=', candrag: '=', action: '&'}, 45 | link: function (scope, element, attrs) { 46 | if (scope.field !== undefined) { 47 | scope.f = scope.field; 48 | scope.new = false; 49 | } 50 | else { 51 | scope.f = {}; 52 | scope.new = true; 53 | } 54 | scope.appendField = function () { 55 | scope.action({field: angular.copy(scope.f)}); 56 | scope.f = {}; 57 | }; 58 | scope.removeField = function (f) { 59 | scope.action({field: f}); 60 | }; 61 | scope.slugify = function (field) { 62 | field.name = appConf.urlify(field.verbose_name); 63 | }; 64 | scope.selfslugify = function (field) { 65 | field.name = appConf.urlify(field.name); 66 | }; 67 | } 68 | }; 69 | }]) 70 | .directive('dynamitOptions', [function () { 71 | return { 72 | templateUrl: 'entries/manager/editor/dynamit-options.html', 73 | restrict: 'A', 74 | transclude: false, 75 | replace: true, 76 | scope: true 77 | }; 78 | }]); 79 | -------------------------------------------------------------------------------- /client/app/entries/manager/manager.js: -------------------------------------------------------------------------------- 1 | angular.module('entries.dynamit', ['entries.dynamit.factory', 'dynamit.editor']) 2 | 3 | .controller('DynamitListCtrl', ['$scope', 'Notification', 'Dynamit', 'Entries', 'emap', 'mapHelpers', 'NAMES', 'SERVER_NAMES', 4 | function ($scope, Notification, Dynamit, Entries, emap, mapHelpers, NAMES, SERVER_NAMES) { 5 | Entries.cleanAll(); 6 | $scope.init = function () { 7 | NAMES.title = SERVER_NAMES.title; 8 | Dynamit.query().then(function (data) { 9 | $scope.Dynamit = Dynamit; 10 | }).catch(); 11 | }; 12 | $scope.init(); 13 | $scope.goToCreatePage = function () { 14 | Dynamit.showEditorModal(); 15 | }; 16 | }]) 17 | .directive('dynamitList', ['$upload', '$log','Notification','Dynamit', 'modalService', 18 | function ($upload, $log, Notification, Dynamit, modalService) { 19 | return { 20 | restrict: 'A', 21 | replace: true, 22 | templateUrl: 'entries/manager/templates/dynamit-list.html', 23 | link: function (scope, element) { 24 | scope.upload = function (files) { 25 | modalService.progressbar.show("Идёт загрузка и обработка файла"); 26 | if (files && files.length) { 27 | for (var i = 0; i < files.length; i++) { 28 | var file = files[i]; 29 | $upload.upload({ 30 | url: 'dynamit_upload/', 31 | fields: {}, 32 | file: file 33 | }) 34 | .progress(uploadProgress) 35 | .success(successUpload) 36 | .error(errorUpload); 37 | } 38 | } 39 | }; 40 | function uploadProgress (evt) { 41 | var progressPercentage = parseInt(100.0 * evt.loaded / evt.total); 42 | $log.info('progress: ' + progressPercentage + '% ' + evt.config.file.name); 43 | } 44 | function successUpload (data, status, headers, config) { 45 | Dynamit.dynamits.unshift(data['dynamit']); 46 | Notification[data['status']['type']](data['status']['result']); 47 | modalService.progressbar.hide(); 48 | } 49 | function errorUpload() { 50 | modalService.progressbar.hide(); 51 | } 52 | } 53 | }; 54 | }]) 55 | 56 | .directive('dynamitInList', ['$window','api','appConf','modalService', 'Dynamit', function ($window, api, appConf, modalService, Dynamit) { 57 | return { 58 | restrict: 'A', 59 | replace: true, 60 | templateUrl: 'entries/manager/templates/dynamit-in-list.html', 61 | link: function (scope, element) { 62 | scope.goToEntry = function (dynamit) { 63 | appConf.goToEntry(dynamit.user, dynamit.slug); 64 | }; 65 | scope.startEdit = function(dynamit) { 66 | Dynamit.showEditorModal(dynamit); 67 | }; 68 | scope.deleteDynamit = function (d) { 69 | modalService.showConfirm('Удалить слой '+ d.name+'?').then(function (result) { 70 | api.dynamit.delete({id: d.id}).$promise.then(function (data) { 71 | Dynamit.dynamits.splice(appConf.getElementIndex(d.id, Dynamit.dynamits), 1); 72 | }); 73 | }); 74 | }; 75 | } 76 | }; 77 | }]); 78 | -------------------------------------------------------------------------------- /client/app/entries/manager/templates/dynamit-in-list.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | {$ d.name $}
5 |
{$ d.description | limitTo: 120 $}
6 | {$ ::d.user $} 7 |   8 |   9 |   10 |   11 |
12 |
13 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /client/app/entries/manager/templates/dynamit-list.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 |
11 | 12 |
19 |
20 | -------------------------------------------------------------------------------- /client/app/entries/manager/templates/sidebar.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 |
5 |
6 | Список слоёв 7 |
8 |
9 |
10 |
11 |
12 |
13 |

14 |
15 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /client/app/entries/sidebar.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 |
5 |
6 | Список 7 |
8 |
9 |
10 | Выбрано:{$ Entries.queryEntryCount $} 11 | 12 |
13 |
14 |
15 |
16 |
17 |

18 |
19 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /client/app/map/mapster.js: -------------------------------------------------------------------------------- 1 | angular.module('mapster', []) 2 | .controller('LeafletCtrl', ['$scope', 'emap', function ($scope, emap) { 3 | var c = this; 4 | 5 | $scope.$on('$destroy', function () { 6 | c.map.remove(); 7 | }); 8 | function init(id) { 9 | emap.init(id); 10 | } 11 | 12 | c.init = init; 13 | }]) 14 | 15 | .directive('leaflet', function leaflet() { 16 | var _id = 'map'; 17 | return { 18 | restrict: 'AE', 19 | replace: true, 20 | controller: 'LeafletCtrl', 21 | template: function (element, attributes) { 22 | var id = attributes.leaflet || _id; 23 | return '
'; 24 | }, 25 | link: function (scope, element, attributes, controller) { 26 | var id = attributes.leaflet || _id; 27 | controller.init(id); 28 | } 29 | }; 30 | }); -------------------------------------------------------------------------------- /client/app/map/services/mapDefaults.js: -------------------------------------------------------------------------------- 1 | angular.module('mapster') 2 | .factory('mapDefaults',['NAMES', function (NAMES) { 3 | function controlDefaults() { 4 | return { 5 | zoom: true, 6 | fullscreen: true, 7 | layers: true, 8 | scale: true, 9 | measure: false, 10 | loading: true, 11 | coordinate: false, 12 | zoomBox: false, 13 | bookmarks: false, 14 | draw: false 15 | }; 16 | } 17 | 18 | function mapDefaults() { 19 | return { 20 | // Default 21 | center: [55.5, 38.0], 22 | zoom: 5, 23 | //layers: layers 24 | minZoom: undefined, 25 | maxZoom: undefined, 26 | maxBounds: undefined, 27 | dragging: true, 28 | touchZoom: true, 29 | scrollWheelZoom: true, 30 | doubleClickZoom: true, 31 | boxZoom: true, 32 | trackResize: true, 33 | closePopupOnClick: true, 34 | zoomControl: false, 35 | attributionControl: false 36 | }; 37 | } 38 | 39 | var imagePath = '/static/img/'; 40 | 41 | var SingleIcon = L.Icon.extend({ 42 | options: { 43 | iconSize: [20, 26], 44 | iconAnchor: [10, 26], 45 | popupAnchor: [-3, -76], 46 | shadowUrl: imagePath+'shadow.svg', 47 | shadowRetinaUrl: imagePath+'shadow.svg', 48 | shadowSize: [18, 6], 49 | shadowAnchor: [0, 9] 50 | } 51 | }); 52 | 53 | var singleCurrentIcon = new SingleIcon({ 54 | iconUrl: imagePath+'single_current.svg', 55 | iconRetinaUrl: imagePath+'single_current.svg' 56 | }); 57 | 58 | var singleIcon = new SingleIcon({ 59 | iconUrl: imagePath+'single_stroke.svg', 60 | iconRetinaUrl: imagePath+'single_stroke.svg' 61 | }); 62 | 63 | 64 | var service = { 65 | singleIcon: singleIcon, 66 | singleCurrentIcon: singleCurrentIcon, 67 | mapDefaults: mapDefaults(), 68 | controlDefaults: controlDefaults(), 69 | 70 | EntryOverlaysHtml: " "+NAMES.entry.short_name 72 | }; 73 | return service; 74 | }]); -------------------------------------------------------------------------------- /client/assets/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/assets/css/.gitkeep -------------------------------------------------------------------------------- /client/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/assets/favicon.ico -------------------------------------------------------------------------------- /client/assets/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | Artemiy Doroshkov -- developers -- rendrom@gmail.com 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | Python, Django, Django-Rest-Framework 15 | JS, AngularJS 1.x, Leaflet.js 16 | HTML5, CSS3 17 | GIS 18 | 19 | -------------------------------------------------------------------------------- /client/assets/img/cluster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/assets/img/cluster_current.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/assets/img/cluster_stroke.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/assets/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/assets/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /client/assets/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/assets/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /client/assets/img/shadow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/assets/img/single.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/assets/img/single_current.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/assets/img/single_selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /client/assets/img/single_stroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/assets/img/single_stroke.png -------------------------------------------------------------------------------- /client/assets/img/single_stroke.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/assets/js/main.js: -------------------------------------------------------------------------------- 1 | var maxCommentLenght = 120; 2 | var beginCommentsLimit = 1; 3 | 4 | 5 | $('#sidebar').on('click',function(){ 6 | $('.dropdown-toggle').each(function () { 7 | var $parent = getParent($(this)); 8 | var relatedTarget = { relatedTarget: this }; 9 | if (!$parent.hasClass('open')) return; 10 | $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)); 11 | if (e.isDefaultPrevented()) return; 12 | $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) 13 | }) 14 | }); 15 | 16 | function getParent($this) { 17 | var selector = $this.attr('data-target'); 18 | if (!selector) { 19 | selector = $this.attr('href'); 20 | selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7 21 | } 22 | var $parent = selector && $(selector); 23 | return $parent && $parent.length ? $parent : $this.parent() 24 | } 25 | 26 | 27 | $('#featureModal').on('hidden.bs.modal', function () { 28 | // TODO: не зависить от класса 29 | $('.animate-item').removeClass('animate-item') 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /client/assets/js/map.js: -------------------------------------------------------------------------------- 1 | 2 | function createPrintMap(id) { 3 | var basemap; 4 | if (DEBUG && (document.location.hostname == "localhost" || document.location.hostname == "127.0.0.1")) { 5 | basemap = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 6 | maxZoom: 19 7 | }); 8 | } 9 | else { 10 | basemap = L.tileLayer(tileServURL + '/tiles/'+ entryBase.layer + '/' +entryBase.epsg +'/{z}/{x}/{y}.png', { 11 | maxZoom: 18, 12 | attribution: entryBase.attribution 13 | }); 14 | } 15 | 16 | var layer = findInMapLayer(id, entryLayer); 17 | var layerLatLng = [layer.getLatLng().lat, layer.getLatLng().lng]; 18 | printmap = L.map('printmap', { 19 | zoomControl:false, 20 | attributionControl: false, 21 | keyboard: false, 22 | dragging: false, 23 | touchZoom: false, 24 | scrollWheelZoom: false, 25 | doubleClickZoom: false, 26 | layers:[basemap] 27 | }).setView(layerLatLng, 16); 28 | var circleMarker = new L.CircleMarker(layerLatLng, {fillColor: 'blue', fillOpacity: 0.5, stroke: false}); 29 | circleMarker.addTo(printmap); 30 | printmap.setActiveArea({ 31 | position: "absolute", 32 | top: "0px", 33 | left: "0px", 34 | right: "0px", 35 | height: $('#printmap').css("height") 36 | }); 37 | printmap.invalidateSize(); 38 | basemap.on('load', function (e) { 39 | setTimeout(function() { 40 | var modalWindow = document.getElementById('printModal'); 41 | var data = modalWindow.className; 42 | modalWindow.className += " html2canvasreset"; 43 | html2canvas([document.getElementById('export-frame')], { 44 | logging: false, 45 | profile: false, 46 | useCORS: true, 47 | background: '#FFFFFF', 48 | onrendered: function (canvas) { 49 | // print.href = img.replace(/^data[:]image\/(png|jpg|jpeg)[;]/i, "data:application/octet-stream;"); 50 | var btnContainer = document.getElementById('saveimg'); 51 | btnContainer.innerHTML = ''; 52 | var img = canvas.toDataURL(); 53 | var link = document.createElement("a"); 54 | link.download = "export_entry_" + id + ".png"; 55 | link.href = img.replace(/^data[:]image\/(png|jpg|jpeg)[;]/i, "data:application/octet-stream;"); 56 | link.className = 'btn btn-default pull-right'; 57 | link.innerHTML = "Сохранить изображение"; 58 | btnContainer.appendChild(link); 59 | modalWindow.className = data; 60 | } 61 | }); 62 | }, 1000); 63 | }); 64 | } 65 | 66 | // 67 | //if (DEBUG) { 68 | //// var zoomtext = document.getElementById("zoom"); 69 | // var zoomtext = document.createElement("div"); 70 | // zoomtext.id = 'zoom'; 71 | // zoomtext.innerHTML = map.getZoom(); 72 | // document.body.appendChild(zoomtext) 73 | // 74 | // map.on('zoomend', function (e) { 75 | // zoomtext.innerHTML = map.getZoom(); 76 | // }) 77 | //} 78 | -------------------------------------------------------------------------------- /client/assets/js/plugins.js: -------------------------------------------------------------------------------- 1 | // Avoid `console` errors in browsers that lack a console. 2 | (function() { 3 | var method; 4 | var noop = function () {}; 5 | var methods = [ 6 | 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 7 | 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 8 | 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 9 | 'timeStamp', 'trace', 'warn' 10 | ]; 11 | var length = methods.length; 12 | var console = (window.console = window.console || {}); 13 | 14 | while (length--) { 15 | method = methods[length]; 16 | 17 | // Only stub undefined methods. 18 | if (!console[method]) { 19 | console[method] = noop; 20 | } 21 | } 22 | }()); 23 | 24 | 25 | //function getCookie(name) { 26 | // var cookieValue = null; 27 | // if (document.cookie && document.cookie != '') { 28 | // var cookies = document.cookie.split(';'); 29 | // for (var i = 0; i < cookies.length; i++) { 30 | // var cookie = jQuery.trim(cookies[i]); 31 | // // Does this cookie string begin with the name we want? 32 | // if (cookie.substring(0, name.length + 1) == (name + '=')) { 33 | // cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 34 | // break; 35 | // } 36 | // } 37 | // } 38 | // return cookieValue; 39 | //} 40 | // 41 | //function csrfSafeMethod(method) { 42 | // // these HTTP methods do not require CSRF protection 43 | // return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 44 | //} 45 | // 46 | //$.ajaxSetup({ 47 | // beforeSend: function(xhr, settings) { 48 | // if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 49 | // xhr.setRequestHeader("X-CSRFToken", csrftoken); 50 | // } 51 | // } 52 | //}); 53 | // 54 | -------------------------------------------------------------------------------- /client/assets/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | Disallow: / -------------------------------------------------------------------------------- /client/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/favicon.ico -------------------------------------------------------------------------------- /client/dist/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /client/dist/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /client/dist/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /client/dist/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /client/dist/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /client/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /client/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /client/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /client/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /client/dist/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | Artemiy Doroshkov -- developers -- rendrom@gmail.com 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | Python, Django, Django-Rest-Framework 15 | JS, AngularJS 1.x, Leaflet.js 16 | HTML5, CSS3 17 | GIS 18 | 19 | -------------------------------------------------------------------------------- /client/dist/img/cluster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/dist/img/cluster_stroke.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/dist/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /client/dist/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /client/dist/img/shadow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/dist/img/single.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/dist/img/single_current.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/dist/img/single_selected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /client/dist/img/single_stroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/client/dist/img/single_stroke.png -------------------------------------------------------------------------------- /client/dist/img/single_stroke.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /client/dist/js/main.js: -------------------------------------------------------------------------------- 1 | var maxCommentLenght = 120; 2 | var beginCommentsLimit = 1; 3 | 4 | 5 | $('#sidebar').on('click',function(){ 6 | $('.dropdown-toggle').each(function () { 7 | var $parent = getParent($(this)); 8 | var relatedTarget = { relatedTarget: this }; 9 | if (!$parent.hasClass('open')) return; 10 | $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)); 11 | if (e.isDefaultPrevented()) return; 12 | $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) 13 | }) 14 | }); 15 | 16 | function getParent($this) { 17 | var selector = $this.attr('data-target'); 18 | if (!selector) { 19 | selector = $this.attr('href'); 20 | selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7 21 | } 22 | var $parent = selector && $(selector); 23 | return $parent && $parent.length ? $parent : $this.parent() 24 | } 25 | 26 | 27 | $('#featureModal').on('hidden.bs.modal', function () { 28 | // TODO: не зависить от класса 29 | $('.animate-item').removeClass('animate-item') 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /client/dist/js/map.js: -------------------------------------------------------------------------------- 1 | 2 | function createPrintMap(id) { 3 | var basemap; 4 | if (DEBUG && (document.location.hostname == "localhost" || document.location.hostname == "127.0.0.1")) { 5 | basemap = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 6 | maxZoom: 19 7 | }); 8 | } 9 | else { 10 | basemap = L.tileLayer(tileServURL + '/tiles/'+ entryBase.layer + '/' +entryBase.epsg +'/{z}/{x}/{y}.png', { 11 | maxZoom: 18, 12 | attribution: entryBase.attribution 13 | }); 14 | } 15 | 16 | var layer = findInMapLayer(id, entryLayer); 17 | var layerLatLng = [layer.getLatLng().lat, layer.getLatLng().lng]; 18 | printmap = L.map('printmap', { 19 | zoomControl:false, 20 | attributionControl: false, 21 | keyboard: false, 22 | dragging: false, 23 | touchZoom: false, 24 | scrollWheelZoom: false, 25 | doubleClickZoom: false, 26 | layers:[basemap] 27 | }).setView(layerLatLng, 16); 28 | var circleMarker = new L.CircleMarker(layerLatLng, {fillColor: 'blue', fillOpacity: 0.5, stroke: false}); 29 | circleMarker.addTo(printmap); 30 | printmap.setActiveArea({ 31 | position: "absolute", 32 | top: "0px", 33 | left: "0px", 34 | right: "0px", 35 | height: $('#printmap').css("height") 36 | }); 37 | printmap.invalidateSize(); 38 | basemap.on('load', function (e) { 39 | setTimeout(function() { 40 | var modalWindow = document.getElementById('printModal'); 41 | var data = modalWindow.className; 42 | modalWindow.className += " html2canvasreset"; 43 | html2canvas([document.getElementById('export-frame')], { 44 | logging: false, 45 | profile: false, 46 | useCORS: true, 47 | background: '#FFFFFF', 48 | onrendered: function (canvas) { 49 | // print.href = img.replace(/^data[:]image\/(png|jpg|jpeg)[;]/i, "data:application/octet-stream;"); 50 | var btnContainer = document.getElementById('saveimg'); 51 | btnContainer.innerHTML = ''; 52 | var img = canvas.toDataURL(); 53 | var link = document.createElement("a"); 54 | link.download = "export_entry_" + id + ".png"; 55 | link.href = img.replace(/^data[:]image\/(png|jpg|jpeg)[;]/i, "data:application/octet-stream;"); 56 | link.className = 'btn btn-default pull-right'; 57 | link.innerHTML = "Сохранить изображение"; 58 | btnContainer.appendChild(link); 59 | modalWindow.className = data; 60 | } 61 | }); 62 | }, 1000); 63 | }); 64 | } 65 | 66 | // 67 | //if (DEBUG) { 68 | //// var zoomtext = document.getElementById("zoom"); 69 | // var zoomtext = document.createElement("div"); 70 | // zoomtext.id = 'zoom'; 71 | // zoomtext.innerHTML = map.getZoom(); 72 | // document.body.appendChild(zoomtext) 73 | // 74 | // map.on('zoomend', function (e) { 75 | // zoomtext.innerHTML = map.getZoom(); 76 | // }) 77 | //} 78 | -------------------------------------------------------------------------------- /client/dist/js/plugins.js: -------------------------------------------------------------------------------- 1 | // Avoid `console` errors in browsers that lack a console. 2 | (function() { 3 | var method; 4 | var noop = function () {}; 5 | var methods = [ 6 | 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 7 | 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 8 | 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 9 | 'timeStamp', 'trace', 'warn' 10 | ]; 11 | var length = methods.length; 12 | var console = (window.console = window.console || {}); 13 | 14 | while (length--) { 15 | method = methods[length]; 16 | 17 | // Only stub undefined methods. 18 | if (!console[method]) { 19 | console[method] = noop; 20 | } 21 | } 22 | }()); 23 | 24 | 25 | //function getCookie(name) { 26 | // var cookieValue = null; 27 | // if (document.cookie && document.cookie != '') { 28 | // var cookies = document.cookie.split(';'); 29 | // for (var i = 0; i < cookies.length; i++) { 30 | // var cookie = jQuery.trim(cookies[i]); 31 | // // Does this cookie string begin with the name we want? 32 | // if (cookie.substring(0, name.length + 1) == (name + '=')) { 33 | // cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 34 | // break; 35 | // } 36 | // } 37 | // } 38 | // return cookieValue; 39 | //} 40 | // 41 | //function csrfSafeMethod(method) { 42 | // // these HTTP methods do not require CSRF protection 43 | // return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 44 | //} 45 | // 46 | //$.ajaxSetup({ 47 | // beforeSend: function(xhr, settings) { 48 | // if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 49 | // xhr.setRequestHeader("X-CSRFToken", csrftoken); 50 | // } 51 | // } 52 | //}); 53 | // 54 | -------------------------------------------------------------------------------- /client/dist/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | Disallow: / -------------------------------------------------------------------------------- /client/less/app.less: -------------------------------------------------------------------------------- 1 | @import "variables.less"; 2 | @import "../node_modules/bootstrap/less/mixins.less"; 3 | @import "./app/base.less"; 4 | @import "./app/dynamit.less"; 5 | @import "./app/entry-detail.less"; 6 | @import "./app/entry-list.less"; 7 | @import "./app/helpers.less"; 8 | @import "./app/leaflet_customizing.less"; 9 | @import "./app/print_modal.less"; 10 | @import "./app/animate.less"; 11 | @import "./app/comments.less"; 12 | @import "./app/gallery.less"; 13 | @import "./app/bootstrap_customizing.less"; -------------------------------------------------------------------------------- /client/less/app/animate.less: -------------------------------------------------------------------------------- 1 | .animate-item { 2 | &.ng-enter { 3 | animation: fadeIn .5s; 4 | } 5 | &.ng-move { 6 | animation: fadeOut .5s; 7 | } 8 | } 9 | .menu-item { 10 | &.ng-hide-remove { 11 | animation: tada 1s; 12 | } 13 | } 14 | .entryid-animate{ 15 | &.ng-hide-remove { 16 | animation: fadeInRight .5s; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/less/app/base.less: -------------------------------------------------------------------------------- 1 | html, body, #container { 2 | height: 100%; 3 | width: 100%; 4 | overflow: hidden; 5 | } 6 | 7 | body { 8 | padding-top: @navbar-height+1; 9 | } 10 | 11 | input[type="radio"], input[type="checkbox"] { 12 | margin: 0; 13 | } 14 | 15 | a { 16 | cursor: pointer; 17 | } 18 | 19 | .as-cursor { 20 | cursor: pointer; 21 | } 22 | 23 | #map, #map-container { 24 | width: auto; 25 | height: 100%; 26 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); 27 | } 28 | 29 | #zoom { 30 | position: absolute; 31 | z-index: 1000; 32 | bottom: 20px; 33 | right: 50px; 34 | tab-size: 14pt; 35 | font-size: 14pt; 36 | } 37 | 38 | #loading { 39 | position: absolute; 40 | width: 220px; 41 | height: 19px; 42 | top: 50%; 43 | left: 50%; 44 | margin: -10px 0 0 -110px; 45 | z-index: 20001; 46 | } 47 | 48 | #features, #feature { 49 | margin: 0px; 50 | border: none; 51 | border-radius: 0px; 52 | -webkit-box-shadow: none; 53 | box-shadow: none; 54 | } 55 | 56 | #feature .panel-body { 57 | padding: 10px; 58 | } 59 | 60 | #aboutTabsContent { 61 | padding-top: 10px; 62 | } 63 | 64 | .loading-details { 65 | position: absolute; 66 | margin: -50px 0 0 -50px; 67 | left: 50%; 68 | top: 50%; 69 | color: #263926; 70 | } 71 | 72 | .progress-bar-full { 73 | width: 100%; 74 | } 75 | 76 | .create-entry-cell { 77 | text-align: center; 78 | cursor: pointer; 79 | background-color: @create-cell-color; 80 | &:hover { 81 | background-color: darken(@create-cell-color, 10%); 82 | } 83 | &.dragover { 84 | background-color: darken(@create-cell-color, 20%); 85 | } 86 | &.dragover-err { 87 | background-color: rgb(221, 116, 138); 88 | } 89 | .create-ico { 90 | color: darken(@create-cell-color, 50%);; 91 | } 92 | } 93 | 94 | #sidebar { 95 | .panel-heading { 96 | // height: 43px; 97 | height: @sidebar-panel-height; 98 | } 99 | 100 | .sidebar-body { 101 | position: absolute; 102 | width: 100%; 103 | top: (@sidebar-panel-height - 1); 104 | bottom: 0px; 105 | left: 0px; 106 | right: 0px; 107 | overflow-y: auto; 108 | overflow-x: hidden; 109 | } 110 | .sidebar-body-detail { 111 | bottom: 55px; 112 | } 113 | } 114 | 115 | .entry-hiden-ico { 116 | display: none; 117 | } 118 | 119 | .entry-row:hover .entry-hiden-ico, .entry-row.hover .entry-hiden-ico { 120 | display: inline; 121 | } 122 | 123 | .panel-footer { 124 | position: absolute; 125 | width: 100%; 126 | bottom: 0; 127 | } 128 | 129 | .table { 130 | margin-bottom: 0px; 131 | } 132 | 133 | .navbar { 134 | .navbar-brand { 135 | font-weight: bold; 136 | font-size: 25px; 137 | color: #FFFFFF; 138 | } 139 | } 140 | 141 | .navbar-collapse { 142 | &.in { 143 | overflow-y: hidden; 144 | } 145 | } 146 | 147 | .navbar-header { 148 | .navbar-icon-container { 149 | margin-right: 15px; 150 | } 151 | .navbar-icon { 152 | line-height: 50px; 153 | height: 50px; 154 | } 155 | a { 156 | &.navbar-icon { 157 | margin-left: 25px; 158 | } 159 | } 160 | } 161 | 162 | .has-feedback { 163 | .form-control-feedback { 164 | position: absolute; 165 | top: 0; 166 | right: 0; 167 | display: block; 168 | width: 34px; 169 | height: 34px; 170 | line-height: 34px; 171 | text-align: center; 172 | } 173 | } 174 | 175 | @media (max-width: 991px) and (min-width: 768px) { 176 | .leaflet-sidebar { 177 | width: 350px; 178 | } 179 | } 180 | 181 | @media (max-width: 767px) { 182 | .leaflet-sidebar { 183 | width: 80%; 184 | max-width: 400px; 185 | } 186 | } 187 | 188 | @media print { 189 | .navbar { 190 | display: none !important; 191 | } 192 | 193 | .leaflet-control-container { 194 | display: none !important; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /client/less/app/bootstrap_customizing.less: -------------------------------------------------------------------------------- 1 | @media (max-width: 767px) { 2 | .ico-dropdown .dropdown-menu a i { 3 | color: inherit; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/less/app/comments.less: -------------------------------------------------------------------------------- 1 | a:hover, a:active, a:focus { 2 | outline: 0; 3 | } 4 | .comment-modal { 5 | display: block; 6 | position: relative; 7 | clear: both; 8 | .modal-dialog { 9 | min-height: 350px; 10 | height: 80%; 11 | } 12 | .comment-list { 13 | list-style: none; 14 | padding: 0; 15 | .comment-block { 16 | .form-control { 17 | border: 1px; 18 | border-radius: 0; 19 | } 20 | } 21 | li { 22 | margin-top: 10px; 23 | margin-bottom: 10px; 24 | } 25 | img { 26 | border-radius: 50%; 27 | width: 42px; 28 | height: 42px; 29 | margin-right: 10px; 30 | margin-top: 20px; 31 | } 32 | p { 33 | margin: 0; 34 | } 35 | span { 36 | font-size: .8em; 37 | color: #aaa; 38 | } 39 | } 40 | } 41 | .comment-modal .modal-content, .comment-modal .modal-body, .comment-modal .row { 42 | height: 100%; 43 | } 44 | .modal-content { 45 | border-radius: 0; 46 | } 47 | .show-more { 48 | -webkit-user-select: none; 49 | color: #999; 50 | display: inline-block; 51 | font-size: 12px; 52 | margin-top: 2px; 53 | vertical-align: middle; 54 | cursor: pointer; 55 | } 56 | .more-comments { 57 | color: #262626; 58 | outline: 0; 59 | } 60 | .more-arrow { 61 | display: inline-block; 62 | margin-top: 0; 63 | vertical-align: top; 64 | } 65 | 66 | .delete-comment-btn { 67 | color: @trash-color; 68 | } -------------------------------------------------------------------------------- /client/less/app/dynamit.less: -------------------------------------------------------------------------------- 1 | #dynamit-editor { 2 | .multi-cbx { 3 | line-height: 100% !important; 4 | } 5 | .tab-pane { 6 | margin: 10px 0 0 0; 7 | } 8 | 9 | .add-field-block { 10 | width: 70%; 11 | margin: 15px auto 0 auto; 12 | height: 30px; 13 | line-height: 30px; 14 | text-align: center; 15 | cursor: pointer; 16 | 17 | &:hover { 18 | background-color: #d3d3d3; 19 | } 20 | } 21 | 22 | .tr-block { 23 | td { 24 | padding: 5px; 25 | 26 | } 27 | } 28 | 29 | .input-block-level { 30 | padding: 5px; 31 | margin: 5px 0; 32 | width: 100%; 33 | min-height: 28px; 34 | box-sizing: border-box; 35 | border-radius: 0; 36 | } 37 | .ng-invalid { 38 | border-color: @state-danger-text; 39 | .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work 40 | &:focus { 41 | border-color: darken(@state-danger-text, 10%); 42 | @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@state-danger-text, 20%); 43 | .box-shadow(@shadow); 44 | } 45 | } 46 | 47 | .trash-block { 48 | width: 30px; 49 | text-align: center; 50 | min-height: 28px; 51 | box-sizing: border-box; 52 | 53 | a { 54 | color: @trash-color; 55 | } 56 | } 57 | 58 | .drag-block { 59 | width: 20px; 60 | text-align: center; 61 | min-height: 28px; 62 | box-sizing: border-box; 63 | } 64 | 65 | .is-public { 66 | cursor: pointer; 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /client/less/app/entry-detail.less: -------------------------------------------------------------------------------- 1 | .adapted-text { 2 | position: absolute; 3 | width: 75%; 4 | white-space: nowrap; 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | } 8 | .attr-row-title { 9 | width: 30%; 10 | } 11 | .attr-input { 12 | width: 100%; 13 | height: 26px; 14 | font-size: 16px; 15 | padding: 0 5px; 16 | } 17 | .entry-form { 18 | li { 19 | & > a { 20 | padding: 5px; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /client/less/app/entry-list.less: -------------------------------------------------------------------------------- 1 | .list-container { 2 | height: 100%; 3 | overflow: hidden; 4 | } 5 | 6 | .text-part { 7 | float: none; 8 | width: auto; 9 | overflow: hidden; 10 | word-wrap:break-word; 11 | padding: 0 20px; 12 | } 13 | 14 | .ico-part { 15 | width: 77px; 16 | float: right; 17 | } 18 | 19 | .list-ico { 20 | display: inline-block; 21 | padding-left: 10px; 22 | } 23 | 24 | .ico-in-list { 25 | font-size: 14px; 26 | line-height: 14px; 27 | /*width: 14px;*/ 28 | /*height: 14px;*/ 29 | text-align: center; 30 | vertical-align: middle; 31 | } 32 | 33 | .previous-entry { 34 | opacity: 0.5; 35 | } -------------------------------------------------------------------------------- /client/less/app/gallery.less: -------------------------------------------------------------------------------- 1 | .gallery-widget { 2 | display: block; 3 | position: relative; 4 | clear: both; 5 | 6 | img { 7 | max-width: 100% !important; 8 | } 9 | 10 | &.dragover { 11 | background: #d3d3d3; 12 | } 13 | 14 | .delete-image-btn { 15 | color: @trash-color; 16 | padding: 5px; 17 | } 18 | 19 | .app-photos { 20 | width: 90%; 21 | margin: 5px auto; 22 | height: 30px; 23 | line-height: 30px; 24 | text-align: center; 25 | cursor: pointer; 26 | 27 | &:hover { 28 | background-color: #d3d3d3; 29 | } 30 | } 31 | 32 | .bgimage { 33 | text-align: right; 34 | color: white; 35 | background-size: cover; 36 | height: 100%; 37 | background-position: center center; 38 | } 39 | 40 | ul[rn-carousel] { 41 | width: 100%; 42 | height: 200px; 43 | margin: 0 auto; 44 | } 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /client/less/app/helpers.less: -------------------------------------------------------------------------------- 1 | .no-padding { 2 | padding: 0; 3 | } 4 | .inherit-color { 5 | color: inherit; 6 | } 7 | .link-color { 8 | color: #0078a8; 9 | } 10 | .white { 11 | color: #FFFFFF; 12 | } 13 | .action-ico { 14 | cursor: pointer; 15 | } 16 | .changer-center { 17 | line-height: 28px; 18 | } 19 | .top-of-list { 20 | padding-bottom: 0; 21 | } 22 | -------------------------------------------------------------------------------- /client/less/app/leaflet_customizing.less: -------------------------------------------------------------------------------- 1 | .leaflet-control-layers { 2 | border-radius: 0; 3 | label { 4 | font-weight: normal; 5 | margin-bottom: 0px; 6 | } 7 | } 8 | .leaflet-bar { 9 | border-radius: 0; 10 | a { 11 | &:first-child { 12 | border-top-left-radius: 0; 13 | border-top-right-radius: 0; 14 | } 15 | &:last-child { 16 | border-bottom-left-radius: 0; 17 | border-bottom-right-radius: 0; 18 | } 19 | } 20 | } 21 | .leaflet-sidebar { 22 | padding: 0px; 23 | z-index: 1020; 24 | & > .leaflet-control { 25 | padding: 0px; 26 | border-radius: 0px; 27 | } 28 | .close { 29 | display: none; 30 | } 31 | } 32 | .leaflet-control-layers-list input[type="radio"], input[type="checkbox"] { 33 | margin: 2px; 34 | } 35 | .mycluster { 36 | background: url("../img/cluster_stroke.svg") repeat scroll 0 0 rgba(0, 0, 0, 0); 37 | font-size: 8px; 38 | text-align: center; 39 | line-height: 28px; 40 | } 41 | .mycluster-shadow { 42 | left: 5px; 43 | z-index: 1; 44 | } 45 | -------------------------------------------------------------------------------- /client/less/app/print_modal.less: -------------------------------------------------------------------------------- 1 | #export-frame { 2 | background-color: white; 3 | overflow-y: scroll; 4 | } 5 | .html2canvasreset { 6 | position: absolute; 7 | overflow: visible !important; 8 | } 9 | #printmap { 10 | width: 100%; 11 | height: 300px; 12 | } 13 | .print-ico { 14 | background: url("../img/single_stroke.png") repeat scroll 0 0 rgba(0, 0, 0, 0); 15 | position: relative; 16 | font-size: 8px; 17 | text-align: center; 18 | line-height: 28px; 19 | z-index: 1; 20 | } 21 | #myHideFrame { 22 | position: absolute; 23 | top: -9999px; 24 | width: 1px; 25 | height: 1px; 26 | } 27 | -------------------------------------------------------------------------------- /client/less/styles.less: -------------------------------------------------------------------------------- 1 | @import "variables.less"; 2 | 3 | //@import "../node_modules/bootswatch/yeti/bootswatch.less"; 4 | 5 | @import "../node_modules/bootstrap/less/mixins.less"; 6 | @import "../node_modules/bootstrap/less/normalize.less"; 7 | @import "../node_modules/bootstrap/less/print.less"; 8 | @import "../node_modules/bootstrap/less/glyphicons.less"; 9 | @import "../node_modules/bootstrap/less/scaffolding.less"; 10 | @import "../node_modules/bootstrap/less/type.less"; 11 | @import "../node_modules/bootstrap/less/code.less"; 12 | @import "../node_modules/bootstrap/less/grid.less"; 13 | @import "../node_modules/bootstrap/less/tables.less"; 14 | @import "../node_modules/bootstrap/less/forms.less"; 15 | @import "../node_modules/bootstrap/less/buttons.less"; 16 | @import "../node_modules/bootstrap/less/component-animations.less"; 17 | @import "../node_modules/bootstrap/less/dropdowns.less"; 18 | @import "../node_modules/bootstrap/less/button-groups.less"; 19 | @import "../node_modules/bootstrap/less/input-groups.less"; 20 | @import "../node_modules/bootstrap/less/navs.less"; 21 | @import "../node_modules/bootstrap/less/navbar.less"; 22 | //@import "../node_modules/bootstrap/less/breadcrumbs.less"; 23 | //@import "../node_modules/bootstrap/less/pagination.less"; 24 | @import "../node_modules/bootstrap/less/pager.less"; 25 | @import "../node_modules/bootstrap/less/labels.less"; 26 | @import "../node_modules/bootstrap/less/badges.less"; 27 | //@import "../node_modules/bootstrap/less/jumbotron.less"; 28 | //@import "../node_modules/bootstrap/less/thumbnails.less"; 29 | @import "../node_modules/bootstrap/less/alerts.less"; 30 | @import "../node_modules/bootstrap/less/progress-bars.less"; 31 | @import "../node_modules/bootstrap/less/media.less"; 32 | @import "../node_modules/bootstrap/less/list-group.less"; 33 | @import "../node_modules/bootstrap/less/panels.less"; 34 | @import "../node_modules/bootstrap/less/responsive-embed.less"; 35 | @import "../node_modules/bootstrap/less/wells.less"; 36 | @import "../node_modules/bootstrap/less/close.less"; 37 | @import "../node_modules/bootstrap/less/modals.less"; 38 | @import "../node_modules/bootstrap/less/tooltip.less"; 39 | @import "../node_modules/bootstrap/less/popovers.less"; 40 | //@import "../node_modules/bootstrap/less/carousel.less"; 41 | @import "../node_modules/bootstrap/less/utilities.less"; 42 | @import "../node_modules/bootstrap/less/responsive-utilities.less"; -------------------------------------------------------------------------------- /client/less/variables.less: -------------------------------------------------------------------------------- 1 | @import "../node_modules/bootstrap/less/variables"; 2 | //@import "../vendor/bootswatch/yeti/variables.less"; 3 | 4 | @sidebar-panel-height: 39px; 5 | 6 | @create-cell-color: lighten(@brand-primary, 13.5%); 7 | 8 | @trash-color: darkred; -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geonote", 3 | "version": "0.1.5", 4 | "dependencies": { 5 | "jquery": "3.1.1", 6 | "angular": "~1.5.x", 7 | "angular-mocks": "~1.5.x", 8 | "angular-route": "~1.5.x", 9 | "angular-resource": "~1.5.x", 10 | "angular-animate": "~1.5.x", 11 | "angular-touch": "1.5.x", 12 | "leaflet.markercluster": "0.5.0", 13 | "leaflet.locatecontrol": "0.55.0", 14 | "leaflet-sidebar": "0.1.9", 15 | "modernizr": "3.3.1", 16 | "font-awesome": "4.6.3", 17 | "animate.css": "3.5.2", 18 | "html2canvas": "0.5.0-beta4", 19 | "angular-bootstrap": "~0.12.0", 20 | "bootswatch": "~3.3.2", 21 | "angular-ui-notification": "0.2.0", 22 | "ng-file-upload": "~3.1.2", 23 | "angular-carousel": "~1.0.1", 24 | "angular-cookies": "~1.5.x", 25 | "angular-ui-sortable": "~0.15.0", 26 | "jquery-ui-dist": "^1.12.1", 27 | "leaflet": "0.7.7", 28 | "leaflet-active-area": "^0.1.1", 29 | "leaflet-draw": "^0.4.0", 30 | "leaflet-groupedlayercontrol": "^0.5.0" 31 | }, 32 | "devDependencies": { 33 | "grunt": "^1.0.1", 34 | "grunt-autoprefixer": "^3.0.4", 35 | "grunt-contrib-less": "^1.0.0", 36 | "grunt-contrib-clean": "^1.0.0", 37 | "grunt-contrib-concat": "^1.0.1", 38 | "grunt-contrib-copy": "^1.0.0", 39 | "grunt-contrib-jshint": "^1.0.0", 40 | "grunt-contrib-uglify": "^2.0.0", 41 | "grunt-contrib-watch": "^1.0.0", 42 | "grunt-html2js": "^0.3.0", 43 | "grunt-ng-annotate": "^2.0.2", 44 | "less-plugin-autoprefix": "^1.3.0", 45 | "less-plugin-clean-css": "^1.5.0", 46 | "ng-annotate": "^1.2.1" 47 | } 48 | } -------------------------------------------------------------------------------- /comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/comments/__init__.py -------------------------------------------------------------------------------- /comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /comments/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /comments/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/comments/views.py -------------------------------------------------------------------------------- /dynamit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/dynamit/__init__.py -------------------------------------------------------------------------------- /dynamit/admin.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from django.contrib import admin 3 | from dynamit.models import DynamitModel, DynamitModelField 4 | from dynamit.utils import reregister_in_admin, dynamo_exist 5 | 6 | from prj.settings import DYNAMIT_IN_ADMIN 7 | 8 | 9 | class ModelFieldInline(admin.TabularInline): 10 | model = DynamitModelField 11 | extra = 10 12 | 13 | 14 | class DynamitModelAdmin(admin.ModelAdmin): 15 | fields = ('name',) 16 | search_fields = ('name',) 17 | list_display = ('name',) 18 | # list_filter = ('app',) 19 | inlines = [ModelFieldInline] 20 | 21 | 22 | admin.site.register(DynamitModel, DynamitModelAdmin) 23 | 24 | 25 | class DynamitModelFieldAdmin(admin.ModelAdmin): 26 | search_fields = ('name', 'slug', 'field_type') 27 | ordering = ('name',) 28 | list_display = ('name', 'slug', 'model') 29 | list_filter = ('model',) 30 | #admin.site.register(DynamicModelField, DynamicModelFieldAdmin) 31 | 32 | 33 | if dynamo_exist(): 34 | if DYNAMIT_IN_ADMIN: 35 | for model in DynamitModel.objects.all(): 36 | reregister_in_admin(model.as_model(), admin.ModelAdmin) 37 | -------------------------------------------------------------------------------- /dynamit/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/dynamit/api/__init__.py -------------------------------------------------------------------------------- /dynamit/api/metadata.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import unicode_literals 3 | from django.utils.encoding import force_text 4 | from rest_framework import serializers 5 | 6 | from rest_framework.metadata import BaseMetadata 7 | from rest_framework.serializers import ListSerializer, OrderedDict 8 | from rest_framework.utils.field_mapping import ClassLookupDict 9 | 10 | 11 | class DynamitMetadata(BaseMetadata): 12 | label_lookup = ClassLookupDict({ 13 | serializers.Field: 'field', 14 | serializers.BooleanField: 'boolean', 15 | serializers.CharField: 'string', 16 | serializers.URLField: 'url', 17 | serializers.EmailField: 'email', 18 | serializers.RegexField: 'regex', 19 | serializers.SlugField: 'slug', 20 | serializers.IntegerField: 'integer', 21 | serializers.FloatField: 'float', 22 | serializers.DecimalField: 'decimal', 23 | serializers.DateField: 'date', 24 | serializers.DateTimeField: 'datetime', 25 | serializers.TimeField: 'time', 26 | serializers.ChoiceField: 'choice', 27 | serializers.MultipleChoiceField: 'multiple choice', 28 | serializers.FileField: 'file upload', 29 | serializers.ImageField: 'image upload', 30 | }) 31 | 32 | def determine_metadata(self, request, view): 33 | metadata = OrderedDict() 34 | serializer = view.get_serializer() 35 | metadata['actions'] = self.get_serializer_info(serializer) 36 | return metadata 37 | 38 | def get_serializer_info(self, serializer): 39 | if hasattr(serializer, 'child'): 40 | serializer = serializer.child 41 | 42 | field_array = {} 43 | for field_name, field in serializer.fields.items(): 44 | if not isinstance(field, ListSerializer): 45 | field_info = self.get_field_info(field) 46 | else: 47 | field_info = self.get_serializer_info(field) 48 | field_array[field_name] = field_info 49 | return field_array 50 | 51 | def get_field_info(self, field): 52 | field_info = OrderedDict() 53 | field_info['type'] = self.label_lookup[field] 54 | field_info['required'] = getattr(field, 'required', False) 55 | 56 | for attr in ['read_only', 'label', 'help_text', 'min_length', 'max_length']: 57 | value = getattr(field, attr, None) 58 | if value is None and attr == 'max_length': 59 | try: 60 | value = field._kwargs[attr] 61 | except: 62 | value = None 63 | if value is not None and value != '': 64 | field_info[attr] = force_text(value, strings_only=True) 65 | 66 | if hasattr(field, 'choices'): 67 | field_info['choices'] = [ 68 | { 69 | 'value': choice_value, 70 | 'display_name': force_text(choice_name, strings_only=True) 71 | } 72 | for choice_value, choice_name in field.choices.items() 73 | ] 74 | 75 | return field_info 76 | -------------------------------------------------------------------------------- /dynamit/api/serializers.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | from dynamit.models import DynamitModel, DynamitModelField 3 | from rest_framework import serializers 4 | 5 | 6 | class DynamitModelFieldSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | fields = ('id', 'verbose_name', 'name', 'field_type', 'null', 'blank', 'unique', 'default', 'help_text', 'order') 9 | read_only_fields = ('model',) 10 | model = DynamitModelField 11 | 12 | 13 | class DynamitModelSerializer(serializers.ModelSerializer): 14 | fields = DynamitModelFieldSerializer(many=True, read_only=False) 15 | 16 | class Meta: 17 | fields = ('id', 'name', 'slug', 'description', 'is_public', 'can_comment', 'photo_gallery', 'fields', 'entryname') 18 | model = DynamitModel 19 | read_only_fields = ('user', 'create_date', 'change_date', 'app') 20 | 21 | def to_representation(self, instance): 22 | ret = super(self.__class__, self).to_representation(instance) 23 | user = instance.user 24 | ret['user'] = user.username 25 | return ret 26 | 27 | def update(self, instance, validated_data): 28 | valid_data = validated_data.pop('fields') 29 | self_data = self.data.get('fields', '') 30 | self_initial = self.initial_data.get('fields', '') 31 | v_ids = [] 32 | if valid_data: 33 | for i, f in itertools.izip(self_initial, valid_data): 34 | v_id = i.get('id') 35 | try: 36 | field = DynamitModelField.objects.get(id=i.get('id')) 37 | for ff in f: 38 | setattr(field, ff, f[ff]) 39 | field.save() 40 | except DynamitModelField.DoesNotExist: 41 | DynamitModelField.objects.get_or_create(model=instance, **f) 42 | v_ids.append(v_id) 43 | 44 | for d in self_data: 45 | d_id = d.get('id') 46 | if d_id not in v_ids: 47 | DynamitModelField.objects.get(id=d_id).delete() 48 | 49 | for i_v in validated_data: 50 | setattr(instance, i_v, validated_data.get(i_v, '')) 51 | instance.save() 52 | return instance 53 | 54 | def create(self, validated_data): 55 | fields_data = validated_data.pop('fields') 56 | dynamit = DynamitModel.objects.create(**validated_data) 57 | if fields_data: 58 | # fields = [] 59 | for f in fields_data: 60 | DynamitModelField.objects.get_or_create(model=dynamit, **f) 61 | # TODO: bulk create with db alter 62 | # field = DynamicModelField(model=dynamit, **f) 63 | # fields.append(field) 64 | # DynamicModelField.objects.bulk_create(fields) 65 | return dynamit 66 | 67 | 68 | -------------------------------------------------------------------------------- /dynamit/api/views.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from django.contrib.auth.models import User 3 | from django.db.models import Q 4 | from dynamit.api.metadata import DynamitMetadata 5 | from dynamit.api.serializers import DynamitModelSerializer, DynamitModelFieldSerializer 6 | from dynamit.models import DynamitModel, DynamitModelField 7 | from entry.api import permissions 8 | from rest_framework import viewsets 9 | from rest_framework.permissions import AllowAny, IsAuthenticated 10 | 11 | 12 | class DynamitModelViewSet(viewsets.ModelViewSet): 13 | queryset = DynamitModel.objects.all().order_by('-create_date') 14 | serializer_class = DynamitModelSerializer 15 | metadata_class = DynamitMetadata 16 | 17 | def get_queryset(self): 18 | user = self.request.user 19 | if isinstance(self.request.user, User): 20 | q = DynamitModel.objects.filter(Q(is_public=True) | Q(user=user)).order_by('-change_date') 21 | else: 22 | q = DynamitModel.objects.filter(is_public=True).order_by('-change_date') 23 | return q 24 | 25 | def perform_create(self, serializer): 26 | if isinstance(self.request.user, User): 27 | serializer.save(user=self.request.user) 28 | 29 | def get_permissions(self): 30 | if self.request.method == 'GET': 31 | return AllowAny(), 32 | else: 33 | return (IsAuthenticated() if self.request.method == 'POST' 34 | else permissions.IsStaffOrTargetUser()), 35 | 36 | 37 | class DynamitModelFieldViewSet(viewsets.ModelViewSet): 38 | queryset = DynamitModelField.objects.all() 39 | serializer_class = DynamitModelFieldSerializer 40 | 41 | def get_permissions(self): 42 | if self.request.method == 'GET': 43 | return AllowAny(), 44 | else: 45 | return (IsAuthenticated() if self.request.method == 'POST' 46 | else permissions.IsStaffOrTargetUser()), 47 | 48 | -------------------------------------------------------------------------------- /dynamit/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/dynamit/migrations/__init__.py -------------------------------------------------------------------------------- /dynamit/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates two different styles of tests (one doctest and one 3 | unittest). These will both pass when you run "manage.py test". 4 | 5 | Replace these with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | class SimpleTest(TestCase): 11 | def test_basic_addition(self): 12 | """ 13 | Tests that 1 + 1 always equals 2. 14 | """ 15 | self.failUnlessEqual(1 + 1, 2) 16 | 17 | __test__ = {"doctest": """ 18 | Another way to test that 1 + 1 is equal to 2. 19 | 20 | >>> 1 + 1 == 2 21 | True 22 | """} 23 | 24 | def mktestmodel(): 25 | from dynamit import models 26 | test, created = models.DynamitModel.objects.get_or_create(name='Test') 27 | foo, created = models.DynamitModelField.objects.get_or_create( 28 | name = 'foo', 29 | model = test, 30 | field_type = 'dynamiccharfield', 31 | null = True, 32 | blank = True, 33 | unique = False, 34 | help_text = 'Test field for Foo', 35 | ) 36 | bar, created = models.DynamitModelField.objects.get_or_create( 37 | name = 'bar', 38 | slug = 'bar_slug', 39 | model = test, 40 | field_type = 'dynamiccharfield', 41 | null = True, 42 | blank = True, 43 | unique = False, 44 | help_text = 'Test field for Bar', 45 | ) 46 | ifield, created = models.DynamitModelField.objects.get_or_create( 47 | name = 'ifield', 48 | slug = 'ifield', 49 | field_type = 'dynamicintegerfield', 50 | help_text = 'hope this helps', 51 | model = test 52 | ) 53 | return test 54 | 55 | model = mktestmodel() 56 | 57 | -------------------------------------------------------------------------------- /dynamit/utils.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.admin.sites import NotRegistered 3 | from django.core.urlresolvers import clear_url_caches 4 | from django.db import connection 5 | from importlib import import_module 6 | from django.contrib import admin 7 | from prj.settings import DYNAMIT_IN_ADMIN 8 | 9 | 10 | def dynamo_exist(): 11 | cursor = connection.cursor() 12 | cursor.execute(""" 13 | SELECT EXISTS( 14 | SELECT 1 15 | FROM pg_catalog.pg_class c 16 | JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace 17 | WHERE n.nspname = 'public' 18 | AND c.relname = 'dynamo_dynamicmodel' 19 | ); 20 | """) 21 | exist = cursor.fetchone()[0] 22 | return exist 23 | 24 | 25 | def get_module_attr(module, attr, fallback=None): 26 | m = import_module(module) 27 | return getattr(m, attr, fallback) 28 | 29 | 30 | def unregister_dynamo(model): 31 | if DYNAMIT_IN_ADMIN: 32 | unregister_from_admin(model) 33 | reload(import_module(settings.ROOT_URLCONF)) 34 | clear_url_caches() 35 | 36 | 37 | def reregister_dynamo(model): 38 | if DYNAMIT_IN_ADMIN: 39 | reregister_in_admin(model) 40 | reload(import_module(settings.ROOT_URLCONF)) 41 | clear_url_caches() 42 | 43 | 44 | def unregister_from_admin(model): 45 | for reg_model in admin.site._registry.keys(): 46 | if model._meta.db_table == reg_model._meta.db_table: 47 | del admin.site._registry[reg_model] 48 | 49 | try: 50 | admin.site.unregister(model) 51 | except NotRegistered: 52 | pass 53 | 54 | 55 | def reregister_in_admin(model, admin_class=None): 56 | if admin_class is None: 57 | admin_class = admin.ModelAdmin 58 | unregister_from_admin(model) 59 | admin.site.register(model, admin_class) 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /dynamit/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/dynamit/views.py -------------------------------------------------------------------------------- /entry/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/entry/__init__.py -------------------------------------------------------------------------------- /entry/admin.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/entry/admin.py -------------------------------------------------------------------------------- /entry/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/entry/api/__init__.py -------------------------------------------------------------------------------- /entry/api/authenticators.py: -------------------------------------------------------------------------------- 1 | from rest_framework.authentication import BasicAuthentication 2 | 3 | 4 | class QuietBasicAuthentication(BasicAuthentication): 5 | def authenticate_header(self, request): 6 | return 'xBasic realm="%s"' % self.www_authenticate_realm -------------------------------------------------------------------------------- /entry/api/filters.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from django.contrib.gis.geos import Point, GEOSGeometry, Polygon 3 | from django.core.cache import cache 4 | from django.db import connection 5 | from django.db.models import Q 6 | from string import Template 7 | from rest_framework.exceptions import ParseError 8 | from rest_framework.filters import BaseFilterBackend 9 | 10 | 11 | class InRegionFilter(BaseFilterBackend): 12 | region_param = 'in_region' # The URL query parameter which contains the region Lat, Lng, Zoomlevel. 13 | 14 | def get_filter_region(self, request): 15 | region_string = request.query_params.get(self.region_param, None) 16 | if not region_string: 17 | return None 18 | poly = get_osm_poly_from_query(region_string) 19 | return poly 20 | 21 | def filter_queryset(self, request, queryset, view): 22 | region_string = request.query_params.get(self.region_param, None) 23 | data = cache.get(region_string) 24 | if data is None: 25 | filter_field = getattr(view, 'bbox_filter_field', None) 26 | # include_overlapping = getattr(view, 'bbox_filter_include_overlapping', False) 27 | 28 | geoDjango_filter = 'intersects' 29 | if not filter_field: 30 | return queryset 31 | region = self.get_filter_region(request) 32 | if not region: 33 | return queryset 34 | 35 | data = queryset.filter(Q(**{'%s__%s' % (filter_field, geoDjango_filter): region})) 36 | data_for_cache = set(data.values_list('id', flat=True)) 37 | try: 38 | # TODO: установить время, настроить memecache, проверка по OSMID 39 | cache.set(region_string, data_for_cache, 60*3) 40 | except Exception as e: 41 | print e.message 42 | return data 43 | else: 44 | to_return = queryset.filter(id__in=data) 45 | return to_return 46 | 47 | 48 | def get_osm_poly_from_query(region_string): 49 | poly = get_region_multipolygon(region_string) 50 | if poly: 51 | geom = GEOSGeometry(poly[0]) 52 | return geom 53 | return None 54 | 55 | 56 | zoom_adm_matching = { 57 | 3: "<='3'", 58 | 4: "<='4'", 59 | 5: "<='4'", 60 | 6: "<='4'", 61 | 7: "<='6'", 62 | 8: "<='6'", 63 | 9: "<='6'", 64 | 10: "<='6'", 65 | 11: "<='8'", 66 | 12: "<='8'", 67 | 13: "<='8'", 68 | 14: "<='11'" 69 | } 70 | 71 | 72 | def get_region_multipolygon(point, geom_column='way', id_field='osm_id', from_srid=900913, to_srid=4326): 73 | cursor = connection.cursor() 74 | try: 75 | y, x, zoom = (float(n) for n in point.split(',')) 76 | zoom = int(zoom) 77 | except ValueError: 78 | raise ParseError("Not valid region string in parameter") 79 | adm_level = "<='4'" 80 | 81 | if zoom in zoom_adm_matching: 82 | adm_level = zoom_adm_matching[zoom] 83 | elif zoom < min(zoom_adm_matching.keys()): 84 | adm_level = zoom_adm_matching[min(zoom_adm_matching.keys())] 85 | elif zoom > max(zoom_adm_matching.keys()): 86 | adm_level = zoom_adm_matching[max(zoom_adm_matching.keys())] 87 | 88 | center = Template("ST_Transform(ST_Setsrid(ST_Makepoint($x,$y), $to_srid), $from_srid)").substitute(locals()) 89 | 90 | sql = Template(""" 91 | WITH lg as 92 | ( 93 | SELECT osm_id, way 94 | FROM planet_osm_polygon AS osm 95 | WHERE cast(admin_level as int) $adm_level 96 | AND ST_Intersects(way, $center) 97 | AND boundary = 'administrative' 98 | ORDER BY admin_level DESC 99 | LIMIT 1 100 | ) 101 | SELECT 102 | ST_Transform(ST_BufFer(ST_Collect(planet_osm_polygon.way),0), 4326) 103 | FROM 104 | planet_osm_polygon, 105 | lg 106 | WHERE planet_osm_polygon.osm_id = lg.osm_id 107 | """) 108 | cursor.execute(sql.substitute(locals())) 109 | return cursor.fetchone() 110 | -------------------------------------------------------------------------------- /entry/api/permissions.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from dynamit.models import DynamitModel 3 | from rest_framework import permissions 4 | 5 | 6 | class IsStaffOrTargetUser(permissions.BasePermission): 7 | def has_object_permission(self, request, view, obj): 8 | return request.user.is_staff or obj.user == request.user 9 | 10 | 11 | class IsStaffOrDynamitOwner(permissions.BasePermission): 12 | 13 | def has_permission(self, request, view): 14 | dynamit = view.kwargs.get('dynamit') 15 | return request.user and (request.user == dynamit.user or request.user.is_staff) 16 | 17 | 18 | class IsOwner(permissions.BasePermission): 19 | def has_object_permission(self, request, view, obj): 20 | return request.user == obj.user 21 | 22 | 23 | class IsPublicOrDynamitUser(permissions.BasePermission): 24 | def has_permission(self, request, view): 25 | dynamit = view.kwargs.get('dynamit') 26 | return (request.user == dynamit.user or request.user.is_staff) or dynamit.is_public 27 | 28 | 29 | class IsAuthenticatedOrPublic(permissions.BasePermission): 30 | 31 | def has_permission(self, request, view): 32 | dynamit = view.kwargs.get('dynamit') 33 | return request.user and request.user.is_authenticated() and ((request.user == dynamit.user or request.user.is_staff) or dynamit.is_public) -------------------------------------------------------------------------------- /entry/api/serializers.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import unicode_literals 3 | from django.contrib.auth.models import User 4 | from easy_thumbnails.files import get_thumbnailer 5 | from rest_framework import serializers 6 | 7 | 8 | class UserSerializer(serializers.ModelSerializer): 9 | class Meta: 10 | model = User 11 | fields = ('id', 'username', 'password', 'first_name', 12 | 'last_name', 'email', 'is_superuser') 13 | read_only_fields = ('id',) 14 | write_only_fields = ('password',) 15 | 16 | def restore_object(self, attrs, instance=None): 17 | user = super(UserSerializer, self).restore_object(attrs, instance) 18 | user.set_password(attrs['password']) 19 | return user 20 | 21 | 22 | class HyperlinkedThumbField(serializers.ImageField): 23 | 24 | def get_attribute(self, obj): 25 | return obj 26 | 27 | def to_internal_value(self, data): 28 | return data 29 | 30 | def to_representation(self, obj): 31 | try: 32 | request = self.context.get('request', '') 33 | if not request and obj.request: 34 | request = obj.request 35 | params = request.data 36 | else: 37 | params = request.query_params 38 | dimensions = params.get('dimensions', '100x100') 39 | try: 40 | size = [int(item) for item in str(dimensions).split('x')[:2]] 41 | if len(size) == 1: 42 | size = [size[0], size[0]] 43 | except: 44 | size = [100, 100] 45 | image = get_thumbnailer(obj.image) 46 | thumbnail_options = {'crop': False, 'size': size} 47 | thumb = image.get_thumbnail(thumbnail_options) 48 | 49 | # image_url = request.build_absolute_uri(thumb.url) 50 | image_url = thumb.url 51 | return image_url 52 | except Exception as e: 53 | return obj.image.path 54 | 55 | 56 | 57 | # class HyperlinkedSorlImageField(serializers.ImageField): 58 | # def __init__(self, dimensions=None, options=None, *args, **kwargs): 59 | # if not options: 60 | # options = {} 61 | # self.dimensions = dimensions 62 | # self.options = options 63 | # super(HyperlinkedSorlImageField, self).__init__(*args, **kwargs) 64 | # 65 | # def to_internal_value(self, value): 66 | # # if (self.dimensions): 67 | # # image = get_thumbnail(value, self.dimensions, **self.options) 68 | # # try: 69 | # # request = self.context.get('request', None) 70 | # # return request.build_absolute_uri(image.url) 71 | # # except Exception as e: 72 | # # return super(HyperlinkedSorlImageField, self).to_native(image.url) 73 | # # else: 74 | # # return super(HyperlinkedSorlImageField, self).to_native(image.url) 75 | # return super(HyperlinkedSorlImageField, self).to_internal_value() 76 | -------------------------------------------------------------------------------- /entry/forms.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/entry/forms.py -------------------------------------------------------------------------------- /entry/models.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import unicode_literals 3 | from django.contrib.gis.geos import Polygon 4 | from django.utils.encoding import python_2_unicode_compatible 5 | from comments.models import Comment 6 | from django.contrib.auth.models import User 7 | from django.contrib.gis.db import models 8 | from django.utils import timezone 9 | 10 | 11 | ENTRY_TYPE = ( 12 | (0, 'case'), 13 | (1, 'point'), 14 | (2, 'line'), 15 | (3, 'polygon'), 16 | ) 17 | 18 | 19 | @python_2_unicode_compatible 20 | class Entry(models.Model): 21 | user = models.ForeignKey(User) 22 | create_date = models.DateTimeField('Дата создания', default=timezone.now) 23 | change_date = models.DateTimeField('Дата обновления', default=timezone.now) 24 | geom = models.GeometryField('На карте', null=True, blank=True) 25 | 26 | class Meta: 27 | abstract = True 28 | verbose_name = 'запись' 29 | verbose_name_plural = 'Записи' 30 | 31 | def __str__(self): 32 | return self.entryid 33 | 34 | def get_geom(self): 35 | entries = self.entry_set.exclude(geom=None) 36 | if entries: 37 | poly = entries.extent() 38 | geom = Polygon.from_bbox(poly) 39 | else: 40 | geom = None 41 | return geom 42 | 43 | def resave_geom(self): 44 | self.geom = self.get_geom() 45 | self.save() 46 | -------------------------------------------------------------------------------- /entry/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/entry/templatetags/__init__.py -------------------------------------------------------------------------------- /entry/templatetags/json_filters.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django import template 4 | from django.utils.safestring import mark_safe 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.filter 10 | def jsonify(o): 11 | return mark_safe(json.dumps(o)) 12 | -------------------------------------------------------------------------------- /entry/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /entry/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.contrib.auth.decorators import login_required 3 | 4 | from entry import views 5 | 6 | urlpatterns = [ 7 | url(r'^delete_all_entry/$', views.delete_all_entry, name='delete_all_entry'), 8 | ] -------------------------------------------------------------------------------- /entry/utils.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/entry/utils.py -------------------------------------------------------------------------------- /entry/views.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from comments.models import Comment 3 | from django.conf import settings 4 | from django.contrib import messages 5 | from django.contrib.auth.decorators import login_required 6 | from django.db import connection 7 | from django.http import HttpResponseRedirect, HttpResponseForbidden, HttpResponse 8 | from django.views.generic import TemplateView 9 | from dynamit.models import DynamitModel 10 | from string import Template 11 | from uploader.models import Upload 12 | import json 13 | import urllib 14 | import urllib2 15 | 16 | 17 | class IndexPage(TemplateView): 18 | template_name = "page/index.html" 19 | 20 | def get_context_data(self, **kwargs): 21 | context = super(IndexPage, self).get_context_data(**kwargs) 22 | js_debug = 1 if settings.JS_DEBUG else 0 23 | context['JSDEBUG'] = js_debug 24 | context['SERVER_NAMES'] = settings.NAMES 25 | return context 26 | 27 | 28 | @login_required() 29 | def delete_all_entry(request): 30 | if request.user.is_superuser: 31 | DynamitModel.objects.all().delete() 32 | Comment.objects.all().delete() 33 | Upload.objects.all().delete() 34 | messages.success(request, 'Данные успешно удалены.') 35 | return HttpResponseRedirect('/') 36 | return HttpResponseForbidden() 37 | 38 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "prj.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /prj/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/prj/__init__.py -------------------------------------------------------------------------------- /prj/local_settings.py.template: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | JS_DEBUG = True 3 | ALLOWED_HOSTS = [] 4 | 5 | COMPRESS_HTML = False 6 | 7 | COMPRESS_ENABLED = True 8 | COMPRESS_OFFLINE = True 9 | # COMPRESS_CSS_FILTERS = ['compressor.filters.yuglify.YUglifyCSSFilter'] 10 | COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter'] 11 | 12 | 13 | DATABASES = { 14 | "default": { 15 | "ENGINE": "django.contrib.gis.db.backends.postgis", 16 | "NAME": "geonote", 17 | "USER": "postgres", 18 | "PASSWORD": "", 19 | "HOST": "127.0.0.1", 20 | "PORT": "5432", 21 | } 22 | } -------------------------------------------------------------------------------- /prj/middleware.py: -------------------------------------------------------------------------------- 1 | import re 2 | from django.utils.html import strip_spaces_between_tags 3 | from django.conf import settings 4 | 5 | RE_MULTISPACE = re.compile(r"\s{2,}") 6 | RE_NEWLINE = re.compile(r"\n") 7 | 8 | 9 | class MinifyHTMLMiddleware(object): 10 | def process_response(self, request, response): 11 | if 'Content-Type' in response and 'text/html' in response['Content-Type'] and settings.COMPRESS_HTML: 12 | response.content = strip_spaces_between_tags(response.content.strip()) 13 | response.content = RE_MULTISPACE.sub(" ", response.content) 14 | response.content = RE_NEWLINE.sub("", response.content) 15 | return response 16 | 17 | -------------------------------------------------------------------------------- /prj/utils.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def get_or_none(model, **kwargs): 4 | try: 5 | return model.objects.get(**kwargs) 6 | except model.DoesNotExist: 7 | return None 8 | 9 | 10 | def lower_keys(x): 11 | if isinstance(x, list): 12 | return [lower_keys(v) for v in x] 13 | if isinstance(x, dict): 14 | return dict((k.lower(), lower_keys(v)) for k, v in x.iteritems()) 15 | return x 16 | 17 | 18 | def parse_bool_string(the_string): 19 | return the_string[0].upper() == 'T' 20 | -------------------------------------------------------------------------------- /prj/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for prj project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "prj.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /prj/wsgi.py.bak: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for prj project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "prj.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /prj/wsgi.py.def: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for prj project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "prj.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.10.2 2 | django-appconf==1.0.2 3 | django-compressor==2.1 4 | django-filter==0.15.3 5 | djangorestframework==3.4.7 6 | djangorestframework-gis==0.10.1 7 | djoser==0.5.1 8 | easy-thumbnails==2.3 9 | Pillow==3.4.1 10 | psycopg2==2.6.2 11 | rcssmin==1.0.6 12 | rjsmin==1.0.12 13 | six==1.10.0 14 | unicode-slugify==0.1.3 15 | Unidecode==0.4.19 16 | xlrd==1.0.0 17 | -------------------------------------------------------------------------------- /templates/403.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Доступ запрещен :( 6 | 28 | 29 | 30 |
31 |

Доступ запрещен :(

32 |

Извините, у вас нет прав для просмотра этой страницы.

33 |
34 | -------------------------------------------------------------------------------- /templates/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ошибка сервера 6 | 28 | 29 | 30 |
31 |

Ошибка сервера:(

32 |

Извините, при обработке запроса на сервере была обнаружена ошибка.

33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/_layouts/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load compress %} 3 | {% load staticfiles %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% block page_title %}{{ SERVER_NAMES.title }}{% endblock %} 19 | 20 | 21 | 22 | {% block meta %}{% endblock %} 23 | 24 | {% block cdn_css %}{% endblock cdn_css %} 25 | 26 | {% block css_nocompress %}{% endblock %} 27 | 28 | {% compress css %} 29 | {% block css %} 30 | 31 | 32 | 33 | {% endblock %} 34 | {% endcompress %} 35 | 36 | {% block cdn_js %} 37 | {% endblock %} 38 | 39 | {% compress js %} 40 | {% block js_header %} 41 | {# #} 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {% endblock %} 56 | {% endcompress %} 57 | 58 | 59 | 60 | 61 | {% block body %} 62 | 63 | {% block header %}{% endblock %} 64 | 65 | {% block content %}{% endblock %} 66 | 67 | {% compress js %} 68 | {% block js %} 69 | 70 | {% endblock js %} 71 | {% endcompress %} 72 | 73 | {% compress js %} 74 | {% block app_js %} 75 | 79 | 80 | 81 | 82 | 83 | {% endblock %} 84 | {% endcompress %} 85 | 86 | {% endblock %} 87 | 88 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /templates/_layouts/modal/aboutModal.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 44 | -------------------------------------------------------------------------------- /templates/_layouts/modal/attributionModal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/_layouts/modal/modals.html: -------------------------------------------------------------------------------- 1 | {% include "_layouts/modal/aboutModal.html" %} 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /templates/_layouts/modal/printModal.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /uploader/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/uploader/__init__.py -------------------------------------------------------------------------------- /uploader/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from uploader.models import Upload, Images 3 | 4 | 5 | admin.site.register(Upload, admin.ModelAdmin) 6 | admin.site.register(Images, admin.ModelAdmin) 7 | -------------------------------------------------------------------------------- /uploader/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/uploader/api/__init__.py -------------------------------------------------------------------------------- /uploader/api/views.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import unicode_literals, print_function 3 | from django.contrib.gis.gdal import DataSource 4 | from rest_framework.permissions import IsAuthenticated 5 | from rest_framework.parsers import FormParser, MultiPartParser 6 | from rest_framework.response import Response 7 | from rest_framework.views import APIView 8 | from dynamit.api.serializers import DynamitModelSerializer 9 | from uploader.api.utils import create_dynamit, CustomMapping, create_response_status 10 | from uploader.models import Upload 11 | import slugify 12 | 13 | 14 | class DynamitUploadView(APIView): 15 | parser_classes = (FormParser, MultiPartParser,) 16 | permission_classes = (IsAuthenticated,) 17 | 18 | def post(self, request, format=None): 19 | file_obj = request.data['file'] 20 | with_error = 0 21 | new_entries = 0 22 | response = {} 23 | if file_obj: 24 | file_name = unicode(file_obj.name) 25 | file_name_parts = file_name.split(".") 26 | file_type = "" 27 | if len(file_name_parts) > 1: 28 | file_type = file_name_parts[-1] 29 | file_name = file_name_parts[:-1] 30 | file_name = slugify.slugify(file_name, only_ascii=True) 31 | file_obj.name = "%s.%s" % (file_name, file_type) 32 | upload = Upload(upload=file_obj, user=request.user) 33 | upload.name = file_name 34 | upload.save() 35 | geo_file = DataSource(upload.upload.path) 36 | for layer in geo_file: 37 | try: 38 | new_dynamit, mapping = create_dynamit(file_name, layer, request.user) 39 | model = new_dynamit.as_model() 40 | cm = CustomMapping(model, geo_file, mapping) 41 | cm.custom_save(meta_attrs={'user': request.user}) 42 | new_entries += layer.num_feat 43 | response['dynamit'] = DynamitModelSerializer(new_dynamit).data 44 | except Exception as e: 45 | print(e.message) 46 | break 47 | 48 | response['status'] = create_response_status(new_entries=new_entries, with_error=with_error) 49 | return Response(status=200, data=response) 50 | response['status'] = {'type': 'error', 'result': 'Произошла ошибка при записи в базу данных'} 51 | return Response(status=400, data=response) 52 | -------------------------------------------------------------------------------- /uploader/forms.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from entry.models import Entry 3 | from six import string_types 4 | from django.conf import settings 5 | from django.contrib.gis import forms 6 | from django.utils.translation import ugettext_lazy as _ 7 | from django.template.defaultfilters import filesizeformat 8 | 9 | 10 | class UploaderForm(forms.Form): 11 | upload = forms.FileField() 12 | 13 | def clean_upload(self): 14 | upload = self.cleaned_data['upload'] 15 | content_type = upload.content_type 16 | if content_type in settings.CONTENT_TYPES: 17 | if upload._size > settings.MAX_UPLOAD_SIZE: 18 | raise forms.ValidationError(_('Please keep filesize under %s. Current filesize %s')\ 19 | % (filesizeformat(settings.MAX_UPLOAD_SIZE), filesizeformat(upload._size))) 20 | else: 21 | raise forms.ValidationError(_('File type is not supported')) 22 | 23 | return upload -------------------------------------------------------------------------------- /uploader/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.2 on 2016-10-19 04:38 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | ('contenttypes', '0002_remove_content_type_name'), 17 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 18 | ] 19 | 20 | operations = [ 21 | migrations.CreateModel( 22 | name='Images', 23 | fields=[ 24 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('name', models.CharField(max_length=255)), 26 | ('image', models.ImageField(upload_to=b'images/%Y/%m/%d')), 27 | ('create_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name=b'\xd0\x94\xd0\xb0\xd1\x82\xd0\xb0 \xd1\x81\xd0\xbe\xd0\xb7\xd0\xb4\xd0\xb0\xd0\xbd\xd0\xb8\xd1\x8f')), 28 | ('change_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name=b'\xd0\x94\xd0\xb0\xd1\x82\xd0\xb0 \xd0\xbe\xd0\xb1\xd0\xbd\xd0\xbe\xd0\xb2\xd0\xbb\xd0\xb5\xd0\xbd\xd0\xb8\xd1\x8f')), 29 | ('object_pk', models.TextField(verbose_name='object ID')), 30 | ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='content_type_set_for_images', to='contenttypes.ContentType', verbose_name='content type')), 31 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 32 | ], 33 | options={ 34 | 'verbose_name': '\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435', 35 | 'verbose_name_plural': '\u0418\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f', 36 | }, 37 | ), 38 | migrations.CreateModel( 39 | name='Upload', 40 | fields=[ 41 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 42 | ('name', models.CharField(max_length=255)), 43 | ('upload', models.FileField(upload_to=b'uploads/%Y/%m/%d')), 44 | ('date', models.DateTimeField(default=django.utils.timezone.now)), 45 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 46 | ], 47 | options={ 48 | 'verbose_name': '\u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b', 49 | 'verbose_name_plural': '\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u043d\u0442', 50 | }, 51 | ), 52 | ] 53 | -------------------------------------------------------------------------------- /uploader/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/uploader/migrations/__init__.py -------------------------------------------------------------------------------- /uploader/models.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from django.contrib.auth.models import User 3 | from django.contrib.contenttypes.models import ContentType 4 | from django.utils.encoding import python_2_unicode_compatible, force_text 5 | from django.db import models 6 | from django.utils import timezone 7 | from django.contrib.contenttypes.fields import GenericForeignKey 8 | from django.utils.translation import ugettext_lazy as _ 9 | 10 | 11 | @python_2_unicode_compatible 12 | class Upload(models.Model): 13 | user = models.ForeignKey(User) 14 | name = models.CharField(max_length=255) 15 | upload = models.FileField(upload_to='uploads/%Y/%m/%d') 16 | date = models.DateTimeField(default=timezone.now) 17 | 18 | class Meta: 19 | verbose_name = u"Загруженный файл" 20 | verbose_name_plural = u"Загружаемый контент" 21 | 22 | def __str__(self): 23 | return u'%s' % self.name 24 | 25 | 26 | class ImageManager(models.Manager): 27 | 28 | def for_model(self, model): 29 | ct = ContentType.objects.get_for_model(model) 30 | qs = self.get_queryset().filter(content_type=ct) 31 | if isinstance(model, models.Model): 32 | qs = qs.filter(object_pk=force_text(model._get_pk_val())) 33 | return qs 34 | 35 | 36 | @python_2_unicode_compatible 37 | class Images(models.Model): 38 | user = models.ForeignKey(User) 39 | name = models.CharField(max_length=255) 40 | image = models.ImageField(upload_to='images/%Y/%m/%d') 41 | create_date = models.DateTimeField('Дата создания', default=timezone.now) 42 | change_date = models.DateTimeField('Дата обновления', default=timezone.now) 43 | 44 | # Content-object field 45 | content_type = models.ForeignKey(ContentType, 46 | verbose_name=_('content type'), 47 | related_name="content_type_set_for_%(class)s") 48 | object_pk = models.TextField(_('object ID')) 49 | content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk") 50 | 51 | objects = ImageManager() 52 | 53 | class Meta: 54 | verbose_name = u"Изображение" 55 | verbose_name_plural = u"Изображения" 56 | 57 | def __str__(self): 58 | return u'%s' % self.name 59 | 60 | -------------------------------------------------------------------------------- /uploader/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /uploader/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendrom/geonote/7d077b5f12a597a4e4c92d9d3b358daf91b39ef3/uploader/views.py --------------------------------------------------------------------------------