Compare commits

..
85 Commits
Author SHA1 Message Date
Akshay Joshi 527e084ef7 Bump version number for release 2018-11-26 15:35:26 +05:30
Akshay Joshi b6842e88ac Update message catalogs 2018-11-26 15:31:05 +05:30
Dave Page f0327f5219 Add the ability to import and export server definitions from a config database. Fixes #3772 2018-11-21 16:09:20 +00:00
Akshay Joshi 3cfc3366d7 Fixed the height and width of the Table dialog. 2018-11-21 18:41:03 +05:30
Akshay Joshi 05e067035f Prevent the debugger controls being pressed again before previous processing is complete. Fixes #3734
Note that this needs to be re-visited in the future, as the current fix is somewhat hacky.
2018-11-20 16:35:27 +00:00
Akshay Joshi 8b5abdfa8f Fix changes to the NOT NULL and default value options in the Table Dialogue. Fixes #3742 2018-11-20 16:29:20 +00:00
Dave Page 782b7419f6 Fix toggle breakpoints buttons in the debugger. Fixes #3736 2018-11-20 16:22:56 +00:00
Dave Page bbb8433af3 Don't double-list the connecting page in the index. 2018-11-19 13:55:18 +00:00
Akshay Joshi ef68cae1f1 Properly report errors when debugging cannot be started. Fixes #3723 2018-11-15 15:18:12 -05:00
Akshay Joshi 9b0889842d Fix dropping of multiple functions/procedures at once. Fixes #3746 2018-11-14 08:40:21 -05:00
Akshay Joshi eae18f6f14 Ensure previous notices are not removed from the Messages tab in the Query Tool if an error occurs during query execution. Fixes #3016 2018-11-14 08:27:47 -05:00
Dave Page 29ed7ec881 Fix standalone setup script execution. 2018-11-05 13:39:43 +00:00
Akshay Joshi 220fdf583e Allow use of 0 (integer) and empty strings as parameters in the debugger. Fixes #3629 2018-11-02 16:44:09 +00:00
Dave Page 628f0d254e Fix resource group multi-drop test. 2018-11-01 08:54:33 +00:00
Dave Page 7dde5d52d0 Fix regression test. 2018-10-31 12:18:13 +00:00
Aditya Toshniwal 95cda78bdb Remove some CSS for the body level that isn't required and may cause issues. 2018-10-31 12:13:13 +00:00
Khushboo Vashi e59eaedfd0 Fix buildfarm failures. 2018-10-31 11:35:13 +00:00
Khushboo Vashi f17979141c Add support for dropping multiple objects at once from the collection Properties panel. Fixes #1513 2018-10-31 10:30:37 +00:00
Akshay Joshi 3359a0de7a Allow the selection order to be preserved in the Select2 control to fix column ordering in data Import/Export. Fixes #3029 2018-10-31 10:09:40 +00:00
Identifier Anonymous 6e03775f05 Japanese translation update. 2018-10-31 09:52:49 +00:00
Akshay Joshi b58f288eec Fixed fatal error while launching the pgAdmin4 3.5. Update the version of the Flask to 0.12.4 for release. Fixes #3730 2018-10-31 13:36:43 +05:30
Murtuza Zabuawala 140372a5cf Fix minor alert layout issue on IE 11. 2018-10-30 09:27:14 +00:00
Akshay Joshi d97431cb4f Bump version number for release. 2018-10-29 22:29:07 +05:30
Akshay Joshi 1e14b0d068 Update message catalogs 2018-10-29 22:21:40 +05:30
Dave Page 4ff0fcdd82 Update to latest Flask, to avoid a security issue. 2018-10-29 14:17:30 +00:00
Akshay Joshi 696fbfb8b9 Fix CREATE Script functionality for EDB-Wrapped functions. Fixes #3676 2018-10-29 11:54:11 +00:00
Dave Page be47390baf Fix the Docker build. 2018-10-29 11:01:47 +00:00
Akshay Joshi 0d5a6eeeff Ensure that utility existence check should work for schema and
other child objects while taking Backup/Restore. Fixes #3722
2018-10-25 18:55:21 +05:30
Akshay Joshi 6ddab02769 1) Fixed process watcher status message when user has stopped the process.
2) Saved the process state in sqlite database.
2018-10-25 17:03:34 +05:30
Aditya Toshniwal 091aa62084 Fixed background colour while replacing a file in File Manager dialog. 2018-10-24 17:53:06 +05:30
Akshay Joshi 2058cdf662 Fixed test cases for Utilities when default_binary_path is not provided in test_config file. 2018-10-22 13:22:35 +05:30
Akshay Joshi 6bc6bc7f60 Ensure that Utilities(Backup/Restore/Maintenence/Import-Export) should not be started
if binary path is wrong and also added 'Stop Process' button to cancel the process.
2018-10-22 12:35:53 +05:30
Akshay Joshi 370df47042 Fixed issue where session files are not deleted first time when pgAdmin4 server started,
it always wait for CHECK_SESSION_FILES_INTERVAL which is 24 hours by default.
2018-10-18 18:02:45 +05:30
Usman Muzaffar 9ae49cbf31 Test case fixes for server mode. 2018-10-18 09:39:09 +01:00
Harshal Dhumal 822d384746 Purge connections from the cache on logout. Fixes #3703 2018-10-17 12:42:41 +01:00
Harshal Dhumal ce31726af5 Fix connection garbage collector. Fixes #3700 2018-10-17 12:35:45 +01:00
Dave Page 494a8ac3fe Release note update 2018-10-17 12:30:01 +01:00
Akshay Joshi 50738db2b8 Fix syntax error when creating new pgAgent schedules with a start
date/time and exception. Fixes #3638

- Fixed syntax error.
- Update/Delete exceptions are not working, SQL is wrong.
- Wrong exception time is shown, it should shown in UTC as datatype of
  the column is timestamp with out timezone.
- Added test cases for Schedule and Steps of pgAgent Job.
2018-10-17 12:28:31 +01:00
Aditya Toshniwal 6fa6939f52 Additional feature test fixes:
1) Changes required for working with Bootstrap 4.
2) Change to fix the timeout exception when waiting for element (tested multiple times on multiple server, did not occur to me thereafter)
3) Removed reset layout after each test case. Instead, delete the layout entry from sqlite db file and do a plain refresh. This will save some time and will also remove dependency on reset layout menu.
4) Disables tree state saving when feature test run starts. Feature tests got confused with auto expanding tree.
2018-10-17 11:50:22 +01:00
Akshay Joshi ef0e53e8fc Fixed pep8 issue 2018-10-15 17:41:21 +05:30
Akshay Joshi fd2c2c4ff7 Fixed minor issue in SQL autocomplete. 2018-10-15 17:36:11 +05:30
Aditya Toshniwal d6c4911135 Fixed calender control issues with Bootstrap 4 2018-10-12 15:02:38 +05:30
Aditya Toshniwal 6991c06937 Additional Bootstrap 4 fixes:
- Calendar control
- Query Tool's Sort/Filter drop down
- Backform hidden controls
- Debugger toolbar
2018-10-11 13:23:59 +01:00
Usman Muzaffar 3f16a0c52c Log process exceptions using ASCII mode to avoid issues with string concatenation. 2018-10-11 13:13:08 +01:00
Aditya Toshniwal 4a6426d3e7 Doc updates after renaming SQL Editor section of the Preference panel. 2018-10-11 09:10:02 +01:00
Khushboo Vashi 235a75202d Fixed minor issue in saving tree state logic. 2018-10-11 11:22:24 +05:30
Dave Page 55cf0cf2f9 Fix message. 2018-10-10 13:30:55 +01:00
Aditya Toshniwal 0565f8dd78 Update to use Bootstrap 3 instead of Bootstrap 4. Fixes #3562 2018-10-10 12:43:26 +01:00
Dave Page bdf90e67a7 When an execution is cancelled in query tool, two different messages are encountered at different time. There is no exact pattern for them. So now, the test case will look for any one of following message in the output text box
i) canceling statement due to user request
ii) Execution cancelled!
2018-10-10 11:23:14 +01:00
Akshay Joshi 7144db7f5a Cleanup session files periodically. Fixes #3674 2018-10-09 11:34:13 +01:00
Murtuza Zabuawala c66840bc8e Fix some styling regressed in #2927. 2018-10-09 11:22:54 +01:00
Aditya Toshniwal 26ad6d4c5d Rename the 'SQL Editor' section of the Preferences to 'Query Tool' as it applies to the whole tool, not just the editor. Fixes #3660 2018-10-08 11:43:02 +01:00
Fred Escallier 87b64cf3c0 Update French translation. 2018-10-08 11:18:59 +01:00
Murtuza Zabuawala 0f1651686b Code refactoring:
- Move the duplicate code for closing the background process dialog into 'test_gui_helper.py',
- Simplify the logic by removing the duplicate steps to insert/update binary paths for each server type from 'test_utils.py'.
2018-10-08 11:09:30 +01:00
Khushboo Vashi 528ea88dec Save the treeview state periodically, and restore it automatically when reconnecting. Fixes #1253 2018-10-08 10:03:19 +01:00
Dave Page 44ef501283 Fix PEP-8 issue. 2018-10-03 10:12:00 +01:00
Usman Muzaffar eb27fa29b1 Test stabilisation, mostly for Windows. Fixes #3136 2018-10-03 10:03:35 +01:00
Identifier Anonymous a89ab88097 Updated Japanese translation. 2018-10-02 15:44:05 +01:00
Dave Page c092495b7a Bump version number for release. 2018-10-01 10:25:19 +01:00
Dave Page 8e2ede21a0 Update message catalogs 2018-10-01 10:23:23 +01:00
Akshay Joshi 15fe26a710 Fix handling of backslashes in the edit grid. Fixes #3551 2018-09-25 10:34:33 -04:00
Dave Page 530017809a Update supported Python versions to match reality. 2018-09-24 16:22:06 -04:00
Akshay Joshi 6c2c18106f Swap the Schema and Schemas icons and Catalog and Catalogs icons that had been used the wrong way around. 2018-09-24 14:11:23 -04:00
Aditya Toshniwal 1b20831338 Ensure changes to Query Tool settings from the Preferences dialogue are applied before executing queries. Fixes #3657 2018-09-19 16:50:11 +01:00
Akshay Joshi 6a46e43567 Ensure the runtime can startup properly if there are wide characters in the logfile path on Windows. Fixes #3464 2018-09-18 15:08:31 +01:00
Akshay Joshi 003889f15e Added null checks and some more startup logs for runtime 2018-09-17 15:16:48 +05:30
Dave Page a2258c83e9 Chance an update to Node 8. Let's see how Jenkins likes this. 2018-09-14 17:04:51 +01:00
Atle Frenvik Sveen 370d9c72ec Update some of the JS dependencies to ensure we can use Node 10. Fixes #3649 2018-09-14 16:55:13 +01:00
Akshay Joshi 026969e3d1 Add a startup logger to the runtime to aid with debugging. 2018-09-14 14:41:52 +01:00
Akshay Joshi 0ec3224212 Fix logic around validation and highlighting of Sort/Filter in the Query Tool. Fixes #3607 2018-09-14 14:18:43 +01:00
Aditya Toshniwal 38ddea038a Fix support for the CLOB datatype in EPAS. Fixes #3596 2018-09-13 13:45:06 +01:00
Akshay Joshi d6c64e8f11 Fixed issue where wrong menu id passed when click on 'View Data' tool button 2018-09-12 11:00:58 +05:30
Akshay Joshi 4010dc80a9 Ensure auto-complete works for objects in schemas other than public and pg_catalog. Fixes #3630 2018-09-11 12:56:14 +01:00
Akshay Joshi 9c5e42c7a6 Used SCSS variables instead of hardcoded value for color and moved code from CSS to SCSS file. 2018-09-11 11:18:37 +05:30
Akshay Joshi 532cbe216a Add shortcuts for View Data and the Query tool to the Browser header bar. Fixes #3564 2018-09-10 15:16:13 +01:00
Dave Page 1850a3a995 Remove unnecessary screenshot. 2018-09-10 13:17:59 +01:00
Usman Muzaffar 22776d2e14 Further stability fixes for the feature tests. 2018-09-10 13:15:55 +01:00
Dave Page 7f1b588c69 Remove package-lock.json to stop Yarn bleating. 2018-09-06 15:00:52 +01:00
Aditya Toshniwal 8f88443e15 Doc updates for optional data point markers and mouse-over tooltips to display values on graphs. Fixes #3514 2018-09-06 14:54:25 +01:00
Aditya Toshniwal a74b9c96c1 Add optional data point markers and mouse-over tooltips to display values on graphs. Fixes #3514
Ensure queries are no longer executed when dashboards are closed. Fixes #3576
2018-09-05 17:25:11 +01:00
Fred Escallier 0e59be237b Update French translation (and rebuild the rest).
1779 of 1994 messages (89%) translated in pgadmin/translations/pl/LC_MESSAGES/messages.po
1994 of 1994 messages (100%) translated in pgadmin/translations/ja/LC_MESSAGES/messages.po
1781 of 1994 messages (89%) translated in pgadmin/translations/ru/LC_MESSAGES/messages.po
1405 of 1994 messages (70%) translated in pgadmin/translations/zh/LC_MESSAGES/messages.po
1449 of 1994 messages (72%) translated in pgadmin/translations/de/LC_MESSAGES/messages.po
1782 of 1994 messages (89%) translated in pgadmin/translations/ko/LC_MESSAGES/messages.po
1994 of 1994 messages (100%) translated in pgadmin/translations/fr/LC_MESSAGES/messages.po
1835 of 1994 messages (92%) translated in pgadmin/translations/es/LC_MESSAGES/messages.po
2018-09-05 17:08:29 +01:00
Identifier Anonymous 6a4689599d Update Japanese translation (and rebuild the rest).
1779 of 1994 messages (89%) translated in pgadmin/translations/pl/LC_MESSAGES/messages.po
1994 of 1994 messages (100%) translated in pgadmin/translations/ja/LC_MESSAGES/messages.po
1781 of 1994 messages (89%) translated in pgadmin/translations/ru/LC_MESSAGES/messages.po
1405 of 1994 messages (70%) translated in pgadmin/translations/zh/LC_MESSAGES/messages.po
1449 of 1994 messages (72%) translated in pgadmin/translations/de/LC_MESSAGES/messages.po
1782 of 1994 messages (89%) translated in pgadmin/translations/ko/LC_MESSAGES/messages.po
1903 of 1994 messages (95%) translated in pgadmin/translations/fr/LC_MESSAGES/messages.po
1835 of 1994 messages (92%) translated in pgadmin/translations/es/LC_MESSAGES/messages.po
2018-09-05 17:03:56 +01:00
Dave Page 6e54a13ba2 Update test cases to match style changes 2018-09-04 12:04:57 +01:00
Dave Page fa1cf7b7a3 Include the 3.4 release notes in the toctree. 2018-09-04 11:47:20 +01:00
Aditya Toshniwal 82c9cca683 Move all CSS into SCSS files for consistency and ease of colour maintenance etc. Fixes #2927 2018-09-04 11:24:51 +01:00
416 changed files with 85759 additions and 32977 deletions

No files matched your search

+1 -1
View File
@@ -27,7 +27,7 @@ Building the Runtime
To build the runtime, the following packages must be installed:
- QT 4.6 or above
- Python 2.6, 2.7 or 3.3+ (2.7 only on Windows)
- Python 2.6, 2.7 or 3.4+
Assuming both qmake and python-config are in the path:
+2
View File
@@ -113,6 +113,8 @@ When you’ve specified the details that will be incorporated into the pg_dump c
.. image:: images/backup_messages.png
:alt: Backup success notification popup
Use the **Stop Process** button to stop the Backup process.
If the backup is successful, a popup window will confirm success. Click *Click here for details* on the popup window to launch the *Process Watcher*. The *Process Watcher* logs all the activity associated with the backup and provides additional information for troubleshooting.
.. image:: images/backup_process_watcher.png
+2
View File
@@ -24,6 +24,8 @@ Click the *Backup* button to build and execute a command based on your selection
.. image:: images/backup_globals_messages.png
:alt: Backup globals success notification popup
Use the **Stop Process** button to stop the Backup process.
If the backup is successful, a popup window will confirm success. Click *Click here for details* on the popup window to launch the *Process Watcher*. The *Process Watcher* logs all the activity associated with the backup and provides additional information for troubleshooting.
.. image:: images/backup_globals_process_watcher.png
+2
View File
@@ -78,6 +78,8 @@ Click the *Backup* button to build and execute a command based on your selection
.. image:: images/backup_server_messages.png
:alt: Backup server success notification popup
Use the **Stop Process** button to stop the Backup process.
If the backup is successful, a popup window will confirm success. Click *Click here for details* on the popup window to launch the *Process Watcher*. The *Process Watcher* logs all the activity associated with the backup and provides additional information for troubleshooting.
.. image:: images/backup_server_process_watcher.png
+22 -4
View File
@@ -4,7 +4,12 @@
`Connecting to a Server`:index:
*******************************
Before you can use the pgAdmin client to manage the objects that reside on your Postgres server, you must define a connection to the server. You can (optionally) use the *Server Group* dialog to create server groups to organize the server connections within the tree control for easier management. To open the *Server Group* dialog, right-click on the *Servers* node of the tree control, and select *Server Group* from the *Create* menu.
Before you can use the pgAdmin client to manage the objects that reside on your
Postgres server, you must define a connection to the server. You can
(optionally) use the *Server Group* dialog to create server groups to organize
the server connections within the tree control for easier management. To open
the *Server Group* dialog, right-click on the *Servers* node of the tree
control, and select *Server Group* from the *Create* menu.
Contents:
@@ -12,7 +17,10 @@ Contents:
server_group_dialog
Use the fields on the *Server* dialog to define the connection properties for each new server that you wish to manage with pgAdmin. To open the *Server* dialog, right-click on the *Servers* node of the tree control, and select *Server* from the *Create* menu.
Use the fields on the *Server* dialog to define the connection properties for
each new server that you wish to manage with pgAdmin. To open the *Server*
dialog, right-click on the *Servers* node of the tree control, and select
*Server* from the *Create* menu.
Contents:
@@ -20,11 +28,21 @@ Contents:
server_dialog
After defining a server connection, right-click on the server name, and select *Connect to server* to authenticate with the server, and start using pgAdmin to manage objects that reside on the server.
After defining a server connection, right-click on the server name, and select
*Connect to server* to authenticate with the server, and start using pgAdmin to
manage objects that reside on the server.
Contents:
.. toctree::
connect_to_server
connect_error
connect_error
Server definitions (and their groups) can be exported to a JSON file and
re-imported to the same or a different system to enable easy pre-configuration
of pgAdmin.
.. toctree::
export_import_servers
+117
View File
@@ -0,0 +1,117 @@
.. _export_import_servers:
****************************************
`Exporting and importing Servers`:index:
****************************************
Server definitions (and their groups) can be exported to a JSON file and
re-imported to the same or a different system to enable easy pre-configuration
of pgAdmin. The ``setup.py`` script is used for this purpose.
.. note:: To export or import servers, you must use the Python interpreter that
is normally used to run pgAdmin to ensure that the required Python
packages are available. In most packages, this can be found in the
Python Virtual Environment that can be found in the installation
directory. When using platform-native packages, the system installation
of Python may be the one used by pgAdmin.
**Exporting Servers**
To export the servers defined in an installation, simply invoke ``setup.py`` with
the ``--dump-servers`` command line option, followed by the name (and if required,
path) to the desired output file. By default, servers owned by the desktop mode
user will be dumped (pgadmin4@pgadmin.org by default - see the DESKTOP_USER
setting in ``config.py``). This can be overridden with the ``--user`` command
line option. For example:
.. code-block:: bash
/path/to/python /path/to/setup.py --dump-servers output_file.json
# or, to specify a non-default user name:
/path/to/python /path/to/setup.py --dump-servers output_file.json --user user@example.com
To export only certain servers, use the ``--servers`` option and list one or
more server IDs. For example:
.. code-block:: bash
/path/to/python /path/to/setup.py --dump-servers output_file.json --server 1 2 5
**Importing Servers**
To import the servers defined in a JSON file, simply invoke ``setup.py`` with
the ``--load-servers`` command line option, followed by the name (and if required,
path) of the JSON file containing the server definitions. Servers will be owned
by the desktop mode user (pgadmin4@pgadmin.org by default - see the DESKTOP_USER
setting in ``config.py``). This can be overridden with the ``--user`` command
line option. For example:
.. code-block:: bash
/path/to/python /path/to/setup.py --load-servers input_file.json
# or, to specify a non-default user name to own the new servers:
/path/to/python /path/to/setup.py --load-servers input_file.json --user user@example.com
If any Servers are defined with a Server Group that is not already present in
the configuration database, the required Group will be created.
**JSON format**
The JSON file format used when importing or exporting servers is quite
straightforward and simply contains a list of servers, with a number of
attributes. The following attributes are required to be present in every server
definition: Name, Group, Port, Username, SSLMode, MaintenanceDB and one of Host,
HostAddr or Service.
Password fields cannot be imported or exported.
The following example shows both a minimally defined and a fully defined server:
.. code-block:: python
{
"Servers": {
"1": {
"Name": "Minimally Defined Server",
"Group": "Server Group 1",
"Port": 5432,
"Username": "postgres",
"Host": "localhost",
"SSLMode": "prefer",
"MaintenanceDB": "postgres"
},
"2": {
"Name: "Fully Defined Server",
"Group": "Server Group 2",
"Host": "host.domain.com",
"HostAddr": "192.168.1.2",
"Port": 5432,
"MaintenanceDB": "postgres",
"Username": "postgres",
"Role": "my_role_name",
"SSLMode": "require",
"Comment": "This server has every option configured in the JSON",
"DBRestriction": "live_db test_db",
"PassFile": "/path/to/pgpassfile",
"SSLCert": "/path/to/sslcert.crt",
"SSLKey": "/path/to/sslcert.key",
"SSLRootCert": "/path/to/sslroot.crt",
"SSLCrl": "/path/to/sslcrl.crl",
"SSLCompression": 1,
"BGColor": "#ff9900",
"FGColor": "#000000",
"Service": "postgresql-10",
"Timeout": 60,
"UseSSHTunnel": 1,
"TunnelHost": "192.168.1.253",
"TunnelPort": 22,
"TunnelUsername": "username",
"TunnelAuthentication": 0
}
}
}
+3 -7
View File
@@ -74,16 +74,12 @@ Contents:
browser
pgadmin_menu_bar
pgadmin_toolbar
pgadmin_tabbed_browser
pgadmin_tree_control
preferences
keyboard_shortcuts
Before using pgAdmin to manage objects that reside on a server, you must define a
connection to the server; for more information please see *Connecting to a Server*:
Contents:
.. toctree::
connecting
connection to the server; for more information please see *Connecting to a Server*
in the next section.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 259 KiB

+2
View File
@@ -53,6 +53,8 @@ After completing the *Import/Export data* dialog, click the *OK* button to perfo
.. image:: images/import_export_complete.png
:alt: Import Export data completion notification
Use the **Stop Process** button to stop the Import/Export process.
Use the *Click here for details* link on the notification to open the *Process Watcher* and review detailed information about the execution of the command that performed the import or export:
.. image:: images/import_export_pw.png
+2
View File
@@ -34,6 +34,8 @@ pgAdmin will inform you when the background process completes:
.. image:: images/maintenance_complete.png
:alt: Maintenance completion notification
Use the **Stop Process** button to stop the Maintenance process.
Use the *Click here for details* link on the notification to open the *Process Watcher* and review detailed information about the execution of the command that performed the import or export:
.. image:: images/maintenance_pw.png
+8 -2
View File
@@ -31,11 +31,17 @@ You can use icons in the *Sessions* table to review or control the state of a se
.. image:: images/main_properties_table.png
:alt: Properties panel
The *Properties* tab displays information about the object selected. Click the *Edit* icon in the toolbar under the browser tabs to launch the *Properties* dialog for the selected object.
The *Properties* tab displays information about the object selected.
Click the *Delete* icon in the toolbar under the browser tab to delete the selected objects in the Properties panel.
Click the *Drop Cascade* icon in the toolbar under the browser tab to delete the selected objects and all dependent objects in the Properties panel.
.. image:: images/main_properties_icons.png
:alt: Object editor icon
Click the *Edit* icon in the toolbar under the browser tabs to launch the *Properties* dialog for the selected object.
To preserve any changes to the *Properties* dialog, click the *Save* icon; your modifications will be displayed in the updated *Properties* tab.
.. image:: images/main_properties_edit.png
@@ -136,4 +142,4 @@ The *Dependents* tab displays a table of objects that depend on the object curre
Additional tabs open when you access the extended functionality offered by pgAdmin tools (such as the Query tool, Debugger, or SQL editor). Use the close icon (X) located in the upper-right corner of each tab to close the tab when you are finished using the tool. Like permanent tabs, these tabs may be repositioned in the pgAdmin client window.
By default, each time you open a tool, pgAdmin will open a new browser tab. You can control this behavior by modifying the *Display* node of the *Preferences* dialog for each tool. To open the *Preferences* dialog, select *Preferences* from the *File* menu.
By default, each time you open a tool, pgAdmin will open a new browser tab. You can control this behavior by modifying the *Display* node of the *Preferences* dialog for each tool. To open the *Preferences* dialog, select *Preferences* from the *File* menu.
+16
View File
@@ -0,0 +1,16 @@
.. _pgadmin_toolbar:
*************************
`pgAdmin Toolbar`:index:
*************************
The pgAdmin toolbar provides shortcut buttons for frequently used features like View Data and the Query Tool
which are most frequently used in pgAdmin.
This toolbar is visible on the Browser panel. Buttons get enabled/disabled based on the selected browser node.
.. image:: /images/pgadmin_toolbar.png
:alt: pgAdmin Toolbar
* Use the :ref:`Query Tool <query_tool>` button to open the Query Tool in the current database context.
* Use the :ref:`View Data <editgrid>` button to view/edit the data stored in a selected table.
* Use the :ref:`Filtered Rows <editgrid>` button to access the Data Filter popup to apply a filter to a set of data for viewing/editing.
+11 -7
View File
@@ -69,9 +69,13 @@ Use the fields on the *Graphs* panel to specify your display preferences for the
.. image:: images/preferences_dashboard_display.png
:alt: Preferences dialog dashboard display options
* When the *Show activity?* switch is set to *True*, activity tables will be displayed on dashboards.
* When the *Show graph data points?* switch is set to *True*, data points will be visible on graph lines.
* When the *Show graphs?* switch is set to *True*, graphs will be displayed on dashboards.
* When the *Show activity?* switch is set to *True*, activity tables will be displayed on dashboards.
* When the *Show mouse hover tooltip?* switch is set to *True*, a tooltip will appear on mouse hover on the graph lines giving the data point details.
**The Debugger Node**
@@ -123,9 +127,9 @@ Use the fields on the *Help* panel to specify the location of help files.
Please note: the default help paths include the *VERSION* placeholder; the $VERSION$ placeholder will be replaced by the current database version.
**The SQL Editor Node**
**The Query Tool Node**
Expand the *SQL Editor* node to access panels that allow you to specify your preferences for the SQL Editor tool.
Expand the *Query Tool* node to access panels that allow you to specify your preferences for the Query Editor tool.
.. image:: images/preferences_sql_auto_completion.png
:alt: Preferences dialog sqleditor auto completion option
@@ -146,13 +150,13 @@ Use the fields on the *CSV Output* panel to control the CSV output.
.. image:: images/preferences_sql_display.png
:alt: Preferences dialog sqleditor display options
Use the fields on the *Display* panel to specify your preferences for the SQL Editor display.
Use the fields on the *Display* panel to specify your preferences for the Query Tool display.
* When the *Connection status* switch is set to *True*, each new instance of the SQL Editor will display connection and transaction status.
* When the *Connection status* switch is set to *True*, each new instance of the Query Tool will display connection and transaction status.
* Use the *Connection status refresh rate* field to specify the number of seconds between connection/transaction status updates.
* When the *Open in new browser tab* switch is set to *True*, each new instance of the SQL Editor will open in a new browser tab.
* When the *Open in new browser tab* switch is set to *True*, each new instance of the Query Tool will open in a new browser tab.
* Use the *Query info notifier timeout* field to control the behaviour of the notifier that is displayed when query execution completes. A value of *-1* will disable the notifier, and a value of 0 will display it until clicked. If a positive value above zero is specified, the notifier will be displayed for the specified number of seconds. The default is *5*.
@@ -203,7 +207,7 @@ Use the fields on the *Results grid* panel to specify your formatting preference
* Use the *Result copy quote character* drop-down listbox to select the quote character for copied data.
* Use the *Result copy quoting* drop-down listbox to select which type of fields require quoting; select *All*, *None*, or *Strings*.
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the sql editor window navigation:
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the query tool window navigation:
.. image:: images/preferences_sql_keyboard_shortcuts.png
:alt: Preferences dialog sql keyboard shortcuts section
+3
View File
@@ -9,6 +9,9 @@ for it.
.. toctree::
release_notes_3_6
release_notes_3_5
release_notes_3_4
release_notes_3_3
release_notes_3_2
release_notes_3_1
+28
View File
@@ -0,0 +1,28 @@
***********
Version 3.4
***********
Release date: 2018-10-04
This release contains a number of features and fixes reported since the release of pgAdmin4 3.3
Features
********
| `Feature #2927 <https://redmine.postgresql.org/issues/2927>`_ - Move all CSS into SCSS files for consistency and ease of colour maintenance etc.
| `Feature #3514 <https://redmine.postgresql.org/issues/3514>`_ - Add optional data point markers and mouse-over tooltips to display values on graphs.
| `Feature #3564 <https://redmine.postgresql.org/issues/3564>`_ - Add shortcuts for View Data and the Query tool to the Browser header bar.
Bug fixes
*********
| `Bug #3464 <https://redmine.postgresql.org/issues/3464>`_ - Ensure the runtime can startup properly if there are wide characters in the logfile path on Windows.
| `Bug #3551 <https://redmine.postgresql.org/issues/3551>`_ - Fix handling of backslashes in the edit grid.
| `Bug #3576 <https://redmine.postgresql.org/issues/3576>`_ - Ensure queries are no longer executed when dashboards are closed.
| `Bug #3596 <https://redmine.postgresql.org/issues/3596>`_ - Fix support for the CLOB datatype in EPAS.
| `Bug #3607 <https://redmine.postgresql.org/issues/3607>`_ - Fix logic around validation and highlighting of Sort/Filter in the Query Tool.
| `Bug #3630 <https://redmine.postgresql.org/issues/3630>`_ - Ensure auto-complete works for objects in schemas other than public and pg_catalog.
| `Bug #3657 <https://redmine.postgresql.org/issues/3657>`_ - Ensure changes to Query Tool settings from the Preferences dialogue are applied before executing queries.
| `Bug #3658 <https://redmine.postgresql.org/issues/3658>`_ - Swap the Schema and Schemas icons and Catalog and Catalogs icons that had been used the wrong way around.
+28
View File
@@ -0,0 +1,28 @@
***********
Version 3.5
***********
Release date: 2018-11-01
This release contains a number of features and fixes reported since the release of pgAdmin4 3.4
Features
********
| `Feature #1253 <https://redmine.postgresql.org/issues/1253>`_ - Save the treeview state periodically, and restore it automatically when reconnecting.
| `Feature #3562 <https://redmine.postgresql.org/issues/3562>`_ - Migrate from Bootstrap 3 to Bootstrap 4.
Bug fixes
*********
| `Bug #3232 <https://redmine.postgresql.org/issues/3232>`_ - Ensure that Utilities(Backup/Restore/Maintenence/Import-Export) should not be started if binary path is wrong and also added 'Stop Process' button to cancel the process.
| `Bug #3638 <https://redmine.postgresql.org/issues/3638>`_ - Fix syntax error when creating new pgAgent schedules with a start date/time and exception.
| `Bug #3674 <https://redmine.postgresql.org/issues/3674>`_ - Cleanup session files periodically.
| `Bug #3660 <https://redmine.postgresql.org/issues/3660>`_ - Rename the 'SQL Editor' section of the Preferences to 'Query Tool' as it applies to the whole tool, not just the editor.
| `Bug #3676 <https://redmine.postgresql.org/issues/3676>`_ - Fix CREATE Script functionality for EDB-Wrapped functions.
| `Bug #3700 <https://redmine.postgresql.org/issues/3700>`_ - Fix connection garbage collector.
| `Bug #3703 <https://redmine.postgresql.org/issues/3703>`_ - Purge connections from the cache on logout.
| `Bug #3722 <https://redmine.postgresql.org/issues/3722>`_ - Ensure that utility existence check should work for schema and other child objects while taking Backup/Restore.
| `Bug #3730 <https://redmine.postgresql.org/issues/3730>`_ - Fixed fatal error while launching the pgAdmin4 3.5. Update the version of the Flask to 0.12.4 for release.
+27
View File
@@ -0,0 +1,27 @@
***********
Version 3.6
***********
Release date: 2018-11-29
This release contains a number of features and fixes reported since the release of pgAdmin4 3.5
Features
********
| `Feature #1513 <https://redmine.postgresql.org/issues/1513>`_ - Add support for dropping multiple objects at once from the collection Properties panel.
| `Feature #3772 <https://redmine.postgresql.org/issues/3772>`_ - Add the ability to import and export server definitions from a config database.
Bug fixes
*********
| `Bug #3016 <https://redmine.postgresql.org/issues/3016>`_ - Ensure previous notices are not removed from the Messages tab in the Query Tool if an error occurs during query execution.
| `Bug #3029 <https://redmine.postgresql.org/issues/3029>`_ - Allow the selection order to be preserved in the Select2 control to fix column ordering in data Import/Export.
| `Bug #3629 <https://redmine.postgresql.org/issues/3629>`_ - Allow use of 0 (integer) and empty strings as parameters in the debugger.
| `Bug #3723 <https://redmine.postgresql.org/issues/3723>`_ - Properly report errors when debugging cannot be started.
| `Bug #3734 <https://redmine.postgresql.org/issues/3734>`_ - Prevent the debugger controls being pressed again before previous processing is complete.
| `Bug #3736 <https://redmine.postgresql.org/issues/3736>`_ - Fix toggle breakpoints buttons in the debugger.
| `Bug #3742 <https://redmine.postgresql.org/issues/3742>`_ - Fix changes to the NOT NULL and default value options in the Table Dialogue.
| `Bug #3746 <https://redmine.postgresql.org/issues/3746>`_ - Fix dropping of multiple functions/procedures at once.
+2
View File
@@ -84,6 +84,8 @@ When you’ve specified the details that will be incorporated into the pg_restor
.. image:: images/restore_messages.png
:alt: Restore dialog notifications
Use the **Stop Process** button to stop the Restore process.
Click *Click here for details* on the popup to launch the *Process Watcher*. The *Process Watcher* logs all the activity associated with the restore, and provides additional information for troubleshooting should the restore command encounter problems.
.. image:: images/restore_process_watcher.png
+4 -3
View File
@@ -8,8 +8,8 @@
#########################################################################
# First of all, build frontend with NodeJS in a separate builder container
# Node-6 with ABI v48 is supported by all needed C++ packages
FROM node:6 AS node-builder
# Node-8 is supported by all needed C++ packages
FROM node:8 AS node-builder
COPY ./pgadmin4/web/ /pgadmin4/web/
WORKDIR /pgadmin4/web
@@ -45,7 +45,8 @@ ENV PYTHONPATH=/pgadmin4
# so that deps do not increase the size of resulting image by remaining in layers
COPY ./pgadmin4/requirements.txt /pgadmin4
RUN set -ex && \
apk add --no-cache --virtual build-deps build-base postgresql-dev libffi-dev && \
apk add --no-cache --virtual build-deps build-base postgresql-dev libffi-dev linux-headers && \
pip install --upgrade pip && \
pip install --no-cache-dir -r requirements.txt && \
apk del --no-cache build-deps
+2 -1
View File
@@ -6,7 +6,7 @@
blinker==1.4
extras==1.0.0
fixtures==3.0.0
Flask==0.12.2
Flask==0.12.4
Flask-Gravatar==0.5.0
Flask-Login==0.3.2
Flask-Mail==0.9.1
@@ -28,6 +28,7 @@ speaklater==1.3
sqlparse==0.2.4
WTForms==2.1
Flask-Paranoid==0.2.0
psutil==5.4.7
################################################################
# Modules specifically requires for Python2.7 or greater version
+2 -2
View File
@@ -8,9 +8,9 @@
<key>CFBundleName</key>
<string>pgAdmin 4</string>
<key>CFBundleShortVersionString</key>
<string>3.3.0</string>
<string>3.6.0</string>
<key>CFBundleVersion</key>
<string>3.3.0</string>
<string>3.6.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NSHumanReadableCopyright</key>
+59
View File
@@ -0,0 +1,59 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Logger.cpp - Logger Utility
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "Logger.h"
Logger* Logger::m_pThis = NULL;
QString Logger::m_sFileName = "";
QFile* Logger::m_Logfile = NULL;
Logger::Logger()
{
}
Logger::~Logger()
{
}
Logger* Logger::GetLogger()
{
if (m_pThis == NULL)
{
m_pThis = new Logger();
m_sFileName = QDir::homePath() + (QString("/.%1.startup.log").arg(PGA_APP_NAME)).remove(" ");
m_Logfile = new QFile;
m_Logfile->setFileName(m_sFileName);
m_Logfile->open(QIODevice::WriteOnly | QIODevice::Text);
m_Logfile->setPermissions(QFile::ReadOwner|QFile::WriteOwner);
}
return m_pThis;
}
void Logger::Log(const QString& sMessage)
{
QString text = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss ") + sMessage + "\n";
if (m_Logfile != NULL)
{
QTextStream out(m_Logfile);
out << text;
}
}
void Logger::ReleaseLogger()
{
if (m_pThis != NULL)
{
if(m_Logfile != NULL)
m_Logfile->close();
delete m_pThis;
m_pThis = NULL;
}
}
+34
View File
@@ -0,0 +1,34 @@
//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Logger.h - Logger Utility
//
//////////////////////////////////////////////////////////////////////////
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
class Logger : public QObject
{
public:
static Logger* GetLogger();
static void ReleaseLogger();
void Log(const QString& sMessage);
private:
Logger();
virtual ~Logger();
private:
static Logger* m_pThis;
static QString m_sFileName;
static QFile *m_Logfile;
};
#endif // LOGGER_H
+74 -12
View File
@@ -10,6 +10,7 @@
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
#include "Logger.h"
// Must be before QT
#include <Python.h>
@@ -175,6 +176,10 @@ Server::Server(quint16 port, QString key, QString logFileName)
qDebug() << "Python path: " << python_path
<< "\nPython Home: " << pythonHome;
Logger::GetLogger()->Log(QString("Python Path: %1").arg(python_path));
Logger::GetLogger()->Log(QString("Python Home: %1").arg(pythonHome));
if (!pythonHome.isEmpty())
{
pythonHome_utf8 = pythonHome.toUtf8();
@@ -190,36 +195,80 @@ Server::Server(quint16 port, QString key, QString logFileName)
#endif
}
Logger::GetLogger()->Log("Initializing Python...");
Py_Initialize();
Logger::GetLogger()->Log("Python initialized.");
// Get the current path
PyObject* sysPath = PySys_GetObject((char*)"path");
// Add new additional path elements
for (i = path_list.size() - 1; i >= 0 ; --i)
if (sysPath != NULL)
{
// Add new additional path elements
Logger::GetLogger()->Log("Adding new additional path elements");
for (i = path_list.size() - 1; i >= 0 ; --i)
{
#ifdef PYTHON2
PyList_Append(sysPath, PyString_FromString(path_list.at(i).toUtf8().data()));
PyList_Append(sysPath, PyString_FromString(path_list.at(i).toUtf8().data()));
#else
#if PY_MINOR_VERSION > 2
PyList_Append(sysPath, PyUnicode_DecodeFSDefault(path_list.at(i).toUtf8().data()));
PyList_Append(sysPath, PyUnicode_DecodeFSDefault(path_list.at(i).toUtf8().data()));
#else
PyList_Append(sysPath, PyBytes_FromString(path_list.at(i).toUtf8().data()));
PyList_Append(sysPath, PyBytes_FromString(path_list.at(i).toUtf8().data()));
#endif
#endif
}
}
else
Logger::GetLogger()->Log("Unable to get the current path.");
// Redirect stderr
Logger::GetLogger()->Log("Redirecting stderr...");
PyObject *sys = PyImport_ImportModule("sys");
if (sys != NULL)
{
PyObject *err = NULL;
#ifdef PYTHON2
PyObject *err = PyFile_FromString(m_logFileName.toUtf8().data(), (char *)"w");
err = PyFile_FromString(m_logFileName.toUtf8().data(), (char *)"w");
#else
FILE *log = fopen(m_logFileName.toUtf8().data(), (char *)"w");
int fd = fileno(log);
PyObject *err = PyFile_FromFd(fd, NULL, (char *)"w", -1, NULL, NULL, NULL, 0);
FILE *log = NULL;
#if defined(Q_OS_WIN)
char *logFile = m_logFileName.toUtf8().data();
size_t fileSize = strlen(logFile) + 1;
wchar_t * wcLogFileName = new wchar_t[fileSize];
mbstowcs (wcLogFileName, logFile, fileSize);
log = _wfopen(wcLogFileName, (wchar_t *)"w");
#else
log = fopen(m_logFileName.toUtf8().data(), (char *)"w");
#endif
QFile(m_logFileName).setPermissions(QFile::ReadOwner|QFile::WriteOwner);
PyObject_SetAttrString(sys, "stderr", err);
if (log != NULL)
{
int fd = fileno(log);
err = PyFile_FromFd(fd, NULL, (char *)"w", -1, NULL, NULL, NULL, 0);
}
else
Logger::GetLogger()->Log(QString("Failed to open log file: %1").arg(m_logFileName));
#if defined(Q_OS_WIN)
if (wcLogFileName != NULL)
{
delete wcLogFileName;
wcLogFileName = NULL;
}
#endif
#endif
QFile(m_logFileName).setPermissions(QFile::ReadOwner|QFile::WriteOwner);
if (err != NULL)
{
PyObject_SetAttrString(sys, "stderr", err);
Logger::GetLogger()->Log("stderr redirected successfully.");
}
else
Logger::GetLogger()->Log(QString("Failed to get the file pointer of: %1 ").arg(m_logFileName));
}
else
Logger::GetLogger()->Log("Failed to import 'sys' module.");
}
Server::~Server()
@@ -263,12 +312,14 @@ bool Server::Init()
if (QFile::exists(m_appfile))
{
qDebug() << "Webapp path: " << m_appfile;
Logger::GetLogger()->Log(QString("Webapp Path: %1").arg(m_appfile));
break;
}
}
if (!QFile::exists(m_appfile))
{
Logger::GetLogger()->Log("Failed to locate pgAdmin4.py, terminating server thread.");
setError(tr("Failed to locate pgAdmin4.py, terminating server thread."));
return false;
}
@@ -279,14 +330,17 @@ bool Server::Init()
void Server::run()
{
// Open the application code and run it.
Logger::GetLogger()->Log("Open the application code and run it.");
FILE *cp = fopen(m_appfile.toUtf8().data(), "r");
if (!cp)
{
Logger::GetLogger()->Log(QString(tr("Failed to open the application file: %1, server thread exiting.")).arg(m_appfile));
setError(QString(tr("Failed to open the application file: %1, server thread exiting.")).arg(m_appfile));
return;
}
// Set the port number and key, and force SERVER_MODE off.
Logger::GetLogger()->Log("Set the port number, key and force SERVER_MODE off");
PyRun_SimpleString(QString("PGADMIN_PORT = %1").arg(m_port).toLatin1());
PyRun_SimpleString(QString("PGADMIN_KEY = '%1'").arg(m_key).toLatin1());
PyRun_SimpleString(QString("SERVER_MODE = False").toLatin1());
@@ -303,10 +357,14 @@ void Server::run()
char* n_argv[] = { m_appfile_utf8.data() };
PySys_SetArgv(1, n_argv);
Logger::GetLogger()->Log("PyRun_SimpleFile launching application server...");
PyObject* PyFileObject = PyFile_FromString(m_appfile_utf8.data(), (char *)"r");
int ret = PyRun_SimpleFile(PyFile_AsFile(PyFileObject), m_appfile_utf8.data());
if (ret != 0)
{
Logger::GetLogger()->Log("Failed to launch the application server, server thread exiting.");
setError(tr("Failed to launch the application server, server thread exiting."));
}
#else
/*
* Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later
@@ -321,8 +379,12 @@ void Server::run()
wchar_t* n_argv[] = { wcAppName };
PySys_SetArgv(1, n_argv);
Logger::GetLogger()->Log("PyRun_SimpleFile launching application server...");
if (PyRun_SimpleFile(cp, m_appfile_utf8.data()) != 0)
{
Logger::GetLogger()->Log("Failed to launch the application server, server thread exiting.");
setError(tr("Failed to launch the application server, server thread exiting."));
}
#endif
fclose(cp);
+24 -1
View File
@@ -36,6 +36,7 @@
#include "TrayIcon.h"
#include "MenuActions.h"
#include "FloatingWindow.h"
#include "Logger.h"
#include <QTime>
@@ -217,6 +218,7 @@ int main(int argc, char * argv[])
menuActions->setLogFile(logFileName);
splash->showMessage(QString(QWidget::tr("Checking for system tray...")), Qt::AlignBottom | Qt::AlignCenter);
Logger::GetLogger()->Log("Checking for system tray...");
// Check system tray is available or not. If not then create one floating window.
FloatingWindow *floatingWindow = NULL;
@@ -235,13 +237,15 @@ int main(int argc, char * argv[])
else
{
splash->showMessage(QString(QWidget::tr("System tray not found, creating floating window...")), Qt::AlignBottom | Qt::AlignCenter);
Logger::GetLogger()->Log("System tray not found, creating floating window...");
// Unable to find tray icon, so creating floting window
floatingWindow = new FloatingWindow();
if (floatingWindow == NULL)
{
QString error = QString(QWidget::tr("Unable to initialize either a tray icon or control window."));
QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
Logger::GetLogger()->Log(error);
Logger::ReleaseLogger();
exit(1);
}
@@ -256,10 +260,14 @@ int main(int argc, char * argv[])
bool done = false;
splash->showMessage(QString(QWidget::tr("Starting pgAdmin4 server...")), Qt::AlignBottom | Qt::AlignCenter);
Logger::GetLogger()->Log("Starting pgAdmin4 server...");
while (done != true)
{
QString msg = QString(QWidget::tr("Creating server object, port:%1, key:%2, logfile:%3")).arg(port).arg(key).arg(logFileName);
Logger::GetLogger()->Log(msg);
server = new Server(port, key, logFileName);
Logger::GetLogger()->Log("Initializing server...");
if (!server->Init())
{
splash->finish(NULL);
@@ -269,9 +277,14 @@ int main(int argc, char * argv[])
QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError());
QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
Logger::GetLogger()->Log(error);
Logger::ReleaseLogger();
exit(1);
}
Logger::GetLogger()->Log("Server initialized.");
Logger::GetLogger()->Log("Starting Server Thread...");
server->start();
// This is a hack to give the server a chance to start and potentially fail. As
@@ -290,6 +303,7 @@ int main(int argc, char * argv[])
QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError());
QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
Logger::GetLogger()->Log(error);
// Allow the user to tweak the Python Path if needed
bool ok;
@@ -315,6 +329,7 @@ int main(int argc, char * argv[])
}
else
{
Logger::ReleaseLogger();
exit(1);
}
@@ -329,6 +344,7 @@ int main(int argc, char * argv[])
// Generate the app server URL
QString appServerUrl = QString("http://127.0.0.1:%1/?key=%2").arg(port).arg(key);
Logger::GetLogger()->Log(QString(QWidget::tr("Application Server URL: %1")).arg(appServerUrl));
// Read the server connection timeout from the registry or set the default timeout.
int timeout = settings.value("ConnectionTimeout", 30).toInt();
@@ -339,6 +355,7 @@ int main(int argc, char * argv[])
QTime endTime = QTime::currentTime().addSecs(timeout);
bool alive = false;
Logger::GetLogger()->Log("The server should be up, we'll attempt to connect and get a response. Ping the server");
while(QTime::currentTime() <= endTime)
{
alive = PingServer(QUrl(appServerUrl));
@@ -352,12 +369,14 @@ int main(int argc, char * argv[])
}
// Attempt to connect one more time in case of a long network timeout while looping
Logger::GetLogger()->Log("Attempt to connect one more time in case of a long network timeout while looping");
if (!alive && !PingServer(QUrl(appServerUrl)))
{
splash->finish(NULL);
QString error(QWidget::tr("The application server could not be contacted."));
QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
Logger::ReleaseLogger();
exit(1);
}
@@ -392,6 +411,8 @@ int main(int argc, char * argv[])
QString error(QWidget::tr("Failed to open the system default web browser. Is one installed?."));
QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error);
Logger::GetLogger()->Log(error);
Logger::ReleaseLogger();
exit(1);
}
}
@@ -402,6 +423,8 @@ int main(int argc, char * argv[])
if (floatingWindow != NULL)
floatingWindow->show();
Logger::GetLogger()->Log("Everything works fine, successfully started pgAdmin4.");
Logger::ReleaseLogger();
return app.exec();
}
+5 -3
View File
@@ -1,4 +1,4 @@
VERSION = 3.3.0.0
VERSION = 3.6.0.0
QMAKE_TARGET_COMPANY = "The pgAdmin Development Team"
QMAKE_TARGET_PRODUCT = "pgAdmin 4"
QMAKE_TARGET_DESCRIPTION = "pgAdmin 4 Desktop Runtime"
@@ -93,14 +93,16 @@ HEADERS = Server.h \
TrayIcon.h \
LogWindow.h \
MenuActions.h \
FloatingWindow.h
FloatingWindow.h \
Logger.h
SOURCES = pgAdmin4.cpp \
Server.cpp \
ConfigWindow.cpp \
TrayIcon.cpp \
LogWindow.cpp \
MenuActions.cpp \
FloatingWindow.cpp
FloatingWindow.cpp \
Logger.cpp
FORMS = ConfigWindow.ui \
LogWindow.ui \
FloatingWindow.ui
+14 -3
View File
@@ -50,7 +50,7 @@ APP_ICON = 'pg-icon'
# Application version number components
APP_RELEASE = 3
APP_REVISION = 3
APP_REVISION = 6
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
# for GA releases.
@@ -59,7 +59,7 @@ APP_SUFFIX = ''
# Numeric application version for upgrade checks. Should be in the format:
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
# zero if needed, and Z represents the suffix, with a leading zero if needed
APP_VERSION_INT = 30300
APP_VERSION_INT = 30600
# DO NOT CHANGE!
# The application version string, constructed from the components
@@ -118,7 +118,7 @@ NODE_BLACKLIST = []
# from it, notably various paths such as LOG_FILE and anything
# using DATA_DIR.
if builtins.SERVER_MODE is None:
if (not hasattr(builtins, 'SERVER_MODE')) or builtins.SERVER_MODE is None:
SERVER_MODE = True
else:
SERVER_MODE = builtins.SERVER_MODE
@@ -387,6 +387,17 @@ SESSION_SKIP_PATHS = [
'/misc/ping'
]
##########################################################################
# Session expiration support
##########################################################################
# SESSION_EXPIRATION_TIME is the interval in Days. Session will be
# expire after the specified number of *days*.
SESSION_EXPIRATION_TIME = 1
# CHECK_SESSION_FILES_INTERVAL is interval in Hours. Application will check
# the session files for cleanup after specified number of *hours*.
CHECK_SESSION_FILES_INTERVAL = 24
##########################################################################
# SSH Tunneling supports only for Python 2.7 and 3.4+
##########################################################################
+33
View File
@@ -0,0 +1,33 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""Added state of the utility process
Revision ID: b5b87fdfcb30
Revises: ece2e76bf60e
Create Date: 2018-10-24 12:37:59.487969
"""
from pgadmin.model import db
# revision identifiers, used by Alembic.
revision = 'b5b87fdfcb30'
down_revision = 'ece2e76bf60e'
branch_labels = None
depends_on = None
def upgrade():
db.engine.execute(
'ALTER TABLE process ADD COLUMN process_state INTEGER DEFAULT 0'
)
def downgrade():
pass
+34
View File
@@ -0,0 +1,34 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
""" Added utility pid to stop process
Revision ID: ece2e76bf60e
Revises: ca00ec32581b
Create Date: 2018-10-18 14:45:13.483068
"""
from pgadmin.model import db
# revision identifiers, used by Alembic.
revision = 'ece2e76bf60e'
down_revision = 'ca00ec32581b'
branch_labels = None
depends_on = None
def upgrade():
db.engine.execute(
'ALTER TABLE process ADD COLUMN utility_pid INTEGER'
)
def downgrade():
pass
+11 -6
View File
@@ -4,6 +4,7 @@
"axios-mock-adapter": "^1.14.1",
"babel-core": "~6.24.0",
"babel-loader": "~7.1.2",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-airbnb": "^2.4.0",
"babel-preset-es2015": "~6.24.0",
"babel-preset-react": "~6.23.0",
@@ -32,8 +33,10 @@
"karma-webpack": "~2.0.13",
"node-sass": "^4.5.3",
"optimize-css-assets-webpack-plugin": "^2.0.0",
"popper.js": "^1.14.4",
"raw-loader": "^0.5.1",
"sass-loader": "^6.0.6",
"sass-loader": "^7.1.0",
"sass-resources-loader": "^1.3.3",
"style-loader": "^0.18.2",
"uglifyjs-webpack-plugin": "^0.4.6",
"url-loader": "^0.5.9",
@@ -54,7 +57,7 @@
"backgrid-select-all": "^0.3.5",
"backgrid-sizeable-columns": "^0.1.1",
"bignumber.js": "^6.0.0",
"bootstrap": "^3.3.7",
"bootstrap": "^4.1.3",
"bootstrap-datepicker": "^1.7.0",
"bootstrap-switch": "3.3.4",
"bowser": "1.6.1",
@@ -63,11 +66,10 @@
"css-loader": "0.14.0",
"cssnano": "^3.10.0",
"dropzone": "^5.1.1",
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
"exports-loader": "~0.6.4",
"flotr2": "^0.1.0",
"flotr2": "git+https://github.com/EnterpriseDB/Flotr2.git",
"font-awesome": "^4.7.0",
"hard-source-webpack-plugin": "^0.4.9",
"hard-source-webpack-plugin": "0.12.0",
"immutability-helper": "^2.2.0",
"imports-loader": "^0.7.1",
"ip-address": "^5.8.9",
@@ -76,6 +78,7 @@
"jquery-ui": "^1.12.1",
"leaflet": "^1.3.3",
"moment": "^2.20.1",
"moment-timezone": "^0.5.21",
"mousetrap": "^1.6.1",
"prop-types": "^15.5.10",
"react": "^16.2.0",
@@ -89,10 +92,12 @@
"spectrum-colorpicker": "^1.8.0",
"sprintf-js": "^1.1.1",
"tablesorter": "^2.30.6",
"tempusdominus-bootstrap-4": "^5.1.2",
"tempusdominus-core": "^5.0.3",
"underscore": "^1.8.3",
"underscore.string": "^3.3.4",
"watchify": "~3.9.0",
"webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker",
"webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker/#5fbdf05a6757464d2fa2c2b017322c97688b2105",
"wkx": "^0.4.5"
},
"scripts": {
+12
View File
@@ -32,6 +32,7 @@ from pgadmin.utils import PgAdminModule, driver
from pgadmin.utils.preferences import Preferences
from pgadmin.utils.session import create_session_interface, pga_unauthorised
from pgadmin.utils.versioned_template_loader import VersionedTemplateLoader
from datetime import timedelta
# If script is running under python3, it will not have the xrange function
# defined
@@ -354,6 +355,10 @@ def create_app(app_name=None):
# register custom unauthorised handler.
app.login_manager.unauthorized_handler(pga_unauthorised)
# Set the permanent session lifetime to the specified value in config file.
app.permanent_session_lifetime = timedelta(
days=config.SESSION_EXPIRATION_TIME)
app.session_interface = create_session_interface(
app, config.SESSION_SKIP_PATHS
)
@@ -545,6 +550,13 @@ def create_app(app_name=None):
def force_session_write(app, user):
session.force_write = True
@user_logged_out.connect_via(app)
def clear_current_user_connections(app, user):
from config import PG_DEFAULT_DRIVER
from pgadmin.utils.driver import get_driver
_driver = get_driver(PG_DEFAULT_DRIVER)
_driver.gc_own()
##########################################################################
# Load plugin modules
##########################################################################
+1 -1
View File
@@ -24,7 +24,7 @@
<div class="col-sm-9">{{ info.current_user }}</div>
</div>
<div class="row">
<div class="col-xs-12 text-right"><img
<div class="col-12 text-right"><img
src="{{ url_for('static', filename='img/logo-right-128.png') }}"
alt="{{ config.APP_NAME }} {{ _('logo') }}"
></div>
-2
View File
@@ -67,11 +67,9 @@ class BrowserModule(PgAdminModule):
('static', 'vendor/codemirror/addon/dialog/dialog.css'),
('static', context_menu_file),
('static', wcdocker_file),
('browser.static', 'css/browser.css'),
('browser.static', 'vendor/aciTree/css/aciTree.css')
]:
stylesheets.append(url_for(endpoint, filename=filename))
stylesheets.append(url_for('browser.browser_css'))
return stylesheets
def get_own_javascripts(self):
@@ -28,6 +28,16 @@ def register_browser_preferences(self):
True, category_label=gettext('Display')
)
self.preference.register(
'display', 'browser_tree_state_save_interval',
gettext("Browser tree state saving interval"), 'integer',
30, category_label=gettext('Display'),
help_str=gettext(
'Browser tree state saving interval in seconds.'
'Use -1 to disable the tree saving mechanism.'
)
)
self.table_row_count_threshold = self.preference.register(
'properties', 'table_row_count_threshold',
gettext("Count rows if estimated less than"), 'integer', 2000,
@@ -232,6 +242,36 @@ def register_browser_preferences(self):
fields=fields
)
self.preference.register(
'keyboard_shortcuts',
'grid_menu_drop_multiple',
gettext('Delete/Drop multiple objects'),
'keyboardshortcut',
{
'alt': True,
'shift': True,
'control': False,
'key': {'key_code': 77, 'char': 'm'}
},
category_label=gettext('Keyboard shortcuts'),
fields=fields
)
self.preference.register(
'keyboard_shortcuts',
'grid_menu_drop_cascade_multiple',
gettext('Drop Cascade multiple objects'),
'keyboardshortcut',
{
'alt': True,
'shift': True,
'control': False,
'key': {'key_code': 85, 'char': 'u'}
},
category_label=gettext('Keyboard shortcuts'),
fields=fields
)
self.preference.register(
'keyboard_shortcuts',
'context_menu',
@@ -585,7 +585,8 @@ class ServerNode(PGChildNodeView):
server_icon_and_background(connected, manager, server),
True,
self.node_type,
connected=False,
connected=connected,
user=manager.user_info if connected else None,
server_type='pg' # default server type
)
)
@@ -104,7 +104,7 @@ class DatabaseView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'nodes': [
{'get': 'node'},
@@ -222,6 +222,15 @@ class DatabaseView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
for row in res['rows']:
if self.manager.db == row['name']:
connected = True
row['canDrop'] = False
else:
conn = self.manager.connection(row['name'], did=row['did'])
connected = conn.connected()
row['canDrop'] = True
return ajax_response(
response=res['rows'],
status=200
@@ -733,44 +742,54 @@ class DatabaseView(PGChildNodeView):
)
@check_precondition(action="drop")
def delete(self, gid, sid, did):
def delete(self, gid, sid, did=None):
"""Delete the database."""
default_conn = self.manager.connection()
SQL = render_template(
"/".join([self.template_path, 'delete.sql']),
did=did, conn=self.conn
)
status, res = default_conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
if res is None:
return make_json_response(
status=410,
success=0,
errormsg=_(
'Error: Object not found.'
),
info=_(
'The specified database could not be found.\n'
)
if did is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [did]}
status = self.manager.release(did=did)
for did in data['ids']:
default_conn = self.manager.connection()
SQL = render_template(
"/".join([self.template_path, 'delete.sql']),
datname=res, conn=self.conn
did=did, conn=self.conn
)
status, msg = default_conn.execute_scalar(SQL)
status, res = default_conn.execute_scalar(SQL)
if not status:
# reconnect if database drop failed.
conn = self.manager.connection(did=did, auto_reconnect=True)
status, errmsg = conn.connect()
return internal_server_error(errormsg=res)
return internal_server_error(errormsg=msg)
if res is None:
return make_json_response(
status=410,
success=0,
errormsg=_(
'Error: Object not found.'
),
info=_(
'The specified database could not be found.\n'
)
)
else:
status = self.manager.release(did=did)
SQL = render_template(
"/".join([self.template_path, 'delete.sql']),
datname=res, conn=self.conn
)
status, msg = default_conn.execute_scalar(SQL)
if not status:
# reconnect if database drop failed.
conn = self.manager.connection(did=did,
auto_reconnect=True)
status, errmsg = conn.connect()
return internal_server_error(errormsg=msg)
return make_json_response(success=1)
@@ -164,12 +164,12 @@ class CastView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'children': [{
'get': 'children'
}],
'delete': [{'delete': 'delete'}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
'msql': [{'get': 'msql'}, {'get': 'msql'}],
@@ -443,7 +443,7 @@ class CastView(PGChildNodeView):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, cid):
def delete(self, gid, sid, did, cid=None):
"""
This function will drop the cast object
:param cid: cast id
@@ -459,50 +459,54 @@ class CastView(PGChildNodeView):
else:
cascade = False
try:
# Get name for cast from cid
sql = render_template("/".join([self.template_path, 'delete.sql']),
cid=cid)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
if not res['rows']:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified cast object could not be found.\n'
)
)
# drop cast
result = res['rows'][0]
sql = render_template("/".join([self.template_path, 'delete.sql']),
castsource=result['castsource'],
casttarget=result['casttarget'],
cascade=cascade
)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Cast dropped"),
data={
'id': cid,
'sid': sid,
'gid': gid,
'did': did
}
if cid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [cid]}
except Exception as e:
return internal_server_error(errormsg=str(e))
for cid in data['ids']:
try:
# Get name for cast from cid
sql = render_template("/".join([self.template_path,
'delete.sql']),
cid=cid)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
if not res['rows']:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified cast object could not be found.\n'
)
)
# drop cast
result = res['rows'][0]
sql = render_template("/".join([self.template_path,
'delete.sql']),
castsource=result['castsource'],
casttarget=result['casttarget'],
cascade=cascade
)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
except Exception as e:
return internal_server_error(errormsg=str(e))
return make_json_response(
success=1,
info=gettext("Cast dropped")
)
@check_precondition
def msql(self, gid, sid, did, cid=None):
@@ -58,6 +58,7 @@ define('pgadmin.node.cast', [
// Define the backform model for cast node
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'oid',
defaults: {
name: undefined, // Name of the cast
encoding: 'UTF8',
@@ -13,6 +13,7 @@
{% else %}
SELECT
ca.oid,
ca.oid as id,
CASE
WHEN {{datlastsysoid}}::OID > ca.oid then True ELSE False
END AS syscast,
@@ -56,4 +57,4 @@
ca.oid > {{datlastsysoid}}::OID
{% endif %}
ORDER BY st.typname, tt.typname
{% endif %}
{% endif %}
@@ -0,0 +1,73 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from __future__ import print_function
import json
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as cast_utils
class CastsMultipleDeleteTestCase(BaseTestGenerator):
""" This class will delete the cast node added under database node. """
skip_on_database = ['gpdb']
scenarios = [
# Fetching default URL for cast node.
('Check Cast Node', dict(url='/browser/cast/obj/'))
]
def setUp(self):
super(CastsMultipleDeleteTestCase, self).setUp()
self.default_db = self.server["db"]
self.database_info = parent_node_dict['database'][-1]
self.db_name = self.database_info['db_name']
self.server["db"] = self.db_name
self.source_type = 'money'
self.target_type = 'bigint'
self.cast_id = cast_utils.create_cast(self.server, self.source_type,
self.target_type)
def runTest(self):
""" This function will delete added cast."""
self.server_id = self.database_info["server_id"]
self.db_id = self.database_info['db_id']
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to database.")
connection = utils.get_db_connection(self.server['db'],
self.server['username'],
self.server['db_password'],
self.server['host'],
self.server['port'],
self.server['sslmode'])
response = cast_utils.verify_cast(connection, self.source_type,
self.target_type)
if len(response) == 0:
raise Exception("Could not find cast.")
delete_response = self.tester.delete(
self.url + str(utils.SERVER_GROUP) + '/' +
str(self.server_id) + '/' + str(self.db_id) +
'/',
data=json.dumps({'ids': [self.cast_id]}),
content_type='html/json',
follow_redirects=True)
self.assertEquals(delete_response.status_code, 200)
def tearDown(self):
"""This function will disconnect test database."""
database_utils.disconnect_database(self, self.server_id,
self.db_id)
self.server['db'] = self.default_db
@@ -164,7 +164,7 @@ class EventTriggerView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'children': [{'get': 'children'}],
@@ -478,7 +478,7 @@ class EventTriggerView(PGChildNodeView):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, etid):
def delete(self, gid, sid, did, etid=None):
"""
This function will delete an existing event trigger object.
@@ -497,48 +497,51 @@ class EventTriggerView(PGChildNodeView):
cascade = True
else:
cascade = False
if etid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [etid]}
try:
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
etid=etid
)
status, name = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=name)
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified event trigger could not be found.\n'
)
for etid in data['ids']:
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
etid=etid
)
status, name = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=name)
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
name=name, cascade=cascade
)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified event trigger could not be found.\n'
)
)
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
name=name, cascade=cascade
)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Event trigger dropped"),
data={
'id': etid,
'sid': sid,
'gid': gid,
'did': did
}
info=gettext("Event trigger dropped")
)
except Exception as e:
return internal_server_error(errormsg=str(e))
return internal_server_error(errormsg=str(e))
@check_precondition
def msql(self, gid, sid, did, etid=None):
@@ -12,6 +12,8 @@ define('pgadmin.node.event_trigger', [
label: gettext('Event Trigger'),
type: 'coll-event_trigger',
columns: ['name', 'eventowner', 'comment'],
canDrop: true,
canDropCascade: false,
});
}
@@ -55,6 +57,7 @@ define('pgadmin.node.event_trigger', [
},
// Define the model for event trigger node
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'oid',
defaults: {
oid: undefined,
name: undefined,
@@ -0,0 +1,107 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import uuid
import json
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.utils import server_utils as server_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression import trigger_funcs_utils
from regression.python_test_utils import test_utils as utils
from . import utils as event_trigger_utils
class EventTriggerMultipleDeleteTestCase(BaseTestGenerator):
""" This class will delete added event trigger under test database. """
scenarios = [
# Fetching default URL for event trigger node.
('Fetch Event Trigger Node URL',
dict(url='/browser/event_trigger/obj/'))
]
def setUp(self):
self.schema_data = parent_node_dict['schema'][-1]
self.server_id = self.schema_data['server_id']
self.db_id = self.schema_data['db_id']
self.schema_name = self.schema_data['schema_name']
self.schema_id = self.schema_data['schema_id']
self.extension_name = "postgres_fdw"
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.db_user = self.server["username"]
self.func_name = "trigger_func_%s" % str(uuid.uuid4())[1:8]
self.trigger_names = ["event_trigger_delete_%s" % (
str(uuid.uuid4())[1:8]), "event_trigger_delete_%s" % (
str(uuid.uuid4())[1:8])]
server_con = server_utils.connect_server(self, self.server_id)
if not server_con["info"] == "Server connected.":
raise Exception("Could not connect to server to add resource "
"groups.")
server_version = 0
if "type" in server_con["data"]:
if server_con["data"]["version"] < 90300:
message = "Event triggers are not supported by PG9.2 " \
"and PPAS9.2 and below."
self.skipTest(message)
self.function_info = trigger_funcs_utils.create_trigger_function(
self.server, self.db_name, self.schema_name, self.func_name,
server_version)
self.event_trigger_ids = []
self.event_trigger_ids.append(event_trigger_utils.create_event_trigger(
self.server, self.db_name, self.schema_name, self.func_name,
self.trigger_names[0]))
self.event_trigger_ids.append(event_trigger_utils.create_event_trigger(
self.server, self.db_name, self.schema_name, self.func_name,
self.trigger_names[1]))
def runTest(self):
""" This function will delete event trigger under test database. """
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
self.server_id, self.db_id)
if not db_con['data']["connected"]:
raise Exception("Could not connect to database.")
schema_response = schema_utils.verify_schemas(self.server,
self.db_name,
self.schema_name)
if not schema_response:
raise Exception("Could not find the schema.")
func_name = self.function_info[1]
func_response = trigger_funcs_utils.verify_trigger_function(
self.server,
self.db_name,
func_name)
if not func_response:
raise Exception("Could not find the trigger function.")
trigger_response = event_trigger_utils.verify_event_trigger(
self.server, self.db_name,
self.trigger_names[0])
if not trigger_response:
raise Exception("Could not find event trigger.")
trigger_response = event_trigger_utils.verify_event_trigger(
self.server, self.db_name,
self.trigger_names[1])
if not trigger_response:
raise Exception("Could not find event trigger.")
data = {'ids': self.event_trigger_ids}
del_response = self.tester.delete(
self.url + str(utils.SERVER_GROUP) + '/' +
str(self.server_id) + '/' +
str(self.db_id) + '/',
follow_redirects=True,
data=json.dumps(data),
content_type='html/json')
self.assertEquals(del_response.status_code, 200)
def tearDown(self):
# Disconnect the database
database_utils.disconnect_database(self, self.server_id, self.db_id)
@@ -123,9 +123,9 @@ class ExtensionView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{'delete': 'delete'}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
'msql': [{'get': 'msql'}, {'get': 'msql'}],
@@ -327,49 +327,53 @@ class ExtensionView(PGChildNodeView):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, eid):
def delete(self, gid, sid, did, eid=None):
"""
This function will drop/drop cascade a extension object
"""
if eid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [eid]}
cascade = True if self.cmd == 'delete' else False
try:
# check if extension with eid exists
SQL = render_template("/".join(
[self.template_path, 'delete.sql']), eid=eid)
status, name = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=name)
for eid in data['ids']:
# check if extension with eid exists
SQL = render_template("/".join(
[self.template_path, 'delete.sql']), eid=eid)
status, name = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=name)
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified extension could not be found.\n'
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified extension could not be found.\n'
)
)
)
# drop extension
SQL = render_template("/".join(
[self.template_path, 'delete.sql']
), name=name, cascade=cascade)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
# drop extension
SQL = render_template("/".join(
[self.template_path, 'delete.sql']
), name=name, cascade=cascade)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Extension dropped"),
data={
'id': did,
'sid': sid,
'gid': gid,
}
info=gettext("Extension dropped")
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@@ -84,6 +84,7 @@ define('pgadmin.node.extension', [
* of the model in schema.
*/
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'eid',
schema: [
{
id: 'name', label: gettext('Name'), first_empty: true,
@@ -0,0 +1,74 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from __future__ import print_function
import json
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as extension_utils
class ExtensionsDeleteMultipleTestCase(BaseTestGenerator):
skip_on_database = ['gpdb']
scenarios = [
# Fetching default URL for extension node.
('Check Extension Node', dict(url='/browser/extension/obj/'))
]
def setUp(self):
""" This function will create extensions."""
super(ExtensionsDeleteMultipleTestCase, self).setUp()
self.schema_data = parent_node_dict['schema'][-1]
self.server_id = self.schema_data['server_id']
self.db_id = self.schema_data['db_id']
self.schema_name = self.schema_data['schema_name']
self.extension_names = ["dblink", "hstore"]
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.extension_ids = []
self.extension_ids.append(extension_utils.create_extension(
self.server, self.db_name, self.extension_names[0],
self.schema_name))
self.extension_ids.append(extension_utils.create_extension(
self.server, self.db_name, self.extension_names[1],
self.schema_name))
def runTest(self):
""" This function will delete extensions added test database. """
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to database.")
response = extension_utils.verify_extension(self.server, self.db_name,
self.extension_names[0])
if not response:
raise Exception("Could not find extension.")
response = extension_utils.verify_extension(self.server, self.db_name,
self.extension_names[1])
if not response:
raise Exception("Could not find extension.")
data = {'ids': self.extension_ids}
delete_response = self.tester.delete(
self.url + str(utils.SERVER_GROUP) + '/' +
str(self.server_id) + '/' + str(self.db_id) + '/',
follow_redirects=True,
data=json.dumps(data),
content_type='html/json'
)
self.assertEquals(delete_response.status_code, 200)
def tearDown(self):
"""This function disconnect the test database. """
database_utils.disconnect_database(self, self.server_id, self.db_id)
@@ -186,11 +186,9 @@ class ForeignDataWrapperView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{
'delete': 'delete'
}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'children': [{'get': 'children'}],
'sql': [{'get': 'sql'}],
@@ -478,7 +476,7 @@ class ForeignDataWrapperView(PGChildNodeView):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, fid):
def delete(self, gid, sid, did, fid=None):
"""
This function will delete the selected foreign data wrapper node.
@@ -488,56 +486,59 @@ class ForeignDataWrapperView(PGChildNodeView):
did: Database ID
fid: foreign data wrapper ID
"""
if fid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [fid]}
if self.cmd == 'delete':
# This is a cascade operation
cascade = True
else:
cascade = False
try:
# Get name of foreign data wrapper from fid
sql = render_template("/".join([self.template_path, 'delete.sql']),
fid=fid, conn=self.conn
)
status, name = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=name)
for fid in data['ids']:
try:
# Get name of foreign data wrapper from fid
sql = render_template("/".join([self.template_path,
'delete.sql']),
fid=fid, conn=self.conn
)
status, name = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=name)
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified foreign data'
' wrapper could not be found.\n'
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified foreign data'
' wrapper could not be found.\n'
)
)
)
# drop foreign data wrapper node
sql = render_template("/".join([self.template_path,
'delete.sql']),
name=name,
cascade=cascade,
conn=self.conn)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
# drop foreign data wrapper node
sql = render_template("/".join([self.template_path,
'delete.sql']),
name=name,
cascade=cascade,
conn=self.conn)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Foreign Data Wrapper dropped"),
data={
'id': fid,
'did': did,
'sid': sid,
'gid': gid,
}
)
except Exception as e:
return internal_server_error(errormsg=str(e))
except Exception as e:
return internal_server_error(errormsg=str(e))
return make_json_response(
success=1,
info=gettext("Foreign Data Wrapper dropped")
)
@check_precondition
def msql(self, gid, sid, did, fid=None):
@@ -183,11 +183,9 @@ class ForeignServerView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{
'delete': 'delete'
}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'children': [{'get': 'children'}],
'sql': [{'get': 'sql'}],
@@ -498,7 +496,7 @@ class ForeignServerView(PGChildNodeView):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, fid, fsid):
def delete(self, gid, sid, did, fid, fsid=None):
"""
This function will delete the selected foreign server node.
@@ -510,6 +508,13 @@ class ForeignServerView(PGChildNodeView):
fsid: foreign server ID
"""
if fsid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [fsid]}
if self.cmd == 'delete':
# This is a cascade operation
cascade = True
@@ -517,43 +522,40 @@ class ForeignServerView(PGChildNodeView):
cascade = False
try:
# Get name of foreign data wrapper from fid
sql = render_template("/".join([self.template_path, 'delete.sql']),
fsid=fsid, conn=self.conn)
status, name = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=name)
for fsid in data['ids']:
# Get name of foreign data wrapper from fid
sql = render_template("/".join([self.template_path,
'delete.sql']),
fsid=fsid, conn=self.conn)
status, name = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=name)
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified foreign server could not be found.\n'
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified foreign server '
'could not be found.\n'
)
)
)
# drop foreign server
sql = render_template("/".join([self.template_path, 'delete.sql']),
name=name, cascade=cascade,
conn=self.conn)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
# drop foreign server
sql = render_template("/".join([self.template_path,
'delete.sql']),
name=name, cascade=cascade,
conn=self.conn)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Foreign Server dropped"),
data={
'id': fsid,
'fid': fid,
'did': did,
'sid': sid,
'gid': gid,
}
info=gettext("Foreign Server dropped")
)
except Exception as e:
@@ -92,6 +92,7 @@ define('pgadmin.node.foreign_server', [
// Defining model for foreign server node
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'fsrvid',
defaults: {
name: undefined,
fsrvtype: undefined,
@@ -11,8 +11,6 @@ from __future__ import print_function
import uuid
from pgadmin.browser.server_groups.servers.databases.extensions.tests import \
utils as extension_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.\
tests import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
@@ -39,11 +37,9 @@ class ForeignServerDeleteTestCase(BaseTestGenerator):
self.db_id = self.schema_data['db_id']
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.schema_name = self.schema_data['schema_name']
self.extension_name = "cube"
self.fdw_name = "test_fdw_%s" % (str(uuid.uuid4())[1:8])
self.fsrv_name = "test_fsrv_%s" % (str(uuid.uuid4())[1:8])
self.extension_id = extension_utils.create_extension(
self.server, self.db_name, self.extension_name, self.schema_name)
self.fdw_id = fdw_utils.create_fdw(self.server, self.db_name,
self.fdw_name)
self.fsrv_id = fsrv_utils.create_fsrv(self.server, self.db_name,
@@ -75,8 +71,9 @@ class ForeignServerDeleteTestCase(BaseTestGenerator):
self.assertEquals(delete_response.status_code, 200)
def tearDown(self):
"""This function disconnect the test database and drop added extension
and dependant objects."""
extension_utils.drop_extension(self.server, self.db_name,
self.extension_name)
database_utils.disconnect_database(self, self.server_id, self.db_id)
"""This function disconnect the test database and drop
added foreign data server and dependant objects."""
fdw_utils.delete_fdw(self.server, self.db_name,
self.fdw_name)
database_utils.disconnect_database(self, self.server_id,
self.db_id)
@@ -0,0 +1,90 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from __future__ import print_function
import uuid
import json
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.\
tests import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as fsrv_utils
class ForeignServerDeleteMultipleTestCase(BaseTestGenerator):
"""This class will add foreign server under FDW node."""
skip_on_database = ['gpdb']
scenarios = [
# Fetching default URL for foreign server node.
('Check FSRV Node', dict(url='/browser/foreign_server/obj/'))
]
def setUp(self):
""" This function will create extension and foreign data wrapper."""
super(ForeignServerDeleteMultipleTestCase, self).setUp()
self.schema_data = parent_node_dict['schema'][-1]
self.server_id = self.schema_data['server_id']
self.db_id = self.schema_data['db_id']
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.schema_name = self.schema_data['schema_name']
self.fdw_name = "test_fdw_%s" % (str(uuid.uuid4())[1:8])
self.fsrv_names = ["test_fsrv_%s" % (str(uuid.uuid4())[1:8]),
"test_fsrv_%s" % (str(uuid.uuid4())[1:8])]
self.fdw_id = fdw_utils.create_fdw(self.server, self.db_name,
self.fdw_name)
self.fsrv_ids = [fsrv_utils.create_fsrv(self.server, self.db_name,
self.fsrv_names[0],
self.fdw_name),
fsrv_utils.create_fsrv(self.server, self.db_name,
self.fsrv_names[1],
self.fdw_name)]
def runTest(self):
"""This function will fetch foreign server present under test
database."""
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to database.")
fdw_response = fdw_utils.verify_fdw(self.server, self.db_name,
self.fdw_name)
if not fdw_response:
raise Exception("Could not find FDW.")
fsrv_response = fsrv_utils.verify_fsrv(self.server, self.db_name,
self.fsrv_names[0])
if not fsrv_response:
raise Exception("Could not find FSRV.")
fsrv_response = fsrv_utils.verify_fsrv(self.server, self.db_name,
self.fsrv_names[1])
if not fsrv_response:
raise Exception("Could not find FSRV.")
data = {'ids': self.fsrv_ids}
delete_response = self.tester.delete(
self.url + str(utils.SERVER_GROUP) + '/' +
str(self.server_id) + '/' + str(self.db_id) +
'/' + str(self.fdw_id) + "/",
data=json.dumps(data),
content_type='html/json',
follow_redirects=True)
self.assertEquals(delete_response.status_code, 200)
def tearDown(self):
"""This function disconnect the test database and drop added
foreign data server and dependant objects."""
fdw_utils.delete_fdw(self.server, self.db_name,
self.fdw_name)
database_utils.disconnect_database(self, self.server_id, self.db_id)
@@ -200,11 +200,9 @@ class UserMappingView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{
'delete': 'delete'
}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'children': [{'get': 'children'}],
'sql': [{'get': 'sql'}],
@@ -506,7 +504,7 @@ class UserMappingView(PGChildNodeView):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, fid, fsid, umid):
def delete(self, gid, sid, did, fid, fsid, umid=None):
"""
This function will delete the selected user mapping node.
@@ -518,6 +516,12 @@ class UserMappingView(PGChildNodeView):
fsid: foreign server ID
umid: User mapping ID
"""
if umid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [umid]}
if self.cmd == 'delete':
# This is a cascade operation
@@ -526,64 +530,59 @@ class UserMappingView(PGChildNodeView):
cascade = False
try:
# Get name of foreign server from fsid
sql = render_template("/".join([self.template_path, 'delete.sql']),
fsid=fsid, conn=self.conn)
status, name = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=name)
for umid in data['ids']:
# Get name of foreign server from fsid
sql = render_template("/".join([self.template_path,
'delete.sql']),
fsid=fsid, conn=self.conn)
status, name = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=name)
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified foreign server could not be found.\n'
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified foreign server '
'could not be found.\n'
)
)
)
sql = render_template("/".join([self.template_path,
'properties.sql']),
umid=umid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
sql = render_template("/".join([self.template_path,
'properties.sql']),
umid=umid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
if not res['rows']:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'The specified user mapping could not be found.\n'
if not res['rows']:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'The specified user mapping could not be found.\n'
)
)
)
data = res['rows'][0]
data = res['rows'][0]
# drop user mapping
sql = render_template("/".join([self.template_path, 'delete.sql']),
data=data, name=name, cascade=cascade,
conn=self.conn)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
# drop user mapping
sql = render_template("/".join([self.template_path,
'delete.sql']),
data=data, name=name, cascade=cascade,
conn=self.conn)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("User Mapping dropped"),
data={
'id': umid,
'fsid': fsid,
'fid': fid,
'did': did,
'sid': sid,
'gid': gid,
}
info=gettext("User Mapping dropped")
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@@ -96,6 +96,7 @@ define('pgadmin.node.user_mapping', [
// Defining model for user mapping node
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'um_oid',
defaults: {
name: undefined,
um_options: [],
@@ -41,11 +41,8 @@ class UserMappingDeleteTestCase(BaseTestGenerator):
self.db_id = self.schema_data['db_id']
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.schema_name = self.schema_data['schema_name']
self.extension_name = "cube"
self.fdw_name = "fdw_%s" % (str(uuid.uuid4())[1:8])
self.fsrv_name = "fsrv_%s" % (str(uuid.uuid4())[1:8])
self.extension_id = extension_utils.create_extension(
self.server, self.db_name, self.extension_name, self.schema_name)
self.fdw_id = fdw_utils.create_fdw(self.server, self.db_name,
self.fdw_name)
self.fsrv_id = fsrv_utils.create_fsrv(self.server, self.db_name,
@@ -83,8 +80,9 @@ class UserMappingDeleteTestCase(BaseTestGenerator):
self.assertEquals(delete_response.status_code, 200)
def tearDown(self):
"""This function disconnect the test database and drop added extension
and dependant objects."""
extension_utils.drop_extension(self.server, self.db_name,
self.extension_name)
database_utils.disconnect_database(self, self.server_id, self.db_id)
"""This function disconnect the test database and drop
foreign data wrapper and dependant objects."""
fdw_utils.delete_fdw(self.server, self.db_name,
self.fdw_name)
database_utils.disconnect_database(self, self.server_id,
self.db_id)
@@ -0,0 +1,91 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from __future__ import print_function
import uuid
import json
from pgadmin.browser.server_groups.servers.databases.extensions.tests import \
utils as extension_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \
foreign_servers.tests import utils as fsrv_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.\
tests import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as um_utils
class UserMappingDeleteMultipleTestCase(BaseTestGenerator):
"""This class will delete user mapping under foreign server node."""
skip_on_database = ['gpdb']
scenarios = [
# Fetching default URL for user mapping node.
('Check user mapping Node', dict(url='/browser/user_mapping/obj/'))
]
def setUp(self):
""" This function will create extension and foreign data wrapper."""
super(UserMappingDeleteMultipleTestCase, self).setUp()
self.schema_data = parent_node_dict['schema'][-1]
self.server_id = self.schema_data['server_id']
self.db_id = self.schema_data['db_id']
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.schema_name = self.schema_data['schema_name']
self.fdw_name = "fdw_%s" % (str(uuid.uuid4())[1:8])
self.fsrv_name = "fsrv_%s" % (str(uuid.uuid4())[1:8])
self.fdw_id = fdw_utils.create_fdw(self.server, self.db_name,
self.fdw_name)
self.fsrv_id = fsrv_utils.create_fsrv(self.server, self.db_name,
self.fsrv_name, self.fdw_name)
self.um_id = um_utils.create_user_mapping(self.server, self.db_name,
self.fsrv_name)
def runTest(self):
"""This function will delete user mapping present under test
database. """
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to database.")
fdw_response = fdw_utils.verify_fdw(self.server, self.db_name,
self.fdw_name)
if not fdw_response:
raise Exception("Could not find FDW.")
fsrv_response = fsrv_utils.verify_fsrv(self.server, self.db_name,
self.fsrv_name)
if not fsrv_response:
raise Exception("Could not find FSRV.")
um_response = um_utils.verify_user_mapping(self.server, self.db_name,
self.fsrv_name)
if not um_response:
raise Exception("Could not find user mapping.")
data = {'ids': [self.um_id]}
delete_response = self.tester.delete(
self.url + str(utils.SERVER_GROUP) + '/' +
str(self.server_id) + '/' + str(self.db_id) +
'/' + str(self.fdw_id) + '/' +
str(self.fsrv_id) + "/",
follow_redirects=True,
data=json.dumps(data),
content_type='html/json')
self.assertEquals(delete_response.status_code, 200)
def tearDown(self):
"""This function disconnect the test database and drop
foreign data wrapper and dependant objects."""
fdw_utils.delete_fdw(self.server, self.db_name, self.fdw_name)
database_utils.disconnect_database(self, self.server_id,
self.db_id)
@@ -95,6 +95,7 @@ define('pgadmin.node.foreign_data_wrapper', [
// Defining model for foreign data wrapper node
model: pgBrowser.Node.Model.extend({
idAttribute: 'fdwoid',
defaults: {
name: undefined,
fdwowner: undefined,
@@ -0,0 +1,76 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from __future__ import print_function
import uuid
import json
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as fdw_utils
class FDWDDeleteMultipleTestCase(BaseTestGenerator):
"""This class will delete foreign data wrappers under test database."""
skip_on_database = ['gpdb']
scenarios = [ # Fetching default URL for foreign_data_wrapper node.
('Check FDW Node',
dict(url='/browser/foreign_data_wrapper/obj/'))]
def setUp(self):
""" This function will create extension and foreign data wrapper."""
super(FDWDDeleteMultipleTestCase, self).setUp()
self.schema_data = parent_node_dict['schema'][-1]
self.server_id = self.schema_data['server_id']
self.db_id = self.schema_data['db_id']
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.schema_name = self.schema_data['schema_name']
self.fdw_names = ["fdw_{0}".format(str(uuid.uuid4())[1:8]),
"fdw_{0}".format(str(uuid.uuid4())[1:8])]
self.fdw_ids = [fdw_utils.create_fdw(self.server, self.db_name,
self.fdw_names[0]),
fdw_utils.create_fdw(self.server, self.db_name,
self.fdw_names[1])]
def runTest(self):
"""This function will fetch foreign data wrapper present under test
database."""
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to database.")
fdw_response = fdw_utils.verify_fdw(self.server, self.db_name,
self.fdw_names[0])
if not fdw_response:
raise Exception("Could not find FDW.")
fdw_response = fdw_utils.verify_fdw(self.server, self.db_name,
self.fdw_names[1])
if not fdw_response:
raise Exception("Could not find FDW.")
data = {'ids': self.fdw_ids}
delete_response = self.tester.delete(self.url +
str(utils.SERVER_GROUP) +
'/' + str(self.server_id) + '/' +
str(self.db_id) + '/',
follow_redirects=True,
data=json.dumps(data),
content_type='html/json')
self.assertEquals(delete_response.status_code, 200)
def tearDown(self):
"""This function disconnect the test database and drop added extension
and dependant objects."""
database_utils.disconnect_database(self, self.server_id,
self.db_id)
@@ -128,6 +128,6 @@ def delete_fdw(server, db_name, fdw_name):
server['port'],
server['sslmode'])
pg_cursor = connection.cursor()
pg_cursor.execute("DROP FOREIGN DATA WRAPPER %s" % fdw_name)
pg_cursor.execute("DROP FOREIGN DATA WRAPPER %s CASCADE" % fdw_name)
connection.commit()
connection.close()
@@ -188,7 +188,7 @@ class LanguageView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
@@ -198,7 +198,7 @@ class LanguageView(PGChildNodeView):
'dependent': [{'get': 'dependents'}],
'get_functions': [{}, {'get': 'get_functions'}],
'get_templates': [{}, {'get': 'get_templates'}],
'delete': [{'delete': 'delete'}]
'delete': [{'delete': 'delete'}, {'delete': 'delete'}]
})
def _init_(self, **kwargs):
@@ -490,7 +490,7 @@ class LanguageView(PGChildNodeView):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, lid):
def delete(self, gid, sid, did, lid=None):
"""
This function will drop the language object
@@ -500,6 +500,13 @@ class LanguageView(PGChildNodeView):
did: Database ID
lid: Language ID
"""
if lid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [lid]}
if self.cmd == 'delete':
# This is a cascade operation
cascade = True
@@ -507,35 +514,30 @@ class LanguageView(PGChildNodeView):
cascade = False
try:
# Get name for language from lid
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
lid=lid, conn=self.conn
)
status, lname = self.conn.execute_scalar(sql)
for lid in data['ids']:
# Get name for language from lid
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
lid=lid, conn=self.conn
)
status, lname = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=lname)
if not status:
return internal_server_error(errormsg=lname)
# drop language
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
lname=lname, cascade=cascade, conn=self.conn
)
status, res = self.conn.execute_scalar(sql)
# drop language
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
lname=lname, cascade=cascade, conn=self.conn
)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Language dropped"),
data={
'id': lid,
'did': did,
'sid': sid,
'gid': gid,
}
info=gettext("Language dropped")
)
except Exception as e:
@@ -59,6 +59,7 @@ define('pgadmin.node.language', [
// Define the model for language node
model: pgBrowser.Node.Model.extend({
idAttribute: 'oid',
defaults: {
name: undefined,
lanowner: undefined,
@@ -0,0 +1,67 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from __future__ import print_function
import uuid
import json
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as language_utils
class LanguagesDeleteMultipleTestCase(BaseTestGenerator):
scenarios = [
('Language delete test case', dict(url='/browser/language/obj/'))
]
def setUp(self):
self.server_data = parent_node_dict["database"][-1]
self.server_id = self.server_data["server_id"]
self.db_id = self.server_data['db_id']
self.db_name = self.server_data["db_name"]
self.lang_names = ["language_%s" % str(uuid.uuid4())[1:8],
"language_%s" % str(uuid.uuid4())[1:8]]
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to database.")
self.language_ids = [language_utils.create_language(
self.server,
self.db_name,
self.lang_names[0]
),
language_utils.create_language(
self.server,
self.db_name,
self.lang_names[1])
]
def runTest(self):
"""This function will delete languages under test database."""
data = {'ids': self.language_ids}
response = self.tester.delete("{0}{1}/{2}/{3}/".format(
self.url, utils.SERVER_GROUP, self.server_id, self.db_id),
follow_redirects=True,
data=json.dumps(data),
content_type='html/json'
)
self.assertEquals(response.status_code, 200)
def tearDown(self):
"""This function disconnect the test database."""
database_utils.disconnect_database(self, self.server_id, self.db_id)
@@ -214,7 +214,7 @@ class SchemaView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'children': [{'get': 'children'}],
'nodes': [{'get': 'nodes'}, {'get': 'nodes'}],
@@ -223,7 +223,8 @@ class SchemaView(PGChildNodeView):
'stats': [{'get': 'statistics'}],
'dependency': [{'get': 'dependencies'}],
'dependent': [{'get': 'dependents'}],
'delete': [{'delete': 'delete'}]
'delete': [{'delete': 'delete'},
{'delete': 'delete'}]
})
def __init__(self, *args, **kwargs):
@@ -645,7 +646,7 @@ It may have been removed by another user.
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, scid):
def delete(self, gid, sid, did, scid=None):
"""
This function will delete an existing schema object
@@ -656,54 +657,56 @@ It may have been removed by another user.
scid: Schema ID
"""
try:
# Get name for schema from did
SQL = render_template(
"/".join([self.template_path, 'sql/get_name.sql']),
_=gettext,
scid=scid
if scid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [scid]}
status, name = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=name)
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified schema could not be found.\n'
)
for scid in data['ids']:
try:
# Get name for schema from did
SQL = render_template(
"/".join([self.template_path, 'sql/get_name.sql']),
_=gettext,
scid=scid
)
# drop schema
SQL = render_template(
"/".join([self.template_path, 'sql/delete.sql']),
_=gettext, name=name, conn=self.conn,
cascade=True if self.cmd == 'delete' else False
)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
status, name = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=name)
return make_json_response(
success=1,
info=gettext("Schema dropped"),
data={
'id': scid,
'sid': sid,
'gid': gid,
'did': did
}
)
if name is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified schema could not be found.\n'
)
)
except Exception as e:
current_app.logger.exception(e)
return internal_server_error(errormsg=str(e))
# drop schema
SQL = render_template(
"/".join([self.template_path, 'sql/delete.sql']),
_=gettext, name=name, conn=self.conn,
cascade=True if self.cmd == 'delete' else False
)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
except Exception as e:
current_app.logger.exception(e)
return internal_server_error(errormsg=str(e))
return make_json_response(
success=1,
info=gettext("Schema dropped")
)
@check_precondition
def msql(self, gid, sid, did, scid=None):
@@ -10,6 +10,8 @@ define('pgadmin.node.catalog_object_column', [
label: gettext('catalog_object_column'),
type: 'coll-catalog_object_column',
columns: ['attname', 'attnum', 'cltype', 'description'],
canDrop: false,
canDropCascade: false,
});
}
@@ -10,6 +10,8 @@ define('pgadmin.node.catalog_object', [
label: gettext('Catalog Objects'),
type: 'coll-catalog_object',
columns: ['name', 'owner', 'description'],
canDrop: false,
canDropCascade: false,
});
}
@@ -161,9 +161,9 @@ class CollationView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{'delete': 'delete'}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'children': [{'get': 'children'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
@@ -482,7 +482,7 @@ class CollationView(PGChildNodeView):
)
@check_precondition
def delete(self, gid, sid, did, scid, coid):
def delete(self, gid, sid, did, scid, coid=None):
"""
This function will delete existing the collation object
@@ -493,6 +493,12 @@ class CollationView(PGChildNodeView):
scid: Schema ID
coid: Collation ID
"""
if coid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [coid]}
# Below will decide if it's simple drop or drop with cascade call
if self.cmd == 'delete':
@@ -502,40 +508,36 @@ class CollationView(PGChildNodeView):
cascade = False
try:
SQL = render_template("/".join([self.template_path,
'get_name.sql']),
scid=scid, coid=coid)
status, name = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=name)
for coid in data['ids']:
SQL = render_template("/".join([self.template_path,
'get_name.sql']),
scid=scid, coid=coid)
status, name = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=name)
if name is None:
return make_json_response(
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified collation could not be found.\n'
if name is None:
return make_json_response(
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified collation could not be found.\n'
)
)
)
SQL = render_template("/".join([self.template_path,
'delete.sql']),
name=name, cascade=cascade,
conn=self.conn)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
SQL = render_template("/".join([self.template_path,
'delete.sql']),
name=name, cascade=cascade,
conn=self.conn)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Collation dropped"),
data={
'id': coid,
'scid': scid,
'did': did
}
info=gettext("Collation dropped")
)
except Exception as e:
@@ -54,6 +54,7 @@ define('pgadmin.node.collation', [
},
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'oid',
defaults: {
name: undefined,
oid: undefined,
@@ -0,0 +1,80 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import uuid
import json
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as collation_utils
class CollationDeleteMultipleTestCase(BaseTestGenerator):
""" This class will delete added collations under schema node. """
skip_on_database = ['gpdb']
scenarios = [
# Fetching default URL for collation node.
('Fetch collation Node URL', dict(url='/browser/collation/obj/'))
]
def setUp(self):
super(CollationDeleteMultipleTestCase, self).setUp()
self.schema_info = parent_node_dict["schema"][-1]
self.schema_name = self.schema_info["schema_name"]
self.db_name = parent_node_dict["database"][-1]["db_name"]
coll_names = ["collation_get_%s" % str(uuid.uuid4())[1:8],
"collation_get_%s" % str(uuid.uuid4())[1:8]]
self.collations = [collation_utils.create_collation(self.server,
self.schema_name,
coll_names[0],
self.db_name),
collation_utils.create_collation(self.server,
self.schema_name,
coll_names[1],
self.db_name)
]
def runTest(self):
""" This function will delete collations under schema node. """
server_id = self.schema_info["server_id"]
db_id = self.schema_info["db_id"]
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
server_id,
db_id)
if not db_con['data']["connected"]:
raise Exception("Could not connect to database.")
schema_response = schema_utils.verify_schemas(self.server,
self.db_name,
self.schema_name)
if not schema_response:
raise Exception("Could not find the schema.")
data = {'ids': [self.collations[0][0], self.collations[1][0]]}
schema_id = self.schema_info["schema_id"]
get_response = self.tester.delete(
self.url + str(utils.SERVER_GROUP) + '/' + str(
server_id) + '/' +
str(db_id) + '/' + str(schema_id) + '/',
content_type='html/json',
data=json.dumps(data),
follow_redirects=True,
)
self.assertEquals(get_response.status_code, 200)
# Disconnect database to delete it
database_utils.disconnect_database(self, server_id, db_id)
def tearDown(self):
pass
@@ -158,9 +158,9 @@ class DomainView(PGChildNodeView, DataTypeReader):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{'delete': 'delete'}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'children': [{'get': 'children'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
@@ -581,7 +581,7 @@ AND relkind != 'c'))"""
)
@check_precondition
def delete(self, gid, sid, did, scid, doid):
def delete(self, gid, sid, did, scid, doid=None):
"""
Drops the Domain object.
@@ -592,6 +592,12 @@ AND relkind != 'c'))"""
scid: Schema Id
doid: Domain Id
"""
if doid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [doid]}
if self.cmd == 'delete':
# This is a cascade operation
@@ -599,45 +605,39 @@ AND relkind != 'c'))"""
else:
cascade = False
SQL = render_template("/".join([self.template_path,
'delete.sql']),
scid=scid, doid=doid)
status, res = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=res)
for doid in data['ids']:
SQL = render_template("/".join([self.template_path,
'delete.sql']),
scid=scid, doid=doid)
status, res = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=res)
if not res['rows']:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified domain could not be found.\n'
if not res['rows']:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified domain could not be found.\n'
)
)
)
name = res['rows'][0]['name']
basensp = res['rows'][0]['basensp']
name = res['rows'][0]['name']
basensp = res['rows'][0]['basensp']
SQL = render_template("/".join([self.template_path,
'delete.sql']),
name=name, basensp=basensp, cascade=cascade)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
SQL = render_template("/".join([self.template_path,
'delete.sql']),
name=name, basensp=basensp, cascade=cascade)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Domain dropped"),
data={
'id': doid,
'scid': scid,
'sid': sid,
'gid': gid,
'did': did
}
info=gettext("Domain dropped")
)
@check_precondition
@@ -167,9 +167,8 @@ class DomainConstraintView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{'delete': 'delete'}],
'children': [{'get': 'children'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
@@ -450,7 +449,7 @@ class DomainConstraintView(PGChildNodeView):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, scid, doid, coid):
def delete(self, gid, sid, did, scid, doid, coid=None):
"""
Drops the Domain Constraint object.
@@ -462,45 +461,47 @@ class DomainConstraintView(PGChildNodeView):
doid: Domain Id
coid: Domain Constraint Id
"""
if coid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [coid]}
try:
SQL = render_template("/".join([self.template_path,
'properties.sql']),
doid=doid, coid=coid)
status, res = self.conn.execute_dict(SQL)
for coid in data['ids']:
SQL = render_template("/".join([self.template_path,
'properties.sql']),
doid=doid, coid=coid)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
if not status:
return internal_server_error(errormsg=res)
if not res['rows']:
return make_json_response(
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified domain constraint could not be found.\n'
if not res['rows']:
return make_json_response(
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified domain constraint '
'could not be found.\n'
)
)
)
data = res['rows'][0]
data = res['rows'][0]
SQL = render_template("/".join([self.template_path,
'delete.sql']),
data=data)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
SQL = render_template("/".join([self.template_path,
'delete.sql']),
data=data)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Domain Constraint dropped"),
data={
'id': doid,
'scid': scid,
'sid': sid,
'gid': gid,
'did': did
}
info=gettext("Domain Constraint dropped")
)
except Exception as e:
@@ -13,6 +13,8 @@ define('pgadmin.node.domain_constraints', [
label: gettext('Domain Constraints'),
type: 'coll-domain_constraints',
columns: ['name', 'description'],
canDrop: true,
canDropCascade: false,
});
}
@@ -59,6 +61,7 @@ define('pgadmin.node.domain_constraints', [
},
canDrop: schemaChildTreeNode.isTreeItemOfChildOfSchema,
model: pgAdmin.Browser.Node.Model.extend({
idAttribute: 'oid',
defaults: {
name: undefined,
oid: undefined,
@@ -120,6 +120,7 @@ define('pgadmin.node.domain', [
},
// Domain Node Model
model: pgBrowser.Node.Model.extend({
idAttribute: 'oid',
initialize: function(attrs, args) {
var isNew = (_.size(attrs) === 0);
if (isNew) {
@@ -23,7 +23,7 @@ class DomainDeleteTestCase(BaseTestGenerator):
""" This class will delete new domain under schema node. """
scenarios = [
# Fetching default URL for domain node.
('Fetch domain Node URL', dict(url='/browser/domain/delete/'))
('Fetch domain Node URL', dict(url='/browser/domain/obj/'))
]
def setUp(self):
@@ -61,8 +61,12 @@ class DomainDeleteTestCase(BaseTestGenerator):
str(db_id) + '/' +
str(self.schema_id) + '/' +
str(domain_id),
content_type='html/json')
content_type='html/json',
follow_redirects=True
)
self.assertEquals(get_response.status_code, 200)
# Disconnect the database
database_utils.disconnect_database(self, server_id, db_id)
@@ -0,0 +1,79 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import uuid
import json
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.tests import utils as \
database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as domain_utils
class DomainDeleteMultipleTestCase(BaseTestGenerator):
""" This class will delete new domains under schema node. """
scenarios = [
# Fetching default URL for domain node.
('Fetch domain Node URL', dict(url='/browser/domain/obj/'))
]
def setUp(self):
super(DomainDeleteMultipleTestCase, self).setUp()
self.database_info = parent_node_dict["database"][-1]
self.db_name = self.database_info["db_name"]
self.schema_info = parent_node_dict["schema"][-1]
self.schema_name = self.schema_info["schema_name"]
self.schema_id = self.schema_info["schema_id"]
self.domain_names = ["domain_delete_%s" % (str(uuid.uuid4())[1:8]),
"domain_delete_%s" % (str(uuid.uuid4())[1:8])]
self.domain_infos = [domain_utils.create_domain(self.server,
self.db_name,
self.schema_name,
self.schema_id,
self.domain_names[0]),
domain_utils.create_domain(self.server,
self.db_name,
self.schema_name,
self.schema_id,
self.domain_names[1])]
def runTest(self):
""" This function will add domain under schema node. """
db_id = self.database_info["db_id"]
server_id = self.database_info["server_id"]
db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
server_id, db_id)
if not db_con['data']["connected"]:
raise Exception("Could not connect to database to get the domain.")
db_name = self.database_info["db_name"]
schema_response = schema_utils.verify_schemas(self.server,
db_name,
self.schema_name)
if not schema_response:
raise Exception("Could not find the schema to get the domain.")
data = {'ids': [self.domain_infos[0][0], self.domain_infos[1][0]]}
url = self.url + str(utils.SERVER_GROUP) + '/' + str(
server_id) + '/' + str(db_id) + '/' + str(self.schema_id) + "/"
# Call GET API to verify the domain
get_response = self.tester.delete(
url,
content_type='html/json',
follow_redirects=True,
data=json.dumps(data))
self.assertEquals(get_response.status_code, 200)
# Disconnect the database
database_utils.disconnect_database(self, server_id, db_id)
def tearDown(self):
pass
@@ -194,9 +194,9 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'delete': [{'delete': 'delete'}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'children': [{'get': 'children'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
@@ -708,7 +708,7 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, scid, foid):
def delete(self, gid, sid, did, scid, foid=None):
"""
Drops the Foreign Table.
@@ -719,6 +719,13 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
scid: Schema Id
foid: Foreign Table Id
"""
if foid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [foid]}
if self.cmd == 'delete':
# This is a cascade operation
cascade = True
@@ -726,47 +733,41 @@ class ForeignTableView(PGChildNodeView, DataTypeReader):
cascade = False
try:
# Fetch Name and Schema Name to delete the foreign table.
SQL = render_template("/".join([self.template_path,
'delete.sql']), scid=scid,
foid=foid)
status, res = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=res)
for foid in data['ids']:
# Fetch Name and Schema Name to delete the foreign table.
SQL = render_template("/".join([self.template_path,
'delete.sql']), scid=scid,
foid=foid)
status, res = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=res)
if not res['rows']:
return make_json_response(
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified foreign table could not be found.\n'
if not res['rows']:
return make_json_response(
success=0,
errormsg=gettext(
'Error: Object not found.'
),
info=gettext(
'The specified foreign table could not be found.\n'
)
)
)
name = res['rows'][0]['name']
basensp = res['rows'][0]['basensp']
name = res['rows'][0]['name']
basensp = res['rows'][0]['basensp']
SQL = render_template("/".join([self.template_path,
'delete.sql']),
name=name,
basensp=basensp,
cascade=cascade)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
SQL = render_template("/".join([self.template_path,
'delete.sql']),
name=name,
basensp=basensp,
cascade=cascade)
status, res = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=gettext("Foreign Table dropped"),
data={
'id': foid,
'scid': scid,
'sid': sid,
'gid': gid,
'did': did
}
info=gettext("Foreign Table dropped")
)
except Exception as e:
@@ -0,0 +1,99 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from __future__ import print_function
import uuid
import json
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \
foreign_servers.tests import utils as fsrv_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers. \
tests import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.utils.route import BaseTestGenerator
from regression import parent_node_dict
from regression.python_test_utils import test_utils as utils
from . import utils as ft_utils
class ForeignTableDeleteMultipleTestCase(BaseTestGenerator):
"""
This class will delete foreign table under database node.
"""
skip_on_database = ['gpdb']
scenarios = [
# Fetching default URL for foreign table node.
('Check foreign table Node', dict(url='/browser/foreign_table/obj/'))
]
def setUp(self):
""" This function will create foreign data wrapper, foreign server
and foreign table. """
super(ForeignTableDeleteMultipleTestCase, self).setUp()
self.schema_data = parent_node_dict['schema'][-1]
self.server_id = self.schema_data['server_id']
self.db_id = self.schema_data['db_id']
self.db_name = parent_node_dict["database"][-1]["db_name"]
self.schema_name = self.schema_data['schema_name']
self.schema_id = self.schema_data['schema_id']
self.fdw_name = "fdw_%s" % (str(uuid.uuid4())[1:8])
self.fsrv_name = "fsrv_%s" % (str(uuid.uuid4())[1:8])
self.ft_name = "ft_%s" % (str(uuid.uuid4())[1:8])
self.fdw_id = fdw_utils.create_fdw(self.server, self.db_name,
self.fdw_name)
self.fsrv_id = fsrv_utils.create_fsrv(self.server, self.db_name,
self.fsrv_name, self.fdw_name)
self.ft_ids = [ft_utils.create_foreign_table(
self.server, self.db_name,
self.schema_name, self.fsrv_name,
"ft_%s" % (str(uuid.uuid4())[1:8])),
ft_utils.create_foreign_table(
self.server, self.db_name,
self.schema_name, self.fsrv_name,
"ft_%s" % (str(uuid.uuid4())[1:8]))
]
def runTest(self):
"""This function will delete foreign table under test database."""
db_con = database_utils.connect_database(self,
utils.SERVER_GROUP,
self.server_id,
self.db_id)
if not db_con["info"] == "Database connected.":
raise Exception("Could not connect to database.")
fsrv_response = fsrv_utils.verify_fsrv(self.server, self.db_name,
self.fsrv_name)
if not fsrv_response:
raise Exception("Could not find Foreign Server.")
data = {'ids': self.ft_ids}
delete_response = self.tester.delete(
self.url + str(utils.SERVER_GROUP) + '/' +
str(self.server_id) + '/' +
str(self.db_id) + '/' +
str(self.schema_id) + '/',
content_type='html/json',
follow_redirects=True,
data=json.dumps(data))
self.assertEquals(delete_response.status_code, 200)
def tearDown(self):
""" This function disconnect the test database. """
database_utils.disconnect_database(self, self.server_id, self.db_id)
@@ -188,12 +188,12 @@ class FtsConfigurationView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
{'get': 'list', 'post': 'create', 'delete': 'delete'}
],
'children': [{
'get': 'children'
}],
'delete': [{'delete': 'delete'}],
'delete': [{'delete': 'delete'}, {'delete': 'delete'}],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
'msql': [{'get': 'msql'}, {'get': 'msql'}],
@@ -531,7 +531,7 @@ class FtsConfigurationView(PGChildNodeView):
)
@check_precondition
def delete(self, gid, sid, did, scid, cfgid):
def delete(self, gid, sid, did, scid, cfgid=None):
"""
This function will drop the FTS Configuration object
:param gid: group id
@@ -540,6 +540,13 @@ class FtsConfigurationView(PGChildNodeView):
:param scid: schema id
:param cfgid: FTS Configuration id
"""
if cfgid is None:
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
else:
data = {'ids': [cfgid]}
# Below will decide if it's simple drop or drop with cascade call
if self.cmd == 'delete':
# This is a cascade operation
@@ -548,49 +555,44 @@ class FtsConfigurationView(PGChildNodeView):
cascade = False
try:
# Get name for FTS Configuration from cfgid
sql = render_template(
"/".join([self.template_path, 'get_name.sql']),
cfgid=cfgid
)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
for cfgid in data['ids']:
# Get name for FTS Configuration from cfgid
sql = render_template(
"/".join([self.template_path, 'get_name.sql']),
cfgid=cfgid
)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
if not res['rows']:
return make_json_response(
success=0,
errormsg=_(
'Error: Object not found.'
),
info=_(
'The specified FTS configuration could not be found.\n'
if not res['rows']:
return make_json_response(
success=0,
errormsg=_(
'Error: Object not found.'
),
info=_(
'The specified FTS configuration '
'could not be found.\n'
)
)
# Drop FTS Configuration
result = res['rows'][0]
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
name=result['name'],
schema=result['schema'],
cascade=cascade
)
# Drop FTS Configuration
result = res['rows'][0]
sql = render_template(
"/".join([self.template_path, 'delete.sql']),
name=result['name'],
schema=result['schema'],
cascade=cascade
)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info=_("FTS Configuration dropped"),
data={
'id': cfgid,
'sid': sid,
'gid': gid,
'did': did,
'scid': scid
}
info=_("FTS Configuration dropped")
)
except Exception as e:
Loaded 100 of 416 files, more files were not shown because too many files have changed in this diff. Show more