├── .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 |
2 |
3 |
7 |
8 |
9 |
Вы уверены что хотите выитй?
10 |
11 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/client/app/common/auth/login/register-form.html:
--------------------------------------------------------------------------------
1 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/client/app/common/auth/login/toolbar.html:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
7 |
8 | -
9 |
12 |
13 | -
14 |
18 |
19 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/client/app/common/comment/templates/comments.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
5 |
{$ modalOptions.bodyText $}
6 |
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 |
2 |
3 | Инструменты
4 |
5 |
22 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
6 |
7 | |
8 |
9 |
10 | |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/client/app/entries/list/templates/entries-table.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/client/app/entries/list/templates/entry-in-list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
34 |
35 | |
--------------------------------------------------------------------------------
/client/app/entries/manager/editor/base-form.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
26 |
27 | Добавить поле
28 |
29 |
--------------------------------------------------------------------------------
/client/app/entries/manager/editor/dynamit-options.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/app/entries/manager/editor/editor-modal.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
33 |
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 |
29 |
30 | |
--------------------------------------------------------------------------------
/client/app/entries/manager/templates/dynamit-list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 | |
13 |
14 |
15 | |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/client/app/entries/manager/templates/sidebar.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Список слоёв
7 |
8 |
9 |
10 |
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 |
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 |
--------------------------------------------------------------------------------
/client/assets/img/cluster_current.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/assets/img/cluster_stroke.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/client/assets/img/single.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/assets/img/single_current.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/assets/img/single_selected.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/client/dist/img/cluster_stroke.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/client/dist/img/single.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/dist/img/single_current.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/dist/img/single_selected.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------
/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 |
3 |
4 |
5 |
9 |
10 |
16 |
17 |
18 |
Разработан: rendrom@gmail.com
19 |
Версия: dev
20 |
21 |
22 |
Добро пожаловать в пилотную версию сайта "{$ NAMES.site $}".
23 |
24 |
25 |
26 |
Информация о разработчиках.
27 |
28 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
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 |
2 |
3 |
4 |
10 |
11 |
12 | {$ NAMES.entry.short_name $}: {$ entryForExport.entryid $}
13 |
14 |
17 |
22 |
23 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
{$ 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 |