From 756421f0cc9a3c2dbd9f4dc7cde1c14446fee321 Mon Sep 17 00:00:00 2001 From: Dimas Ciputra Date: Tue, 29 Aug 2017 21:39:03 +0700 Subject: [PATCH] Update master branch from develop (#253) * Fix import resources * initiated login * Change button to pushbutton * change password using password field * Use PyQt from qgis package * Change variables, try login without mixin * fixing login api * Add logger handler from inasafe * Update network mixin * cleand code : add comment * clean code : change naming convention * Remove pyc files from source * Ignore pycharm dir * Add network mixin test * Fix things : - Fix tests - Restructure project file * Update header * Change to QgsNetworkAccessManager * Add travis - Add travis - Update tests * Clean up code * Fix translation test * Change travis gitter webhooks * Clean up code * Add travis badge * added options ui * added options ui * fix test case * save authtoken * fix code style * update docstring (#26) * Update readme. Removed readme.txt. Removed resources.qrc. Remove pb_tool.cfg * Add class to fetch organizations * - Update docstring - Use MagicMock for testing - Rename variables * Update docstring * Update docstring * save setting to QSetting * update docstring * save setting to QSetting * update docstring and create setting test * update docstring * update docstring * update checking url instance * update setting test * update docstring * update setting key * Check pep8 (#31) * Add cadasta to menu bar * update test cadasta login dialog * update test cadasta login dialog * change naming function * update test case of cadasta login dialog * added fetch projects api * added fetch projects api * update docstring * update docstring * fix test pep8 * added cadasta dialog parent class * fix typo * fixing bug init style on parent * fixing bug init style on parent * update docstring * remove custom style for dialog * added download data wizard * added download data wizard * added magicmock to test * clean code for pep8 * update translation framework * update translation framework * fix bug plugin isn't translated in qgis * fixing fixed layout * use the testing framework from QGIS * Add landscape file. * Initial project creation * Attribute selection * Change to stacked widget * Add project creation step 3 * Update step 2 project creation * Get geojson layer * change download project using wizard * Fix tests * Update tests * Update tests * Progress bar to view progress percentage in step 3 * Set button connection in init to avoid duplicated connection * update base wizard layout * clean code for translation * Deactivate organisation button when loading * Get canvas extent * Add geojson library * Revert "Add geojson library" This reverts commit 758bd55fe02fc4ea338aafcce09ab68c51cef544. * Export extent to geojson using qgis api * Reset attributes in step02 if layer is changed in step01 * move login as wizard stacked widget * added logo to wizard * add resources to resources folder * Fix tests * Update project layer geojson path * Fix ui layout * Change QComboBox to QgsMapLayerComboBox in step1 * move plugin under vector menu * move plugin under vector menu * put cadasta * change metadata * Use QgsFieldComboBox, update step 3 * Clean up codes * Fix pythonic names in wizard * Add unit test for url validation * Fix travis * update layout * clean code * clean code * Add landscape status in readme and small fix. * Add token to network request if available * Upload project and locations * Using json post request instead * Enable/disable menu that requires authentication * Clean up code * added contact dialog * added contact database * move login as widget * added contact dialog * clean code * fix test case * clean code * clean code * fix contact bug and validation of contact * fix contact bug and validation of contact * clean code * Add contact multiple selection list Formatting variables * clean code * Create wizard dialog class * Fix docstring * Project basic information update (#67) * Add parties when creating project * Update doctstring * Update format string * Upload relationship attribute when creating a project (#70) * Add relationship attribute to cadasta * Add ApiConnect class * added helper * use message class for building helper * clean code * Filter organization againts project permission (#73) * Filter organization againts project permission * Add test case * Bug fix on project creation (#74) * fix delete contact, multiple delete contact * clean code * Fix create project if there is no location * Update locations data * Update add new location * Clean up code * Clean up code * update UX * update UX * update UX * added release script * added release script * update release script * fix No Field translation * rolling back qgis.PyQt to PyQt4 * Set QGIS minimum version to 2.14 (#93) * Fix project creation: (#94) - Fix validation check if no location type is provided - Remove attribute dropdown * parsing properties incoming geojson * parsing properties incoming geojson * clean code * fix bug * save and get basic information (#97) * save and get basic information * add signal for selected layer. turn off update when no basic information provided * clean code * check enabled of update project based on project permission * add checking enabled of update in download project * allow update after create * added icons * distinct layer by type * fix travis * Update update wizard (#102) * Download project and the attributes * Get downloaded projects to update wizard * Update project and spatial information * Try to update relationship attribute from csv * Save attribute to non geometry layer, will save to file if qgis exited * Download parties to non geometry layer * Update parties * Fix tests and pep8 * added license * added license * change about * questionnaire * added get questionnaire api * updated UI for questionnaire * update questionnaire * implement update questionnaire in create project * remove questionnaire in update project * clean code * revert download project to be able to download all project * fix bugs * clean code * allow parties and relationship * change metada version * fix questionnaire bug * Fix update project is greyed (#110) * Fix update project is greyed * Fix pep8 * Get rel and party id from basic information (#111) * Fix test (#112) * Show party attributes from questionnaire (#113) * Fix pep8 * Fix download relationship and party attributes * Fix update for different layer type * Fix project creation * fix pep error * fix questionnaire updated before upload location * Fix party attributes are not downloaded * Download and parse attributes into qgsfield table * Fix update from attributes * Update relationship attributes * Update project creation * update questionnaire * update questionnaire (2) * Fix no module qgsmaplayercombobox * Fix project creation with questionnaire * Fix questionnaire attributes * Bugs fixing (#140) * Make url not required * Fix projects order * Fix close button in about dialog * Fix skewed cadasta icon * Fix title size * Fix pep8 * update error message detail (#141) * added requirement checker on submit (#143) * Fixes (#142) * Fix typos * Make primary buttons bigger * Rename options to user settings * Update ui * Fix on windows * Fix missing icon in windows * Fix pep8 * Fix function not called * Fixes (#153) * Remove attributes column after download * Update contact after download project * Fix project creation * Update project update * Add contacts when download project * Do not show downloaded project in list available projects (#155) * Do not show downloaded project in list available projects * Fix pep8 * make questionnaire included everytime (#157) * make download project automatically (#156) * make download project automatically * update error message * fix failed test * Change no field to ---- no field ---- (#161) * Grey out downloaded project (#160) * Add project details and add contacts button (#159) * Add project details and add contacts button * Fix header * Tidy up header * pep8 * Remove contacts menu * add advanced for questionnaire (#158) * add advanced for questionnaire * change questionnaire layout * Add about dialog (#162) * Add checkbox to see all public project (#163) * Add checkbox to see all public project * pep8 * Fix key error * Get downloaded project automatically in update menu (#164) * Get downloaded project automatically in update menu * Fix test * Fix error when updating a project (#171) * Fix wording (#183) * Make log panel grey (#182) * Change add button to link (#181) * Fixes (#187) * Fix save token issue * Fix tests * Change default domain to platform-staging-api * Remove credential in test * Pep8 * Get orgs automatically (#188) * Get orgs automatically * Fix #146: Update text error * Fix test * Fix tab order * Fix empty strings translated to None (#184) * Fix black strings translated to None * Update json dumps * Pep8 * Fix bugs (#192) * fix DateTime fields are not included * Fix org dropdown width * Fix test * Fix dropdown org view in windows (#193) * Update Travis build status image * Support paginated responses (#154) * Support paginated responses * Merge branch 'develop' into support-pagination * Update init to pass in URL * Bugfixes (#199) * Normalize URL format * Handle first page of GeoJSON data * Expect paginated responses * Import cleanup * Fix tests * Fixup * Handle paginated response * Fix if no results in data json (#207) * Handle custom attributes in update location (#210) * Handle custom attributes in update existing location * Update add new location with custom attribute * text changes to user settings * Add support for non english project (#213) * Upload project with non roman names * Update non english name project * Make clear button always active (#228) * Make clear button always active * Fix pep8 * For public projects, only download location layer (#230) * Fix scroll to view error messages on log when submitting data (#231) * Fix unchecking public project still loads public project (#229) * Fix columns that have been joined should not be uploaded (#233) * Remove failed messages when button clear is clicked (#234) * Add link to help on JSON format from Advanced (#240) * Auto close windows when completed (#241) * Remove index from questionnaire (#244) * Fix login another account (#245) * Move help link to questionnaire dialog (#246) * Fix if custom attributes added in questionnaire (#247) * Add json hightlighter (#248) * Clean up questionnaire (#249) * Activate close button in download project dialog (#250) * Fix download private project (#252) * Set username and password input disabled (#255) * Fix pep8 --- cadasta/api/login.py | 3 +- .../gui/tools/helper/content/cadasta_help.py | 10 +- .../gui/tools/helper/content/options_help.py | 83 +++++++++++ .../gui/tools/utilities/edit_text_dialog.py | 71 +++++++++- cadasta/gui/tools/utilities/questionnaire.py | 96 +++++++++---- cadasta/gui/tools/widget/options_widget.py | 26 +++- .../tools/wizard/step_project_creation02.py | 4 + .../tools/wizard/step_project_creation03.py | 39 ++++-- .../tools/wizard/step_project_download01.py | 6 +- .../tools/wizard/step_project_download02.py | 56 ++++++-- .../gui/tools/wizard/step_project_update01.py | 2 +- .../gui/tools/wizard/step_project_update03.py | 59 ++++++-- cadasta/gui/ui/utilities/edit_text_dialog.ui | 25 +++- .../gui/ui/wizard/step_project_creation02.ui | 57 +++++--- .../gui/ui/wizard/step_project_creation03.ui | 7 +- .../gui/ui/wizard/step_project_update03.ui | 4 +- cadasta/utilities/utilities.py | 15 +- resources/questionnaire.json | 132 +++++------------- 18 files changed, 500 insertions(+), 195 deletions(-) create mode 100644 cadasta/gui/tools/helper/content/options_help.py diff --git a/cadasta/api/login.py b/cadasta/api/login.py index 2e1cc7e..a4e4f64 100644 --- a/cadasta/api/login.py +++ b/cadasta/api/login.py @@ -19,7 +19,7 @@ class Login(BaseApi): - post_data = QByteArray() + post_data = None def __init__(self, domain, username, password, on_finished=None): """Constructor. @@ -38,6 +38,7 @@ def __init__(self, domain, username, password, on_finished=None): :type on_finished: Function """ super(Login, self).__init__(domain + '/api/v1/account/login/?') + self.post_data = QByteArray() self.post_data.append("username=%s&" % username) self.post_data.append("password=%s" % password) diff --git a/cadasta/gui/tools/helper/content/cadasta_help.py b/cadasta/gui/tools/helper/content/cadasta_help.py index 9186ac0..654ae73 100644 --- a/cadasta/gui/tools/helper/content/cadasta_help.py +++ b/cadasta/gui/tools/helper/content/cadasta_help.py @@ -48,8 +48,11 @@ def content(): message = m.Message() message.add(m.Paragraph(tr( - 'You can find updated documentation and suggested workflows on our main ' - 'documentation pages: QGIS chapter. (requires internet access to view)'))) + 'You can find updated documentation and suggested workflows ' + 'on our main ' + 'documentation pages: QGIS chapter. (requires internet ' + 'access to view)'))) message.add(m.Paragraph(tr( 'There are three windows that will help you ' 'to manage your project\'s data.'))) @@ -64,6 +67,7 @@ def content(): message.add(bullets) message.add(m.Paragraph(tr( - 'Use the User Settings window to log in to your account and get started!' + 'Use the User Settings window to log in to your account ' + 'and get started!' ''))) return message diff --git a/cadasta/gui/tools/helper/content/options_help.py b/cadasta/gui/tools/helper/content/options_help.py new file mode 100644 index 0000000..b7e4861 --- /dev/null +++ b/cadasta/gui/tools/helper/content/options_help.py @@ -0,0 +1,83 @@ +# coding=utf-8 +"""Help for options.""" + +from cadasta.utilities.i18n import tr +from extras import messaging as m +from extras.messaging import styles + +INFO_STYLE = styles.INFO_STYLE + +__author__ = 'Irwan Fathurrahman ' +__date__ = '03/01/17' + + +def options_help(): + """Help message for Options. + + :returns: A message object containing helpful information. + :rtype: messaging.message.Message + """ + message = m.Message() + message.add(heading()) + message.add(content()) + return message + + +def heading(): + """Helper method that returns just the header. + + This method was added so that the text could be reused in the + other contexts. + + :returns: A heading object. + :rtype: safe.messaging.heading.Heading + """ + message = m.Heading(tr('Options Help'), **INFO_STYLE) + return message + + +def content(): + """Helper method that returns just the content. + + This method was added so that the text could be reused in the + other contexts. + + :returns: A message object without brand element. + :rtype: safe.messaging.message.Message + """ + + message = m.Message() + + message.add(m.Paragraph(tr( + 'Options will help you redefine url of Cadasta that is used as ' + 'source. And also it create a credential to be used on submit ' + 'new or updated projects.'))) + + message.add(m.Paragraph(tr( + 'There are 3 input that all of that are required.'))) + + bullets = m.BulletedList() + bullets.add(m.Text( + m.ImportantText(tr('Cadasta URL')), + + tr('- overwrite current url as cadasta source.' + 'default is https://platform-staging-api.cadasta.org/') + )) + bullets.add(m.Text( + m.ImportantText(tr('Cadasta Username')), + tr('- username that will be used for other request, e.g: create ' + 'project') + )) + bullets.add(m.Text( + m.ImportantText(tr('Cadasta Password')) + + )) + message.add(bullets) + + message.add(m.Paragraph(tr( + 'Fill out the form with your username and password. Click \'Connect\' ' + 'button ' + 'to login. If that is successful click the \'Save\' button to save ' + 'the settings.'))) + message.add(m.ImportantText(tr('Note that your password is not saved.'))) + return message diff --git a/cadasta/gui/tools/utilities/edit_text_dialog.py b/cadasta/gui/tools/utilities/edit_text_dialog.py index 438d0ad..17ec63d 100644 --- a/cadasta/gui/tools/utilities/edit_text_dialog.py +++ b/cadasta/gui/tools/utilities/edit_text_dialog.py @@ -14,7 +14,15 @@ import logging from qgis.PyQt.QtCore import pyqtSignal from qgis.PyQt.QtGui import ( - QDialog, + QDialog +) +from PyQt4.QtCore import QUrl, QRegExp, Qt +from PyQt4.QtGui import ( + QDesktopServices, + QColor, + QTextCharFormat, + QFont, + QSyntaxHighlighter ) from cadasta.utilities.resources import get_ui_class @@ -56,11 +64,19 @@ def __init__(self, parent=None, iface=None, text=""): QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle('Cadasta Questionnaire') + self.highlighter = Highlighter(self.edit_text.document()) self.show() - self.edit_text.setText(text) + self.edit_text.setPlainText(text) self.ok_button.clicked.connect( self.close_edit_text_dialog ) + self.data_schema_help.mousePressEvent = self.show_advanced_help + + def show_advanced_help(self, event): + """Show advanced help + """ + QDesktopServices().openUrl( + QUrl("https://cadasta.github.io/api-docs/#questionnaires")) def close_edit_text_dialog(self): """Function that call when ok button is clicked. @@ -75,3 +91,54 @@ def get_text(self): :rtype: str """ return self.edit_text.toPlainText() + + +class Highlighter(QSyntaxHighlighter): + def __init__(self, parent=None): + super(Highlighter, self).__init__(parent) + + self.highlighting_rules = [] + + value_format = QTextCharFormat() + value_format.setForeground(Qt.darkRed) + self.highlighting_rules.append(( + QRegExp("\\btrue\\b|\\bnull\\b|\\bfalse\\b|\\b[0-9]+\\b"), + value_format + )) + + quotation_format = QTextCharFormat() + quotation_format.setForeground(Qt.darkGreen) + self.highlighting_rules.append((QRegExp("\".*\""), + quotation_format)) + + self.comment_start_expression = QRegExp("/\\*") + self.comment_end_expression = QRegExp("\\*/") + + def highlightBlock(self, text): + for pattern, highlight_format in self.highlighting_rules: + expression = QRegExp(pattern) + index = expression.indexIn(text) + while index >= 0: + length = expression.matchedLength() + self.setFormat(index, length, highlight_format) + index = expression.indexIn(text, index + length) + + self.setCurrentBlockState(0) + + start_index = 0 + if self.previousBlockState() != 1: + start_index = self.comment_start_expression.indexIn(text) + + while start_index >= 0: + end_index = self.comment_end_expression.indexIn(text, start_index) + + if end_index == -1: + self.setCurrentBlockState(1) + comment_length = len(text) - start_index + else: + comment_length = end_index - start_index + \ + self.comment_end_expression.matchedLength() + + start_index = self.comment_start_expression.indexIn( + text, + start_index + comment_length) diff --git a/cadasta/gui/tools/utilities/questionnaire.py b/cadasta/gui/tools/utilities/questionnaire.py index f811f9e..bb74df6 100644 --- a/cadasta/gui/tools/utilities/questionnaire.py +++ b/cadasta/gui/tools/utilities/questionnaire.py @@ -140,28 +140,22 @@ def generate_new_questionnaire( for field in current_layer.fields(): field_name = field.name() if field_name != 'id': - if field_name not in attributes_in_questionnaire: - if field_name not in mapped_fields: - try: - # check location attributes in question group - location_attributes["questions"].append( - { - "index": index, - "name": field_name, - "label": field_name, - "type": mapping_type[ - field.typeName().lower() - ], - "required": False, - "constraint": 'null', - "default": 'null', - "hint": 'null', - "relevant": 'null', - } - ) - except KeyError: - pass - index += 1 + if field_name not in mapped_fields: + try: + # check location attributes in question group + location_attributes["questions"].append( + { + "name": field_name, + "label": field_name, + "type": mapping_type[ + field.typeName().lower() + ], + "required": False + } + ) + except KeyError: + pass + index += 1 # insert into questionnaire index = -1 @@ -171,15 +165,69 @@ def generate_new_questionnaire( break if index == -1: - location_attributes['index'] = 1 questionnaire['question_groups'].append( location_attributes) else: - location_attributes['index'] = index questionnaire['question_groups'][index] = location_attributes return json.dumps(questionnaire, indent=4) + def add_index(self, question_object, index): + """Add index to question. + + :param question_object: object to be added index + :type question_object: dict + + :param index: current index + :type index: int + + :return: latest index + :rtype: int + """ + if 'index' not in question_object: + question_object['index'] = index + else: + if question_object['index'] - index != 1: + question_object['index'] = index + 1 + index += 1 + return index + + def validate_questionnaire(self, questionnaire): + """Validate and fix questionnaire file. + + :param questionnaire: questionnaire string + :type questionnaire: str + + :return: validated questionnaire + :rtype: str + """ + questionnaire_obj = json.loads(questionnaire) + index = 1 + for question in questionnaire_obj['questions']: + index = self.add_index(question, index) + + if 'options' in question: + option_index = 1 + for option in question['options']: + option_index = self.add_index(option, option_index) + + index = 1 + for question_group in questionnaire_obj['question_groups']: + index = self.add_index(question_group, index) + + if 'questions' in question_group: + question_index = 1 + for question in question_group['questions']: + question_index = self.add_index(question, question_index) + if 'hint' not in question: + question['hint'] = 'null' + if 'default' not in question: + question['default'] = 'null' + if 'relevant' not in question: + question['relevant'] = 'null' + + return json.dumps(questionnaire_obj) + def update_questionnaire( self, organization_slug, project_slug, questionnaire): """Update questionnaire of selected project. diff --git a/cadasta/gui/tools/widget/options_widget.py b/cadasta/gui/tools/widget/options_widget.py index 100ed1f..4227d94 100644 --- a/cadasta/gui/tools/widget/options_widget.py +++ b/cadasta/gui/tools/widget/options_widget.py @@ -79,7 +79,7 @@ def set_widgets(self): tr('Clear') ) - self.clear_button.setEnabled(False) + self.clear_button.setEnabled(True) self.clear_button.clicked.connect( self.clear_information ) @@ -90,9 +90,16 @@ def set_widgets(self): self.auth_token = get_authtoken() if self.auth_token: - self.clear_button.setEnabled(True) self.test_connection_button.setEnabled(False) self.username_input.setText(get_setting('username')) + self.username_input.setReadOnly(True) + self.password_input.setReadOnly(True) + self.username_input.setStyleSheet( + "QLineEdit { background-color: '#d9d9d9'; color: '#5b5b5b'; " + "selection-background-color: '#969292'; }") + self.password_input.setStyleSheet( + "QLineEdit { background-color: '#d9d9d9'; color: '#5b5b5b'; " + "selection-background-color: '#969292'; }") self.token_status.setText( tr('Auth token is saved.') ) @@ -103,11 +110,15 @@ def set_widgets(self): def clear_information(self): """Clear login information.""" + self.username_input.setReadOnly(False) + self.password_input.setReadOnly(False) + self.username_input.setStyleSheet("") + self.password_input.setStyleSheet("") self.username_input.clear() self.password_input.clear() + self.ok_label.clear() delete_authtoken() delete_setting('username') - self.clear_button.setEnabled(False) self.test_connection_button.setEnabled(True) self.token_status.setText( tr( @@ -166,6 +177,7 @@ def on_finished(self, result): """On finished function when tools request is finished.""" self.ok_label.setVisible(True) + self.clear_button.setEnabled(True) if 'auth_token' in result: self.auth_token = result['auth_token'] self.save_button.setEnabled(True) @@ -191,6 +203,14 @@ def save_authtoken(self): save_url_instance(self.url) self.save_button.setEnabled(False) self.save_organizations() + self.username_input.setReadOnly(True) + self.password_input.setReadOnly(True) + self.username_input.setStyleSheet( + "QLineEdit { background-color: '#d9d9d9'; color: '#5b5b5b'; " + "selection-background-color: '#969292'; }") + self.password_input.setStyleSheet( + "QLineEdit { background-color: '#d9d9d9'; color: '#5b5b5b'; " + "selection-background-color: '#969292'; }") def save_organizations(self): """Save organizations of user. diff --git a/cadasta/gui/tools/wizard/step_project_creation02.py b/cadasta/gui/tools/wizard/step_project_creation02.py index 34dd918..67437b8 100644 --- a/cadasta/gui/tools/wizard/step_project_creation02.py +++ b/cadasta/gui/tools/wizard/step_project_creation02.py @@ -11,6 +11,8 @@ """ +import logging + from cadasta.gui.tools.utilities.edit_text_dialog import EditTextDialog from cadasta.gui.tools.utilities.questionnaire import QuestionnaireUtility from cadasta.gui.tools.wizard.wizard_step import WizardStep @@ -24,6 +26,8 @@ FORM_CLASS = get_wizard_step_ui_class(__file__) +LOGGER = logging.getLogger('CadastaQGISPlugin') + class StepProjectCreation2(WizardStep, FORM_CLASS, QuestionnaireUtility): """Step 2 for project creation.""" diff --git a/cadasta/gui/tools/wizard/step_project_creation03.py b/cadasta/gui/tools/wizard/step_project_creation03.py index f68f588..f2fb347 100644 --- a/cadasta/gui/tools/wizard/step_project_creation03.py +++ b/cadasta/gui/tools/wizard/step_project_creation03.py @@ -21,6 +21,7 @@ QgsFeature, QCoreApplication ) +import json from PyQt4.QtCore import QByteArray, QVariant from cadasta.gui.tools.wizard.wizard_step import WizardStep from cadasta.utilities.i18n import tr @@ -33,6 +34,7 @@ ) from cadasta.common.setting import get_csv_path from cadasta.vector import tools +from cadasta.gui.tools.utilities.questionnaire import QuestionnaireUtility __copyright__ = "Copyright 2016, Cadasta" __license__ = "GPL version 3" @@ -44,7 +46,7 @@ LOGGER = logging.getLogger('CadastaQGISPlugin') -class StepProjectCreation3(WizardStep, FORM_CLASS): +class StepProjectCreation3(WizardStep, FORM_CLASS, QuestionnaireUtility): """Step 3 for project creation.""" upload_increment = 20 @@ -61,14 +63,24 @@ def __init__(self, parent=None): self.current_progress = 0 self.data = None self.spatial_api = None + self.step_1_data = None + self.step_2_data = None + self.questionnaire = None def set_widgets(self): """Set all widgets on the tab.""" + self.text_edit.setStyleSheet( + "background-color: #f0f0f0; color: #757575" + ) self.progress_bar.setVisible(False) self.lbl_status.setText( tr('Are you sure to upload the data?') ) self.submit_button.setFocus() + self.step_1_data = self.parent.step_1_data() + self.step_2_data, self.questionnaire = self.parent.step_2_data() + # Validate questionnaire data + self.questionnaire = self.validate_questionnaire(self.questionnaire) def set_status(self, status): """Show status in label and text edit. @@ -100,18 +112,16 @@ def processing_data(self): self.set_progress_bar(self.current_progress + 25) - step_1_data = self.parent.step_1_data() self.set_progress_bar(self.current_progress + 25) - step_2_data, questionnaire = self.parent.step_2_data() self.set_progress_bar(self.current_progress + 25) - self.data = step_1_data - self.data['questionnaire'] = questionnaire + self.data = self.step_1_data + self.data['questionnaire'] = self.questionnaire # Finalize the data for location in self.data['locations']['features']: - for cadasta_field, layer_field in step_2_data.iteritems(): + for cadasta_field, layer_field in self.step_2_data.iteritems(): properties = location['properties'] if layer_field in properties: try: @@ -201,7 +211,7 @@ def upload_project(self): # after creating location, questionnaire is blocked if self.data['questionnaire']: upload_questionnaire_attribute = True - self.update_questionnaire_project() + self.upload_questionnaire_project() total_locations = len(self.data['locations']['features']) if total_locations > 0: self.upload_locations(upload_questionnaire_attribute) @@ -216,6 +226,7 @@ def upload_project(self): ) self.set_status(tr('Finished')) + self.parent.close() def rerender_saved_layer(self): """Rerender saved layer on cadasta.""" @@ -278,6 +289,11 @@ def upload_locations(self, update_questionnaire_attribute): failed = 0 + try: + questionnaire = json.loads(self.questionnaire) + except ValueError: + questionnaire = {} + for location in self.data['locations']['features']: post_data = { 'geometry': location['geometry'], @@ -294,6 +310,11 @@ def upload_locations(self, update_questionnaire_attribute): if 'id' in post_data['attributes']: del post_data['attributes']['id'] + for question in \ + questionnaire['question_groups'][0]['questions']: + if question['name'] not in post_data['attributes']: + post_data['attributes'][question['name']] = u'' + connector = ApiConnect(get_url_instance() + post_url) post_data = Utilities.json_dumps(post_data) status, result = self._call_json_post( @@ -497,8 +518,8 @@ def get_next_step(self): """ return None - def update_questionnaire_project(self): - """Update questionnaire.""" + def upload_questionnaire_project(self): + """Upload questionnaire.""" self.set_status( tr('Update questionnaire') ) diff --git a/cadasta/gui/tools/wizard/step_project_download01.py b/cadasta/gui/tools/wizard/step_project_download01.py index 94da599..361697c 100644 --- a/cadasta/gui/tools/wizard/step_project_download01.py +++ b/cadasta/gui/tools/wizard/step_project_download01.py @@ -82,6 +82,8 @@ def set_widgets(self): self.add_contact_label.mousePressEvent = self.add_contact_label_clicked self.set_enabled_add_contact_label(False) self.project_combo_box.setFocus() + set_setting('public_project', + self.public_projects_checkbox.checkState() == Qt.Checked) def add_contact_label_clicked(self, event): """Handler for add_contact_label clicked. """ @@ -148,6 +150,7 @@ def get_available_projects_finished(self, result): """ self.throbber_loader.setVisible(False) self.project_combo_box.clear() + self.public_projects_checkbox.setEnabled(True) if result[0]: projects = sorted(result[1], key=itemgetter('slug')) @@ -211,7 +214,7 @@ def project_combo_box_changed(self): if project['description']: self.project_description_label.setText( - self.tr(project['description'].encode('utf-8'))) + project['description']) else: self.project_description_label.setText( self.tr(project['name'].encode('utf-8'))) @@ -254,6 +257,7 @@ def project_combo_box_changed(self): def get_available_projects(self): """Get available projects.""" self.throbber_loader.setVisible(True) + self.public_projects_checkbox.setEnabled(False) self.project_api = Project( on_finished=self.get_available_projects_finished) diff --git a/cadasta/gui/tools/wizard/step_project_download02.py b/cadasta/gui/tools/wizard/step_project_download02.py index 2d07fe8..f87763b 100644 --- a/cadasta/gui/tools/wizard/step_project_download02.py +++ b/cadasta/gui/tools/wizard/step_project_download02.py @@ -1,4 +1,5 @@ -# coding=utf-8 +#!/usr/bin/python +# -*- coding: utf-8 -*- """ Cadasta project download step -**Cadasta Wizard** @@ -37,6 +38,7 @@ from cadasta.api.api_connect import ApiConnect from cadasta.common.setting import get_url_instance from cadasta.vector import tools +from cadasta.common.setting import get_setting __copyright__ = "Copyright 2016, Cadasta" __license__ = "GPL version 3" @@ -71,7 +73,7 @@ def set_widgets(self): self.warning_label.setText(self.loading_label_string) self.get_project_spatial( self.project['organization']['slug'], self.project['slug']) - self.parent.next_button.setEnabled(False) + self.parent.next_button.setEnabled(True) def validate_step(self): """Check if the step is valid. @@ -118,25 +120,48 @@ def organization_projects_spatial_call_finished(self, result): organization_slug, project_slug) self.progress_bar.setValue(50) - relationship_layer = self.relationships_layer(vlayers) - self.progress_bar.setValue(80) - party_layer = self.parties_layer() + + download_relationship_and_party = False + + if self.project['access'] == 'public': + # Get organization + status, results = self.organisation_api.summary_organization( + organization_slug) + + if status and 'users' in results: + for user in results['users']: + if user['username'] == get_setting('username'): + download_relationship_and_party = True + break + else: + download_relationship_and_party = True + + relationship_layer_id = None party_layer_id = None - if party_layer: - party_layer_id = party_layer.id() + + if download_relationship_and_party: + relationship_layer = self.relationships_layer(vlayers) + if relationship_layer: + relationship_layer_id = relationship_layer.id() + + party_layer = self.parties_layer() + if party_layer: + party_layer_id = party_layer.id() + + self.progress_bar.setValue(80) QCoreApplication.processEvents() Utilities.save_project_basic_information( self.project, vlayers, - relationship_layer.id(), + relationship_layer_id, party_layer_id ) else: pass self.progress_bar.setValue(self.progress_bar.maximum()) - self.parent.next_button.setEnabled(True) self.warning_label.setText(self.loaded_label_string) + self.parent.close() def save_organizations(self): """Save organizations of user. @@ -166,6 +191,7 @@ def parties_layer(self): :param vector_layer: QGS vector layer in memory :type vector_layer: QgsVectorLayer """ + organization_slug = self.project['organization']['slug'] project_slug = self.project['slug'] attribute = 'parties' @@ -175,8 +201,8 @@ def parties_layer(self): if os.path.isfile(csv_path): os.remove(csv_path) - api = '/api/v1/organizations/{organization_slug}/projects/' \ - '{project_slug}/parties/'.format( + api = u'/api/v1/organizations/{organization_slug}/projects/' \ + u'{project_slug}/parties/'.format( organization_slug=organization_slug, project_slug=project_slug) @@ -308,12 +334,13 @@ def relationships_layer(self, vector_layers): :return: Relationship layer :rtype: QgsVectorLayer """ + organization_slug = self.project['organization']['slug'] project_slug = self.project['slug'] attribute = 'relationships' - api = '/api/v1/organizations/{organization_slug}/projects/' \ - '{project_slug}/spatial/{spatial_unit_id}/relationships/' + api = u'/api/v1/organizations/{organization_slug}/projects/' \ + u'{project_slug}/spatial/{spatial_unit_id}/relationships/' csv_path = get_csv_path( organization_slug, @@ -346,10 +373,11 @@ def relationships_layer(self, vector_layers): for index, feature in enumerate(vector_layer.getFeatures()): attributes = feature.attributes() + spatial_unit_id = attributes[spatial_id_index] spatial_api = api.format( organization_slug=organization_slug, project_slug=project_slug, - spatial_unit_id=attributes[spatial_id_index] + spatial_unit_id=spatial_unit_id ) connector = ApiConnect(get_url_instance() + spatial_api) status, results = connector.get(paginated=True) diff --git a/cadasta/gui/tools/wizard/step_project_update01.py b/cadasta/gui/tools/wizard/step_project_update01.py index 7893c3b..d4074ff 100644 --- a/cadasta/gui/tools/wizard/step_project_update01.py +++ b/cadasta/gui/tools/wizard/step_project_update01.py @@ -130,7 +130,7 @@ def project_combo_box_changed(self): try: if project['information']['description']: self.project_description_label.setText( - self.tr(project['information']['description'])) + project['information']['description']) else: self.project_description_label.setText( self.tr('No description')) diff --git a/cadasta/gui/tools/wizard/step_project_update03.py b/cadasta/gui/tools/wizard/step_project_update03.py index 05d748f..8e255de 100644 --- a/cadasta/gui/tools/wizard/step_project_update03.py +++ b/cadasta/gui/tools/wizard/step_project_update03.py @@ -54,6 +54,9 @@ def __init__(self, parent=None): def set_widgets(self): """Set all widgets on the tab.""" + self.text_edit.setStyleSheet( + "background-color: #f0f0f0; color: #757575" + ) self.project = self.parent.project['information'] if 'layers' in self.project: for project_layer in self.project['layers']: @@ -143,8 +146,8 @@ def update_project(self): def update_spatial_location(self): """Update spatial information.""" - update_loc_api = '/api/v1/organizations/{organization_slug}/' \ - 'projects/{project_slug}/spatial/{spatial_unit_id}/' + update_loc_api = u'/api/v1/organizations/{organization_slug}/' \ + u'projects/{project_slug}/spatial/{spatial_unit_id}/' location_type_idx = self.layer.fieldNameIndex('type') location_id_idx = self.layer.fieldNameIndex('id') @@ -152,9 +155,17 @@ def update_spatial_location(self): for layer in self.vlayers: features = layer.getFeatures() - # Remove unneeded fields - field_names = [field.name() for field in - layer.pendingFields()] + # Get field names + field_names = [] + layer_prefix = self.project['organization']['slug'] + '/' + \ + self.project['slug'] + + for field_name in layer.pendingFields(): + if layer_prefix not in field_name.name() and \ + 'relationships_' not in field_name.name() and \ + 'parties_' not in field_name.name(): + field_names.append(field_name.name()) + field_names.remove('id') field_names.remove('type') @@ -223,12 +234,19 @@ def update_relationship_attributes(self): relationship_type_idx = 2 attributes_idx = 4 - update_api = '/api/v1/organizations/{organization_slug}/projects/' \ - '{project_slug}/relationships/tenure/{relationship_id}/' + update_api = u'/api/v1/organizations/{organization_slug}/projects/' \ + u'{project_slug}/relationships/tenure/{relationship_id}/' - field_names = [ - field.name() for field in relationship_layer.pendingFields() - ] + # Get field names + field_names = [] + layer_prefix = self.project['organization']['slug'] + '/' + \ + self.project['slug'] + + for field_name in relationship_layer.pendingFields(): + if layer_prefix not in field_name.name() and \ + 'relationships_' not in field_name.name() and \ + 'parties_' not in field_name.name(): + field_names.append(field_name.name()) # Remove unneeded fields field_names.remove('spatial_id') @@ -281,11 +299,21 @@ def update_party_attributes(self): name_idx = 1 type_idx = 2 - update_api = '/api/v1/organizations/{organization_slug}/projects/' \ - '{project_slug}/parties/{party_id}/' + update_api = u'/api/v1/organizations/{organization_slug}/projects/' \ + u'{project_slug}/parties/{party_id}/' + + # Get field names + field_names = [] + layer_prefix = self.project['organization']['slug'] + '/' + \ + self.project['slug'] + + for field_name in party_layer.pendingFields(): + if layer_prefix not in field_name.name() and \ + 'relationships_' not in field_name.name() and \ + 'parties_' not in field_name.name(): + field_names.append(field_name.name()) # Remove unneeded fields - field_names = [field.name() for field in party_layer.pendingFields()] field_names.remove('id') field_names.remove('name') field_names.remove('type') @@ -362,6 +390,7 @@ def upload_update(self): ) self.set_progress_bar(100) + self.parent.close() def upload_update_locations( self, @@ -417,8 +446,8 @@ def add_new_locations(self, geometry, location_type, attributes=None): through the project's questionnaire :type attributes: dict """ - api = '/api/v1/organizations/{organization_slug}/projects/' \ - '{project_slug}/spatial/'.format( + api = u'/api/v1/organizations/{organization_slug}/projects/' \ + u'{project_slug}/spatial/'.format( organization_slug=self.project['organization']['slug'], project_slug=self.project['slug']) diff --git a/cadasta/gui/ui/utilities/edit_text_dialog.ui b/cadasta/gui/ui/utilities/edit_text_dialog.ui index c0575ad..4d59c1b 100644 --- a/cadasta/gui/ui/utilities/edit_text_dialog.ui +++ b/cadasta/gui/ui/utilities/edit_text_dialog.ui @@ -33,10 +33,19 @@ - + + 0 + + + 0 + + + 0 + + 0 - + 0 @@ -49,7 +58,17 @@ 9 - + + + PointingHandCursor + + + Data Schema Help + + + + + diff --git a/cadasta/gui/ui/wizard/step_project_creation02.ui b/cadasta/gui/ui/wizard/step_project_creation02.ui index 1b62195..57b2016 100644 --- a/cadasta/gui/ui/wizard/step_project_creation02.ui +++ b/cadasta/gui/ui/wizard/step_project_creation02.ui @@ -129,23 +129,46 @@ 10 - - - - 75 - true - - - - PointingHandCursor - - - > Advanced - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + + + + + 0 + 0 + + + + + 75 + true + + + + PointingHandCursor + + + > Advanced + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + diff --git a/cadasta/gui/ui/wizard/step_project_creation03.ui b/cadasta/gui/ui/wizard/step_project_creation03.ui index aa657c0..71e6d92 100644 --- a/cadasta/gui/ui/wizard/step_project_creation03.ui +++ b/cadasta/gui/ui/wizard/step_project_creation03.ui @@ -156,10 +156,13 @@ - false + true - ForbiddenCursor + ArrowCursor + + + false true diff --git a/cadasta/gui/ui/wizard/step_project_update03.ui b/cadasta/gui/ui/wizard/step_project_update03.ui index aa657c0..0339a10 100644 --- a/cadasta/gui/ui/wizard/step_project_update03.ui +++ b/cadasta/gui/ui/wizard/step_project_update03.ui @@ -156,10 +156,10 @@ - false + true - ForbiddenCursor + ArrowCursor true diff --git a/cadasta/utilities/utilities.py b/cadasta/utilities/utilities.py index 71ed995..14a312f 100644 --- a/cadasta/utilities/utilities.py +++ b/cadasta/utilities/utilities.py @@ -15,6 +15,7 @@ import logging import os import shutil +import unicodedata from qgis.core import ( QgsVectorLayer, QgsMapLayerRegistry, @@ -375,13 +376,23 @@ def get_downloaded_projects(organization): os.path.join(dirpath, f)).split('.')[1].split( splitter) names = [] + + try: + project_name = abs_path[-2].decode('utf-8') + except (UnicodeDecodeError, UnicodeEncodeError): + project_name = unicode(abs_path[-2]) + + normalized = unicodedata.normalize( + 'NFKC', + project_name) + names.append(abs_path[-3]) - names.append(abs_path[-2]) + names.append(normalized) names.append(abs_path[-1]) + list_files.append( '/'.join(names) ) - projects = [] for layer in QgsMapLayerRegistry.instance().mapLayers().values(): diff --git a/resources/questionnaire.json b/resources/questionnaire.json index cec8b65..734fbf8 100644 --- a/resources/questionnaire.json +++ b/resources/questionnaire.json @@ -1,6 +1,5 @@ { "filename": "", - "title": "", "id_string": "", "questions": [ { @@ -8,81 +7,57 @@ "label": "Label", "type": "ST", "required": false, - "constraint": null, "default": null, "hint": null, - "relevant": null, - "index":1 + "relevant": null }, { "name": "end", "label": "Label", "type": "EN", "required": false, - "constraint": null, "default": null, "hint": null, - "relevant": null, - "index":2 + "relevant": null }, { "name": "today", "label": "Label", "type": "TD", "required": false, - "constraint": null, "default": null, "hint": null, - "relevant": null, - "index":3 + "relevant": null }, { "name": "deviceid", "label": "Label", "type": "DI", "required": false, - "constraint": null, - "default": null, - "hint": null, - "relevant": null, - "index":4 - }, - { - "name": "title", - "label": "Cadasta Platform - UAT Survey", - "type": "NO", - "required": false, - "constraint": null, "default": null, "hint": null, - "relevant": null, - "index":5 + "relevant": null }, { "name": "party_type", "label": "Party Classification", "type": "S1", "required": true, - "constraint": null, "default": null, "hint": null, "relevant": null, - "index":6, "options": [ { "name": "GR", - "label": "Group", - "index": 1 + "label": "Group" }, { "name": "IN", - "label": "Individual", - "index": 2 + "label": "Individual" }, { "name": "CO", - "label": "Corporation", - "index": 3 + "label": "Corporation" } ] }, @@ -91,73 +66,59 @@ "label": "Party Name", "type": "TX", "required": true, - "constraint": null, "default": null, "hint": null, - "relevant": null, - "index":7 + "relevant": null }, { "name": "location_geometry", "label": "Location of Parcel", "type": "GT", "required": false, - "constraint": null, "default": null, "hint": null, - "relevant": null, - "index":8 + "relevant": null }, { "name": "location_type", "label": "What is the land feature?", "type": "S1", "required": true, - "constraint": null, "default": null, "hint": null, "relevant": null, - "index":9, "options": [ { "name": "PA", - "label": "Parcel", - "index": 1 + "label": "Parcel" }, { "name": "CB", - "label": "Community Boundary", - "index": 2 + "label": "Community Boundary" }, { "name": "BU", - "label": "Building", - "index": 3 + "label": "Building" }, { "name": "AP", - "label": "Apartment", - "index": 4 + "label": "Apartment" }, { "name": "PX", - "label": "Project Extent", - "index": 5 + "label": "Project Extent" }, { "name": "RW", - "label": "Right-of-way", - "index": 6 + "label": "Right-of-way" }, { "name": "NP", - "label": "National Park Boundary", - "index": 7 + "label": "National Park Boundary" }, { "name": "MI", - "label": "Miscellaneous", - "index": 8 + "label": "Miscellaneous" } ] }, @@ -166,106 +127,85 @@ "label": "What is the social tenure type?", "type": "S1", "required": true, - "constraint": null, "default": null, "hint": null, "relevant": null, - "index":10, "options": [ { "name": "AL", - "label": "All Types", - "index": 1 + "label": "All Types" }, { "name": "CR", - "label": "Carbon Rights", - "index": 2 + "label": "Carbon Rights" }, { "name": "CO", - "label": "Concessionary Rights", - "index": 3 + "label": "Concessionary Rights" }, { "name": "CU", - "label": "Customary Rights", - "index": 4 + "label": "Customary Rights" }, { "name": "EA", - "label": "Easement", - "index": 5 + "label": "Easement" }, { "name": "ES", - "label": "Equitable Servitude", - "index": 6 + "label": "Equitable Servitude" }, { "name": "FH", - "label": "Freehold", - "index": 7 + "label": "Freehold" }, { "name": "GR", - "label": "Grazing Rights", - "index": 8 + "label": "Grazing Rights" }, { "name": "HR", - "label": "Hunting/Fishing/Harvest Rights", - "index": 9 + "label": "Hunting/Fishing/Harvest Rights" }, { "name": "IN", - "label": "Indigenous Land Rights", - "index": 10 + "label": "Indigenous Land Rights" }, { "name": "JT", - "label": "Joint Tenancy", - "index": 11 + "label": "Joint Tenancy" }, { "name": "LH", - "label": "Leasehold", - "index": 12 + "label": "Leasehold" }, { "name": "LL", - "label": "Longterm leasehold", - "index": 13 + "label": "Longterm leasehold" }, { "name": "MR", - "label": "Mineral Rights", - "index": 14 + "label": "Mineral Rights" }, { "name": "OC", - "label": "Occupancy (No Documented Rights)", - "index": 15 + "label": "Occupancy (No Documented Rights)" }, { "name": "TN", - "label": "Tenancy (Documented Sub-lease)", - "index": 16 + "label": "Tenancy (Documented Sub-lease)" }, { "name": "TC", - "label": "Tenancy in Common", - "index": 17 + "label": "Tenancy in Common" }, { "name": "UC", - "label": "Undivided Co-ownership", - "index": 18 + "label": "Undivided Co-ownership" }, { "name": "WR", - "label": "Water Rights", - "index": 19 + "label": "Water Rights" } ] }