Compare commits

...
Author SHA1 Message Date
Neel Patel 8dca4b0e0b Fixed an syntantical issue, which was not supported by QtWebkit.
The issue was regression of the patch for the 'SlickGrid'.

Fixes #1621
2016-08-30 14:43:50 +05:30
Ashesh Vashi f7dd8cbab4 Bump version for RC1 release 2016-08-29 20:37:44 +05:30
Murtuza Zabuawala c56e4667df SlickGrid Integration in to query tool. Fixes #1618 2016-08-29 20:17:01 +05:30
Murtuza Zabuawala 689ef8d8d1 Fixes issues specific to SYNONYMS
- On droping a synonyms - the query should be modified in SQL pane.
- A proper message should be given on trying to drop an already dropped
  synonyms.
- Handle the synonyms with special characters.

Fixes #1607, #1608, #1609
2016-08-29 20:25:18 +05:30
Ashesh Vashi b6f307256b Sorting the data during tree data loading using the natural sort
algorithm.

This patch takes care of:
* Consistent behaviour during create, update operation on any node.
  - It should return the node data during creating a new object, or
    updating the existing one.
* Now that - we have consistent behaviour during these operations, we
  can consistently move, update the tree item based the node data from
  the server.
* Implemented the methods for each node to get the information about
  that particular node only.
* Using the above changes to implement the 'refresh' operation on tree
  node properly.

I must thank Surinder, and Harshal for helping me implement the 'node'
method, and also changed the behaviour of create & update methods to
return the node data for most of the nodes.

Fixes #1244
2016-08-29 20:06:58 +05:30
Neel Patel 2c6f6609bc Debugger listener starting error displayed if user try to debug plpgsql cs_fmt_browser_version function that contains parameters. Fixes #1238 2016-08-29 13:47:15 +05:30
Neel Patel f5249f2e18 Set break point option should present in pgAdmin4 for trigger. Fixes #1226 2016-08-29 13:43:43 +05:30
Surinder Kumar 2d75eac117 SqlFieldControl enhancements 2016-08-29 13:21:45 +05:30
Ashesh Vashi f12d981a9d Handling the bad/lost connection of a database server.
Made backend changes for:
* Taking care of the connection status in the psycopg2 driver. And, when
  the connection is lost, it throws a exception with 503 http status
  message, and connection lost information in it.
* Allowing the flask application to propagate the exceptions even in the
  release mode.
* Utilising the existing password (while reconnection, if not
  disconnected explicitly).
* Introduced a new ajax response message 'service_unavailable' (http
  status code: 503), which suggests temporary service unavailable.

Client (front-end) changes:
* To handle the connection lost of a database server for different
  operations by generating proper events, and handle them properly.

Removed the connection status check code from different nodes, so that
- it generates the proper exception, when accessing the non-alive
  connection.

Fixes #1387
2016-08-29 12:01:35 +05:30
Akshay Joshi 1b05464a04 Getting 'AttributeError' in pgadmin4/web/pgadmin/browser/server_groups/servers/__init__.py. Fixes #1464 2016-08-26 14:25:33 +05:30
Murtuza Zabuawala 5a78dd7ad3 Implemented Synonym node for EPAS 2016-08-26 13:01:57 +05:30
Akshay Joshi 7b72448ec9 Internal sever error displayed if create New user mapping with public user. Fixes #1468 2016-08-26 11:23:15 +05:30
Murtuza Zabuawala 05473a7a35 domain node: id and pid is not being generated. Fixes #1586 2016-08-25 14:07:04 +05:30
Akshay Joshi e016869ad5 Trigger issues through right click. Fixes #1209 2016-08-24 12:10:54 +05:30
Murtuza Zabuawala 8d7b840e7e Fix startup navigation message. 2016-08-23 12:41:41 +01:00
Priyanka Shendge a535eddfcd Add schema and database child node regression tests. 2016-08-23 11:50:41 +01:00
Dave Page 0ce8b031f8 More sensible defaults for the mail server settings. 2016-08-23 11:41:45 +01:00
Ashesh Vashi 6775adc4c7 Cleanup old code in server_groups.js. Fixes #1247 2016-08-23 09:32:10 +05:30
Harshal Dhumal 58a79e4fc6 Fix parsing issue in packages. 2016-08-22 16:49:32 +01:00
Harshal Dhumal cf1be2a320 Add support for Packages on EPAS. 2016-08-22 12:30:16 +01:00
Surinder Kumar fe54a124da Don't offer to create resource groups on disconnected servers. Fixes #1569 2016-08-22 12:21:33 +01:00
Surinder Kumar 4c2020cdc7 Allow the use of the tab key to select options in combo boxes. Fixes #1251 2016-08-22 12:18:38 +01:00
Surinder Kumar 5690dfda16 Allow template databases to be used when creating new databases. Fixes #1570 2016-08-19 16:35:42 +01:00
Dave Page d5f6c63ead More string tweaks. 2016-08-19 11:48:34 +01:00
Surinder Kumar 761b54ce69 Allow the user to configure tabs in the SQL editor. Fixes #1406 2016-08-19 11:40:19 +01:00
Dave Page 0f99c3b60c Clarify font sizing description. 2016-08-19 11:23:07 +01:00
Dave Page 80f1c2e7ac Correct the win-build directory in .gitignore. 2016-08-19 11:20:29 +01:00
Khushboo Vashi eca47efb03 Update MFizz font library. Fixes #1311 2016-08-19 11:17:42 +01:00
Dave Page 33b4817008 Ensure fractional sizes work for the SQL font. 2016-08-19 11:08:42 +01:00
Akshay Joshi 86858b18f6 Fix validation of numeric preference min/max values. Fixes #1567 2016-08-19 11:00:05 +01:00
Surinder Kumar d5b0cd29d8 Display full text from backgrid cells that overflow in a tooltip. Fixes #1442 2016-08-19 10:54:13 +01:00
Akshay Joshi 7dffb020f1 Support CREATE LANGUAGE. Fixes #1252 2016-08-19 10:26:02 +01:00
Murtuza Zabuawala 013ff1090e Ensure PG/EPAS configuration warnings are shown appropriate for the server type. Fixes #1563 2016-08-18 17:18:20 +01:00
Murtuza Zabuawala d6391c7e9b Properly display messages from the server in the query tool. Fixes #1523 2016-08-18 17:08:40 +01:00
Akshay Joshi 2b1b60bc2b Reduce main menu height a little. Fixes #1453 2016-08-18 16:51:22 +01:00
Murtuza Zabuawala 8e0a61f556 Fixed typo in security labels macro Fixes #1457 2016-08-18 18:03:30 +05:30
Dave Page 050937a32a Fix WSGI support. 2016-08-18 13:43:00 +01:00
Neel Patel 48a50ce20b Give more useful error messages when unable to debug functions. Fixes #1224 2016-08-17 15:23:10 +01:00
Dave Page 6701bb191d String fix. 2016-08-17 15:10:33 +01:00
Harshal Dhumal 1056e9553f Show default database privileges in properties mode. Fixes #1538 2016-08-17 14:22:56 +01:00
Dave Page 91e3d3267f On reflection, don't allow the user to specify the unit when setting the SQL font size. Always use em's. 2016-08-17 13:45:52 +01:00
Dave Page b3f25cb4bb Allow customisation of the CodeMirror font size. Fixes #1565 2016-08-17 13:20:05 +01:00
Murtuza Zabuawala 9ec05d6bb1 Fix an issue where system level catalog are also displayed in PPAS server under relation of create new table like option. Fixes #1530 2016-08-16 16:29:50 +01:00
Murtuza Zabuawala c336e8a743 Fix an issue in foreign tables node where it was displaying catalog tables in inherits options causing internal server error. Fixes RM#1520 2016-08-16 15:44:44 +01:00
Dave Page 99b4a0fe5b Add unit tests for schemas, collations and trigger functions. 2016-08-16 12:54:02 +01:00
Surinder Kumar 67f481ab11 Prevent creation of FTS objects in catalogs. Fixes #1122 2016-08-16 12:49:19 +01:00
Surinder Kumar 6d839a2924 Prevent a query being executed whilst one is already in progress. Fixes #1532 2016-08-16 12:10:16 +01:00
Neel Patel a3e8ba93ae Ensure that argc/argv are set for the runtime interpretor, and that
PYTHONHOME is properly configured.
2016-08-16 11:29:42 +01:00
Akshay Joshi 035066bd41 Fix query tool button/menu inconsistencies. Fixes #1461 2016-08-09 16:23:24 +01:00
Navnath Gadakh 81e2bc1e80 Test suite enhancements:
1. The user will specify the tablespace path in test_config.json.in
2.  If tablespace path not found, skip the test cases for that server(Only tablespace test cases)
3.  Add the skipped test summary in the test result. (Now it's showing on console + in log file, but need to update in a final enhanced test summary report. Which is research point we will work on that after finishing all nodes API test cases)
4.  Removed the test_ prefix from the values in the config files.
5. Add tablespace and roles tests
2016-08-09 16:05:40 +01:00
Dave Page 2b13d55016 Add missing updates from the previous commit. 2016-08-09 12:17:02 +01:00
Akshay Joshi 9dfc7bff35 Add missing collection node child counts. Fixes #1250 2016-08-09 12:12:05 +01:00
Dave Page 33457e6a90 Update message catalog. 2016-08-08 16:51:03 +01:00
Dave Page 48384cd6cd Bump version. 2016-08-08 16:48:10 +01:00
Harshal Dhumal 8918b8894d Fixed privileges parsing issue while editing privileges of existing object. Fixes #1417 2016-08-08 16:31:57 +01:00
Dave Page a025c0ebf0 Missing comma. Sigh. 2016-08-08 15:39:06 +01:00
Dave Page 2aea97b798 Fix titles of help pages. 2016-08-08 15:30:24 +01:00
Surinder Kumar 1bcac0b67c Fix more of the funky keyboard shortcuts. 2016-08-08 15:26:37 +01:00
Neel Patel 37ca3579f3 Fix a JS error in the runtime caused by f78024808e (#1177) 2016-08-08 14:42:14 +01:00
Surinder Kumar 5900848842 Sequence related validation/fixes. Fixes #1119
1) Add proper validation checks for fields.
2) Fixed wrong sql generation due to incorrect conditions in template.
2016-08-08 12:47:20 +01:00
Surinder Kumar 7cf4ac2474 Fix RE-SQL for Trigger Functions which was missing an 'AS'. Fixes #1524 2016-08-08 12:17:54 +01:00
Harshal Dhumal f78024808e Prevent the user attempting to run external commands if the bin path is not configured. Fixes #1177 2016-08-08 11:59:37 +01:00
Khushboo Vashi 8e099e29c3 Fix various escaping issues. Fixes #1527 2016-08-05 12:20:00 +01:00
Surinder Kumar a43f053a10 Fix file upload in File Manager in pgAdmin4 Runtime. Fixes #1397 2016-08-04 16:34:30 +01:00
Khushboo Vashi b00e94e904 Fix drop trigger function, function and procedures. Fixes #1467 2016-08-04 15:24:03 +01:00
Surinder Kumar 42f8745aba Fix error running VACUUM FULL FREEZE ANALYZE VERBOSE. Fixes #1519 2016-08-04 12:13:34 +01:00
Harshal Dhumal cb35c46f03 Fix View Data options for tables with columns that need quoting. Fixes #1492 2016-08-04 11:54:36 +01:00
Murtuza Zabuawala 8c433fbfce Fix display of numerics in the query tool. Fixes #1463 2016-08-04 11:31:24 +01:00
Harshal Dhumal dfceb63986 Add save password option in sever create mode. Fixes #1450
Do a little string/layout cleanup whilst passing.
2016-08-03 15:55:45 +01:00
Khushboo Vashi 68497b00f6 Fix dropping of Domains. Fixes #1470 2016-08-03 15:44:24 +01:00
Harshal Dhumal de568b3021 Fix renaming of constraints from the table dialogue. Fixes #1500
1] datamodel.js: For collection type added check before adding model into "changed" list instead of adding it blindly.

2] Type casting from str to int of column properties like attlen, attpricision.

3] Added missing data formating when adding new column to existing table (from table edit mode).

4] Added more validation for Foreign key constraint.

5] Column.js: Column grid (in table edit mode) show proper disabled color for disabled cell when grid renders.

6] All constraints and index js added idattribute to distinguish which s/constraint or s/index is updated on server side.

7] Column update.sql: Fixed sql when altering column data type. The issue was when we alter data type from which has length and precision to data type which don't have these properties and vice versa.
For e.g. alter data type numeric(10,12) to real (and vice versa)

8] Renaming constraint (RM1500).

9] simplejson KeyError handing for Python 2.7.
2016-08-03 15:39:14 +01:00
Dave Page 5d52767ba7 Move the dashboard tab filters into a button bar, and add a refresh button. Fixes #1505 2016-08-03 13:39:32 +01:00
Dave Page 4f90a5d4f4 Move button bar related classes together. 2016-08-03 13:38:12 +01:00
Dave Page 1ec1e9663c Ignore the pickle. 2016-08-03 10:25:10 +01:00
Harshal Dhumal ec3b9bb15b Allow to create the foreign table with inherits options. Fixes #1260 2016-08-02 09:38:56 +05:30
Murtuza Zabuawala 01252a13cf Further misc quoting/encoding related fixes. 2016-08-01 15:23:06 +01:00
Akshay Joshi e1cc3dded2 Allow customisable delete confirmation messages in backgrid. 2016-08-01 15:18:57 +01:00
Dave Page 16304aafd2 s/buttton/button/g, per Anthony DeBarros. Fixes #1518 2016-08-01 15:16:14 +01:00
Surinder Kumar 9c4eed70b4 Use the same keyboard shortcuts in the query tool as pgAdmin 3. Refs #1478
Per discussion on the mailing list, the shortcuts override the browser defaults (where there are any), but only when the query tool has focus.
2016-08-01 15:10:52 +01:00
Dmitriy Olshevskiy 94aa36f3d5 Remove redundant -- marker in the SQL panel on the properties dialogues. 2016-08-01 14:28:14 +01:00
Dave Page 5987af1b44 Minor readme tweak. 2016-07-27 15:35:11 +01:00
Navnath Gadakh 5c3c543d2e Test suite improvements:
- Test framework support API testing with multiple server for this we need to modify test_config.json(for user it’s test_config.json.in) and test_advanced_config.json(for user it’s test_advanced_config.json.in). Server details of PG and  PPAS are included in both .in files.

- Removed the logic of logging in  the test client on each test scenario(As per Khushboo's comment in previous email).  We need this logic in test cases under ‘browser/tests/’ as for test scenarios like change password and  invalid login test cases as test client should be logged out first. So, as per this the code is slightly modified in ‘browser/tests/’.
2016-07-27 15:33:36 +01:00
Murtuza Zabuawala b6e8d195dc Security label providers are an optional parameter. Fixes #1478. 2016-07-27 15:01:16 +01:00
Dave Page f4f8273994 Display basic query info when execution completes in the query tool. Fixes #1507 2016-07-27 11:36:13 +01:00
Murtuza Zabuawala 06e881570a Fix numerous encoding issues. Fixes #1307. Fixes #1479 2016-07-26 15:05:14 +01:00
Harshal Dhumal 166d42953c Give a more useful error message if attempting to drop an object that doesn't exist. Fixes #1154 2016-07-26 14:40:02 +01:00
Murtuza Zabuawala d3d96d5c05 Disable SQLALCHEMY track modification feature as it puts overhead on system, and if not set, now emits a warning. 2016-07-26 13:01:56 +01:00
Akshay Joshi e803464ee0 Replace 'Add' buttons with '+' 2016-07-26 12:54:59 +01:00
daze 19306934a0 Property 'URL' (of type string) not quoted in browser.js template when extending pgAdmin.Browser. Fixes #1504
Without quotes, the property can be mistakenly treated as a regex:

e.g.: URL: /pgadmin/browser/,

When that happens, JS engine throws "SyntaxError: invalid regular expression flag b" and the whole web interface freezes.
This problem usually occurs when pgadmin is hosted inside a subdirectory (https://host/pgadmin/ instead of https://host/).
2016-07-26 12:47:46 +01:00
Dave Page 790b28fb61 Flask-Babel 0.11+ compatibility. Fixes #1496 2016-07-25 17:23:10 +01:00
Harshal Dhumal 875360d1c8 When we use deps functionality, backform controls re-render then. But before we re-render them we must remove old grid (if any) otherwise is causes js error "Unable to call exitEditMode of undefined". Fixes #1497 2016-07-25 14:31:17 +01:00
Harshal Dhumal 9dd3cf2399 Make Save/Save As menu options work a little more sanely. Fixes #1460 2016-07-25 12:55:06 +01:00
Neel Patel dbd3c7f4c5 Allow creation of FDWs if the extension is in a non-default schema. Fixes #1320
Root cause:-

When user change schema of already created fdw extension then while creating the new fdw we should append the <schema_name>.<fdw_handler_name>.
Previously we added only <fdw_handler_name> so while executing the query it will not be able to find fdw handler name without schema name.

e.g. If user changed the fdw handler 'postgres_fdw_handler' under schema 'test' then we should display the handler  name under fdw as "test. postgres_fdw_handler".

Solution:-

With this patch, we have added <schema_name>.<fdw_handler_name> so while creating or updating the fdw, proper sql will be generated.
2016-07-25 12:48:11 +01:00
Neel Patel dcf5bfc495 Runtime fixes:
- Compilation error with Qt4 as "QUrlQuery" class was introduced since Qt5.
- Client side download code has been removed as now we support server side download.
- We should not delete sender object inside slot. If we do so it may crash the application. To avoid this, we used Qt's "deleteLater" which will delete the objects once the processing of all the events have been done.
2016-07-25 12:39:57 +01:00
Surinder Kumar 91559c622b Fixes for function, procedure and trigger reverse SQL. Fixes #1293. Fixes #1294
1. RM#1293 - SQL pane is not displaying GRANT queries in functions node
2. RM#1294 - Comments are not visible in sql pane for trigger functions and functions

Explanation:
Previously we we using 'get_defintion.sql' template to generate SQL for SQL pane for functions, procedure & trigger functions node.
but GRANT statements and COMMENTS changes were missing. In order to fix that, now we have used both 'create.sql' and 'get_definition.sql' templates to generate full SQL query for SQL pane.
2016-07-22 16:38:21 +01:00
Dave Page 60659975ed Stop using deprecated imports. 2016-07-22 16:25:23 +01:00
Dave Page a014cf82a1 Use a numeric version check for upgrades. 2016-07-22 16:14:57 +01:00
Dave Page f783aeac63 Change button label to initcap. 2016-07-21 17:18:15 +01:00
Murtuza Zabuawala 3d7b40e111 Fix support for security labels. Fixes #1457 2016-07-21 16:35:35 +01:00
Harshal Dhumal a7d7577c7c Query tool file handling cleanup. Fixes #1460
- Opening a file should set the tab name to the filename
- Editing a file should add a * to the tab name to indicate the file is dirty
- Saving changes to a file should clear the *
- The Save button should have a drop-down menu, with a Save As option.
2016-07-21 13:44:42 +01:00
Murtuza Zabuawala 419ead9121 Ensure ajax requests are sent on second click of an object. Fixes #1462 2016-07-21 13:26:09 +01:00
Ashesh Vashi 417af3676d Using better way to find the current object in the properties panel. 2016-07-19 15:30:39 +05:30
Ashesh Vashi 8b35a60c08 Generate the proper unique name for the duplicate column name.
Thanks Murtuza for reporting the issue.
2016-07-19 11:27:03 +05:30
Dave Page 8e29d16eec Beta 3 version bump 2016-07-18 16:34:00 +01:00
Harshal Dhumal 83a1535f89 Fix saving of query output as CSV data. Fixes #1405 2016-07-18 15:29:09 +01:00
Neel Patel a6024fc49a Support server side download in runtime application. 2016-07-18 15:28:20 +01:00
Dave Page ae9bbc0305 Ignore regression test files. 2016-07-18 14:50:49 +01:00
Dave Page f17c2e3b84 Enhancements to the regression test suite.
Navnath Gadakh and Priyanka Shendge
2016-07-18 14:50:21 +01:00
Harshal Dhumal c7d25c33f2 Make the Parameter grid use a workflow consistent with other grids. Fixes #1241
1. Altered variable control to make its UI consistent with privileges and Security labels.
2. Changed datamodel.js to handle duplicate rows at datamodel level and not UI/Control level. (See variable control for example)
2016-07-18 11:50:42 +01:00
Murtuza Zabuawala 5560d5b334 Properly support the interval type. Fixes #1437 2016-07-18 09:40:45 +01:00
Ashesh Vashi 9ba6bafb2b Using own version of cursor class to allow us to take care of the
duplicate name in column description.
2016-07-16 10:42:51 +05:30
Murtuza Zabuawala d15dfac60f FIx a hang in the query tool that could occur if a column type couldn't be determined. Fixes #1438 2016-07-15 12:59:01 +01:00
Murtuza Zabuawala e94b14e7d7 Fix the issue in MATERIALIZED VIEW node where user was not able to add/update security label. Fixes #1458 2016-07-15 12:13:57 +01:00
Murtuza Zabuawala 517a797947 Fix query tool display of various types:
date
reltime
abstime
time with timezone
time without timezone
2016-07-15 11:59:45 +01:00
Surinder Kumar 1a129cf30e File management dialogue enhancements:
- Double click on file/folder icon or name will navigate to the folder/file.
- Selected file/folder can be renamed by Rename button only. Double click rename is removed.
- Filename text box will not hide on smaller screen. Setting minimum width of File Browser fixed it.
- Path at top will only show directory name, not the file name and it is greyed out
2016-07-15 10:50:25 +01:00
Surinder Kumar efb254c966 Ensure newly created database can be dropped. 2016-07-15 10:14:11 +01:00
Murtuza Zabuawala 573abbe6ef Allow users to select a data type with the keyboard only when creating a column in the subnode grid. Sanitise the list of fields shown. 2016-07-15 10:12:23 +01:00
Murtuza Zabuawala fdcb9d2a57 Fix an issue in procedure node where users were not able to create a new procedure when they provide arguments. Fixes #1416 2016-07-13 14:57:08 +01:00
Murtuza Zabuawala f79ca32e45 Allow selection of 'PUBLIC' as a role in privileges. Fixes #1416 2016-07-13 14:51:25 +01:00
Murtuza Zabuawala ae692d2416 Display comments on languages. Fixes #1412 2016-07-13 14:45:34 +01:00
Surinder Kumar f58c4df85d Fix incomplete reverse engineered SQL for databases. Fixes #1420 2016-07-13 12:42:01 +01:00
Surinder Kumar 9ad0bd1026 Fix clear query option which was opening a query tool in new window in Mac Runtime 2016-07-13 12:35:22 +01:00
Surinder Kumar 47e849dee0 The spinner icon is not visible while executing query or running debugger in Runtime environment specific to Ubuntu. This issue is related to RM#1186
To make it work, we need to explicitly set -webkit-animation property to 'none' for classes "sql-editor-busy-icon.fa-pulse" and ".debugger-container fa-pulse".

'fa-pulse' class is responsible to spin the busy icon, but setting its -webkit-animation property to none will not spin icon but icon and message will be shown.
2016-07-13 12:32:05 +01:00
Neel Patel d22ea8bf61 Properly support file downloads in the desktop runtime. Fixes #1405. Fixes #1342 2016-07-13 12:26:24 +01:00
Ashesh Vashi 1ada5031cd 'hasattr' function on '__builtins__' for 'xrange' function does not
return correct information from a script, but works well on interactive
shell. Used a more proper check for Python > 2 instead of the current
implementation.

Thanks Vishal for the report.
2016-07-12 13:21:21 +05:30
Ashesh Vashi 6fa4065eee Do not try to show the database dashboard, when clicked on one of the
children node. This implementation will allow to add new nodes at server
level without modifying the dashboard code. I found the issue during the
pgAgent Jobs node implementation.
2016-07-11 16:31:52 +05:30
Ashesh Vashi 2b809523eb [Python 3 Compability] Made changes to make the new server-side session
implementation works with Python 3.
2016-07-11 16:29:06 +05:30
Ashesh Vashi 889b69ac82 [Python 3 compability] Allow to run the auto-discovery implementation on
Python 3. Refs #1389
2016-07-11 01:18:24 +05:30
Ashesh Vashi 61698b7b4d Do not use the sqlite for session handling, as the old implementation is
optimized to work from multiple threads. It has too many frequent
transaction from multiple threads, and that tends to result into the
'database is locked' error of sqlite.

With the new implemenation, we're using the caching mechanism, which
keep the data in the memory all the time, and saves it on request
completion, and loads it only for the first time. Also, it will storage
the data using pickle, which will be faster than accessing sqlite.

Fixes #1329
2016-07-08 16:57:46 +05:30
Surinder Kumar 52d1af63a5 Scroll to the new row when adding one in the query tool. Fixes #1435 2016-07-07 12:35:44 +01:00
Harshal Dhumal 4077de2187 Fix Select2 issues. Fixes #1418. Fixes #1434
1. Select2 control fixed for multi-select mode.
2. Index constrains (primary and unique key) fixed column listing issue.
3. Table node "inherited from" (Select2 control) was not showing table name with public schema
2016-07-05 12:58:48 +01:00
Surinder Kumar 6c6660e576 Resolve "Move to last page" warning shown unnecessarily issue. Fixes #1422 2016-07-05 12:20:40 +01:00
Surinder Kumar 1bd27b847b Grey the SQL panel in edit data mode. Fixes #1423
Introduced a new class 'cm_disabled' with css property background-color to '#EEEEE'
Query tool in edit mode, add the class 'cm_disabled' to set background color to dark and set the cursor property to 'nocursor' to disable editor.
Increased the query tool title padding to fix the issue of whitespace below the title.
2016-07-05 12:11:36 +01:00
Murtuza Zabuawala 3ee8861d44 Fix JSON type rendering. Fixes #1404 2016-07-04 10:05:59 +01:00
Dave Page d4c3b1bcff Fix trigger function help link. Fixes #1410 2016-07-02 17:24:21 +01:00
Susan Douglas 8a12850525 Re-work the getting started section, and add trigger function support. Fixes #1403 2016-07-02 17:20:51 +01:00
Dave Page 51a33e57c9 Fix blurred text in Alertify on webkit. Fixes #1347 2016-07-02 17:13:32 +01:00
Dave Page 9c39fe2f2e Fix architecture detection on 32 bit Windows. Fixes #1427 2016-07-01 15:50:28 +01:00
Dave Page 7b3b7736d9 Resolve loading panel does not hide in Mac runtime. Fixes #1425 2016-07-01 10:20:43 +01:00
Dave Page c45960df5e Beta 2.1 - a favour for our friends in QA. 2016-06-30 16:05:23 +01:00
Dave Page d79524ff60 Require passwords to be at least 6 chars long in setup.py. Fixes #1322 2016-06-30 14:16:30 +01:00
Dave Page a1d8ca8c1c Fix subnode header colour 2016-06-30 14:07:26 +01:00
Dave Page 685b2f4ab7 Correct label 2016-06-30 14:05:10 +01:00
Dave Page aa44122a8a Fix query execution keys in the Mac runtime. Needs more work to sanitize with CodeMirror, but this is a step further. Fixes #1336 2016-06-30 09:56:15 +01:00
Dave Page 80e108aa55 Fix regression test harness to allow for removal of the schema version from config.py 2016-06-30 09:46:46 +01:00
Dave Page 71443deceb Fix email validation 2016-06-30 09:46:06 +01:00
Dave Page 0282ef7992 Use a standard colour for error highlighting. 2016-06-29 20:54:08 +01:00
Dave Page c4504f1dc2 Localise the loading message. 2016-06-29 20:48:09 +01:00
Dave Page d795f37fe5 Prefix the version number with 'v' in the Loading message. 2016-06-29 20:47:11 +01:00
Dave Page b8c9ccc939 Use Alt+Shift for runtime shortcuts to avoid collisions with app shortcuts. 2016-06-29 20:31:40 +01:00
Murtuza Zabuawala 8246fc56e8 Offer the various serial pseudo-types as column type options. Fixes #1393 2016-06-29 19:50:43 +01:00
Surinder Kumar 53434030c1 Consistent busy indication. Fixes #1242 2016-06-29 12:16:02 +01:00
Harshal Dhumal 9396cb03d5 Fix intermittent CodeMirror rendering issue. Fixes #1399 2016-06-29 11:19:04 +01:00
Surinder Kumar 5d0c160fb8 Show a loading indicator until things are ready to rock. Fixes #1400 2016-06-29 10:46:48 +01:00
Dave Page 1222681888 Revert "Display busy indicators in a much more consistent way. Fixes #1242"
This reverts commit 7fbefa507b which seems to be
missing things.
2016-06-29 10:32:20 +01:00
Dave Page 7fbefa507b Display busy indicators in a much more consistent way. Fixes #1242 2016-06-27 17:09:37 +01:00
Murtuza Zabuawala 4267545e08 Handle special chars and spaces properly in select2 controls. Fixes #1292 2016-06-24 14:09:32 +01:00
Murtuza Zabuawala 4bb849c58c Allow toggling of the Primary Key option in the un-expanded column grid when defining a table. Fixes #1235 2016-06-24 14:05:59 +01:00
Dave Page 78d329fc47 Don't blink the elapsed time - it's distracting. 2016-06-24 14:01:23 +01:00
Murtuza Zabuawala e9ad27678f Allow maintenance operations on Index/Primary key/Unique constraints. Fixes #1390 2016-06-24 13:59:16 +01:00
Dave Page d63b54610b Move the config database schema version out of the configuration file and into the model class.
Turns out using the config file isn't a good idea if users copy config.py to config_local.py, as it prevents upgrades to the database. This has the added side-effect of simplifying future changes, as you only need to edit 2 files to modify the config DB now, not 3.
2016-06-24 12:50:52 +01:00
Surinder Kumar a7e1e25cf4 Add a title bar to the treeview browser, as this is the only (currently) supported way to fix docking issues with our code in wcDocker. Fixes #1184
Original patch by Surinder. Simplified and re-styled by me.
2016-06-24 10:12:57 +01:00
Dave Page 8f643a1946 Styling tweaks 2016-06-24 10:04:51 +01:00
Dave Page a1f979c3e4 Fix icon spacing 2016-06-24 09:45:21 +01:00
Dave Page 30682c2a68 String fix. 2016-06-24 09:27:29 +01:00
Dave Page 1acc1a7678 Auto-discover locally installed servers at login. Fixes #1389 2016-06-23 16:27:06 +01:00
Murtuza Zabuawala e4ef927da3 Fix tablespace statistics. Fixes #1196 2016-06-23 13:06:05 +01:00
Murtuza Zabuawala 8b0e65dc57 Allow default ACLs to be specified when creating schemas. Fixes #1108. 2016-06-23 13:03:18 +01:00
Dave Page 5928f70a1d Fix default ACL queries which were using invalid values for defaclobjtype. 2016-06-23 13:02:30 +01:00
Surinder Kumar 09569a23bd Fix "number.toFixed is not function" JS error.
Issue:
We got the following error in console log
"number.toFixed is not function"

Steps to reproduce:
1) Click on statistics tab.
1) Select any database node.
2) then select Databases collection node.
3) The above error will appear into console.

Reason:
I discussed the issue with @Murtaza and here we are explicitly
converting values at server side to string as JSON do not directly
support NaN & Infinity values. Because of which NumberFormatter receives
values of type string and breaks.

Solution:
Overrides the NumberFormatter to support NaN & Infinity values.
and we need to parse the values again in float at client side.
2016-06-23 12:46:48 +01:00
Dave Page 4983eb2f1c Fix SQL formatting. Fixes #1234 2016-06-23 12:14:23 +01:00
Murtuza Zabuawala 97d49af43a Fix constraint on server table to allow port 65535 to be used. Fixes #1388 2016-06-23 11:43:50 +01:00
Murtuza Zabuawala e5ee592470 Don't offer an option to drop the current maintenance database. Fixes #1382 2016-06-23 11:37:32 +01:00
Ashesh Vashi b78b3904cd Use PUBLIC (in caps) whenever used as a keyword. Fixes #1138 2016-06-23 09:15:09 +05:30
782 changed files with 56937 additions and 10834 deletions

No files matched your search

+1 -1
View File
@@ -23,8 +23,8 @@ pgadmin4.log
/mac-build
/src-build
/pip-build
/win-build
/dist
pkg/win32/win-build
pkg/win32/installer.iss
runtime/Makefile.Debug
runtime/Makefile.Release
+4 -4
View File
@@ -1,10 +1,10 @@
.. _backup_globals_dialog:
****************************
The Backup Globals... Dialog
****************************
*************************
The Backup Globals Dialog
*************************
Use the *Backup Globals...* dialog to create a plain-text script that recreates all of the database objects within a cluster, and the global objects that are shared by those databases. Global objects include tablespaces, roles, and object properties. You can use the pgAdmin *Query Tool* to play back a plain-text script, and recreate the objects in the backup.
Use the *Backup Globals* dialog to create a plain-text script that recreates all of the database objects within a cluster, and the global objects that are shared by those databases. Global objects include tablespaces, roles, and object properties. You can use the pgAdmin *Query Tool* to play back a plain-text script, and recreate the objects in the backup.
.. image:: images/backup_globals_general.png
+7 -6
View File
@@ -1,10 +1,10 @@
.. _backup_server_dialog:
****************************
The Backup Server... Dialog
****************************
*************************
The Backup Server Dialog
*************************
Use the *Backup Server...* dialog to create a plain-text script that will recreate the selected server. You can use the pgAdmin *Query Tool* to play back a plain-text script, and recreate the server.
Use the *Backup Server* dialog to create a plain-text script that will recreate the selected server. You can use the pgAdmin *Query Tool* to play back a plain-text script, and recreate the server.
.. image:: images/backup_server_general.png
@@ -24,5 +24,6 @@ Click the *Backup* button to build and execute a command based on your selection
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
␍If the backup is unsuccessful, review the error message returned by the *Process Watcher* to resolve any issue.
.. image:: images/backup_server_process_watcher.png
If the backup is unsuccessful, review the error message returned by the *Process Watcher* to resolve any issue.
+16 -10
View File
@@ -4,21 +4,27 @@
Connecting to a Server
**********************
Before you can use the pgAdmin client to manage your Postgres installation, you must define a connection to the server.
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:
.. toctree::
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.
Contents:
.. toctree::
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.
Contents:
.. toctree::
connect_to_server
connect_error
connect_error
+31 -6
View File
@@ -4,9 +4,31 @@
Getting Started
***************
pgAdmin 4 features a highly-customizable display that features drag-and-drop panels that you can arrange to make the best use of your desktop environment.
Pre-compiled and configured installation packages for pgAdmin 4 are available for a number of desktop environments; we recommend using an installer whenever possible. A standard installation using the pgAdmin installer is a server deployment.
When you install pgAdmin in server mode, you will be prompted to provide a role name and pgAdmin password. The first role registered with pgAdmin will be an administrative user. When you authenticate with pgAdmin, pgAdmin displays the server definitions associated with that login role in the tree control. The tree control provides an elegant overview of the managed servers, and the objects that reside on each server. Right-click on a node within the tree control to access context-sensitive menus that provide quick access to management tasks for the selected object.
In a Server Deployment, the pgAdmin application is deployed behind a webserver or with the WSGI interface.
If you install pgAdmin in server mode, you will be prompted to provide a role name and pgAdmin password when you initially connect to pgAdmin. The first role registered with pgAdmin will be an administrative user; the administrative role can use the pgAdmin *User Management* dialog to create and manage additional pgAdmin user accounts. When a user authenticates with pgAdmin, the pgAdmin tree control displays the server definitions associated with that login role.
Contents:
.. toctree::
server_deployment
pgadmin_login
pgadmin_user
In a Desktop Deployment, the pgAdmin application is configured to use the desktop runtime environment to host and display the program on a supported platform. Typically, users will install a pre-built package to run pgAdmin in desktop mode, but a manual desktop deployment can be installed and though it is more difficult to setup, it may be useful for developers interested in understanding how pgAdmin works.
Contents:
.. toctree::
:maxdepth: 2
desktop_deployment
The pgAdmin 4 client features a highly-customizable display that features drag-and-drop panels that you can arrange to make the best use of your desktop environment.
The tree control provides an elegant overview of the managed servers, and the objects that reside on each server. Right-click on a node within the tree control to access context-sensitive menus that provide quick access to management tasks for the selected object.
The tabbed browser provide quick access to statistical information about each object in the tree control, and pgAdmin tools and utilities (such as the Query tool and the debugger). pgAdmin opens additional feature tabs each time you access the extended functionality offered by pgAdmin tools; you can open, close, and re-arrange feature tabs as needed.
@@ -14,17 +36,20 @@ Use the *Preferences* dialog to customize the content and colors of the pgAdmin
*Help* buttons in the lower-left corner of each dialog will open the online help for the dialog. You can access additional Postgres help by navigating through the *Help* menu, and selecting the name of the resource that you wish to open.
An administrative role can use the pgAdmin User Management dialog to create and manage additional pgAdmin user accounts.
Contents:
.. toctree::
pgadmin_login
browser
pgadmin_menu_bar
pgadmin_tabbed_browser
pgadmin_tree_control
preferences
pgadmin_user
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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

+4 -4
View File
@@ -1,10 +1,10 @@
.. _maintenance_dialog:
*************************
The Maintenance... Dialog
*************************
**********************
The Maintenance Dialog
**********************
Use the *Maintenance...* dialog to VACUUM, ANALYZE, REINDEX or CLUSTER a database or selected database objects.
Use the *Maintenance* dialog to VACUUM, ANALYZE, REINDEX or CLUSTER a database or selected database objects.
.. image:: images/maintenance.png
+1
View File
@@ -30,6 +30,7 @@ Contents:
procedure_dialog
schema_dialog
sequence_dialog
trigger_function_dialog
type_dialog
user_mapping_dialog
view_dialog
+4 -2
View File
@@ -50,8 +50,10 @@ Expand the **SQL Editor** node to specify your preferences for the SQL Editor to
.. image:: images/preferences_sql_display.png
Use the *Display* dialog to specify your preferences for the SQL Editor display. Specify a value in the *Items per page in grid* to instruct the *SQL Editor* on how many rows to display per page.
The default is *50*.
Use the *Display* dialog to specify your preferences for the SQL Editor display.
* Specify a value in the *Items per page in grid* field to instruct the *SQL Editor* on how many rows to display per page. The default is *50*.
* Use the *Query info notifier timeout* 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*.
.. image:: images/preferences_sql_explain_options.png
+17 -2
View File
@@ -71,7 +71,7 @@ application may be configured similarly to the example below:
<VirtualHost *>
ServerName pgadmin.example.com
WSGIDaemonProcess pgadmin processes=1
WSGIDaemonProcess pgadmin processes=1 threads=25
WSGIScriptAlias / /opt/pgAdmin4/web/pgAdmin4.wsgi
<Directory /opt/pgAdmin4/web>
@@ -80,4 +80,19 @@ application may be configured similarly to the example below:
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
</VirtualHost>
**Note:** If you're using Apache HTTPD 2.4 or later, replace the lines:
.. code-block:: apache
Order deny,allow
Allow from all
with:
.. code-block:: apache
Require all granted
Adjust as needed to suit your access control requirements.
+102
View File
@@ -0,0 +1,102 @@
.. _trigger_function_dialog:
***************************
The Trigger function Dialog
***************************
Use the *Trigger function* dialog to create or manage a trigger_function. A trigger function defines the action that will be invoked when a trigger fires.
The *Trigger function* dialog organizes the development of a trigger function through the following dialog tabs: *General*, *Definition*, *Options*, *Parameters* and *Security*. The *SQL* tab displays the SQL code generated by dialog selections.
.. image:: images/trigger_function_general.png
Use the fields in the *General* tab to identify the trigger function:
* Use the *Name* field to add a descriptive name for the trigger function. The name will be displayed in the *pgAdmin* tree control. Please note that trigger functions will be invoked in alphabetical order.
* Use the drop-down listbox next to *Owner* to select a role.
* Select the name of the schema in which the trigger function will reside from the drop-down listbox in the *Schema* field.
* Store notes about the trigger function in the *Comment* field.
Click the *Definition* tab to continue.
.. image:: images/trigger_function_definition.png
Use the fields in the *Definition* tab to define the trigger function:
* Use the drop-down listbox next to *Return type* to specify the pseudotype that is associated with the trigger function:
* Select *trigger* if you are creating a DML trigger.
* Select *event_trigger* if you are creating a DDL trigger.
* Use the drop-down listbox next to *Language* to select the implementation language. The default is *plpgsql*.
* Use the *Code* field to write the code that will execute when the trigger function is called.
Click the *Options* tab to continue.
.. image:: images/trigger_function_options.png
Use the fields in the *Options* tab to describe or modify the action of the trigger function:
* Use the drop-down listbox next to *Volatility* to select one of the following:
* *VOLATILE* indicates that the trigger function value can change even within a single table scan.
* *STABLE* indicates that the trigger function cannot modify the database, and that within a single table scan it will consistently return the same result for the same argument values.
* *IMMUTABLE* indicates that the trigger function cannot modify the database and always returns the same result when given the same argument values.
* Move the *Returns a Set?* switch to indicate if the trigger function returns a set that includes multiple rows. The default is *No*.
* Move the *Strict?* switch to indicate if the trigger function always returns NULL whenever any of its arguments are NULL. If *Yes*, the function is not executed when there are NULL arguments; instead a NULL result is assumed automatically. The default is *No*.
* Move the *Security of definer?* switch to specify that the trigger function is to be executed with the privileges of the user that created it. The default is *No*.
* Move the *Window?* switch to indicate that the trigger function is a window function rather than a plain function. The default is *No*. This is currently only useful for trigger functions written in C.
* Use the *Estimated cost* field to specify a positive number representing the estimated execution cost for the trigger function, in units of cpu_operator_cost. If the function returns a set, this is the cost per returned row.
* Use the *Estimated rows* field to specify a positive number giving the estimated number of rows that the query planner should expect the trigger function to return. This is only allowed when the function is declared to return a set. The default assumption is 1000 rows.
* Move the *Leak proof?* switch to indicate whether the trigger function has side effects. The default is *No*. This option can only be set by the superuser.
Click the *Parameters* tab to continue.
.. image:: images/trigger_function_parameters.png
Use the fields in the *Parameters* tab to specify settings that will be applied when the trigger function is invoked:
* Use the drop-down listbox next to *Parameter Name* in the *Parameters* panel to select a parameter.
* Click the *Add* button to add the variable to *Name* field in the table.
* Use the *Value* field to specify the value that will be associated with the selected variable. This field is context-sensitive.
Click the *Security* tab to continue.
.. image:: images/trigger_function_security.png
Use the *Security* tab to assign privileges and define security labels.
Use the *Privileges* panel to assign usage privileges for the trigger function to a role. Click *Add* to to add a role to the table.
* Select the name of the role from the drop-down listbox in the *Grantee* field.
* Click inside the *Privileges* field. Check the boxes to the left of one or more privileges to grant the selected privilege to the specified user.
* Select the name of a role from the drop-down listbox in the *Grantor* field. The default grantor is the owner of the database.
Click *Add* to assign additional privileges; to discard a privilege, click the trash icon to the left of the row and confirm deletion in the *Delete Row* popup.
Use the *Security Labels* panel to define security labels applied to the trigger function. Click *Add* to add each security label selection:
* Specify a security label provider in the *Provider* field. The named provider must be loaded and must consent to the proposed labeling operation.
* Specify a a security label in the *Security Label* field. The meaning of a given label is at the discretion of the label provider. PostgreSQL places no restrictions on whether or how a label provider must interpret security labels; it merely provides a mechanism for storing them.
Click *Add* to assign additional security labels; to discard a security label, click the trash icon to the left of the row and confirm deletion in the *Delete Row* popup.
Click the *SQL* tab to continue.
Your entries in the *Trigger function* dialog generate a SQL command (see an example below). Use the *SQL* tab for review; revisit other tabs to modify the SQL command.
**Example**
The following is an example of the sql command generated by user selections in the *Trigger function* dialog:
.. image:: images/trigger_function_sql.png
The example shown demonstrates creating a trigger function named *emp_stamp* that checks for a new employee's name, and checks that the employee's salary is a positive value.
* Click the *Info* button (i) to access online help. View context-sensitive help in the *Tabbed browser*, where a new tab displays the PostgreSQL core documentation.
* Click the *Save* button to save work.
* Click the *Cancel* button to exit without saving work.
* Click the *Reset* button to restore configuration parameters.
+4 -1
View File
@@ -18,7 +18,7 @@ Underscore.string 387ab72d49 MIT http://epeli.github.io/underscore.s
Backform.js 5859b4f9db MIT https://github.com/AmiliaApp/backform
Backbone 1.1.2 MIT http://backbonejs.org
font-Awesome 4.5 SIL OFL http://fortawesome.github.io/Font-Awesome/
font-mfizz 1.2 MIT http://fizzed.com/oss/font-mfizz
font-mfizz 2.3 MIT http://fizzed.com/oss/font-mfizz
backgrid.js 0.3.5 MIT http://backgridjs.com/
backbone.undo 0.2 MIT http://backbone.undojs.com/
bootstrap-switch 3.3.2 MIT http://www.bootstrap-switch.org/
@@ -30,3 +30,6 @@ backgrid-select-all 1a00053 MIT https://github.com/wyuenho/backgrid
dropzone 4e20bd4 MIT https://github.com/enyo/dropzone
Filemanager 7e060c2 MIT https://github.com/simogeo/Filemanager
Unit (Length) d8e6237 MIT https://github.com/heygrady/Units/blob/master/Length.min.js
Natural Sort 9565816 MIT https://github.com/javve/natural-sort/blob/master/index.js
SlickGrid 2.2.4 MIT https://github.com/6pac/SlickGrid
jQuery-UI 1.11.3 MIT https://jqueryui.com/
+9 -8
View File
@@ -4,15 +4,15 @@ blinker==1.3
django-htmlmin==0.8.0
extras==0.0.3
fixtures==2.0.0
Flask==0.10.1
Flask-Babel==0.9
Flask==0.11.1
Flask-Babel==0.11.1
Flask-Gravatar==0.4.2
Flask-Login==0.2.11
Flask-Login==0.3.2
Flask-Mail==0.9.1
Flask-Principal==0.4.0
Flask-Security==1.7.4
Flask-SQLAlchemy==2.0
Flask-WTF==0.11
Flask-Security==1.7.5
Flask-SQLAlchemy==2.1
Flask-WTF==0.12
html5lib==1.0b3
importlib==1.0.3
itsdangerous==0.24
@@ -21,7 +21,7 @@ linecache2==1.0.0
MarkupSafe==0.23
passlib==1.6.2
pbr==1.9.1
psycopg2==2.5.2
psycopg2==2.6.2
pycrypto==2.6.1
pyrsistent==0.11.13
python-dateutil==2.5.0
@@ -30,7 +30,7 @@ pytz==2014.10
simplejson==3.6.5
six==1.9.0
speaklater==1.3
SQLAlchemy==0.9.8
SQLAlchemy==1.0.14
testscenarios==0.5.0
testtools==2.0.0
traceback2==1.4.0
@@ -38,3 +38,4 @@ unittest2==1.1.0
Werkzeug==0.9.6
WTForms==2.0.2
sqlparse==0.1.19
ordereddict; python_version < '2.7'
+8 -8
View File
@@ -4,15 +4,15 @@ blinker==1.3
django-htmlmin==0.8.0
extras==0.0.3
fixtures==2.0.0
Flask==0.10.1
Flask-Babel==0.9
Flask==0.11.1
Flask-Babel==0.11.1
Flask-Gravatar==0.4.2
Flask-Login==0.2.11
Flask-Login==0.3.2
Flask-Mail==0.9.1
Flask-Principal==0.4.0
Flask-Security==1.7.4
Flask-SQLAlchemy==2.0
Flask-WTF==0.11
Flask-Security==1.7.5
Flask-SQLAlchemy==2.1
Flask-WTF==0.12
html5lib==1.0b3
itsdangerous==0.24
Jinja2==2.7.3
@@ -20,7 +20,7 @@ linecache2==1.0.0
MarkupSafe==0.23
passlib==1.6.2
pbr==1.9.1
psycopg2==2.5.2
psycopg2==2.6.2
pycrypto==2.6.1
pyrsistent==0.11.13
python-dateutil==2.5.0
@@ -29,7 +29,7 @@ pytz==2014.10
simplejson==3.6.5
six==1.9.0
speaklater==1.3
SQLAlchemy==0.9.8
SQLAlchemy==1.0.14
testscenarios==0.5.0
testtools==2.0.0
traceback2==1.4.0
+345 -4
View File
@@ -23,7 +23,8 @@
#include <QInputDialog>
#include <QLineEdit>
#endif
#include <QNetworkRequest>
#include <QNetworkReply>
// App headers
#include "BrowserWindow.h"
#include "ConfigWindow.h"
@@ -42,6 +43,15 @@ BrowserWindow::BrowserWindow(QString url)
m_widget = NULL;
m_toolBtnBack = NULL;
m_toolBtnForward = NULL;
m_downloadStarted = 0;
m_downloadCancelled = 0;
m_file = NULL;
m_downloadFilename = "";
m_defaultFilename = "";
m_progressDialog = NULL;
m_last_open_folder_path = "";
m_dir = "";
m_reply = NULL;
m_appServerUrl = url;
@@ -83,6 +93,11 @@ BrowserWindow::BrowserWindow(QString url)
// Register the slot on tab index change
connect(m_tabWidget,SIGNAL(currentChanged(int )),this,SLOT(tabIndexChanged(int )));
// Listen for download file request from the web page
m_mainWebView->page()->setForwardUnsupportedContent(true);
connect(m_mainWebView->page(), SIGNAL(downloadRequested(const QNetworkRequest &)), this, SLOT(download(const QNetworkRequest &)));
connect(m_mainWebView->page(), SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*)));
m_mainWebView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
// Restore the geometry
@@ -90,6 +105,9 @@ BrowserWindow::BrowserWindow(QString url)
restoreGeometry(settings.value("Browser/Geometry").toByteArray());
restoreState(settings.value("Browser/WindowState").toByteArray());
// The last save location
m_last_open_folder_path = settings.value("Browser/LastSaveLocation", QDir::homePath()).toString();
// Display the app
m_initialLoad = true;
m_loadAttempt = 1;
@@ -110,12 +128,12 @@ void BrowserWindow::closeEvent(QCloseEvent *event)
void BrowserWindow::createActions()
{
// Open an arbitrary URL
openUrlShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U), this);
openUrlShortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_U), this);
openUrlShortcut->setContext(Qt::ApplicationShortcut);
connect(openUrlShortcut, SIGNAL(activated()), this, SLOT(openUrl()));
// Set the Python Path
preferencesShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_P), this);
preferencesShortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_P), this);
preferencesShortcut->setContext(Qt::ApplicationShortcut);
connect(preferencesShortcut, SIGNAL(activated()), this, SLOT(preferences()));
@@ -125,7 +143,7 @@ void BrowserWindow::createActions()
connect(exitShortcut, SIGNAL(activated()), this, SLOT(close()));
// About box
aboutShortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_A), this);
aboutShortcut = new QShortcut(QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_A), this);
aboutShortcut->setContext(Qt::ApplicationShortcut);
connect(aboutShortcut, SIGNAL(activated()), this, SLOT(about()));
}
@@ -199,6 +217,312 @@ int BrowserWindow::findURLTab(const QUrl &name)
return 0;
}
// Below slot will be called when user right click on download link and select "Save Link..." option from context menu
void BrowserWindow::download(const QNetworkRequest &request)
{
// Check that request contains data for download at client side
QUrl name;
if (m_downloadStarted)
{
// Inform user that a download is already started
QMessageBox::information(this, tr("Download warning"), tr("File download already in progress: %1").arg(m_defaultFilename));
return;
}
m_defaultFilename = QFileInfo(request.url().toString()).fileName();
// Open the dialog to save file
QFileDialog save_dialog(this);
save_dialog.setAcceptMode(QFileDialog::AcceptSave);
save_dialog.setWindowTitle(tr("Save file"));
save_dialog.setDirectory(m_last_open_folder_path);
save_dialog.selectFile(m_defaultFilename);
// Register the slot for directory travesing when file dialog is opened and save the last open directory
QObject::connect(&save_dialog, SIGNAL(directoryEntered(const QString &)), this, SLOT(current_dir_path(const QString &)));
m_dir = m_last_open_folder_path;
QString fileName = "";
QString f_name = "";
if (save_dialog.exec() == QDialog::Accepted) {
fileName = save_dialog.selectedFiles().first();
f_name = fileName.replace(m_dir, "");
// Remove the first character(/) from fiename
f_name.remove(0,1);
m_defaultFilename = f_name;
}
else
return;
fileName = m_dir + fileName;
// Clear the last open directory path
m_dir.clear();
#ifdef __APPLE__
// Check that user has given valid file name or not - forward slash is not allowed in file name
// In Mac OSX, forward slash is converted to colon(:) by Qt so we need to check for colon.
if (f_name.indexOf(":") != -1)
{
QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
return;
}
#else
// Check that user has given valid file name or not - forward slash is not allowed in file name
if (f_name.indexOf("/") != -1)
{
QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
return;
}
#endif
if (fileName.isEmpty())
return;
else
{
m_downloadFilename = fileName;
QNetworkRequest newRequest = request;
newRequest.setAttribute(QNetworkRequest::User, fileName);
QObject *obj_web_page = QObject::sender();
if (obj_web_page != NULL)
{
QWebPage *sender_web_page = dynamic_cast<QWebPage*>(obj_web_page);
if (sender_web_page != NULL)
{
QNetworkAccessManager *networkManager = sender_web_page->networkAccessManager();
QNetworkReply *reply = networkManager->get(newRequest);
if (reply != NULL)
{
m_downloadStarted = 1;
m_downloadCancelled = 0;
// Connect the signal for downloadProgress and downloadFinished
connect( reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadFileProgress(qint64, qint64)) );
connect( reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
}
}
}
}
}
// Below slot will be called when file download is in progress
void BrowserWindow::downloadFileProgress(qint64 readData, qint64 totalData)
{
QNetworkReply *reply = ((QNetworkReply*)sender());
QNetworkRequest request = reply->request();
QVariant v = request.attribute(QNetworkRequest::User);
// When download is canceled by user then no need to write data to file
if (m_downloadCancelled)
return;
if(reply != NULL && reply->error() != QNetworkReply::NoError)
{
qDebug() << "Network error occurred whilst downloading: " << m_defaultFilename;
return;
}
// Download is started so open the file
if (!m_file)
{
m_file = new QFile(m_downloadFilename);
if (!m_file->open(QIODevice::WriteOnly))
{
qDebug() << "Error opening file: " << m_downloadFilename;
m_downloadFilename.clear();
m_defaultFilename.clear();
m_downloadStarted = 0;
return;
}
// Create progress bar dialog
m_progressDialog = new QProgressDialog (tr("Downloading file: %1 ").arg(m_defaultFilename), "Cancel", readData, totalData, this);
m_progressDialog->setWindowModality(Qt::WindowModal);
m_progressDialog->setWindowTitle(tr("Download progress"));
m_progressDialog->setMinimumWidth(450);
m_progressDialog->setMinimumHeight(80);
m_progressDialog->setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint);
// Register slot for file download cancel request
QObject::connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(progressCanceled()));
m_reply = reply;
// Show downloading progress bar
m_progressDialog->show();
}
if (m_file)
{
// Write data to file
m_file->write(reply->read(readData));
m_progressDialog->setValue(readData);
// As read data and totalData difference is zero means downloading is finished.
if ((totalData - readData) == 0 ||
(readData != 0 && totalData == -1))
{
// As downloading is finished so remove progress bar dialog
if (m_progressDialog)
{
m_progressDialog->deleteLater();
m_progressDialog = NULL;
}
m_downloadStarted = 0;
m_downloadFilename.clear();
m_defaultFilename.clear();
m_downloadCancelled = 0;
if (m_file)
{
m_file->close();
delete m_file;
m_file = NULL;
}
if (m_reply)
m_reply = NULL;
}
}
}
// Below slot will be called when user cancel the downloading file which is in progress.
void BrowserWindow::progressCanceled()
{
m_downloadCancelled = 1;
if (m_progressDialog)
{
m_progressDialog->deleteLater();
m_progressDialog = NULL;
}
if (m_file)
{
m_file->close();
// Remove the file from file system as downloading is canceled by user
m_file->remove();
delete m_file;
m_file = NULL;
}
if (m_reply)
{
m_reply->abort();
m_reply = NULL;
}
m_downloadFilename.clear();
m_defaultFilename.clear();
m_downloadStarted = 0;
}
// Below slot will called when file download is finished
void BrowserWindow::downloadFinished()
{
if (m_progressDialog)
{
m_progressDialog->deleteLater();
m_progressDialog = NULL;
}
m_downloadFilename.clear();
m_defaultFilename.clear();
m_downloadStarted = 0;
m_downloadCancelled = 0;
if (m_file)
{
m_file->close();
delete m_file;
m_file = NULL;
}
if (m_reply)
m_reply = NULL;
}
// Below slot will be called when user directly click on any download link
void BrowserWindow::unsupportedContent(QNetworkReply * reply)
{
#if QT_VERSION >= 0x050000
// Extract filename and query from encoded URL
QUrlQuery query_data(reply->url());
QString file_name = query_data.queryItemValue("filename");
QString query = query_data.queryItemValue("query");
#else
QUrl url(reply->url());
QString file_name = url.queryItemValue("filename");
QString query = url.queryItemValue("query");
#endif
if (m_downloadStarted)
{
// Inform user that download is already started
QMessageBox::information(this, tr("Download warning"), tr("File download already in progress: %1").arg(m_defaultFilename));
return;
}
// If encoded URL contains 'filename' attribute then use that filename in file dialog.
if (file_name.isEmpty() && query.isEmpty())
m_defaultFilename = QFileInfo(reply->url().toString()).fileName();
else
m_defaultFilename = file_name;
QFileDialog save_dialog(this);
save_dialog.setAcceptMode(QFileDialog::AcceptSave);
save_dialog.setWindowTitle(tr("Save file"));
save_dialog.setDirectory(m_last_open_folder_path);
save_dialog.selectFile(m_defaultFilename);
QObject::connect(&save_dialog, SIGNAL(directoryEntered(const QString &)), this, SLOT(current_dir_path(const QString &)));
m_dir = m_last_open_folder_path;
QString fileName = "";
QString f_name = "";
if (save_dialog.exec() == QDialog::Accepted) {
fileName = save_dialog.selectedFiles().first();
f_name = fileName.replace(m_dir, "");
// Remove the first character(/) from fiename
f_name.remove(0,1);
m_defaultFilename = f_name;
}
else
return;
fileName = m_dir + fileName;
// Clear last open folder path
m_dir.clear();
#ifdef __APPLE__
// Check that user has given valid file name or not - forward slash is not allowed in file name
// In Mac OSX, forward slash is converted to colon(:) by Qt so we need to check for colon.
if (f_name.indexOf(":") != -1)
{
QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
return;
}
#else
// Check that user has given valid file name or not - forward slash is not allowed in file name
if (f_name.indexOf("/") != -1)
{
QMessageBox::information(this, tr("File name error"), tr("Invalid file name"));
return;
}
#endif
if (fileName.isEmpty())
return;
else
{
m_downloadFilename = fileName;
if (reply != NULL)
{
m_downloadStarted = 1;
m_downloadCancelled = 0;
connect( reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadFileProgress(qint64, qint64)));
connect( reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
}
}
}
// Slot: When the tab index change, hide/show the toolbutton displayed on tab
void BrowserWindow::tabIndexChanged(int index)
{
@@ -340,9 +664,21 @@ void BrowserWindow::tabTitleChanged(const QString &str)
}
}
void BrowserWindow::current_dir_path(const QString &dir)
{
m_dir = dir;
m_last_open_folder_path = dir;
QSettings settings;
settings.setValue("Browser/LastSaveLocation", m_last_open_folder_path);
}
// Slot: Link is open from pgAdmin mainwindow
void BrowserWindow::urlLinkClicked(const QUrl &name)
{
// Check that request contains the data download at client side
QNetworkRequest request;
// First check is there any tab opened with same URL then open it again.
int tabFound = findURLTab(name);
@@ -353,6 +689,11 @@ void BrowserWindow::urlLinkClicked(const QUrl &name)
m_addNewGridLayout->setContentsMargins(0, 0, 0, 0);
m_addNewWebView = new WebViewWindow(m_addNewTab);
// Listen for the download request from the web page
m_addNewWebView->page()->setForwardUnsupportedContent(true);
connect(m_addNewWebView->page(), SIGNAL(downloadRequested(const QNetworkRequest &)), this, SLOT(download(const QNetworkRequest &)));
connect(m_addNewWebView->page(), SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*)));
m_widget = new QWidget(m_addNewTab);
m_toolBtnBack = new QToolButton(m_widget);
m_toolBtnBack->setFixedHeight(PGA_BTN_SIZE);
+15
View File
@@ -54,6 +54,12 @@ public slots:
void tabIndexChanged(int index);
void goBackPage();
void goForwardPage();
void download(const QNetworkRequest &request);
void unsupportedContent(QNetworkReply * reply);
void downloadFinished();
void downloadFileProgress(qint64 , qint64 );
void progressCanceled();
void current_dir_path(const QString &dir);
private:
QString m_appServerUrl;
@@ -79,6 +85,15 @@ private:
bool m_initialLoad;
int m_loadAttempt;
QString m_downloadFilename;
int m_downloadStarted;
int m_downloadCancelled;
QFile *m_file;
QProgressDialog *m_progressDialog;
QString m_defaultFilename;
QString m_last_open_folder_path;
QString m_dir;
QNetworkReply *m_reply;
void createActions();
void pause(int seconds = 1);
+47 -6
View File
@@ -69,8 +69,9 @@ Server::Server(quint16 port)
QString python_path = settings.value("PythonPath").toString();
// Get the application directory
QString app_dir = qApp->applicationDirPath();
QString path_env = qgetenv("PATH");
QString app_dir = qApp->applicationDirPath(),
path_env = qgetenv("PATH"),
pythonHome;
QStringList path_list;
int i;
@@ -85,6 +86,7 @@ Server::Server(quint16 port)
QFileInfo venvLibPath(app_dir + "/../Resources/venv/lib/python");
QFileInfo venvDynLibPath(app_dir + "/../Resources/venv/lib/python/lib-dynload");
QFileInfo venvSitePackagesPath(app_dir + "/../Resources/venv/lib/python/site-packages");
QFileInfo venvPath(app_dir + "/../Resources/venv");
// Prepend the bin directory to the path
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
@@ -92,6 +94,7 @@ Server::Server(quint16 port)
add_to_path(python_path, venvLibPath.canonicalFilePath());
add_to_path(python_path, venvDynLibPath.canonicalFilePath());
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
add_to_path(pythonHome, venvPath.canonicalFilePath());
#elif defined(Q_OS_WIN)
// In the case we're running in a release application, we need to ensure the
@@ -103,6 +106,7 @@ Server::Server(quint16 port)
QFileInfo venvLibPath(app_dir + "/../venv/Lib");
QFileInfo venvDLLsPath(app_dir + "/../venv/DLLs");
QFileInfo venvSitePackagesPath(app_dir + "/../venv/Lib/site-packages");
QFileInfo venvPath(app_dir + "/../venv");
// Prepend the bin directory to the path
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
@@ -110,12 +114,14 @@ Server::Server(quint16 port)
add_to_path(python_path, venvLibPath.canonicalFilePath());
add_to_path(python_path, venvDLLsPath.canonicalFilePath());
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
add_to_path(pythonHome, venvPath.canonicalFilePath());
#else
// Build (and canonicalise) the virtual environment path
QFileInfo venvBinPath(app_dir + "/../venv/bin");
QFileInfo venvLibPath(app_dir + "/../venv/lib/python");
QFileInfo venvDynLibPath(app_dir + "/../venv/lib/python/lib-dynload");
QFileInfo venvSitePackagesPath(app_dir + "/../venv/lib/python/site-packages");
QFileInfo venvPath(app_dir + "/../venv");
// Prepend the bin directory to the path
add_to_path(path_env, venvBinPath.canonicalFilePath(), true);
@@ -123,6 +129,7 @@ Server::Server(quint16 port)
add_to_path(python_path, venvLibPath.canonicalFilePath());
add_to_path(python_path, venvDynLibPath.canonicalFilePath());
add_to_path(python_path, venvSitePackagesPath.canonicalFilePath());
add_to_path(pythonHome, venvPath.canonicalFilePath());
#endif
qputenv("PATH", path_env.toUtf8().data());
@@ -149,10 +156,21 @@ Server::Server(quint16 port)
qputenv("PYTHONPATH", python_path.toUtf8().data());
}
qDebug() << "Full Python path: " << python_path;
qDebug() << "Python path: " << python_path
<< "\nPython Home: " << pythonHome;
if (!pythonHome.isEmpty())
{
#ifdef PYTHON2
Py_SetPythonHome(pythonHome.toUtf8().data());
#else
char *python_home = pythonHome.toUtf8().data();
const size_t cSize = strlen(python_home) + 1;
wchar_t* wcPythonHome = new wchar_t[cSize];
mbstowcs (wcPythonHome, python_home, cSize);
python_path = settings.value("PythonPath").toString();
qDebug() << "User Python path: " << python_path;
Py_SetPythonHome(wcPythonHome);
#endif
}
Py_Initialize();
@@ -234,10 +252,33 @@ void Server::run()
// Run the app!
#ifdef PYTHON2
/*
* Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later
* versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator,
* which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory.
* Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation.
*/
char* n_argv[] = { m_appfile.toUtf8().data() };
PySys_SetArgv(1, n_argv);
PyObject* PyFileObject = PyFile_FromString(m_appfile.toUtf8().data(), (char *)"r");
if (PyRun_SimpleFile(PyFile_AsFile(PyFileObject), m_appfile.toUtf8().data()) != 0)
int ret = PyRun_SimpleFile(PyFile_AsFile(PyFileObject), m_appfile.toUtf8().data());
if (ret != 0)
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
* versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator,
* which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory.
* Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation.
*/
char *appName = m_appfile.toUtf8().data();
const size_t cSize = strlen(appName)+1;
wchar_t* wcAppName = new wchar_t[cSize];
mbstowcs (wcAppName, appName, cSize);
wchar_t* n_argv[] = { wcAppName };
PySys_SetArgv(1, n_argv);
int fd = fileno(cp);
PyObject* PyFileObject = PyFile_FromFd(fd, m_appfile.toUtf8().data(), (char *)"r", -1, NULL, NULL,NULL,1);
if (PyRun_SimpleFile(fdopen(PyObject_AsFileDescriptor(PyFileObject),"r"), m_appfile.toUtf8().data()) != 0)
+41 -17
View File
@@ -22,30 +22,46 @@ from logging import *
APP_NAME = 'pgAdmin 4'
APP_ICON = 'icon-postgres-alt'
##########################################################################
# Application settings
##########################################################################
# NOTE!!!
# If you change any of APP_RELEASE, APP_REVISION or APP_SUFFIX, then you
# must also change APP_VERSION_INT to match.
# Application version number components
APP_RELEASE = 1
APP_REVISION = 0
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
# for GA releases.
APP_SUFFIX = 'beta2'
APP_SUFFIX = 'rc1'
# 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 = 10005
# DO NOT CHANGE!
# The application version string, constructed from the components
APP_VERSION = '%s.%s-%s' % (APP_RELEASE, APP_REVISION, APP_SUFFIX)
# Copyright string for display in the app
APP_COPYRIGHT = 'Copyright 2013 - 2016, The pgAdmin Development Team'
##########################################################################
# Misc stuff
##########################################################################
# Path to the online help.
HELP_PATH = '../../../docs/en_US/_build/html/'
# Languages we support in the UI
LANGUAGES = {
'en': 'English',
'fr': 'Français'
'en': 'English'
}
# DO NOT CHANGE!
# The application version string, constructed from the components
APP_VERSION = '%s.%s-%s' % (APP_RELEASE, APP_REVISION, APP_SUFFIX)
# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING!
# List of modules to skip when dynamically loading
MODULE_BLACKLIST = ['test']
@@ -137,7 +153,10 @@ SECURITY_PASSWORD_SALT = 'SuperSecret3'
SECURITY_PASSWORD_HASH = 'pbkdf2_sha512'
# Should HTML be minified on the fly when not in debug mode?
MINIFY_HTML = True
# Note: This is disabled by default as it will error when processing the
# docs. If the serving of docs is handled by an Apache HTTPD
# instance (rather than via the app), then it can be safely enabled.
MINIFY_HTML = False
##########################################################################
# Server Connection Driver Settings
@@ -154,10 +173,6 @@ MAX_SESSION_IDLE_TIME = 60
# User account and settings storage
##########################################################################
# The schema version number for the configuration database
# DO NOT CHANGE UNLESS YOU ARE A PGADMIN DEVELOPER!!
SETTINGS_SCHEMA_VERSION = 11
# The default path to the SQLite database used to store user accounts and
# settings. This default places the file in the same directory as this
# config file, but generates an absolute path for use througout the app.
@@ -204,11 +219,14 @@ SESSION_COOKIE_NAME = 'pga4_session'
# These settings are used when running in web server mode for confirming
# and resetting passwords etc.
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USERNAME = 'username'
MAIL_PASSWORD = 'SuperSecret'
# See: http://pythonhosted.org/Flask-Mail/ for more info
MAIL_SERVER = 'localhost'
MAIL_PORT = 25
MAIL_USE_SSL = False
MAIL_USE_TLS = False
MAIL_USERNAME = ''
MAIL_PASSWORD = ''
MAIL_DEBUG = False
##########################################################################
# Mail content settings
@@ -253,6 +271,12 @@ STORAGE_DIR = os.path.join(
##########################################################################
THREADED_MODE = True
##########################################################################
# Do not allow SQLALCHEMY to track modification as it is going to be
# deprecated in future
##########################################################################
SQLALCHEMY_TRACK_MODIFICATIONS = False
##########################################################################
# Local config settings
##########################################################################
+21 -14
View File
@@ -23,6 +23,11 @@ if sys.path[0] != root:
import config
from pgadmin import create_app
# Get the config database schema version. We store this in pgadmin.model
# as it turns out that putting it in the config files isn't a great idea
from pgadmin.model import SCHEMA_VERSION
config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION
##########################################################################
# Sanity checks
##########################################################################
@@ -75,18 +80,20 @@ else:
# Let the application save the status about the runtime for using it later.
app.PGADMIN_RUNTIME = PGADMIN_RUNTIME
# Output a startup message if we're not under the runtime
if not PGADMIN_RUNTIME:
print("Starting %s. Please navigate to http://localhost:%d in your browser." %
(config.APP_NAME, server_port))
sys.stdout.flush()
# Output a startup message if we're not under the runtime and startup.
# If we're under WSGI, we don't need to worry about this
if __name__ == '__main__':
if not PGADMIN_RUNTIME:
print("Starting %s. Please navigate to http://%s:%d in your browser." %
(config.APP_NAME, config.DEFAULT_SERVER, server_port))
sys.stdout.flush()
try:
app.run(
host=config.DEFAULT_SERVER,
port=server_port,
use_reloader=((not PGADMIN_RUNTIME) and app.debug),
threaded=config.THREADED_MODE
)
except IOError:
app.logger.error("Error starting the app server: %s", sys.exc_info())
try:
app.run(
host=config.DEFAULT_SERVER,
port=server_port,
use_reloader=((not PGADMIN_RUNTIME) and app.debug),
threaded=config.THREADED_MODE
)
except IOError:
app.logger.error("Error starting the app server: %s", sys.exc_info())
+8
View File
@@ -0,0 +1,8 @@
import os
import sys
root = os.path.dirname(os.path.realpath(__file__))
if sys.path[0] != root:
sys.path.insert(0, root)
from pgAdmin4 import app as application
+146 -8
View File
@@ -10,25 +10,35 @@
"""The main pgAdmin module. This handles the application initialisation tasks,
such as setup of logging, dynamic loading of modules etc."""
import logging
import sys
import os, sys
from collections import defaultdict
from importlib import import_module
from flask import Flask, abort, request, current_app
from flask.ext.babel import Babel
from flask.ext.security import Security, SQLAlchemyUserDatastore
from flask_babel import Babel, gettext
from flask_login import user_logged_in
from flask_security import Security, SQLAlchemyUserDatastore
from flask_mail import Mail
from flask_security.utils import login_user
from htmlmin.minify import html_minify
from pgadmin.utils import PgAdminModule, driver
from pgadmin.utils.session import ServerSideSessionInterface
from pgadmin.utils.session import create_session_interface
from werkzeug.local import LocalProxy
from werkzeug.utils import find_modules
from pgadmin.model import db, Role, User, Version
from pgadmin.model import db, Role, Server, ServerGroup, User, Version
# Configuration settings
import config
# If script is running under python3, it will not have the xrange function
# defined
winreg = None
if sys.version_info.major >= 3:
xrange = range
if os.name == 'nt':
import winreg
elif os.name == 'nt':
import _winreg as winreg
class PgAdmin(Flask):
def find_submodules(self, basemodule):
@@ -114,11 +124,12 @@ def create_app(app_name=config.APP_NAME):
# Removes unwanted whitespace from render_template function
app.jinja_env.trim_blocks = True
app.config.from_object(config)
app.config.update(dict(PROPAGATE_EXCEPTIONS=True))
##########################################################################
# Setup session management
##########################################################################
app.session_interface = ServerSideSessionInterface(config.SESSION_DB_PATH)
app.session_interface = create_session_interface(app)
##########################################################################
# Setup logging and log the application startup
@@ -137,7 +148,7 @@ def create_app(app_name=config.APP_NAME):
logger.setLevel(logging.INFO)
# File logging
fh = logging.FileHandler(config.LOG_FILE)
fh = logging.FileHandler(config.LOG_FILE, encoding='utf-8')
fh.setLevel(config.FILE_LOG_LEVEL)
fh.setFormatter(logging.Formatter(config.FILE_LOG_FORMAT))
app.logger.addHandler(fh)
@@ -211,9 +222,136 @@ def create_app(app_name=config.APP_NAME):
from setup import do_upgrade
do_upgrade(app, user_datastore, security, version)
# Load all available serve drivers
# Load all available server drivers
driver.init_app(app)
##########################################################################
# Register any local servers we can discover
##########################################################################
@user_logged_in.connect_via(app)
def on_user_logged_in(sender, user):
# Keep hold of the user ID
user_id = user.id
# Get the first server group for the user
servergroup_id = 1
servergroups = ServerGroup.query.filter_by(
user_id=user_id
).order_by("id")
if servergroups.count() > 0:
servergroup = servergroups.first()
servergroup_id = servergroup.id
'''Add a server to the config database'''
def add_server(user_id, servergroup_id, name, superuser, port, discovery_id, comment):
# Create a server object if needed, and store it.
servers = Server.query.filter_by(
user_id=user_id,
discovery_id=svr_discovery_id
).order_by("id")
if servers.count() > 0:
return;
svr = Server(user_id=user_id,
servergroup_id=servergroup_id,
name=name,
host='localhost',
port=port,
maintenance_db='postgres',
username=superuser,
ssl_mode='prefer',
comment=svr_comment,
discovery_id=discovery_id)
db.session.add(svr)
db.session.commit()
# Figure out what servers are present
if winreg is not None:
proc_arch = os.environ['PROCESSOR_ARCHITECTURE'].lower()
try:
proc_arch64 = os.environ['PROCESSOR_ARCHITEW6432'].lower()
except:
proc_arch64 = None
if proc_arch == 'x86' and not proc_arch64:
arch_keys = {0}
elif proc_arch == 'x86' or proc_arch == 'amd64':
arch_keys = {winreg.KEY_WOW64_32KEY, winreg.KEY_WOW64_64KEY}
for arch_key in arch_keys:
for server_type in { 'PostgreSQL', 'EnterpriseDB'}:
try:
root_key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
"SOFTWARE\\" + server_type + "\Services", 0,
winreg.KEY_READ | arch_key
)
for i in xrange(0, winreg.QueryInfoKey(root_key)[0]):
inst_id = winreg.EnumKey(root_key, i)
inst_key = winreg.OpenKey(root_key, inst_id)
svr_name = winreg.QueryValueEx(
inst_key, 'Display Name'
)[0]
svr_superuser = winreg.QueryValueEx(
inst_key, 'Database Superuser'
)[0]
svr_port = winreg.QueryValueEx(inst_key, 'Port')[0]
svr_discovery_id = inst_id
svr_comment = gettext(
"Auto-detected %s installation with the data directory at %s" % (
winreg.QueryValueEx(
inst_key, 'Display Name'
)[0],
winreg.QueryValueEx(
inst_key, 'Data Directory'
)[0]
)
)
add_server(
user_id, servergroup_id, svr_name,
svr_superuser, svr_port,
svr_discovery_id, svr_comment
)
inst_key.Close()
except:
pass
else:
# We use the postgres-winreg.ini file on non-Windows
try:
from configparser import ConfigParser
except ImportError:
from ConfigParser import ConfigParser # Python 2
registry = ConfigParser()
try:
registry.read('/etc/postgres-reg.ini')
sections = registry.sections()
# Loop the sections, and get the data from any that are PG or PPAS
for section in sections:
if section.startswith('PostgreSQL/') or section.startswith('EnterpriseDB/'):
svr_name = registry.get(section, 'Description')
svr_superuser = registry.get(section, 'Superuser')
svr_port = registry.getint(section, 'Port')
svr_discovery_id = section
svr_comment = gettext("Auto-detected %s installation with the data directory at %s" % (
registry.get(section, 'Description'),
registry.get(section, 'DataDirectory')))
add_server(user_id, servergroup_id, svr_name, svr_superuser, svr_port, svr_discovery_id, svr_comment)
except:
pass
##########################################################################
# Load plugin modules
##########################################################################
+2 -2
View File
@@ -13,8 +13,8 @@ MODULE_NAME = 'about'
import sys
from flask import Response, render_template, __version__, url_for
from flask.ext.babel import gettext
from flask.ext.security import current_user, login_required
from flask_babel import gettext
from flask_security import current_user, login_required
from pgadmin.utils import PgAdminModule
from pgadmin.utils.menu import MenuItem
+26 -6
View File
@@ -12,9 +12,9 @@ from abc import ABCMeta, abstractmethod, abstractproperty
import six
from flask import current_app, render_template, url_for, make_response, flash
from flask.ext.babel import gettext
from flask.ext.login import current_user
from flask.ext.security import login_required
from flask_babel import gettext
from flask_login import current_user
from flask_security import login_required
from flask_gravatar import Gravatar
from pgadmin.settings import get_setting
from pgadmin.utils import PgAdminModule
@@ -189,11 +189,10 @@ class BrowserModule(PgAdminModule):
def register_preferences(self):
self.show_system_objects = self.preference.register(
'display', 'show_system_objects',
gettext("Show system objects"), 'boolean', False,
gettext("Show system objects?"), 'boolean', False,
category_label=gettext('Display')
)
blueprint = BrowserModule(MODULE_NAME, __name__)
@@ -462,7 +461,7 @@ def index():
pass
if data is not None:
if data['pgadmin4']['version'] != config.APP_VERSION:
if data['pgadmin4']['version_int'] > config.APP_VERSION_INT:
msg = render_template(
MODULE_NAME + "/upgrade.html",
current_version=config.APP_VERSION,
@@ -495,6 +494,15 @@ def browser_js():
edbas_help_path_pref = prefs.preference('edbas_help_path')
edbas_help_path = edbas_help_path_pref.get()
# Get sqleditor options
prefs = Preferences.module('sqleditor')
editor_tab_size_pref = prefs.preference('tab_size')
editor_tab_size = editor_tab_size_pref.get()
editor_use_spaces_pref = prefs.preference('use_spaces')
editor_use_spaces = editor_use_spaces_pref.get()
for submodule in current_blueprint.submodules:
snippets.extend(submodule.jssnippets)
return make_response(
@@ -504,6 +512,8 @@ def browser_js():
jssnippets=snippets,
pg_help_path=pg_help_path,
edbas_help_path=edbas_help_path,
editor_tab_size=editor_tab_size,
editor_use_spaces=editor_use_spaces,
_=gettext
),
200, {'Content-Type': 'application/x-javascript'})
@@ -557,6 +567,16 @@ def collection_js():
def browser_css():
"""Render and return CSS snippets from the nodes and modules."""
snippets = []
# Get configurable options
prefs = Preferences.module('sqleditor')
sql_font_size_pref = prefs.preference('sql_font_size')
sql_font_size = round(float(sql_font_size_pref.get()), 2)
if sql_font_size != 0:
snippets.append('.CodeMirror { font-size: %sem; }' % str(sql_font_size))
for submodule in blueprint.submodules:
snippets.extend(submodule.csssnippets)
return make_response(
+3 -2
View File
@@ -11,7 +11,7 @@ from abc import ABCMeta, abstractmethod
import six
from flask import url_for, render_template
from flask.ext.babel import gettext
from flask_babel import gettext
from pgadmin.browser import BrowserPluginModule
from pgadmin.browser.utils import PGChildModule
from pgadmin.utils import PgAdminModule
@@ -85,7 +85,8 @@ class CollectionNodeModule(PgAdminModule, PGChildModule):
"_type": 'coll-%s' % (self.node_type),
"_id": parent_id,
"_pid": parent_id,
"module": 'pgadmin.node.%s' % self.node_type
"module": 'pgadmin.node.%s' % self.node_type,
"nodes": [self.node_type]
}
for key in kwargs:
+46 -21
View File
@@ -8,16 +8,16 @@
##########################################################################
"""Defines views for management of server groups"""
import json
import simplejson as json
from abc import ABCMeta, abstractmethod
import six
from flask import request, render_template, make_response, jsonify
from flask.ext.babel import gettext
from flask.ext.security import current_user
from flask_babel import gettext
from flask_security import current_user
from pgadmin.browser import BrowserPluginModule
from pgadmin.browser.utils import NodeView
from pgadmin.utils.ajax import make_json_response, \
from pgadmin.utils.ajax import make_json_response, gone, \
make_response as ajax_response
from pgadmin.utils.menu import MenuItem
@@ -131,7 +131,7 @@ class ServerGroupView(NodeView):
if sg is None:
return make_json_response(
status=417,
status=410,
success=0,
errormsg=gettext(
'The specified server group could not be found.'
@@ -156,7 +156,9 @@ class ServerGroupView(NodeView):
user_id=current_user.id,
id=gid).first()
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
if servergroup is None:
return make_json_response(
@@ -176,7 +178,17 @@ class ServerGroupView(NodeView):
status=410, success=0, errormsg=e.message
)
return make_json_response(result=request.form)
return jsonify(
node=self.blueprint.generate_browser_node(
gid,
None,
servergroup.name,
"icon-%s" % self.node_type,
True,
self.node_type,
can_delete=True # This is user created hence can deleted
)
)
def properties(self, gid):
"""Update the server-group properties"""
@@ -188,7 +200,7 @@ class ServerGroupView(NodeView):
if sg is None:
return make_json_response(
status=417,
status=410,
success=0,
errormsg=gettext(
'The specified server group could not be found.'
@@ -201,7 +213,10 @@ class ServerGroupView(NodeView):
)
def create(self):
data = request.form if request.form else json.loads(request.data.decode())
"""Creates new server-group """
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
if data[u'name'] != '':
try:
check_sg = ServerGroup.query.filter_by(
@@ -227,7 +242,7 @@ class ServerGroupView(NodeView):
return jsonify(
node=self.blueprint.generate_browser_node(
"%d" % (sg.id), None,
"%d" % (sg.id),None,
sg.name,
"icon-%s" % self.node_type,
True,
@@ -278,19 +293,29 @@ class ServerGroupView(NodeView):
if gid is None:
groups = ServerGroup.query.filter_by(user_id=current_user.id)
else:
groups = ServerGroup.query.filter_by(user_id=current_user.id,
id=gid).first()
for group in groups:
nodes.append(
self.blueprint.generate_browser_node(
"%d" % (group.id), None,
group.name,
"icon-%s" % self.node_type,
True,
self.node_type
for group in groups:
nodes.append(
self.blueprint.generate_browser_node(
"%d" % (group.id), None,
group.name,
"icon-%s" % self.node_type,
True,
self.node_type
)
)
else:
group = ServerGroup.query.filter_by(user_id=current_user.id,
id=gid).first()
if not group:
return gone(errormsg="Couldn't find the server-group!")
nodes = self.blueprint.generate_browser_node(
"%d" % (group.id), None,
group.name,
"icon-%s" % self.node_type,
True,
self.node_type
)
return make_json_response(data=nodes)
@@ -7,14 +7,13 @@
#
##########################################################################
import json
import traceback
import simplejson as json
import pgadmin.browser.server_groups as sg
from flask import render_template, request, make_response, jsonify, \
current_app, url_for
from flask.ext.babel import gettext
from flask.ext.security import current_user
from flask_babel import gettext
from flask_security import current_user
from pgadmin.browser.server_groups.servers.types import ServerType
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, bad_request, forbidden, \
@@ -269,6 +268,12 @@ class ServerNode(PGChildNodeView):
wal_pause=wal_paused
)
)
if not len(res):
return gone(errormsg=gettext(
'The specified server group with id# {0} could not be found.'
))
return make_json_response(result=res)
def node(self, gid, sid):
@@ -283,7 +288,7 @@ class ServerNode(PGChildNodeView):
success=0,
errormsg=gettext(
gettext(
"Couldn't find the server with id# %s!"
"Couldn't find the server with id# {0}!"
).format(sid)
)
)
@@ -337,6 +342,7 @@ class ServerNode(PGChildNodeView):
# TODO:: A server, which is connected, can not be deleted
if servers is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext(
'The specified server could not be found.\n'
@@ -355,14 +361,8 @@ class ServerNode(PGChildNodeView):
success=0,
errormsg=e.message)
try:
info = traceback.format_exc()
except Exception as e:
current_app.logger.exception(e)
info = str(e)
return make_json_response(success=1,
info=info)
info=gettext("Server deleted"))
def update(self, gid, sid):
"""Update the server settings"""
@@ -371,6 +371,7 @@ class ServerNode(PGChildNodeView):
if server is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext("Could not find the required server.")
)
@@ -400,7 +401,9 @@ class ServerNode(PGChildNodeView):
}
idx = 0
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
@@ -444,9 +447,8 @@ class ServerNode(PGChildNodeView):
if not conn.connected():
manager.update(server)
return make_json_response(
success=1,
data=self.blueprint.generate_browser_node(
return jsonify(
node=self.blueprint.generate_browser_node(
"%d" % (server.id), server.servergroup_id,
server.name,
"icon-server-not-connected" if not connected else
@@ -507,6 +509,7 @@ class ServerNode(PGChildNodeView):
if server is None:
return make_json_response(
status=410,
success=0,
errormsg=gettext("Could not find the required server.")
)
@@ -554,7 +557,9 @@ class ServerNode(PGChildNodeView):
u'role'
]
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
for arg in required_args:
if arg not in data:
@@ -566,6 +571,8 @@ class ServerNode(PGChildNodeView):
)
)
server = None
try:
server = Server(
user_id=current_user.id,
@@ -592,8 +599,10 @@ class ServerNode(PGChildNodeView):
manager.update(server)
conn = manager.connection()
have_password = False
if 'password' in data and data["password"] != '':
# login with password
have_password = True
password = data['password']
password = encrypt(password, current_user.password)
else:
@@ -614,6 +623,10 @@ class ServerNode(PGChildNodeView):
errormsg=gettext("Unable to connect to server:\n\n%s" % errmsg)
)
else:
if 'save_password' in data and data['save_password'] and have_password:
setattr(server, 'password', password)
db.session.commit()
user = manager.user_info
connected = True
icon = "icon-pg"
@@ -707,8 +720,16 @@ class ServerNode(PGChildNodeView):
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
res = conn.connected()
return make_json_response(data={'connected': conn.connected()})
if res:
from pgadmin.utils.exception import ConnectionLost
try:
conn.execute_scalar('SELECT 1')
except ConnectionLost:
res = False
return make_json_response(data={'connected': res})
def connect(self, gid, sid):
"""
@@ -740,14 +761,21 @@ class ServerNode(PGChildNodeView):
if user is None:
return unauthorized(gettext("Unauthorized request."))
data = request.form if request.form else json.loads(request.data) if \
request.data else {}
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
) if request.data else {}
password = None
save_password = False
# Connect the Server
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
if 'password' not in data:
if server.password is None:
conn_passwd = getattr(conn, 'password', None)
if conn_passwd is None and server.password is None:
# Return the password template in case password is not
# provided, or password has not been saved earlier.
return make_json_response(
@@ -766,19 +794,15 @@ class ServerNode(PGChildNodeView):
data['save_password'] if password and \
'save_password' in data else False
# Encrypt the password before saving with user's login password key.
try:
password = encrypt(password, user.password) \
if password is not None else server.password
except Exception as e:
current_app.logger.exception(e)
return internal_server_error(errormsg=e.message)
# Connect the Server
from pgadmin.utils.driver import get_driver
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(sid)
conn = manager.connection()
# Encrypt the password before saving with user's login password key.
try:
password = encrypt(password, user.password) \
if password is not None else server.password
except Exception as e:
current_app.logger.exception(e)
return internal_server_error(errormsg=e.message)
status = True
try:
status, errmsg = conn.connect(
password=password,
@@ -786,13 +810,18 @@ class ServerNode(PGChildNodeView):
)
except Exception as e:
current_app.logger.exception(e)
# TODO::
# Ask the password again (if existing password couldn't be
# descrypted)
if e.message:
return internal_server_error(errormsg=e.message)
else:
return internal_server_error(errormsg=str(e))
return make_json_response(
success=0,
status=401,
result=render_template(
'servers/password.html',
server_label=server.name,
username=server.username,
errmsg=e.message if e.message else str(e),
_=gettext
)
)
if not status:
current_app.logger.error(
@@ -855,6 +884,7 @@ class ServerNode(PGChildNodeView):
manager.server_type
),
'connected': True,
'server_type': manager.server_type,
'type': manager.server_type,
'version': manager.version,
'db': manager.db,
@@ -969,7 +999,7 @@ class ServerNode(PGChildNodeView):
sid: Server id
"""
try:
data = json.loads(request.form['data'])
data = json.loads(request.form['data'], encoding='utf-8')
if data and ('password' not in data or
data['password'] == '' or
'newPassword' not in data or
@@ -9,13 +9,13 @@
"""Implements the Database Node"""
import json
import simplejson as json
import re
from functools import wraps
import pgadmin.browser.server_groups.servers as servers
from flask import render_template, make_response, current_app, request, jsonify
from flask.ext.babel import gettext as _
from flask_babel import gettext as _
from pgadmin.browser.collection import CollectionNodeModule
from pgadmin.browser.server_groups.servers.databases.utils import \
parse_sec_labels_from_db, parse_variables_from_db
@@ -24,7 +24,7 @@ from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error, unauthorized
from pgadmin.utils.ajax import precondition_required, gone
from pgadmin.utils.ajax import gone
from pgadmin.utils.driver import get_driver
from config import PG_DEFAULT_DRIVER
@@ -99,6 +99,7 @@ class DatabaseView(PGChildNodeView):
{'get': 'list', 'post': 'create'}
],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'get_databases': [{'get': 'get_databases'}, {'get': 'get_databases'}],
'sql': [{'get': 'sql'}],
'msql': [{'get': 'msql'}, {'get': 'msql'}],
'stats': [{'get': 'statistics'}, {'get': 'statistics'}],
@@ -126,17 +127,15 @@ class DatabaseView(PGChildNodeView):
def wrapped(self, *args, **kwargs):
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
if self.manager is None:
return gone(errormsg="Couldn't find the server.")
if action and action in ["drop"]:
self.conn = self.manager.connection()
elif 'did' in kwargs:
self.conn = self.manager.connection(did=kwargs['did'])
else:
self.conn = self.manager.connection()
# If DB not connected then return error to browser
if not self.conn.connected():
return precondition_required(
_("Connection to the server has been lost!")
)
ver = self.manager.version
# we will set template path for sql scripts
@@ -146,6 +145,7 @@ class DatabaseView(PGChildNodeView):
self.template_path = 'databases/sql/9.2_plus'
else:
self.template_path = 'databases/sql/9.1_plus'
return f(self, *args, **kwargs)
return wrapped
@@ -155,7 +155,9 @@ class DatabaseView(PGChildNodeView):
@check_precondition(action="list")
def list(self, gid, sid):
last_system_oid = 0 if self.blueprint.show_system_objects else \
(self.manager.db_info[self.manager.did])['datlastsysoid']
(self.manager.db_info[self.manager.did])['datlastsysoid'] \
if self.manager.db_info is not None and \
self.manager.did in self.manager.db_info else 0
SQL = render_template(
"/".join([self.template_path, 'properties.sql']),
conn=self.conn, last_system_oid=last_system_oid
@@ -170,11 +172,14 @@ class DatabaseView(PGChildNodeView):
status=200
)
@check_precondition(action="nodes")
def nodes(self, gid, sid):
def get_nodes(self, gid, sid, show_system_templates=False):
res = []
last_system_oid = 0 if self.blueprint.show_system_objects else \
(self.manager.db_info[self.manager.did])['datlastsysoid']
last_system_oid = 0 if self.blueprint.show_system_objects or \
show_system_templates else (
(self.manager.db_info[self.manager.did])['datlastsysoid'] \
if self.manager.db_info is not None and \
self.manager.did in self.manager.db_info else 0
)
SQL = render_template(
"/".join([self.template_path, 'nodes.sql']),
@@ -186,13 +191,16 @@ class DatabaseView(PGChildNodeView):
return internal_server_error(errormsg=rset)
for row in rset['rows']:
if self.manager.db == row['name']:
dbname = row['name']
if hasattr(str, 'decode'):
dbname = dbname.decode('utf-8')
if self.manager.db == dbname:
connected = True
canDisConn = False
canDrop = canDisConn = False
else:
conn = self.manager.connection(row['name'])
conn = self.manager.connection(dbname)
connected = conn.connected()
canDisConn = True
canDrop = canDisConn = True
res.append(
self.blueprint.generate_browser_node(
@@ -205,10 +213,25 @@ class DatabaseView(PGChildNodeView):
tablespace=row['spcname'],
allowConn=row['datallowconn'],
canCreate=row['cancreate'],
canDisconn=canDisConn
canDisconn=canDisConn,
canDrop = canDrop
)
)
return res
@check_precondition(action="nodes")
def nodes(self, gid, sid):
res = self.get_nodes(gid, sid)
return make_json_response(
data=res,
status=200
)
@check_precondition(action="get_databases")
def get_databases(self, gid, sid):
res = self.get_nodes(gid, sid, True)
return make_json_response(
data=res,
status=200
@@ -226,7 +249,10 @@ class DatabaseView(PGChildNodeView):
return internal_server_error(errormsg=rset)
for row in rset['rows']:
if self.manager.db == row['name']:
db = row['name']
if hasattr(str, 'decode'):
db = db.decode('utf-8')
if self.manager.db == db:
connected = True
else:
conn = self.manager.connection(row['name'])
@@ -256,6 +282,11 @@ class DatabaseView(PGChildNodeView):
)
status, res = self.conn.execute_dict(SQL)
if len(res['rows']) == 0:
return gone(
_("Couldnot find the database on the server.")
)
if not status:
return internal_server_error(errormsg=res)
@@ -381,26 +412,22 @@ class DatabaseView(PGChildNodeView):
This function to return list of avialable encodings
"""
res = [{'label': '', 'value': ''}]
try:
SQL = render_template(
"/".join([self.template_path, 'get_encodings.sql'])
)
status, rset = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
SQL = render_template(
"/".join([self.template_path, 'get_encodings.sql'])
)
status, rset = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=rset)
for row in rset['rows']:
res.append(
{'label': row['encoding'], 'value': row['encoding']}
)
return make_json_response(
data=res,
status=200
for row in rset['rows']:
res.append(
{'label': row['encoding'], 'value': row['encoding']}
)
except Exception as e:
return internal_server_error(errormsg=str(e))
return make_json_response(
data=res,
status=200
)
@check_precondition(action="get_ctypes")
def get_ctypes(self, gid, sid, did=None):
@@ -413,25 +440,21 @@ class DatabaseView(PGChildNodeView):
res.append(
{'label': val, 'value': val}
)
try:
SQL = render_template(
"/".join([self.template_path, 'get_ctypes.sql'])
)
status, rset = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
SQL = render_template(
"/".join([self.template_path, 'get_ctypes.sql'])
)
status, rset = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=rset)
for row in rset['rows']:
if row['cname'] not in default_list:
res.append({'label': row['cname'], 'value': row['cname']})
for row in rset['rows']:
if row['cname'] not in default_list:
res.append({'label': row['cname'], 'value': row['cname']})
return make_json_response(
data=res,
status=200
)
except Exception as e:
return internal_server_error(errormsg=str(e))
return make_json_response(
data=res,
status=200
)
@check_precondition(action="create")
def create(self, gid, sid):
@@ -440,7 +463,9 @@ class DatabaseView(PGChildNodeView):
u'name'
]
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
for arg in required_args:
if arg not in data:
@@ -451,72 +476,64 @@ class DatabaseView(PGChildNodeView):
"Could not find the required parameter (%s)." % arg
)
)
try:
# The below SQL will execute CREATE DDL only
SQL = render_template(
"/".join([self.template_path, 'create.sql']),
data=data, conn=self.conn
)
# The below SQL will execute CREATE DDL only
SQL = render_template(
"/".join([self.template_path, 'create.sql']),
data=data, conn=self.conn
)
status, msg = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=msg)
if 'datacl' in data:
data['datacl'] = parse_priv_to_db(data['datacl'], 'DATABASE')
# The below SQL will execute rest DMLs because we can not execute CREATE with any other
SQL = render_template(
"/".join([self.template_path, 'grant.sql']),
data=data, conn=self.conn
)
SQL = SQL.strip('\n').strip(' ')
if SQL and SQL != "":
status, msg = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=msg)
if 'datacl' in data:
data['datacl'] = parse_priv_to_db(data['datacl'], 'DATABASE')
# We need oid of newly created database
SQL = render_template(
"/".join([self.template_path, 'properties.sql']),
name=data['name'], conn=self.conn, last_system_oid=0
)
SQL = SQL.strip('\n').strip(' ')
if SQL and SQL != "":
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
# The below SQL will execute rest DMLs because we can not execute CREATE with any other
SQL = render_template(
"/".join([self.template_path, 'grant.sql']),
data=data, conn=self.conn
)
SQL = SQL.strip('\n').strip(' ')
if SQL and SQL != "":
status, msg = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=msg)
# We need oid of newly created database
SQL = render_template(
"/".join([self.template_path, 'properties.sql']),
name=data['name'], conn=self.conn, last_system_oid=0
)
SQL = SQL.strip('\n').strip(' ')
if SQL and SQL != "":
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
response = res['rows'][0]
return jsonify(
node=self.blueprint.generate_browser_node(
response['did'],
sid,
response['name'],
icon="icon-database-not-connected",
connected=False,
tablespace=response['default_tablespace'],
allowConn=True,
canCreate=response['cancreate'],
canDisconn=True
)
)
except Exception as e:
return make_json_response(
status=410,
success=0,
errormsg=e.message
response = res['rows'][0]
return jsonify(
node=self.blueprint.generate_browser_node(
response['did'],
sid,
response['name'],
icon="icon-database-not-connected",
connected=False,
tablespace=response['default_tablespace'],
allowConn=True,
canCreate=response['cancreate'],
canDisconn=True,
canDrop=True
)
)
@check_precondition(action="update")
def update(self, gid, sid, did):
"""Update the database."""
data = request.form if request.form else json.loads(
request.data.decode()
request.data, encoding='utf-8'
)
info = "nothing to update."
if did is not None:
# Fetch the name of database for comparison
@@ -531,95 +548,82 @@ class DatabaseView(PGChildNodeView):
if len(rset['rows']) == 0:
return gone(
_("Couldnot find the database on the server.")
_("Could not find the database on the server.")
)
data['old_name'] = (rset['rows'][0])['name']
if 'name' not in data:
data['name'] = data['old_name']
try:
status = self.manager.release(did=did)
conn = self.manager.connection()
for action in ["rename_database", "tablespace"]:
SQL = self.get_offline_sql(gid, sid, data, did, action)
SQL = SQL.strip('\n').strip(' ')
if SQL and SQL != "":
status, msg = conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=msg)
info = "Database updated."
self.conn = self.manager.connection(database=data['name'], auto_reconnect=True)
status, errmsg = self.conn.connect()
SQL = self.get_online_sql(gid, sid, data, did)
status = self.manager.release(did=did)
conn = self.manager.connection()
for action in ["rename_database", "tablespace"]:
SQL = self.get_offline_sql(gid, sid, data, did, action)
SQL = SQL.strip('\n').strip(' ')
if SQL and SQL != "":
status, msg = self.conn.execute_scalar(SQL)
status, msg = conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=msg)
info = "Database updated."
self.conn = self.manager.connection(database=data['name'], auto_reconnect=True)
status, errmsg = self.conn.connect()
return make_json_response(
success=1,
info=info,
data={
'id': did,
'sid': sid,
'gid': gid,
}
)
SQL = self.get_online_sql(gid, sid, data, did)
SQL = SQL.strip('\n').strip(' ')
if SQL and SQL != "":
status, msg = self.conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=msg)
except Exception as e:
return make_json_response(
status=410,
success=0,
errormsg=str(e)
return jsonify(
node=self.blueprint.generate_browser_node(
did,
sid,
data['name'],
"pg-icon-{0}".format(self.node_type)
)
)
@check_precondition(action="drop")
def delete(self, gid, sid, did):
"""Delete the database."""
try:
default_conn = self.manager.connection()
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'
)
)
else:
status = self.manager.release(did=did)
SQL = render_template(
"/".join([self.template_path, 'delete.sql']),
did=did, conn=self.conn
datname=res, conn=self.conn
)
status, res = default_conn.execute_scalar(SQL)
status, msg = default_conn.execute_scalar(SQL)
if not status:
return internal_server_error(errormsg=res)
# reconnect if database drop failed.
conn = self.manager.connection(did=did, auto_reconnect=True)
status, errmsg = conn.connect()
if res is None:
return make_json_response(
success=0,
errormsg=_(
'The specified database could not be found.\n'
)
)
else:
return internal_server_error(errormsg=msg)
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)
except Exception as e:
return make_json_response(
success=0,
errormsg=str(e))
return make_json_response(success=1)
@@ -631,28 +635,21 @@ class DatabaseView(PGChildNodeView):
data = {}
for k, v in request.args.items():
try:
data[k] = json.loads(v)
data[k] = json.loads(v, encoding='utf-8')
except ValueError:
data[k] = v
try:
status, res = self.get_sql(gid, sid, data, did)
status, res = self.get_sql(gid, sid, data, did)
if not status:
return res
if not status:
return res
res = re.sub('\n{2,}', '\n\n', res)
SQL = res.strip('\n').strip(' ')
res = re.sub('\n{2,}', '\n\n', res)
SQL = res.strip('\n').strip(' ')
return make_json_response(
data=SQL,
status=200
)
except Exception as e:
current_app.logger.exception(e)
return make_json_response(
data=_("-- modified SQL"),
status=200
)
return make_json_response(
data=SQL,
status=200
)
def get_sql(self, gid, sid, data, did=None):
SQL = ''
@@ -668,7 +665,7 @@ class DatabaseView(PGChildNodeView):
return False, internal_server_error(errormsg=rset)
if len(rset['rows']) == 0:
return False, gone(
return gone(
_("Could not find the database on the server.")
)
@@ -703,7 +700,7 @@ class DatabaseView(PGChildNodeView):
acls = render_template(
"/".join([self.template_path, 'allowed_privs.json'])
)
acls = json.loads(acls)
acls = json.loads(acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
@@ -736,7 +733,7 @@ class DatabaseView(PGChildNodeView):
acls = render_template(
"/".join([self.template_path, 'allowed_privs.json'])
)
acls = json.loads(acls)
acls = json.loads(acls, encoding='utf-8')
except Exception as e:
current_app.logger.exception(e)
@@ -791,7 +788,10 @@ class DatabaseView(PGChildNodeView):
server.
"""
last_system_oid = 0 if self.blueprint.show_system_objects else \
(self.manager.db_info[self.manager.did])['datlastsysoid']
(self.manager.db_info[self.manager.did])['datlastsysoid'] \
if self.manager.db_info is not None and \
self.manager.did in self.manager.db_info else 0
status, res = self.conn.execute_dict(
render_template(
"/".join([self.template_path, 'stats.sql']),
@@ -859,9 +859,20 @@ class DatabaseView(PGChildNodeView):
frmtd_variables = parse_variables_from_db(res1['rows'])
result.update(frmtd_variables)
sql_header = "-- Database: {0}\n\n-- ".format(result['name'])
if hasattr(str, 'decode'):
sql_header = sql_header.decode('utf-8')
sql_header += render_template(
"/".join([self.template_path, 'delete.sql']),
datname=result['name'], conn=self.conn
)
SQL = self.get_new_sql(gid, sid, result, did)
SQL = re.sub('\n{2,}', '\n\n', SQL)
SQL = sql_header + '\n' + SQL
SQL = SQL.strip('\n')
return ajax_response(response=SQL)
@check_precondition()
@@ -9,17 +9,16 @@
"""Implements Cast Node"""
import json
import simplejson as json
from functools import wraps
import pgadmin.browser.server_groups.servers.databases as databases
from flask import render_template, make_response, request, jsonify
from flask.ext.babel import gettext
from flask_babel import gettext
from pgadmin.browser.collection import CollectionNodeModule
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error
from pgadmin.utils.ajax import precondition_required
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
make_response as ajax_response, gone
from pgadmin.utils.driver import get_driver
from config import PG_DEFAULT_DRIVER
@@ -201,17 +200,8 @@ class CastView(PGChildNodeView):
self = args[0]
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
self.conn = self.manager.connection(did=kwargs['did'])
# If DB not connected then return error to browser
if not self.conn.connected():
return precondition_required(
gettext(
"Connection to the server has been lost!"
)
)
ver = self.manager.version
# we will set template path for sql scripts
if ver >= 90100:
self.template_path = 'cast/sql/9.1_plus'
# Set template path for the SQL scripts
self.template_path = 'cast/sql/9.1_plus'
return f(*args, **kwargs)
@@ -226,9 +216,13 @@ class CastView(PGChildNodeView):
:param did: database id
:return:
"""
last_system_oid = 0 if self.blueprint.show_system_objects else \
(self.manager.db_info[did])['datlastsysoid'] \
if self.manager.db_info is not None and \
did in self.manager.db_info else 0
sql = render_template(
"/".join([self.template_path, 'properties.sql']),
datlastsysoid=self.manager.db_info[did]['datlastsysoid'],
datlastsysoid=last_system_oid,
showsysobj=self.blueprint.show_system_objects
)
status, res = self.conn.execute_dict(sql)
@@ -255,9 +249,13 @@ class CastView(PGChildNodeView):
:return:
"""
res = []
last_system_oid = 0 if self.blueprint.show_system_objects else \
(self.manager.db_info[did])['datlastsysoid'] \
if self.manager.db_info is not None and \
did in self.manager.db_info else 0
sql = render_template(
"/".join([self.template_path, 'nodes.sql']),
datlastsysoid=self.manager.db_info[did]['datlastsysoid'],
datlastsysoid=last_system_oid,
showsysobj=self.blueprint.show_system_objects
)
status, rset = self.conn.execute_2darray(sql)
@@ -280,7 +278,9 @@ class CastView(PGChildNodeView):
@check_precondition
def node(self, gid, sid, did, cid):
res = []
"""
This function will fetch properties of the cast node
"""
sql = render_template(
"/".join([self.template_path, 'nodes.sql']),
cid=cid
@@ -290,18 +290,17 @@ class CastView(PGChildNodeView):
return internal_server_error(errormsg=rset)
for row in rset['rows']:
res.append(
self.blueprint.generate_browser_node(
return make_json_response(
data=self.blueprint.generate_browser_node(
row['oid'],
did,
row['name'],
icon="icon-fts_template"
))
),
status=200
)
return make_json_response(
data=res,
status=200
)
return gone(errormsg=gettext("Could not find the specified cast."))
@check_precondition
def properties(self, gid, sid, did, cid):
@@ -313,17 +312,24 @@ class CastView(PGChildNodeView):
:param cid: cast id
:return:
"""
last_system_oid = (self.manager.db_info[did])['datlastsysoid'] if \
self.manager.db_info is not None and \
did in self.manager.db_info else 0
sql = render_template(
"/".join([self.template_path, 'properties.sql']),
cid=cid,
datlastsysoid=self.manager.db_info[did]['datlastsysoid'],
datlastsysoid=last_system_oid,
showsysobj=self.blueprint.show_system_objects
)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
result = res['rows'][0]
if len(res['rows']) == 0:
return gone(
gettext("Could not find the cast information.")
)
return ajax_response(
response=res['rows'][0],
@@ -345,7 +351,9 @@ class CastView(PGChildNodeView):
'trgtyp'
]
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
for arg in required_args:
if arg not in data:
return make_json_response(
@@ -365,10 +373,14 @@ class CastView(PGChildNodeView):
return internal_server_error(errormsg=res)
# we need oid to to add object in tree at browser, below sql will gives the same
last_system_oid = 0 if self.blueprint.show_system_objects else \
(self.manager.db_info[did])['datlastsysoid'] \
if self.manager.db_info is not None and \
did in self.manager.db_info else 0
sql = render_template("/".join([self.template_path, 'properties.sql']),
srctyp=data['srctyp'],
trgtyp=data['trgtyp'],
datlastsysoid=self.manager.db_info[did]['datlastsysoid'],
datlastsysoid=last_system_oid,
showsysobj=self.blueprint.show_system_objects
)
status, cid = self.conn.execute_scalar(sql)
@@ -396,35 +408,23 @@ class CastView(PGChildNodeView):
:param gid: group id
:return:
"""
data = request.form if request.form else json.loads(request.data.decode())
sql = self.get_sql(gid, sid, did, data, cid)
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
try:
if sql and sql.strip('\n') and sql.strip(' '):
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
sql, name = self.get_sql(gid, sid, did, data, cid)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info="Cast updated",
data={
'id': cid,
'sid': sid,
'gid': gid,
'did': did
}
)
else:
return make_json_response(
success=1,
info="Nothing to update",
data={
'id': cid,
'sid': sid,
'gid': gid,
'did': did
}
return jsonify(
node=self.blueprint.generate_browser_node(
cid,
did,
name,
"icon-{0}".format(self.node_type)
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@@ -454,6 +454,18 @@ class CastView(PGChildNodeView):
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']),
@@ -490,17 +502,18 @@ class CastView(PGChildNodeView):
:return:
"""
data = request.args
sql = self.get_sql(gid, sid, did, data, cid)
if isinstance(sql, str) and sql and sql.strip('\n') and sql.strip(' '):
try:
sql, name = self.get_sql(gid, sid, did, data, cid)
sql = sql.strip('\n').strip(' ')
if sql == '':
sql = "--modified SQL"
return make_json_response(
data=sql,
status=200
)
else:
return make_json_response(
data="--modified SQL",
status=200
)
except Exception as e:
return internal_server_error(errormsg=str(e))
def get_sql(self, gid, sid, did, data, cid=None):
"""
@@ -512,31 +525,32 @@ class CastView(PGChildNodeView):
:param data: model data
:return:
"""
try:
if cid is not None:
sql = render_template("/".join([self.template_path, 'properties.sql']),
cid=cid,
datlastsysoid=self.manager.db_info[did]['datlastsysoid'],
showsysobj=self.blueprint.show_system_objects)
status, res = self.conn.execute_dict(sql)
if cid is not None:
last_system_oid = 0 if self.blueprint.show_system_objects else \
(self.manager.db_info[did])['datlastsysoid'] \
if self.manager.db_info is not None and \
did in self.manager.db_info else 0
sql = render_template("/".join([self.template_path, 'properties.sql']),
cid=cid,
datlastsysoid=last_system_oid,
showsysobj=self.blueprint.show_system_objects)
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)
old_data = res['rows'][0]
sql = render_template(
"/".join([self.template_path, 'update.sql']),
data=data, o_data=old_data
)
old_data = res['rows'][0]
sql = render_template(
"/".join([self.template_path, 'update.sql']),
data=data, o_data=old_data
)
return str(sql), data['name'] if 'name' in data else old_data['name']
else:
if 'srctyp' in data and 'trgtyp' in data:
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, conn=self.conn)
else:
if 'srctyp' in data and 'trgtyp' in data:
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, conn=self.conn)
else:
sql = "-- incomplete definition"
return str(sql)
except Exception as e:
return internal_server_error(errormsg=str(e))
return "-- incomplete definition", None
return str(sql), data['srctyp'] + "->" + data["trgtyp"]
@check_precondition
def get_functions(self, gid, sid, did, cid=None):
@@ -549,7 +563,10 @@ class CastView(PGChildNodeView):
:return:
"""
res = []
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
sql = render_template("/".join([self.template_path, 'functions.sql']),
srctyp=data['srctyp'],
trgtyp=data['trgtyp'])
@@ -0,0 +1,16 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
class CastTestGenerator(BaseTestGenerator):
def runTest(self):
return []
@@ -0,0 +1,65 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from . import utils as cast_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
class CastsAddTestCase(BaseTestGenerator):
scenarios = [
# Fetching default URL for cast node.
('Check Cast Node', dict(url='/browser/cast/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to server
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add database
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
def runTest(self):
""" This function will add cast under database node. """
cast_utils.add_cast(self.tester)
@classmethod
def tearDownClass(cls):
"""
This function deletes the added cast, database, server and the
'parent_id.pkl' file which is created in setUpClass.
:return: None
"""
cast_utils.delete_cast(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,102 @@
# # #################################################################
# #
# # pgAdmin 4 - PostgreSQL Tools
# #
# # Copyright (C) 2013 - 2016, The pgAdmin Development Team
# # This software is released under the PostgreSQL Licence
# #
# # ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from regression.test_utils import get_ids
from . import utils as cast_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from regression.test_setup import advanced_config_data
import json
class CastsDeleteTestCase(BaseTestGenerator):
""" This class will fetch the cast node added under database node. """
scenarios = [
# Fetching default URL for cast node.
('Check Cast Node', dict(url='/browser/cast/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add cast(s) to databases
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to server
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add database
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add cast(s) to database(s)
cast_utils.add_cast(cls.tester)
def runTest(self):
""" This function will delete added cast(s)."""
all_id = get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
cast_ids_dict = all_id["cid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(self.tester,
utils.SERVER_GROUP,
server_id,
db_id)
if len(db_con) == 0:
raise Exception("No database(s) to delete for server id %s"
% server_id)
cast_id = cast_ids_dict[server_id]
cast_get_data = cast_utils.verify_cast(self.tester,
utils.SERVER_GROUP,
server_id,
db_id, cast_id)
if cast_get_data.status_code == 200:
delete_response = self.tester.delete(
self.url + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(cast_id),
follow_redirects=True)
response_data = json.loads(delete_response.data.decode('utf-8'))
self.assertTrue(response_data['success'], 1)
@classmethod
def tearDownClass(cls):
"""
This function delete the added cast, database, server and the
'parent_id.pkl' file which is created in setUpClass.
:return: None
"""
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,87 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from regression.test_utils import get_ids
from . import utils as cast_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
class CastsGetTestCase(BaseTestGenerator):
""" This class will fetch the cast node added under database node. """
scenarios = [
# Fetching default URL for cast node.
('Check Cast Node', dict(url='/browser/cast/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function used to add the sever, database, and cast
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to server
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add database
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
cast_utils.add_cast(cls.tester)
def runTest(self):
""" This function will get added cast."""
all_id = get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
cast_ids_dict = all_id["cid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(self.tester,
utils.SERVER_GROUP,
server_id,
db_id)
if len(db_con) == 0:
raise Exception("No database(s) to delete for server id %s"
% server_id)
cast_id = cast_ids_dict[server_id]
cast_get_data = cast_utils.verify_cast(self.tester,
utils.SERVER_GROUP,
server_id,
db_id, cast_id)
self.assertEquals(cast_get_data.status_code, 200)
@classmethod
def tearDownClass(cls):
"""
This function deletes the added cast, database, server and the
'parent_id.pkl' file which is created in setup() function.
:return: None
"""
cast_utils.delete_cast(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,107 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from regression.test_utils import get_ids
from . import utils as cast_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from regression.test_setup import advanced_config_data
import json
class CastsPutTestCase(BaseTestGenerator):
""" This class will fetch the cast node added under database node. """
scenarios = [
# Fetching default URL for cast node.
('Check Cast Node', dict(url='/browser/cast/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add cast(s) to databases
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to server
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add database
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
cast_utils.add_cast(cls.tester)
def runTest(self):
""" This function will update added cast."""
all_id = get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
cast_ids_dict = all_id["cid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(self.tester,
utils.SERVER_GROUP,
server_id,
db_id)
if len(db_con) == 0:
raise Exception("No database(s) to delete for server id %s"
% server_id)
cast_id = cast_ids_dict[server_id]
cast_get_data = cast_utils.verify_cast(self.tester,
utils.SERVER_GROUP,
server_id,
db_id, cast_id)
if cast_get_data.status_code == 200:
data = {
"description": advanced_config_data["cast_update_data"]
["comment"],
"id": cast_id
}
put_response = self.tester.put(
self.url + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(
db_id) +
'/' + str(cast_id),
data=json.dumps(data),
follow_redirects=True)
self.assertEquals(put_response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""
This function deletes the added cast, database, server and the
'parent_id.pkl' file which is created in setUpClass.
:return: None
"""
cast_utils.delete_cast(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,145 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
import os
import pickle
import json
from regression.test_setup import advanced_config_data, pickle_path
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
CAST_URL = '/browser/cast/obj/'
def get_cast_config_data(server_connect_data):
adv_config_data = None
db_user = server_connect_data['data']['user']['name']
# Get the config data of appropriate db user
for config_test_data in advanced_config_data['casts_credentials']:
if db_user == config_test_data['owner']:
adv_config_data = config_test_data
data = {
"castcontext": adv_config_data
['cast_context'],
"encoding": adv_config_data
['encoding'],
"name": adv_config_data
['name'],
"srctyp": adv_config_data
['source_type'],
"trgtyp": adv_config_data
['target_type']
}
return data
def add_cast(tester):
"""
This function add the cast in the existing database
:param tester: test object
:type tester: flask test object
:return:None
"""
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
server_group = utils.config_data['server_group']
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(tester, server_group,
server_id, db_id)
if db_con['data']['connected']:
server_connect_response = server_utils.verify_server(
tester, server_group, server_id)
data = get_cast_config_data(server_connect_response)
response = tester.post(CAST_URL + str(server_group) + '/' +
str(server_id) + '/' + str(
db_id) + '/',
data=json.dumps(data),
content_type='html/json')
assert response.status_code == 200
response_data = json.loads(response.data.decode('utf-8'))
write_cast_info(response_data, server_id)
def write_cast_info(response_data, server_id):
"""
This function writes the server's details to file parent_id.pkl
:param response_data: server's data
:type response_data: list of dictionary
:param pickle_id_dict: contains ids of server,database,tables etc.
:type pickle_id_dict: dict
:return: None
"""
cast_id = response_data['node']['_id']
pickle_id_dict = utils.get_pickle_id_dict()
if os.path.isfile(pickle_path):
existing_server_id = open(pickle_path, 'rb')
tol_server_id = pickle.load(existing_server_id)
pickle_id_dict = tol_server_id
if 'cid' in pickle_id_dict:
if pickle_id_dict['cid']:
# Add the cast_id as value in dict
pickle_id_dict["cid"][0].update({server_id: cast_id})
else:
# Create new dict with server_id and cast_id
pickle_id_dict["cid"].append({server_id: cast_id})
cast_output = open(pickle_path, 'wb')
pickle.dump(pickle_id_dict, cast_output)
cast_output.close()
def verify_cast(tester, server_group, server_id, db_id, cast_id):
cast_response = tester.get(CAST_URL + str(server_group) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(cast_id),
content_type='html/json')
return cast_response
def delete_cast(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
cast_ids_dict = all_id["cid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(tester, utils.SERVER_GROUP,
server_id,
db_id)
if len(db_con) == 0:
raise Exception("No database(s) to delete for server id %s"
% server_id)
cast_id = cast_ids_dict[server_id]
cast_get_data = verify_cast(tester, utils.SERVER_GROUP,
server_id,
db_id, cast_id)
if cast_get_data.status_code == 200:
delete_response = tester.delete(
CAST_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(cast_id),
follow_redirects=True)
return delete_response
@@ -7,19 +7,19 @@
#
##########################################################################
import json
import simplejson as json
import re
from functools import wraps
import pgadmin.browser.server_groups.servers.databases as database
from flask import render_template, make_response, request, jsonify
from flask.ext.babel import gettext
from flask_babel import gettext
from pgadmin.browser.collection import CollectionNodeModule
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error
from pgadmin.utils.ajax import precondition_required
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
make_response as ajax_response
from pgadmin.utils.driver import get_driver
from pgadmin.utils.ajax import gone
from config import PG_DEFAULT_DRIVER
@@ -188,22 +188,12 @@ class EventTriggerView(PGChildNodeView):
@wraps(f)
def wrap(*args, **kwargs):
# Here args[0] will hold self & kwargs will hold gid,sid,did
# Here - args[0] will always hold self & kwargs will hold gid, sid, did
self = args[0]
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
self.conn = self.manager.connection(did=kwargs['did'])
# If DB not connected then return error to browser
if not self.conn.connected():
return precondition_required(
gettext(
"Connection to the server has been lost!"
)
)
ver = self.manager.version
if ver >= 90300:
self.template_path = 'event_triggers/sql/9.3_plus'
self.template_path = 'event_triggers/sql/9.3_plus'
return f(*args, **kwargs)
@@ -269,6 +259,55 @@ class EventTriggerView(PGChildNodeView):
status=200
)
@check_precondition
def node(self, gid, sid, did, etid):
"""
This function will fetch properties of trigger node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
etid: Event trigger ID
Returns:
Json object of trigger node
"""
sql = render_template("/".join([self.template_path, 'nodes.sql']),
etid=etid)
status, res = self.conn.execute_2darray(sql)
if not status:
return internal_server_error(errormsg=res)
for row in res['rows']:
return make_json_response(
data=self.blueprint.generate_browser_node(
row['oid'],
did,
row['name'],
icon="icon-%s" % self.node_type
),
status=200
)
return gone(gettext("Could not find the specified event trigger."))
def _formatter(self, result):
"""
This function is ued to parse security lables
"""
seclabels = []
if 'seclabels' in result and result['seclabels'] is not None:
for sec in result['seclabels']:
sec = re.search(r'([^=]+)=(.*$)', sec)
seclabels.append({
'provider': sec.group(1),
'label': sec.group(2)
})
result['seclabels'] = seclabels
return result
@check_precondition
def properties(self, gid, sid, did, etid):
"""
@@ -289,17 +328,14 @@ class EventTriggerView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
result = res['rows'][0]
sec_labels = []
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the event trigger information.")
)
result = res['rows'][0]
result = self._formatter(result)
if 'seclabels' in result and result['seclabels'] is not None:
for sec in result['seclabels']:
sec = re.search(r'([^=]+)=(.*$)', sec)
sec_labels.append({
'provider': sec.group(1),
'securitylabel': sec.group(2)
})
result.update({"seclabels": sec_labels})
return ajax_response(
response=result,
status=200
@@ -319,7 +355,10 @@ class EventTriggerView(PGChildNodeView):
Returns:
"""
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
required_args = {
'name': 'Name',
'eventowner': 'Owner',
@@ -382,7 +421,9 @@ class EventTriggerView(PGChildNodeView):
Returns:
"""
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
try:
sql = self.get_sql(data, etid)
@@ -444,6 +485,18 @@ class EventTriggerView(PGChildNodeView):
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'
)
)
sql = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade)
status, res = self.conn.execute_scalar(sql)
if not status:
@@ -481,13 +534,14 @@ class EventTriggerView(PGChildNodeView):
data = {}
for k, v in request.args.items():
try:
data[k] = json.loads(v)
data[k] = json.loads(v, encoding='utf-8')
except ValueError:
data[k] = v
try:
sql = self.get_sql(data, etid)
sql = sql.strip('\n').strip(' ')
if sql == '':
sql = "--modified SQL"
return make_json_response(
data=sql,
status=200
@@ -516,7 +570,15 @@ class EventTriggerView(PGChildNodeView):
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the event trigger information.")
)
old_data = res['rows'][0]
old_data = self._formatter(old_data)
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
@@ -535,7 +597,7 @@ class EventTriggerView(PGChildNodeView):
err.append(required_args.get(arg, arg))
if err:
return make_json_response(
status=400,
status=410,
success=0,
errormsg=gettext(
"Could not find the required parameter %s." % err
@@ -568,6 +630,7 @@ class EventTriggerView(PGChildNodeView):
return internal_server_error(errormsg=res)
result = res['rows'][0]
result = self._formatter(result)
sql = render_template("/".join([self.template_path, 'create.sql']), data=result, conn=self.conn)
sql += "\n\n"
@@ -579,6 +642,8 @@ class EventTriggerView(PGChildNodeView):
return internal_server_error(errormsg=db_name)
sql_header = "-- Event Trigger: {0} on database {1}\n\n-- ".format(result['name'], db_name)
if hasattr(str, 'decode'):
sql_header = sql_header.decode('utf-8')
sql_header += render_template(
"/".join([self.template_path, 'delete.sql']),
@@ -2,35 +2,6 @@ define(
['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
function($, _, S, pgAdmin, pgBrowser, alertify) {
// Extend the browser's node model class to create a security model
var SecurityLabelModel = pgAdmin.Browser.Node.Model.extend({
defaults: {
provider: undefined,
securitylabel: undefined
},
// Define the schema for the Security Label
schema: [
{id: 'provider', label:'Provider', type:'text', group: null, editable: true},
{id: 'securitylabel', label:'Security Label', type: 'text', group:null, extraHeaderClasses: 'cellwidth-40', editable: true},
],
validate: function() {
// Clear any existing errors.
this.errorModel.clear()
if (_.isUndefined(this.get('provider')) || String(this.get('provider')).replace(/^\s+|\s+$/g, '') == '') {
var msg = '{{ _('Provider cannot be empty.') }}';
this.errorModel.set('provider',msg);
return msg;
}
if (_.isUndefined(this.get('securitylabel')) || String(this.get('securitylabel')).replace(/^\s+|\s+$/g, '') == '') {
var msg = '{{ _('Security Label cannot be empty.') }}';
this.errorModel.set('securitylabel',msg);
return msg;
}
return null;
}
});
// Extend the browser's collection class for event trigger collection
if (!pgBrowser.Nodes['coll-event_trigger']) {
var databases = pgAdmin.Browser.Nodes['coll-event_trigger'] =
@@ -145,10 +116,11 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
},{
id: 'when', label:'{{ _('When') }}', type: 'multiline', group: "Definition",
},{
id: 'providers', label: 'Security Labels', type: 'collection', group: "Security Labels",
model: SecurityLabelModel, control: 'unique-col-collection', mode: ['edit', 'create'],
canAdd: true, canDelete: true, uniqueCol : ['provider'],
columns: ['provider','securitylabel']
id: 'seclabels', label: '{{ _('Security Labels') }}',
model: pgBrowser.SecLabelModel, editable: false, type: 'collection',
group: '{{ _('Security') }}', mode: ['edit', 'create'],
min_version: 90200, canAdd: true,
canEdit: false, canDelete: true, control: 'unique-col-collection'
}
],
// event trigger model data validation.
@@ -1,4 +1,4 @@
{% import 'macros/security.macros' as SECLABLE %}
{% import 'macros/security.macros' as SECLABEL %}
{% if data %}
{% if data.enabled and data.enabled != "O" %}
ALTER EVENT TRIGGER {{ conn|qtIdent(data.name) }}
@@ -15,10 +15,10 @@ ALTER EVENT TRIGGER {{ conn|qtIdent(data.name) }}
COMMENT ON EVENT TRIGGER {{ conn|qtIdent(data.name) }}
IS {{ data.comment|qtLiteral }};
{% endif %}
{% if data.providers and data.providers|length > 0 %}
{% if data.seclabels and data.seclabels|length > 0 %}
{% for r in data.providers %}
{{ SECLABLE.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.securitylabel) }}
{% for r in data.seclabels %}
{{ SECLABEL.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.label) }}
{% endfor %}{% endif %}
ALTER EVENT TRIGGER {{ conn|qtIdent(data.name) }}
@@ -1,3 +1,6 @@
SELECT e.oid, e.evtname AS name
FROM pg_event_trigger e
{% if etid %}
WHERE e.oid={{etid}}::int
{% endif %}
ORDER BY e.evtname
@@ -4,7 +4,7 @@ e.evtenabled AS enabled,
e.evtfoid AS eventfuncoid, quote_ident(n.nspname) || '.' || e.evtfoid::regproc AS eventfunname,
array_to_string(array(select quote_literal(x) from unnest(evttags) as t(x)), ', ') AS when,
pg_catalog.obj_description(e.oid, 'pg_event_trigger') AS comment,
(SELECT array_agg(provider || '=' || label) FROM pg_shseclabel sl1 WHERE sl1.objoid=e.oid) AS seclabels,
(SELECT array_agg(provider || '=' || label) FROM pg_seclabel sl1 WHERE sl1.objoid=e.oid) AS seclabels,
p.prosrc AS source, p.pronamespace AS schemaoid, l.lanname AS language
FROM pg_event_trigger e
LEFT OUTER JOIN pg_proc p ON p.oid=e.evtfoid
@@ -1,4 +1,4 @@
{% import 'macros/security.macros' as SECLABLE %}
{% import 'macros/security.macros' as SECLABEL %}
{% if data %}
{% if (data.eventfunname and data.eventfunname != o_data.eventfunname) or
(data.eventname and data.eventname != o_data.eventname) or
@@ -37,25 +37,25 @@ ALTER EVENT TRIGGER {{ conn|qtIdent(data.name) }}
{% endif %}
{% endif %}
{% if data.providers and
data.providers|length > 0
%}{% set seclabels = data.providers %}
{% if data.seclabels and
data.seclabels|length > 0
%}{% set seclabels = data.seclabels %}
{% if 'deleted' in seclabels and seclabels.deleted|length > 0 %}
{% for r in seclabels.deleted %}
{{ SECLABLE.DROP(conn, 'EVENT TRIGGER', data.name, r.provider) }}
{{ SECLABEL.DROP(conn, 'EVENT TRIGGER', data.name, r.provider) }}
{% endfor %}
{% endif %}
{% if 'added' in seclabels and seclabels.added|length > 0 %}
{% for r in seclabels.added %}
{{ SECLABLE.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.securitylabel) }}
{{ SECLABEL.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.label) }}
{% endfor %}
{% endif %}
{% if 'changed' in seclabels and seclabels.changed|length > 0 %}
{% for r in seclabels.changed %}
{{ SECLABLE.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.securitylabel) }}
{{ SECLABEL.APPLY(conn, 'EVENT TRIGGER', data.name, r.provider, r.label) }}
{% endfor %}
{% endif %}
{% endif %}
@@ -0,0 +1,16 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
class EventTriggerGeneratorTestCase(BaseTestGenerator):
def runTest(self):
return []
@@ -0,0 +1,79 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.schemas.functions.tests \
import utils as func_utils
from . import utils as event_trigger_utils
class EventTriggerAddTestCase(BaseTestGenerator):
""" This class will add new event trigger under schema node. """
scenarios = [
# Fetching default URL for event trigger node.
('Fetch Event Trigger Node URL',
dict(url='/browser/event_trigger/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add trigger function(s) to schema(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
func_utils.add_trigger_function(cls.tester, cls.server_connect_response,
cls.server_ids)
def runTest(self):
""" This function will add event trigger under database node. """
event_trigger_utils.add_event_trigger(self.tester)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
:return: None
"""
event_trigger_utils.delete_event_trigger(cls.tester)
func_utils.delete_trigger_function(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,88 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.schemas.functions.tests \
import utils as func_utils
from . import utils as event_trigger_utils
import json
class EventTriggerDeleteTestCase(BaseTestGenerator):
""" This class will fetch added event trigger under database node. """
scenarios = [
# Fetching default URL for event trigger node.
('Fetch Event Trigger Node URL',
dict(url='/browser/event_trigger/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add trigger function(s) to schema(s)
5. Add event trigger(s) to database(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
func_utils.add_trigger_function(cls.tester, cls.server_connect_response,
cls.server_ids)
event_trigger_utils.add_event_trigger(cls.tester)
def runTest(self):
""" This function will delete event trigger under database node. """
del_response = event_trigger_utils.delete_event_trigger(self.tester)
del_respdata = json.loads(del_response.data.decode("utf-8"))
self.assertTrue(del_respdata['success'], 1)
@classmethod
def tearDownClass(cls):
"""
This function delete the added schema, database, server and parent
id file
:return: None
"""
func_utils.delete_trigger_function(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,97 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tests import\
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.schemas.functions.tests \
import utils as func_utils
from . import utils as event_trigger_utils
class EventTriggerGetTestCase(BaseTestGenerator):
""" This class will fetch added event trigger under schema node. """
scenarios = [
# Fetching default URL for event trigger node.
('Fetch Event Trigger Node URL',
dict(url='/browser/event_trigger/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add trigger function(s) to schema(s)
5. Add event trigger(s) to database(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
func_utils.add_trigger_function(cls.tester, cls.server_connect_response,
cls.server_ids)
event_trigger_utils.add_event_trigger(cls.tester)
def runTest(self):
""" This function will fetch event trigger under database node. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
event_trigger_ids_dict = all_id["etid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
event_trigger_id = event_trigger_ids_dict[server_id]
response = event_trigger_utils.verify_event_trigger(
self.tester, utils.SERVER_GROUP, server_id, db_id,
event_trigger_id)
self.assertEquals(response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
:return: None
"""
event_trigger_utils.delete_event_trigger(cls.tester)
func_utils.delete_trigger_function(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,114 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from pgadmin.browser.server_groups.servers.databases.schemas.functions.tests \
import utils as func_utils
from . import utils as event_trigger_utils
import json
from regression.test_setup import advanced_config_data
class EventTriggerPutTestCase(BaseTestGenerator):
""" This class will fetch added event trigger under database node. """
scenarios = [
# Fetching default URL for event trigger node.
('Fetch Event Trigger Node URL',
dict(url='/browser/event_trigger/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add trigger function(s) to schema(s)
5. Add event trigger(s) to database(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
func_utils.add_trigger_function(cls.tester, cls.server_connect_response,
cls.server_ids)
event_trigger_utils.add_event_trigger(cls.tester)
def runTest(self):
""" This function will update event trigger under database node. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
event_trigger_ids_dict = all_id["etid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
event_trigger_id = event_trigger_ids_dict[server_id]
response = event_trigger_utils.verify_event_trigger(
self.tester, utils.SERVER_GROUP, server_id, db_id,
event_trigger_id)
if response.status_code == 200:
data = \
{
"comment":
advanced_config_data['event_trigger_update_data']
['comment'],
"id": event_trigger_id
}
put_response = self.tester.put(
self.url + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(event_trigger_id),
data=json.dumps(data),
follow_redirects=True)
self.assertEquals(put_response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
:return: None
"""
event_trigger_utils.delete_event_trigger(cls.tester)
func_utils.delete_trigger_function(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,148 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
import os
import pickle
import json
import uuid
from regression.test_setup import advanced_config_data, pickle_path
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.schemas.functions.tests \
import utils as func_utils
EVENT_TRIGGER_URL = '/browser/event_trigger/obj/'
def get_event_trigger_config_data(schema_name, server_connect_data,
trigger_func_name):
adv_config_data = None
db_user = server_connect_data['data']['user']['name']
# Get the config data of appropriate db user
for config_test_data in advanced_config_data['event_trigger_credentials']:
if db_user == config_test_data['owner']:
adv_config_data = config_test_data
data = {
"enabled": adv_config_data['enable'],
"eventfunname": "{0}.{1}".format(schema_name, trigger_func_name),
"eventname": adv_config_data['event_name'],
"eventowner": adv_config_data['owner'],
"name": "event_trigger_{}".format(str(uuid.uuid4())[1:4]),
"providers": adv_config_data['provider']
}
return data
def add_event_trigger(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
schema_info_dict = all_id["scid"][0]
trigger_func_info_dict = all_id["tfnid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(tester, utils.SERVER_GROUP,
server_id, db_id)
if db_con['data']["connected"]:
server_connect_response = server_utils.verify_server(
tester, utils.SERVER_GROUP, server_id)
schema_info = schema_info_dict[int(server_id)]
trigger_func_list = trigger_func_info_dict[int(server_id)]
trigger_func_info = \
filter(lambda x: x[2] == "event_trigger", trigger_func_list)[0]
trigger_func_name = trigger_func_info[1].replace("()", "")
trigger_func_id = trigger_func_info[0]
trigger_func_response = \
func_utils.verify_trigger_function(tester, server_id,
db_id, schema_info[0],
trigger_func_id)
if trigger_func_response.status_code == 200:
data = get_event_trigger_config_data(
schema_info[1],
server_connect_response, trigger_func_name)
response = tester.post(
EVENT_TRIGGER_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) +
'/', data=json.dumps(data),
content_type='html/json')
assert response.status_code == 200
response_data = json.loads(response.data.decode('utf-8'))
write_event_trigger_info(response_data, server_id)
def write_event_trigger_info(response_data, server_id):
"""
This function writes the schema id into parent_id.pkl
:param response_data: extension add response data
:type response_data: dict
:param server_id: server id
:type server_id: str
:return: None
"""
event_trigger_id = response_data['node']['_id']
pickle_id_dict = utils.get_pickle_id_dict()
if os.path.isfile(pickle_path):
existing_server_id = open(pickle_path, 'rb')
tol_server_id = pickle.load(existing_server_id)
pickle_id_dict = tol_server_id
if 'etid' in pickle_id_dict:
if pickle_id_dict['etid']:
# Add the event_trigger_id as value in dict
pickle_id_dict["etid"][0].update({server_id: event_trigger_id})
else:
# Create new dict with server_id and event_trigger_id
pickle_id_dict["etid"].append({server_id: event_trigger_id})
event_trigger_output = open(pickle_path, 'wb')
pickle.dump(pickle_id_dict, event_trigger_output)
event_trigger_output.close()
def verify_event_trigger(tester, server_group, server_id, db_id,
event_trigger_id):
response = tester.get(EVENT_TRIGGER_URL + str(server_group) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(event_trigger_id),
content_type='html/json')
return response
def delete_event_trigger(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
event_trigger_ids_dict = all_id["etid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
event_trigger_id = event_trigger_ids_dict[server_id]
response = verify_event_trigger(tester,
utils.SERVER_GROUP,
server_id,
db_id,
event_trigger_id)
if response.status_code == 200:
del_response = tester.delete(
EVENT_TRIGGER_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' +
str(db_id) + '/' +
str(event_trigger_id),
follow_redirects=True)
return del_response
@@ -9,17 +9,18 @@
""" Implements Extension Node """
import json
import simplejson as json
from functools import wraps
import pgadmin.browser.server_groups.servers.databases as databases
from flask import render_template, make_response, request, jsonify
from flask.ext.babel import gettext
from flask_babel import gettext
from pgadmin.browser.collection import CollectionNodeModule
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error
from pgadmin.utils.driver import get_driver
from pgadmin.utils.ajax import gone
from config import PG_DEFAULT_DRIVER
@@ -191,6 +192,30 @@ class ExtensionView(PGChildNodeView):
status=200
)
@check_precondition
def node(self, gid, sid, did, eid):
"""
This function will fetch the properties of extension
"""
SQL = render_template("/".join([self.template_path, 'properties.sql']),
eid=eid)
status, rset = self.conn.execute_2darray(SQL)
if not status:
return internal_server_error(errormsg=rset)
for row in rset['rows']:
return make_json_response(
data=self.blueprint.generate_browser_node(
row['eid'],
did,
row['name'],
'icon-extension'
),
status=200
)
return gone(gettext("Could not find the specified event trigger."))
@check_precondition
def properties(self, gid, sid, did, eid):
"""
@@ -202,6 +227,11 @@ class ExtensionView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the extension information.")
)
return ajax_response(
response=res['rows'][0],
status=200
@@ -216,8 +246,10 @@ class ExtensionView(PGChildNodeView):
'name'
]
data = request.form if request.form else \
json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
for arg in required_args:
if arg not in data:
return make_json_response(
@@ -263,37 +295,25 @@ class ExtensionView(PGChildNodeView):
"""
This function will update an extension object
"""
data = request.form if request.form else \
json.loads(request.data.decode())
SQL = self.getSQL(gid, sid, data, did, eid)
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
try:
if SQL and isinstance(SQL, basestring) and \
SQL.strip('\n') and SQL.strip(' '):
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
SQL, name = self.getSQL(gid, sid, data, did, eid)
SQL = SQL.strip('\n').strip(' ')
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info="Extension updated",
data={
'id': eid,
'sid': sid,
'gid': gid
}
return jsonify(
node=self.blueprint.generate_browser_node(
eid,
did,
name,
icon="icon-%s" % self.node_type
)
else:
return make_json_response(
success=1,
info="Nothing to update",
data={
'id': did,
'sid': sid,
'gid': gid
}
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@@ -310,6 +330,19 @@ class ExtensionView(PGChildNodeView):
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'
)
)
# drop extension
SQL = render_template("/".join(
[self.template_path, 'delete.sql']
@@ -337,18 +370,18 @@ class ExtensionView(PGChildNodeView):
This function returns modified SQL
"""
data = request.args.copy()
SQL = self.getSQL(gid, sid, data, did, eid)
if SQL and isinstance(SQL, basestring) and SQL.strip('\n') \
and SQL.strip(' '):
try:
SQL, name = self.getSQL(gid, sid, data, did, eid)
SQL = SQL.strip('\n').strip(' ')
if SQL == '':
SQL = "--modified SQL"
return make_json_response(
data=SQL,
status=200
)
else:
return make_json_response(
data=gettext('-- Modified SQL --'),
status=200
)
except Exception as e:
return internal_server_error(errormsg=str(e))
def getSQL(self, gid, sid, data, did, eid=None):
"""
@@ -357,28 +390,33 @@ class ExtensionView(PGChildNodeView):
required_args = [
'name'
]
try:
if eid is not None:
SQL = render_template("/".join(
[self.template_path, 'properties.sql']
), eid=eid)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
old_data = res['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
SQL = render_template("/".join(
[self.template_path, 'update.sql']
), data=data, o_data=old_data)
else:
SQL = render_template("/".join(
[self.template_path, 'create.sql']
), data=data)
return SQL
except Exception as e:
return internal_server_error(errormsg=str(e))
if eid is not None:
SQL = render_template("/".join(
[self.template_path, 'properties.sql']
), eid=eid)
status, res = self.conn.execute_dict(SQL)
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the extension information.")
)
old_data = res['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
SQL = render_template("/".join(
[self.template_path, 'update.sql']
), data=data, o_data=old_data)
return SQL, data['name'] if 'name' in data else old_data['name']
else:
SQL = render_template("/".join(
[self.template_path, 'create.sql']
), data=data)
return SQL, data['name']
@check_precondition
def avails(self, gid, sid, did):
@@ -0,0 +1,16 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
class ExtensionGeneratorTestCase(BaseTestGenerator):
def runTest(self):
return []
@@ -0,0 +1,72 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from . import utils as extension_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.schemas.tests import\
utils as schema_utils
class ExtensionsAddTestCase(BaseTestGenerator):
scenarios = [
# Fetching default URL for extension node.
('Check Extension Node', dict(url='/browser/extension/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
def runTest(self):
""" This function will add extension under 1st server of tree node. """
extension_utils.add_extensions(self.tester)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
:return: None
"""
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,74 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
import json
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from . import utils as extension_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
class ExtensionsDeleteTestCase(BaseTestGenerator):
scenarios = [
# Fetching default URL for extension node.
('Check Extension Node', dict(url='/browser/extension/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) to database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
def runTest(self):
""" This function will add extension under 1st server of tree node. """
delete_respdata = extension_utils.delete_extension(self.tester)
self.assertTrue(delete_respdata['success'], 1)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
"""
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,87 @@
#################################################################
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
##################################################################
import json
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from . import utils as extension_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
class ExtensionsGetTestCase(BaseTestGenerator):
scenarios = [
# Fetching default URL for extension node.
('Check Extension Node', dict(url='/browser/extension/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
extension_utils.add_extensions(cls.tester)
def runTest(self):
""" This function will add extension under 1st server of tree node. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
extension_ids_dict = all_id["eid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
extension_id = extension_ids_dict[server_id]
response = extension_utils.verify_extensions(self.tester,
utils.SERVER_GROUP,
server_id, db_id,
extension_id)
self.assertEquals(response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
"""
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,104 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
import json
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
utils as schema_utils
from . import utils as extension_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
class ExtensionsPutTestCase(BaseTestGenerator):
scenarios = [
# Fetching default URL for extension node.
('Check Extension Node', dict(url='/browser/extension/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
extension_utils.add_extensions(cls.tester)
def runTest(self):
""" This function will add extension under 1st server of tree node. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
extension_ids_dict = all_id["eid"][0]
schema_info_dict = all_id["scid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
extension_id = extension_ids_dict[server_id]
response = extension_utils.verify_extensions(self.tester,
utils.SERVER_GROUP,
server_id, db_id,
extension_id)
if response.status_code == 200:
schema_name = schema_info_dict[int(server_id)][1]
data = \
{
"id": extension_id,
"schema": schema_name
}
put_response = self.tester.put(
self.url + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(extension_id),
data=json.dumps(data),
follow_redirects=True)
self.assertEquals(put_response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
"""
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,134 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
import os
import pickle
import json
from regression.test_setup import advanced_config_data, pickle_path
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
EXTENSION_URL = '/browser/extension/obj/'
def get_extension_config_data(schema_name, server_connect_data):
adv_config_data = None
db_user = server_connect_data['data']['user']['name']
# Get the config data of appropriate db user
for config_test_data in advanced_config_data['extension_credentials']:
if db_user == config_test_data['owner']:
adv_config_data = config_test_data
data = {
"name": adv_config_data['name'],
"relocatable": adv_config_data['relocate'],
"schema": schema_name,
"version": adv_config_data['version']
}
return data
def write_extension_info(response_data, server_id):
"""
This function writes the schema id into parent_id.pkl
:param response_data: extension add response data
:type response_data: dict
:param server_id: server id
:type server_id: str
:return: None
"""
extension_id = response_data['node']['_id']
# schema_name = str(response_data['node']['label'])
pickle_id_dict = utils.get_pickle_id_dict()
if os.path.isfile(pickle_path):
existing_server_id = open(pickle_path, 'rb')
tol_server_id = pickle.load(existing_server_id)
pickle_id_dict = tol_server_id
if 'eid' in pickle_id_dict:
if pickle_id_dict['eid']:
# Add the extension_id as value in dict
pickle_id_dict["eid"][0].update({server_id: extension_id})
else:
# Create new dict with server_id and extension_id
pickle_id_dict["eid"].append({server_id: extension_id})
extension_output = open(pickle_path, 'wb')
pickle.dump(pickle_id_dict, extension_output)
extension_output.close()
def add_extensions(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
schema_info_dict = all_id["scid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(tester, utils.SERVER_GROUP,
server_id, db_id)
if db_con['data']["connected"]:
server_connect_response = server_utils.verify_server(
tester, utils.SERVER_GROUP, server_id)
schema_name = schema_info_dict[int(server_id)][1]
data = get_extension_config_data(schema_name,
server_connect_response)
response = tester.post(
EXTENSION_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(
db_id) + '/',
data=json.dumps(data),
content_type='html/json')
assert response.status_code == 200
response_data = json.loads(response.data.decode('utf-8'))
write_extension_info(response_data, server_id)
def verify_extensions(tester, server_group, server_id, db_id, extension_id):
response = tester.get(EXTENSION_URL + str(server_group) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(extension_id),
content_type='html/json')
return response
def delete_extension(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
extension_ids_dict = all_id["eid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
extension_id = extension_ids_dict[server_id]
response = verify_extensions(tester,
utils.SERVER_GROUP,
server_id, db_id,
extension_id)
if response.status_code == 200:
delete_response = tester.delete(
EXTENSION_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' +
str(db_id) + '/' +
str(extension_id),
follow_redirects=True)
delete_respdata = json.loads(delete_response.data.decode())
return delete_respdata
@@ -9,20 +9,20 @@
"""Implements Foreign Data Wrapper Node"""
import json
import simplejson as json
from functools import wraps
import pgadmin.browser.server_groups.servers as servers
from flask import render_template, make_response, request, jsonify
from flask.ext.babel import gettext
from flask_babel import gettext
from pgadmin.browser.collection import CollectionNodeModule
from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
parse_priv_to_db
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error
from pgadmin.utils.ajax import precondition_required
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
make_response as ajax_response
from pgadmin.utils.driver import get_driver
from pgadmin.utils.ajax import gone
from config import PG_DEFAULT_DRIVER
@@ -211,17 +211,8 @@ class ForeignDataWrapperView(PGChildNodeView):
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
self.conn = self.manager.connection(did=kwargs['did'])
# If DB not connected then return error to browser
if not self.conn.connected():
return precondition_required(
gettext(
"Connection to the server has been lost!"
)
)
ver = self.manager.version
# we will set template path for sql scripts
if ver >= 90300:
# Set the template path for the SQL scripts
if self.manager.version >= 90300:
self.template_path = 'foreign_data_wrappers/sql/9.3_plus'
else:
self.template_path = 'foreign_data_wrappers/sql/9.1_plus'
@@ -286,6 +277,36 @@ class ForeignDataWrapperView(PGChildNodeView):
status=200
)
@check_precondition
def node(self, gid, sid, did, fid):
"""
This function will fetch properties of foreign data wrapper node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
fid: Foreign data wrapper ID
"""
sql = render_template("/".join([self.template_path, 'properties.sql']),
conn=self.conn, fid=fid)
status, r_set = self.conn.execute_2darray(sql)
if not status:
return internal_server_error(errormsg=r_set)
for row in r_set['rows']:
return make_json_response(
data=self.blueprint.generate_browser_node(
row['fdwoid'],
did,
row['name'],
icon="icon-foreign_data_wrapper"
),
status=200
)
return gone(gettext("Could not find the specified foreign data wrapper."))
def tokenize_options(self, option_value):
"""
This function will tokenize the string stored in database
@@ -321,6 +342,11 @@ class ForeignDataWrapperView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the foreign data wrapper information.")
)
if res['rows'][0]['fdwoptions'] is not None:
res['rows'][0]['fdwoptions'] = self.tokenize_options(res['rows'][0]['fdwoptions'])
@@ -355,7 +381,9 @@ class ForeignDataWrapperView(PGChildNodeView):
'name'
]
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
for arg in required_args:
if arg not in data:
return make_json_response(
@@ -422,36 +450,25 @@ class ForeignDataWrapperView(PGChildNodeView):
did: Database ID
fid: foreign data wrapper ID
"""
data = request.form if request.form else json.loads(request.data.decode())
sql = self.get_sql(gid, sid, data, did, fid)
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
try:
if sql and sql.strip('\n') and sql.strip(' '):
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
sql, name = self.get_sql(gid, sid, data, did, fid)
return make_json_response(
success=1,
info="Foreign Data Wrapper updated",
data={
'id': fid,
'did': did,
'sid': sid,
'gid': gid
}
)
else:
return make_json_response(
success=1,
info="Nothing to update",
data={
'id': fid,
'did': did,
'sid': sid,
'gid': gid
}
)
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return jsonify(
node=self.blueprint.generate_browser_node(
fid,
did,
name,
icon="icon-%s" % self.node_type
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@@ -478,6 +495,18 @@ class ForeignDataWrapperView(PGChildNodeView):
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'
)
)
# drop foreign data wrapper node
sql = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade,
conn=self.conn)
@@ -513,15 +542,21 @@ class ForeignDataWrapperView(PGChildNodeView):
data = {}
for k, v in request.args.items():
try:
data[k] = json.loads(v)
data[k] = json.loads(v, encoding='utf-8')
except ValueError:
data[k] = v
try:
sql, name = self.get_sql(gid, sid, data, did, fid)
sql = self.get_sql(gid, sid, data, did, fid)
if sql and sql.strip('\n') and sql.strip(' '):
return make_json_response(data=sql, status=200)
else:
return make_json_response(data='-- Modified SQL --', status=200)
if sql == '':
sql = "--modified SQL"
return make_json_response(
data=sql,
status=200
)
except Exception as e:
return internal_server_error(errormsg=str(e))
def get_sql(self, gid, sid, data, did, fid=None):
"""
@@ -537,89 +572,94 @@ class ForeignDataWrapperView(PGChildNodeView):
required_args = [
'name'
]
try:
if fid is not None:
sql = render_template("/".join([self.template_path, 'properties.sql']), fid=fid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
if res['rows'][0]['fdwoptions'] is not None:
res['rows'][0]['fdwoptions'] = self.tokenize_options(res['rows'][0]['fdwoptions'])
if fid is not None:
sql = render_template("/".join([self.template_path, 'properties.sql']), fid=fid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
for key in ['fdwacl']:
if key in data and data[key] is not None:
if 'added' in data[key]:
data[key]['added'] = parse_priv_to_db(data[key]['added'], ['U'])
if 'changed' in data[key]:
data[key]['changed'] = parse_priv_to_db(data[key]['changed'], ['U'])
if 'deleted' in data[key]:
data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'], ['U'])
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the foreign data wrapper information.")
)
old_data = res['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
if res['rows'][0]['fdwoptions'] is not None:
res['rows'][0]['fdwoptions'] = self.tokenize_options(res['rows'][0]['fdwoptions'])
new_list_add = []
new_list_change = []
for key in ['fdwacl']:
if key in data and data[key] is not None:
if 'added' in data[key]:
data[key]['added'] = parse_priv_to_db(data[key]['added'], ['U'])
if 'changed' in data[key]:
data[key]['changed'] = parse_priv_to_db(data[key]['changed'], ['U'])
if 'deleted' in data[key]:
data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'], ['U'])
# Allow user to set the blank value in fdwvalue field in option model
if 'fdwoptions' in data and 'added' in data['fdwoptions']:
for item in data['fdwoptions']['added']:
new_dict_add = {}
if item['fdwoption']:
if 'fdwvalue' in item and item['fdwvalue'] and item['fdwvalue'] != '':
new_dict_add.update(item);
else:
new_dict_add.update({'fdwoption': item['fdwoption'], 'fdwvalue': ''})
old_data = res['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
new_list_add.append(new_dict_add)
new_list_add = []
new_list_change = []
data['fdwoptions']['added'] = new_list_add
# Allow user to set the blank value in fdwvalue field in option model
if 'fdwoptions' in data and 'added' in data['fdwoptions']:
for item in data['fdwoptions']['added']:
new_dict_add = {}
if item['fdwoption']:
if 'fdwvalue' in item and item['fdwvalue'] and item['fdwvalue'] != '':
new_dict_add.update(item);
else:
new_dict_add.update({'fdwoption': item['fdwoption'], 'fdwvalue': ''})
# Allow user to set the blank value in fdwvalue field in option model
if 'fdwoptions' in data and 'changed' in data['fdwoptions']:
for item in data['fdwoptions']['changed']:
new_dict_change = {}
if item['fdwoption']:
if 'fdwvalue' in item and item['fdwvalue'] and item['fdwvalue'] != '':
new_dict_change.update(item);
else:
new_dict_change.update({'fdwoption': item['fdwoption'], 'fdwvalue': ''})
new_list_add.append(new_dict_add)
new_list_change.append(new_dict_change)
data['fdwoptions']['added'] = new_list_add
data['fdwoptions']['changed'] = new_list_change
# Allow user to set the blank value in fdwvalue field in option model
if 'fdwoptions' in data and 'changed' in data['fdwoptions']:
for item in data['fdwoptions']['changed']:
new_dict_change = {}
if item['fdwoption']:
if 'fdwvalue' in item and item['fdwvalue'] and item['fdwvalue'] != '':
new_dict_change.update(item);
else:
new_dict_change.update({'fdwoption': item['fdwoption'], 'fdwvalue': ''})
sql = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data,
conn=self.conn)
else:
for key in ['fdwacl']:
if key in data and data[key] is not None:
data[key] = parse_priv_to_db(data[key], ['U'])
new_list_change.append(new_dict_change)
new_list = []
data['fdwoptions']['changed'] = new_list_change
# Allow user to set the blank value in fdwvalue field in option model
if 'fdwoptions' in data:
for item in data['fdwoptions']:
new_dict = {}
if item['fdwoption']:
if 'fdwvalue' in item and item['fdwvalue'] and item['fdwvalue'] != '':
new_dict.update(item);
else:
new_dict.update({'fdwoption': item['fdwoption'], 'fdwvalue': ''})
sql = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data,
conn=self.conn)
return sql, data['name'] if 'name' in data else old_data['name']
else:
for key in ['fdwacl']:
if key in data and data[key] is not None:
data[key] = parse_priv_to_db(data[key], ['U'])
new_list.append(new_dict)
new_list = []
data['fdwoptions'] = new_list
# Allow user to set the blank value in fdwvalue field in option model
if 'fdwoptions' in data:
for item in data['fdwoptions']:
new_dict = {}
if item['fdwoption']:
if 'fdwvalue' in item and item['fdwvalue'] and item['fdwvalue'] != '':
new_dict.update(item);
else:
new_dict.update({'fdwoption': item['fdwoption'], 'fdwvalue': ''})
new_list.append(new_dict)
data['fdwoptions'] = new_list
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, conn=self.conn)
sql += "\n"
return sql, data['name']
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, conn=self.conn)
sql += "\n"
return sql
except Exception as e:
return internal_server_error(errormsg=str(e))
@check_precondition
def sql(self, gid, sid, did, fid):
@@ -666,6 +706,9 @@ class ForeignDataWrapperView(PGChildNodeView):
""".format(res['rows'][0]['name'])
if hasattr(str, 'decode'):
sql_header = sql_header.decode('utf-8')
sql = sql_header + sql
return ajax_response(response=sql)
@@ -689,7 +732,7 @@ class ForeignDataWrapperView(PGChildNodeView):
return internal_server_error(errormsg=r_set)
for row in r_set['rows']:
res.append({'label': row['fdwvalue'], 'value': row['fdwvalue']})
res.append({'label': row['schema_prefix_fdw_val'], 'value': row['schema_prefix_fdw_val']})
return make_json_response(data=res, status=200)
@@ -715,7 +758,7 @@ class ForeignDataWrapperView(PGChildNodeView):
return internal_server_error(errormsg=r_set)
for row in r_set['rows']:
res.append({'label': row['fdwhan'], 'value': row['fdwhan']})
res.append({'label': row['schema_prefix_fdw_hand'], 'value': row['schema_prefix_fdw_hand']})
return make_json_response(
data=res,
@@ -9,20 +9,20 @@
"""Implements Foreign Server Node"""
import json
import simplejson as json
from functools import wraps
import pgadmin.browser.server_groups.servers.databases as databases
from flask import render_template, make_response, request, jsonify
from flask.ext.babel import gettext
from flask_babel import gettext
from pgadmin.browser.collection import CollectionNodeModule
from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
parse_priv_to_db
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error
from pgadmin.utils.ajax import precondition_required
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
make_response as ajax_response
from pgadmin.utils.driver import get_driver
from pgadmin.utils.ajax import gone
from config import PG_DEFAULT_DRIVER
@@ -204,18 +204,8 @@ class ForeignServerView(PGChildNodeView):
self = args[0]
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
self.conn = self.manager.connection(did=kwargs['did'])
# If DB not connected then return error to browser
if not self.conn.connected():
return precondition_required(
gettext(
"Connection to the server has been lost!"
)
)
ver = self.manager.version
# we will set template path for sql scripts
if ver >= 90300:
# Set the template path for the SQL scripts
if self.manager.version >= 90300:
self.template_path = 'foreign_servers/sql/9.3_plus'
else:
self.template_path = 'foreign_servers/sql/9.1_plus'
@@ -281,6 +271,41 @@ class ForeignServerView(PGChildNodeView):
status=200
)
@check_precondition
def node(self, gid, sid, did, fid, fsid):
"""
This function will fetch properites foreign server node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
fid: Foreign data wrapper ID
fsid: Foreign server ID
"""
res = []
sql = render_template("/".join([self.template_path, 'properties.sql']),
fsid=fsid, conn=self.conn)
status, r_set = self.conn.execute_2darray(sql)
if not status:
return internal_server_error(errormsg=r_set)
for row in r_set['rows']:
return make_json_response(
data=self.blueprint.generate_browser_node(
row['fsrvid'],
fid,
row['name'],
icon="icon-foreign_server"
),
status=200
)
return gone(gettext("Could not find the specified foreign server."))
def tokenizeOptions(self, option_value):
"""
This function will tokenize the string stored in database
@@ -319,6 +344,11 @@ class ForeignServerView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the foreign server information.")
)
if res['rows'][0]['fsrvoptions'] is not None:
res['rows'][0]['fsrvoptions'] = self.tokenizeOptions(res['rows'][0]['fsrvoptions'])
@@ -356,7 +386,9 @@ class ForeignServerView(PGChildNodeView):
'name'
]
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
for arg in required_args:
if arg not in data:
return make_json_response(
@@ -428,37 +460,25 @@ class ForeignServerView(PGChildNodeView):
fsid: foreign server ID
"""
data = request.form if request.form else json.loads(request.data.decode())
sql = self.get_sql(gid, sid, data, did, fid, fsid)
try:
if sql and sql.strip('\n') and sql.strip(' '):
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
return make_json_response(
success=1,
info="Foreign server updated",
data={
'id': fsid,
'fid': fid,
'did': did,
'sid': sid,
'gid': gid
}
)
else:
return make_json_response(
success=1,
info="Nothing to update",
data={
'id': fsid,
'fid': fid,
'did': did,
'sid': sid,
'gid': gid
}
try:
sql, name = self.get_sql(gid, sid, data, did, fid, fsid)
sql = sql.strip('\n').strip(' ')
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return jsonify(
node=self.blueprint.generate_browser_node(
fsid,
fid,
name,
icon="icon-%s" % self.node_type
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@@ -488,6 +508,19 @@ class ForeignServerView(PGChildNodeView):
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'
)
)
# drop foreign server
sql = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade,
conn=self.conn)
@@ -526,21 +559,21 @@ class ForeignServerView(PGChildNodeView):
data = {}
for k, v in request.args.items():
try:
data[k] = json.loads(v)
data[k] = json.loads(v, encoding='utf-8')
except ValueError:
data[k] = v
try:
sql, name = self.get_sql(gid, sid, data, did, fid, fsid)
if sql == '':
sql = "--modified SQL"
sql = self.get_sql(gid, sid, data, did, fid, fsid)
if sql and sql.strip('\n') and sql.strip(' '):
return make_json_response(
data=sql,
status=200
)
else:
return make_json_response(
data='-- Modified SQL --',
status=200
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
def get_sql(self, gid, sid, data, did, fid, fsid=None):
"""
@@ -558,96 +591,96 @@ class ForeignServerView(PGChildNodeView):
required_args = [
'name'
]
try:
if fsid is not None:
sql = render_template("/".join([self.template_path, 'properties.sql']), fsid=fsid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
if res['rows'][0]['fsrvoptions'] is not None:
res['rows'][0]['fsrvoptions'] = self.tokenizeOptions(res['rows'][0]['fsrvoptions'])
if fsid is not None:
sql = render_template("/".join([self.template_path, 'properties.sql']), fsid=fsid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
for key in ['fsrvacl']:
if key in data and data[key] is not None:
if 'added' in data[key]:
data[key]['added'] = parse_priv_to_db(data[key]['added'], ['U'])
if 'changed' in data[key]:
data[key]['changed'] = parse_priv_to_db(data[key]['changed'], ['U'])
if 'deleted' in data[key]:
data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'], ['U'])
if res['rows'][0]['fsrvoptions'] is not None:
res['rows'][0]['fsrvoptions'] = self.tokenizeOptions(res['rows'][0]['fsrvoptions'])
old_data = res['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
for key in ['fsrvacl']:
if key in data and data[key] is not None:
if 'added' in data[key]:
data[key]['added'] = parse_priv_to_db(data[key]['added'], ['U'])
if 'changed' in data[key]:
data[key]['changed'] = parse_priv_to_db(data[key]['changed'], ['U'])
if 'deleted' in data[key]:
data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'], ['U'])
new_list_add = []
new_list_change = []
old_data = res['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
# Allow user to set the blank value in fsrvvalue field in option model
if 'fsrvoptions' in data and 'added' in data['fsrvoptions']:
for item in data['fsrvoptions']['added']:
new_dict_add = {}
if item['fsrvoption']:
if 'fsrvvalue' in item and item['fsrvvalue'] and item['fsrvvalue'] != '':
new_dict_add.update(item);
else:
new_dict_add.update({'fsrvoption': item['fsrvoption'], 'fsrvvalue': ''})
new_list_add = []
new_list_change = []
new_list_add.append(new_dict_add)
# Allow user to set the blank value in fsrvvalue field in option model
if 'fsrvoptions' in data and 'added' in data['fsrvoptions']:
for item in data['fsrvoptions']['added']:
new_dict_add = {}
if item['fsrvoption']:
if 'fsrvvalue' in item and item['fsrvvalue'] and item['fsrvvalue'] != '':
new_dict_add.update(item);
else:
new_dict_add.update({'fsrvoption': item['fsrvoption'], 'fsrvvalue': ''})
data['fsrvoptions']['added'] = new_list_add
new_list_add.append(new_dict_add)
# Allow user to set the blank value in fsrvvalue field in option model
if 'fsrvoptions' in data and 'changed' in data['fsrvoptions']:
for item in data['fsrvoptions']['changed']:
new_dict_change = {}
if item['fsrvoption']:
if 'fsrvvalue' in item and item['fsrvvalue'] and item['fsrvvalue'] != '':
new_dict_change.update(item);
else:
new_dict_change.update({'fsrvoption': item['fsrvoption'], 'fsrvvalue': ''})
data['fsrvoptions']['added'] = new_list_add
new_list_change.append(new_dict_change)
# Allow user to set the blank value in fsrvvalue field in option model
if 'fsrvoptions' in data and 'changed' in data['fsrvoptions']:
for item in data['fsrvoptions']['changed']:
new_dict_change = {}
if item['fsrvoption']:
if 'fsrvvalue' in item and item['fsrvvalue'] and item['fsrvvalue'] != '':
new_dict_change.update(item);
else:
new_dict_change.update({'fsrvoption': item['fsrvoption'], 'fsrvvalue': ''})
data['fsrvoptions']['changed'] = new_list_change
new_list_change.append(new_dict_change)
sql = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data,
conn=self.conn)
else:
sql = render_template("/".join([self.template_path, 'properties.sql']), fdwid=fid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
data['fsrvoptions']['changed'] = new_list_change
fdw_data = res['rows'][0]
sql = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data,
conn=self.conn)
return sql, data['name'] if 'name' in data else old_data['name']
else:
sql = render_template("/".join([self.template_path, 'properties.sql']), fdwid=fid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
for key in ['fsrvacl']:
if key in data and data[key] is not None:
data[key] = parse_priv_to_db(data[key], ['U'])
fdw_data = res['rows'][0]
new_list = []
for key in ['fsrvacl']:
if key in data and data[key] is not None:
data[key] = parse_priv_to_db(data[key], ['U'])
if 'fsrvoptions' in data:
for item in data['fsrvoptions']:
new_dict = {}
if item['fsrvoption']:
if 'fsrvvalue' in item and item['fsrvvalue'] and item['fsrvvalue'] != '':
new_dict.update(item);
else:
new_dict.update({'fsrvoption': item['fsrvoption'], 'fsrvvalue': ''})
new_list = []
new_list.append(new_dict)
if 'fsrvoptions' in data:
for item in data['fsrvoptions']:
new_dict = {}
if item['fsrvoption']:
if 'fsrvvalue' in item and item['fsrvvalue'] and item['fsrvvalue'] != '':
new_dict.update(item);
else:
new_dict.update({'fsrvoption': item['fsrvoption'], 'fsrvvalue': ''})
data['fsrvoptions'] = new_list
new_list.append(new_dict)
data['fsrvoptions'] = new_list
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, fdwdata=fdw_data,
conn=self.conn)
sql += "\n"
return sql, data['name']
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, fdwdata=fdw_data,
conn=self.conn)
sql += "\n"
return sql
except Exception as e:
return internal_server_error(errormsg=str(e))
@check_precondition
def sql(self, gid, sid, did, fid, fsid):
@@ -703,6 +736,8 @@ class ForeignServerView(PGChildNodeView):
-- DROP SERVER {0}
""".format(res['rows'][0]['name'])
if hasattr(str, 'decode'):
sql_header = sql_header.decode('utf-8')
sql = sql_header + sql
@@ -1,4 +1,4 @@
SELECT 'fsrvacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
SELECT 'fsrvacl' as deftype, COALESCE(gt.rolname, 'PUBLIC') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
FROM
(SELECT
d.grantee, d.grantor, d.is_grantable,
@@ -2,11 +2,11 @@
{% if fsid %}
WITH umapData AS
(
SELECT u.oid AS um_oid, CASE WHEN u.umuser = 0::oid THEN 'public'::name ELSE a.rolname END AS name,
SELECT u.oid AS um_oid, CASE WHEN u.umuser = 0::oid THEN 'PUBLIC'::name ELSE a.rolname END AS name,
array_to_string(u.umoptions, ',') AS umoptions FROM pg_user_mapping u
LEFT JOIN pg_authid a ON a.oid = u.umuser WHERE u.umserver = {{ fsid }}::OID
)
SELECT um.um_oid, name, dep.deptype FROM umapData um
LEFT JOIN pg_depend dep ON dep.objid=um.um_oid
{% endif %}
{% endif %}
@@ -1,4 +1,4 @@
SELECT 'fsrvacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
SELECT 'fsrvacl' as deftype, COALESCE(gt.rolname, 'PUBLIC') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
FROM
(SELECT
d.grantee, d.grantor, d.is_grantable,
@@ -2,11 +2,11 @@
{% if fsid %}
WITH umapData AS
(
SELECT u.oid AS um_oid, CASE WHEN u.umuser = 0::oid THEN 'public'::name ELSE a.rolname END AS name,
SELECT u.oid AS um_oid, CASE WHEN u.umuser = 0::oid THEN 'PUBLIC'::name ELSE a.rolname END AS name,
array_to_string(u.umoptions, ',') AS umoptions FROM pg_user_mapping u
LEFT JOIN pg_authid a ON a.oid = u.umuser WHERE u.umserver = {{ fsid }}::OID
)
SELECT um.um_oid, name, dep.deptype FROM umapData um
LEFT JOIN pg_depend dep ON dep.objid=um.um_oid
{% endif %}
{% endif %}
@@ -0,0 +1,16 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
class ForeignServersGeneratorTestCase(BaseTestGenerator):
def runTest(self):
return []
@@ -0,0 +1,88 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests \
import utils as fdw_utils
from . import utils as fsrv_utils
class ForeignServerAddTestCase(BaseTestGenerator):
"""
This class will add foreign server under database node.
"""
scenarios = [
# Fetching default URL for foreign server node.
('Check FSRV Node', dict(url='/browser/foreign_server/obj/'))
]
@classmethod
def setUpClass(cls):
""""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
:return: None"
"""
# Add the server(s)
server_utils.add_server(cls.tester)
# Connect to server(s)
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add database(s) to connected server(s)
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) under connected database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
# Add foreign data wrapper(s) to extension(s)
fdw_utils.add_fdw(cls.tester)
def runTest(self):
""" This function will add foreign server under database node. """
fsrv_utils.add_fsrv(self.tester)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
:return: None
"""
fsrv_utils.delete_fsrv(cls.tester)
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,94 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests\
import utils as fdw_utils
from . import utils as fsrv_utils
class ForeignServerDeleteTestCase(BaseTestGenerator):
"""
This class will add foreign server under FDW node.
"""
scenarios = [
# Fetching default URL for foreign server node.
('Check FSRV Node', dict(url='/browser/foreign_server/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
6. Add foreign server(s) to foreign data wrapper(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) under connected database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
# Add foreign data wrapper(s) to extension(s)
fdw_utils.add_fdw(cls.tester)
# Add foreign server(s) to foreign data wrapper
fsrv_utils.add_fsrv(cls.tester)
#
def runTest(self):
""" This function will delete foreign server under FDW node. """
delete_respdata = fsrv_utils.delete_fsrv(self.tester)
self.assertTrue(delete_respdata['success'], 1)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
:return: None
"""
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,110 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests\
import utils as fdw_utils
from . import utils as fsrv_utils
class ForeignServerGetTestCase(BaseTestGenerator):
"""
This class will add foreign server under FDW node.
"""
scenarios = [
# Fetching default URL for foreign server node.
('Check FSRV Node', dict(url='/browser/foreign_server/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
6. Add foreign server(s) to foreign data wrapper(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) under connected database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
# Add foreign data wrapper(s) to extension(s)
fdw_utils.add_fdw(cls.tester)
# Add foreign server(s) to foreign data wrapper
fsrv_utils.add_fsrv(cls.tester)
#
def runTest(self):
""" This function will fetch foreign server under FDW node. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
fsrv_ids_dict = all_id["fsid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
fsrv_id = fsrv_ids_dict[server_id]
response = fsrv_utils.verify_fsrv(self.tester, utils.SERVER_GROUP,
server_id, db_id,
fdw_id, fsrv_id)
self.assertEquals(response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
:return: None
"""
fsrv_utils.delete_fsrv(cls.tester)
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,128 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests\
import utils as fdw_utils
from . import utils as fsrv_utils
from regression.test_setup import advanced_config_data
import json
class ForeignServerPutTestCase(BaseTestGenerator):
"""
This class will add foreign server under FDW node.
"""
scenarios = [
# Fetching default URL for foreign server node.
('Check FSRV Node', dict(url='/browser/foreign_server/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
6. Add foreign server(s) to foreign data wrapper(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) under connected database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
# Add foreign data wrapper(s) to extension(s)
fdw_utils.add_fdw(cls.tester)
# Add foreign server(s) to foreign data wrapper
fsrv_utils.add_fsrv(cls.tester)
#
def runTest(self):
""" This function will update foreign server under FDW node. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
fsrv_ids_dict = all_id["fsid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
fsrv_id = fsrv_ids_dict[server_id]
response = fsrv_utils.verify_fsrv(self.tester, utils.SERVER_GROUP,
server_id, db_id,
fdw_id, fsrv_id)
if response.status_code == 200:
data = \
{
"description": advanced_config_data['FSRV_update_data']
['comment'],
"id": fsrv_id
}
put_response = self.tester.put(
self.url + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(fdw_id) + '/' +
str(fsrv_id), data=json.dumps(data),
follow_redirects=True)
self.assertEquals(put_response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""
This function deletes the added foreign server(s) ,
foreign data wrapper(s), extension(s), schema(s), database(s), server(s)
and parent id file
:return: None
"""
fsrv_utils.delete_fsrv(cls.tester)
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,149 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
import os
import pickle
import json
from regression.test_setup import advanced_config_data, pickle_path
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
import uuid
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests \
import utils as fdw_utils
FSRV_URL = '/browser/foreign_server/obj/'
def get_fsrv_config_data(server_connect_data):
adv_config_data = None
db_user = server_connect_data['data']['user']['name']
# Get the config data of appropriate db user
for config_test_data in advanced_config_data['fsrv_credentials']:
if db_user == config_test_data['owner']:
adv_config_data = config_test_data
data = {
"fsrvacl": adv_config_data['fsrv_acl'],
"fsrvoptions": adv_config_data['fsrv_options'],
"fsrvowner": adv_config_data['owner'],
"name": "fsrv_{}".format(str(uuid.uuid4())[1:4])
}
return data
def add_fsrv(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(tester,
utils.SERVER_GROUP,
server_id, db_id)
if db_con['data']['connected']:
server_connect_response = server_utils.verify_server(
tester, utils.SERVER_GROUP, server_id)
fdw_id = fdw_ids_dict[server_id]
response = fdw_utils.verify_fdws(tester,
utils.SERVER_GROUP,
server_id, db_id,
fdw_id)
if response.status_code == 200:
data = get_fsrv_config_data(server_connect_response)
response = tester.post(
FSRV_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(
db_id) +
'/' + str(fdw_id) + '/',
data=json.dumps(data),
content_type='html/json')
assert response.status_code == 200
response_data = json.loads(response.data.decode())
write_fsrv_info(response_data, server_id)
def write_fsrv_info(response_data, server_id):
"""
This function writes the schema id into parent_id.pkl
:param response_data: foreign server add response data
:type response_data: dict
:param server_id: server id
:type server_id: str
:return: None
"""
fsrv_id = response_data['node']['_id']
pickle_id_dict = utils.get_pickle_id_dict()
if os.path.isfile(pickle_path):
existing_server_id = open(pickle_path, 'rb')
tol_server_id = pickle.load(existing_server_id)
pickle_id_dict = tol_server_id
if 'fsid' in pickle_id_dict:
if pickle_id_dict['fsid']:
# Add the FSRV_id as value in dict
pickle_id_dict["fsid"][0].update({server_id: fsrv_id})
else:
# Create new dict with server_id and fsrv_id
pickle_id_dict["fsid"].append({server_id: fsrv_id})
fsrv_output = open(pickle_path, 'wb')
pickle.dump(pickle_id_dict, fsrv_output)
fsrv_output.close()
def verify_fsrv(tester, server_group, server_id, db_id, fdw_id, fsrv_id):
response = tester.get(FSRV_URL + str(server_group) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(fdw_id) + '/' + str(fsrv_id),
content_type='html/json')
return response
def delete_fsrv(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
fsrv_ids_dict = all_id["fsid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
fsrv_id = fsrv_ids_dict[server_id]
response = verify_fsrv(tester, utils.SERVER_GROUP,
server_id, db_id,
fdw_id, fsrv_id)
if response.status_code == 200:
delete_response = tester.delete(
FSRV_URL + str(utils.SERVER_GROUP) +
'/' + str(server_id) + '/' +
str(db_id) + '/' +
str(fdw_id) + '/' +
str(fsrv_id),
follow_redirects=True)
delete_respdata = json.loads(delete_response.data.decode())
return delete_respdata
@@ -9,18 +9,18 @@
"""Implements User Mapping Node"""
import json
import simplejson as json
from functools import wraps
import pgadmin.browser.server_groups.servers as servers
from flask import render_template, make_response, request, jsonify
from flask.ext.babel import gettext
from flask_babel import gettext
from pgadmin.browser.collection import CollectionNodeModule
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error
from pgadmin.utils.ajax import precondition_required
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
make_response as ajax_response
from pgadmin.utils.driver import get_driver
from pgadmin.utils.ajax import gone
from config import PG_DEFAULT_DRIVER
@@ -219,21 +219,9 @@ class UserMappingView(PGChildNodeView):
self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
self.conn = self.manager.connection(did=kwargs['did'])
# If DB not connected then return error to browser
if not self.conn.connected():
return precondition_required(
gettext(
"Connection to the server has been lost!"
)
)
# Set the template path for the SQL scripts
self.template_path = 'user_mappings/sql/9.1_plus'
ver = self.manager.version
# we will set template path for sql scripts
if ver >= 90100:
self.template_path = 'user_mappings/sql/9.1_plus'
return f(*args, **kwargs)
return wrap
@@ -297,6 +285,39 @@ class UserMappingView(PGChildNodeView):
status=200
)
@check_precondition
def node(self, gid, sid, did, fid, fsid, umid):
"""
This function will fetch properties of user mapping node.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
fid: Foreign data wrapper ID
fsid: Foreign server ID
umid: User mapping ID
"""
sql = render_template("/".join([self.template_path, 'properties.sql']),
conn=self.conn, umid=umid)
status, r_set = self.conn.execute_2darray(sql)
if not status:
return internal_server_error(errormsg=r_set)
for row in r_set['rows']:
return make_json_response(
data=self.blueprint.generate_browser_node(
row['um_oid'],
fsid,
row['name'],
icon="icon-user_mapping"
),
status=200
)
return gone(gettext("Could not find the specified user mapping."))
def tokenizeOptions(self, option_value):
"""
This function will tokenize the string stored in database
@@ -336,8 +357,10 @@ class UserMappingView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
if res['rows'][0]['name'] == 'public':
res['rows'][0]['name'] = 'PUBLIC'
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the user mapping information.")
)
if res['rows'][0]['umoptions'] is not None:
res['rows'][0]['umoptions'] = self.tokenizeOptions(res['rows'][0]['umoptions'])
@@ -364,7 +387,9 @@ class UserMappingView(PGChildNodeView):
'name'
]
data = request.form if request.form else json.loads(request.data.decode())
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
for arg in required_args:
if arg not in data:
return make_json_response(
@@ -438,39 +463,24 @@ class UserMappingView(PGChildNodeView):
umid: User mapping ID
"""
data = request.form if request.form else json.loads(request.data.decode())
sql = self.get_sql(gid, sid, data, did, fid, fsid, umid)
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
try:
if sql and sql.strip('\n') and sql.strip(' '):
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
sql, name = self.get_sql(gid, sid, data, did, fid, fsid, umid)
sql = sql.strip('\n').strip(' ')
status, res = self.conn.execute_scalar(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info="User Mapping updated",
data={
'id': umid,
'fsid': fsid,
'fid': fid,
'did': did,
'sid': sid,
'gid': gid
}
)
else:
return make_json_response(
success=1,
info="Nothing to update",
data={
'id': umid,
'fsid': fsid,
'fid': fid,
'did': did,
'sid': sid,
'gid': gid
}
return jsonify(
node=self.blueprint.generate_browser_node(
umid,
fsid,
name,
icon="icon-%s" % self.node_type
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@@ -502,11 +512,32 @@ class UserMappingView(PGChildNodeView):
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'
)
)
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'
)
)
data = res['rows'][0]
# drop user mapping
@@ -549,21 +580,20 @@ class UserMappingView(PGChildNodeView):
data = {}
for k, v in request.args.items():
try:
data[k] = json.loads(v)
data[k] = json.loads(v, encoding='utf-8')
except ValueError:
data[k] = v
try:
sql, name = self.get_sql(gid, sid, data, did, fid, fsid, umid)
if sql == '':
sql = "--modified SQL"
sql = self.get_sql(gid, sid, data, did, fid, fsid, umid)
if sql and sql.strip('\n') and sql.strip(' '):
return make_json_response(
data=sql,
status=200
)
else:
return make_json_response(
data='-- Modified SQL --',
status=200
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
def get_sql(self, gid, sid, data, did, fid, fsid, umid=None):
"""
@@ -582,93 +612,92 @@ class UserMappingView(PGChildNodeView):
required_args = [
'name'
]
try:
if umid is not None:
sql = render_template("/".join([self.template_path, 'properties.sql']), umid=umid, conn=self.conn)
if umid is not None:
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 res['rows'][0]['umoptions'] is not None:
res['rows'][0]['umoptions'] = self.tokenizeOptions(res['rows'][0]['umoptions'])
old_data = res['rows'][0]
sql = render_template("/".join([self.template_path, 'properties.sql']), fserid=fsid, conn=self.conn)
status, res1 = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res1)
fdw_data = res1['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
new_list_add = []
new_list_change = []
# Allow user to set the blank value in fdwvalue field in option model
if 'umoptions' in data and 'added' in data['umoptions']:
for item in data['umoptions']['added']:
new_dict_add = {}
if item['umoption']:
if 'umvalue' in item and item['umvalue'] and item['umvalue'] != '':
new_dict_add.update(item);
else:
new_dict_add.update({'umoption': item['umoption'], 'umvalue': ''})
new_list_add.append(new_dict_add)
data['umoptions']['added'] = new_list_add
# Allow user to set the blank value in fdwvalue field in option model
if 'umoptions' in data and 'changed' in data['umoptions']:
for item in data['umoptions']['changed']:
new_dict_change = {}
if item['umoption']:
if 'umvalue' in item and item['umvalue'] and item['umvalue'] != '':
new_dict_change.update(item);
else:
new_dict_change.update({'umoption': item['umoption'], 'umvalue': ''})
new_list_change.append(new_dict_change)
data['umoptions']['changed'] = new_list_change
sql = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data,
fdwdata=fdw_data, conn=self.conn)
return sql, data['name'] if 'name' in data else old_data['name']
else:
sql = render_template("/".join([self.template_path, 'properties.sql']), fserid=fsid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
fdw_data = res['rows'][0]
if res['rows'][0]['umoptions'] is not None:
res['rows'][0]['umoptions'] = self.tokenizeOptions(res['rows'][0]['umoptions'])
new_list = []
old_data = res['rows'][0]
if 'umoptions' in data:
for item in data['umoptions']:
new_dict = {}
if item['umoption']:
if 'umvalue' in item and item['umvalue'] \
and item['umvalue'] != '':
new_dict.update(item);
else:
new_dict.update(
{'umoption': item['umoption'],
'umvalue': ''}
)
new_list.append(new_dict)
sql = render_template("/".join([self.template_path, 'properties.sql']), fserid=fsid, conn=self.conn)
status, res1 = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res1)
data['umoptions'] = new_list
fdw_data = res1['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
new_list_add = []
new_list_change = []
# Allow user to set the blank value in fdwvalue field in option model
if 'umoptions' in data and 'added' in data['umoptions']:
for item in data['umoptions']['added']:
new_dict_add = {}
if item['umoption']:
if 'umvalue' in item and item['umvalue'] and item['umvalue'] != '':
new_dict_add.update(item);
else:
new_dict_add.update({'umoption': item['umoption'], 'umvalue': ''})
new_list_add.append(new_dict_add)
data['umoptions']['added'] = new_list_add
# Allow user to set the blank value in fdwvalue field in option model
if 'umoptions' in data and 'changed' in data['umoptions']:
for item in data['umoptions']['changed']:
new_dict_change = {}
if item['umoption']:
if 'umvalue' in item and item['umvalue'] and item['umvalue'] != '':
new_dict_change.update(item);
else:
new_dict_change.update({'umoption': item['umoption'], 'umvalue': ''})
new_list_change.append(new_dict_change)
data['umoptions']['changed'] = new_list_change
sql = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data,
fdwdata=fdw_data, conn=self.conn)
else:
sql = render_template("/".join([self.template_path, 'properties.sql']), fserid=fsid, conn=self.conn)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
fdw_data = res['rows'][0]
new_list = []
if 'umoptions' in data:
for item in data['umoptions']:
new_dict = {}
if item['umoption']:
if 'umvalue' in item and item['umvalue'] \
and item['umvalue'] != '':
new_dict.update(item);
else:
new_dict.update(
{'umoption': item['umoption'],
'umvalue': ''}
)
new_list.append(new_dict)
data['umoptions'] = new_list
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, fdwdata=fdw_data,
conn=self.conn)
sql += "\n"
return sql
except Exception as e:
return internal_server_error(errormsg=str(e))
sql = render_template("/".join([self.template_path, 'create.sql']), data=data, fdwdata=fdw_data,
conn=self.conn)
sql += "\n"
return sql, data['name']
@check_precondition
def sql(self, gid, sid, did, fid, fsid, umid):
@@ -709,6 +738,8 @@ class UserMappingView(PGChildNodeView):
-- DROP USER MAPPING FOR {0} SERVER {1}
""".format(res['rows'][0]['name'], fdw_data['name'])
if hasattr(str, 'decode'):
sql_header = sql_header.decode('utf-8')
sql = sql_header + sql
@@ -5,5 +5,5 @@ SELECT srvname as name FROM pg_foreign_server srv LEFT OUTER JOIN pg_foreign_dat
{% endif %}
{# ============= Drop/Delete cascade user mapping ============= #}
{% if name and data %}
DROP USER MAPPING FOR {{ conn|qtIdent(data.name) }} SERVER {{ conn|qtIdent(name) }}
DROP USER MAPPING FOR {% if data.name == "CURRENT_USER" or data.name == "PUBLIC" %}{{ data.name }}{% else %}{{ conn|qtIdent(data.name) }}{% endif %} SERVER {{ conn|qtIdent(name) }}
{% endif %}
@@ -8,16 +8,16 @@ WHERE srv.oid = {{fserid}}::int
{% if fsid or umid or fdwdata or data %}
WITH umapData AS
(
SELECT u.oid AS um_oid, CASE WHEN u.umuser = 0::oid THEN 'public'::name ELSE a.rolname END AS name,
SELECT u.oid AS um_oid, CASE WHEN u.umuser = 0::oid THEN 'PUBLIC'::name ELSE a.rolname END AS name,
array_to_string(u.umoptions, ',') AS umoptions FROM pg_user_mapping u
LEFT JOIN pg_authid a ON a.oid = u.umuser {% if fsid %} WHERE u.umserver = {{fsid}}::int {% endif %} {% if umid %} WHERE u.oid= {{umid}}::int {% endif %}
)
SELECT * FROM umapData
{% if data %}
WHERE {% if data.name == "CURRENT_USER" %} name = {{data.name}} {% elif data.name == "PUBLIC" %} name = {{data.name.lower()|qtLiteral}} {% else %} name = {{data.name|qtLiteral}} {% endif %}
WHERE {% if data.name == "CURRENT_USER" %} name = {{data.name}} {% elif data.name == "PUBLIC" %} name = {{data.name|qtLiteral}} {% else %} name = {{data.name|qtLiteral}} {% endif %}
{% endif %}
{% if fdwdata %}
WHERE fdw.fdwname = {{fdwdata.name|qtLiteral}}::text
{% endif %}
ORDER BY 2;
{% endif %}
{% endif %}
@@ -0,0 +1,16 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
class UserMappingGeneratorTestCase(BaseTestGenerator):
def runTest(self):
return []
@@ -0,0 +1,98 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests\
import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.\
foreign_servers.tests import utils as fsrv_utils
from . import utils as um_utils
class UserMappingAddTestCase(BaseTestGenerator):
"""
This class will add user mapping under foreign server node.
"""
scenarios = [
# Fetching default URL for user mapping node.
('Check user mapping Node', dict(url='/browser/user_mapping/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
6. Add foreign server(s) to foreign data wrapper(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) under connected database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
# Add foreign data wrapper(s) to extension(s)
fdw_utils.add_fdw(cls.tester)
# Add foreign server(s) to foreign data wrapper
fsrv_utils.add_fsrv(cls.tester)
def runTest(self):
""" This function will add user mapping under foreign server node. """
um_utils.add_um(self.tester)
@classmethod
def tearDownClass(cls):
"""
This function deletes the added user mapping(s), foreign server(s),
foreign data wrapper(s), extension(s), schema(s), database(s),
server(s) and parent id file
:return: None
"""
um_utils.delete_um(cls.tester)
fsrv_utils.delete_fsrv(cls.tester)
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,102 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests \
import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.\
foreign_servers.tests import utils as fsrv_utils
from . import utils as um_utils
class UserMappingDeleteTestCase(BaseTestGenerator):
"""
This class will delete user mapping under foreign server node.
"""
scenarios = [
# Fetching default URL for user mapping node.
('Check user mapping Node', dict(url='/browser/user_mapping/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
6. Add foreign server(s) to foreign data wrapper(s)
7. Add user mapping(s) to foreign server(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) under connected database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
# Add foreign data wrapper(s) to extension(s)
fdw_utils.add_fdw(cls.tester)
# Add foreign server(s) to foreign data wrapper
fsrv_utils.add_fsrv(cls.tester)
# Add user mapping(s) to foreign server(s)
um_utils.add_um(cls.tester)
def runTest(self):
""" This function delete user mapping under foreign server node. """
delete_respdata = um_utils.delete_um(self.tester)
self.assertTrue(delete_respdata['success'], 1)
@classmethod
def tearDownClass(cls):
"""
This function deletes the added foreign server(s) ,
foreign data wrapper(s), extension(s), schema(s), database(s),
server(s) and parent id file
:return: None
"""
fsrv_utils.delete_fsrv(cls.tester)
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,120 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests\
import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.\
foreign_servers.tests import utils as fsrv_utils
from . import utils as um_utils
class UserMappingGetTestCase(BaseTestGenerator):
"""
This class will add user mapping under foreign server node.
"""
scenarios = [
# Fetching default URL for user mapping node.
('Check user mapping Node', dict(url='/browser/user_mapping/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
6. Add foreign server(s) to foreign data wrapper(s)
7. Add user mapping(s) to foreign server(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) under connected database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
# Add foreign data wrapper(s) to extension(s)
fdw_utils.add_fdw(cls.tester)
# Add foreign server(s) to foreign data wrapper
fsrv_utils.add_fsrv(cls.tester)
# Add user mapping(s) to foreign server(s)
um_utils.add_um(cls.tester)
def runTest(self):
""" This function will fetch user mapping added to foreign server
node. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
fsrv_ids_dict = all_id["fsid"][0]
um_ids_dict = all_id["umid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
fsrv_id = fsrv_ids_dict[server_id]
um_id = um_ids_dict[server_id]
response = um_utils.verify_um(self.tester, utils.SERVER_GROUP,
server_id, db_id,
fdw_id, fsrv_id, um_id)
self.assertEquals(response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""
This function delete the added foreign server(s) ,
foreign data wrapper(s), extension(s), schema(s), database(s),
server(s) and parent id file
:return: None
"""
um_utils.delete_um(cls.tester)
fsrv_utils.delete_fsrv(cls.tester)
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,138 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests \
import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.\
foreign_servers.tests import utils as fsrv_utils
from . import utils as um_utils
from regression.test_setup import advanced_config_data
import json
class UserMappingPutTestCase(BaseTestGenerator):
"""
This class will update user mapping under foreign server node.
"""
scenarios = [
# Fetching default URL for user mapping node.
('Check user mapping Node', dict(url='/browser/user_mapping/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
6. Add foreign server(s) to foreign data wrapper(s)
7. Add user mapping(s) to foreign server(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
# Add schema(s) under connected database(s)
schema_utils.add_schemas(cls.tester)
# Add extension(s) to schema(s)
extension_utils.add_extensions(cls.tester)
# Add foreign data wrapper(s) to extension(s)
fdw_utils.add_fdw(cls.tester)
# Add foreign server(s) to foreign data wrapper
fsrv_utils.add_fsrv(cls.tester)
# Add user mapping(s) to foreign server(s)
um_utils.add_um(cls.tester)
def runTest(self):
""" This function update user mapping under foreign server node. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
fsrv_ids_dict = all_id["fsid"][0]
um_ids_dict = all_id["umid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
fsrv_id = fsrv_ids_dict[server_id]
um_id = um_ids_dict[server_id]
response = um_utils.verify_um(self.tester, utils.SERVER_GROUP,
server_id, db_id,
fdw_id, fsrv_id, um_id)
if response.status_code == 200:
data = \
{
"id": um_id,
"umoptions":
advanced_config_data['user_mapping_update_data']
['options']
}
put_response = self.tester.put(
self.url + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(fdw_id) + '/' +
str(fsrv_id) + '/' + str(um_id),
data=json.dumps(data),
follow_redirects=True)
self.assertEquals(put_response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""
This function deletes the added foreign server(s) ,
foreign data wrapper(s), extension(s), schema(s), database(s),
server(s) and parent id file
:return: None
"""
um_utils.delete_um(cls.tester)
fsrv_utils.delete_fsrv(cls.tester)
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,156 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
import os
import pickle
import json
from regression.test_setup import advanced_config_data, pickle_path
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.tests \
import utils as fdw_utils
from pgadmin.browser.server_groups.servers.databases.foreign_data_wrappers.\
foreign_servers.tests import utils as fsrv_utils
UM_URL = '/browser/user_mapping/obj/'
def get_um_config_data(server_connect_data):
adv_config_data = None
db_user = server_connect_data['data']['user']['name']
# Get the config data of appropriate db user
for config_test_data in advanced_config_data['user_mapping_credentials']:
if db_user == config_test_data['owner']:
adv_config_data = config_test_data
data = {
"name": adv_config_data['name'],
"um_options": adv_config_data['option'],
"umoptions": adv_config_data['options']
}
return data
def add_um(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
fsrv_ids_dict = all_id["fsid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(tester,
utils.SERVER_GROUP,
server_id, db_id)
if db_con['data']['connected']:
server_connect_response = server_utils.verify_server(
tester, utils.SERVER_GROUP, server_id)
fdw_id = fdw_ids_dict[server_id]
fdw_response = fdw_utils.verify_fdws(tester,
utils.SERVER_GROUP,
server_id, db_id,
fdw_id)
fsrv_id = fsrv_ids_dict[server_id]
fsrv_response = fsrv_utils.verify_fsrv(tester, utils.SERVER_GROUP,
server_id, db_id,
fdw_id, fsrv_id)
if fsrv_response.status_code == 200:
data = get_um_config_data(server_connect_response)
response = tester.post(
UM_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(
db_id) +
'/' + str(fdw_id) + '/' + str(fsrv_id) + '/',
data=json.dumps(data),
content_type='html/json')
assert response.status_code == 200
response_data = json.loads(response.data.decode())
write_um_info(response_data, server_id)
def write_um_info(response_data, server_id):
"""
This function writes the schema id into parent_id.pkl
:param response_data: foreign server add response data
:type response_data: dict
:param server_id: server id
:type server_id: str
:return: None
"""
um_id = response_data['node']['_id']
pickle_id_dict = utils.get_pickle_id_dict()
if os.path.isfile(pickle_path):
existing_server_id = open(pickle_path, 'rb')
tol_server_id = pickle.load(existing_server_id)
pickle_id_dict = tol_server_id
if 'umid' in pickle_id_dict:
if pickle_id_dict['umid']:
# Add the umid as value in dict
pickle_id_dict["umid"][0].update({server_id: um_id})
else:
# Create new dict with server_id and umid
pickle_id_dict["umid"].append({server_id: um_id})
fsrv_output = open(pickle_path, 'wb')
pickle.dump(pickle_id_dict, fsrv_output)
fsrv_output.close()
def verify_um(tester, server_group, server_id, db_id, fdw_id, fsrv_id, um_id):
response = tester.get(UM_URL + str(server_group) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(fdw_id) + '/' + str(fsrv_id) + '/' + str(
um_id),
content_type='html/json')
return response
def delete_um(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
fsrv_ids_dict = all_id["fsid"][0]
um_ids_dict = all_id["umid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
fsrv_id = fsrv_ids_dict[server_id]
um_id = um_ids_dict[server_id]
response = verify_um(tester, utils.SERVER_GROUP,
server_id, db_id,
fdw_id, fsrv_id, um_id)
if response.status_code == 200:
delete_response = tester.delete(
UM_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(fdw_id) + '/' +
str(fsrv_id) + '/' + str(um_id),
follow_redirects=True)
delete_respdata = json.loads(delete_response.data.decode())
return delete_respdata
@@ -1,4 +1,4 @@
SELECT 'fdwacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
SELECT 'fdwacl' as deftype, COALESCE(gt.rolname, 'PUBLIC') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
FROM
(SELECT
d.grantee, d.grantor, d.is_grantable,
@@ -3,9 +3,9 @@
{% if data.name %}
CREATE FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}{% if data.fdwvalue %}
VALIDATOR {{ conn|qtIdent(data.fdwvalue) }}{%endif%}{% if data.fdwhan %}
VALIDATOR {{ data.fdwvalue }}{%endif%}{% if data.fdwhan %}
HANDLER {{ conn|qtIdent(data.fdwhan) }}{% endif %}{% if data.fdwoptions %}
HANDLER {{ data.fdwhan }}{% endif %}{% if data.fdwoptions %}
{% set addAlter = "False" %}
{% for variable in data.fdwoptions %}
@@ -1,2 +1,5 @@
{# ============= Get the handlers of foreign data wrapper ============= #}
SELECT proname as fdwhan FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace WHERE pronargs=0 AND prorettype=3115;
SELECT nspname, proname as fdwhan,
quote_ident(nspname)||'.'||quote_ident(proname) AS schema_prefix_fdw_hand
FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace
WHERE pronargs=0 AND prorettype=3115;
@@ -1,9 +1,13 @@
{# ============= Get all the properties of foreign data wrapper ============= #}
SELECT fdw.oid as fdwoid, fdwname as name, fdwhandler, fdwvalidator, vh.proname as fdwhan, vp.proname as fdwvalue, description,
array_to_string(fdwoptions, ',') AS fdwoptions, pg_get_userbyid(fdwowner) as fdwowner, array_to_string(fdwacl::text[], ', ') as acl
SELECT fdw.oid as fdwoid, fdwname as name, fdwhandler, fdwvalidator, description,
array_to_string(fdwoptions, ',') AS fdwoptions, pg_get_userbyid(fdwowner) as fdwowner, array_to_string(fdwacl::text[], ', ') as acl,
quote_ident(vp_nsp.nspname)||'.'||quote_ident(vp.proname) AS fdwvalue,
quote_ident(vh_nsp.nspname)||'.'||quote_ident(vh.proname) AS fdwhan
FROM pg_foreign_data_wrapper fdw
LEFT OUTER JOIN pg_proc vh on vh.oid=fdwhandler
LEFT OUTER JOIN pg_proc vp on vp.oid=fdwvalidator
LEFT OUTER JOIN pg_namespace vh_nsp ON vh_nsp.oid=vh.pronamespace
LEFT OUTER JOIN pg_namespace vp_nsp ON vp_nsp.oid=vp.pronamespace
LEFT OUTER JOIN pg_description des ON (des.objoid=fdw.oid AND des.objsubid=0 AND des.classoid='pg_foreign_data_wrapper'::regclass)
{% if fid %}
WHERE fdw.oid={{fid}}::int
@@ -11,4 +15,4 @@ WHERE fdw.oid={{fid}}::int
{% if fname %}
WHERE fdw.fdwname={{ fname|qtLiteral }}::text
{% endif %}
ORDER BY fdwname
ORDER BY fdwname
@@ -15,7 +15,7 @@ ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
{# ============= Update foreign data wrapper validator ============= #}
{% if data.fdwvalue and data.fdwvalue != o_data.fdwvalue %}
ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
VALIDATOR {{ conn|qtIdent(data.fdwvalue) }};
VALIDATOR {{ data.fdwvalue }};
{% endif %}
{% if data.fdwvalue == '' and data.fdwvalue != o_data.fdwvalue %}
@@ -26,7 +26,7 @@ ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
{# ============= Update foreign data wrapper handler ============= #}
{% if data.fdwhan and data.fdwhan != o_data.fdwhan %}
ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
HANDLER {{ conn|qtIdent(data.fdwhan) }};
HANDLER {{ data.fdwhan }};
{% endif %}
{% if data.fdwhan == '' and data.fdwhan != o_data.fdwhan %}
@@ -1,2 +1,5 @@
{# ============= Get the validators of foreign data wrapper ============= #}
SELECT proname as fdwvalue FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace WHERE proargtypes[0]=1009 AND proargtypes[1]=26;
SELECT nspname, proname as fdwvalue,
quote_ident(nspname)||'.'||quote_ident(proname) AS schema_prefix_fdw_val
FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace
WHERE proargtypes[0]=1009 AND proargtypes[1]=26;
@@ -1,4 +1,4 @@
SELECT 'fdwacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
SELECT 'fdwacl' as deftype, COALESCE(gt.rolname, 'PUBLIC') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
FROM
(SELECT
d.grantee, d.grantor, d.is_grantable,
@@ -18,4 +18,4 @@ FROM
) d
LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid)
LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid)
GROUP BY g.rolname, gt.rolname
GROUP BY g.rolname, gt.rolname
@@ -3,9 +3,9 @@
{% if data.name %}
CREATE FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}{% if data.fdwvalue %}
VALIDATOR {{ conn|qtIdent(data.fdwvalue) }}{%endif%}{% if data.fdwhan %}
VALIDATOR {{ data.fdwvalue }}{%endif%}{% if data.fdwhan %}
HANDLER {{ conn|qtIdent(data.fdwhan) }}{% endif %}{% if data.fdwoptions %}
HANDLER {{ data.fdwhan }}{% endif %}{% if data.fdwoptions %}
{% set addAlter = "False" %}
{% for variable in data.fdwoptions %}
@@ -1,2 +1,5 @@
{# ============= Get the handlers of foreign data wrapper ============= #}
SELECT proname as fdwhan FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace WHERE pronargs=0 AND prorettype=3115;
SELECT nspname, proname as fdwhan,
quote_ident(nspname)||'.'||quote_ident(proname) AS schema_prefix_fdw_hand
FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace
WHERE pronargs=0 AND prorettype=3115;
@@ -1,9 +1,13 @@
{# ============= Get all the properties of foreign data wrapper ============= #}
SELECT fdw.oid as fdwoid, fdwname as name, fdwhandler, fdwvalidator, vh.proname as fdwhan, vp.proname as fdwvalue, description,
array_to_string(fdwoptions, ',') AS fdwoptions, pg_get_userbyid(fdwowner) as fdwowner, array_to_string(fdwacl::text[], ', ') as acl
SELECT fdw.oid as fdwoid, fdwname as name, fdwhandler, fdwvalidator, description,
array_to_string(fdwoptions, ',') AS fdwoptions, pg_get_userbyid(fdwowner) as fdwowner, array_to_string(fdwacl::text[], ', ') as acl,
quote_ident(vp_nsp.nspname)||'.'||quote_ident(vp.proname) AS fdwvalue,
quote_ident(vh_nsp.nspname)||'.'||quote_ident(vh.proname) AS fdwhan
FROM pg_foreign_data_wrapper fdw
LEFT OUTER JOIN pg_proc vh on vh.oid=fdwhandler
LEFT OUTER JOIN pg_proc vp on vp.oid=fdwvalidator
LEFT OUTER JOIN pg_namespace vh_nsp ON vh_nsp.oid=vh.pronamespace
LEFT OUTER JOIN pg_namespace vp_nsp ON vp_nsp.oid=vp.pronamespace
LEFT OUTER JOIN pg_description des ON (des.objoid=fdw.oid AND des.objsubid=0 AND des.classoid='pg_foreign_data_wrapper'::regclass)
{% if fid %}
WHERE fdw.oid={{fid}}::int
@@ -15,7 +15,7 @@ ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
{# ============= Update foreign data wrapper validator ============= #}
{% if data.fdwvalue and data.fdwvalue != o_data.fdwvalue %}
ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
VALIDATOR {{ conn|qtIdent(data.fdwvalue) }};
VALIDATOR {{ data.fdwvalue }};
{% endif %}
{% if data.fdwvalue == '' and data.fdwvalue != o_data.fdwvalue %}
@@ -26,7 +26,7 @@ ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
{# ============= Update foreign data wrapper handler ============= #}
{% if data.fdwhan and data.fdwhan != o_data.fdwhan %}
ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
HANDLER {{ conn|qtIdent(data.fdwhan) }};
HANDLER {{ data.fdwhan }};
{% endif %}
{% if data.fdwhan == '' and data.fdwhan != o_data.fdwhan %}
@@ -1,2 +1,5 @@
{# ============= Get the validators of foreign data wrapper ============= #}
SELECT proname as fdwvalue FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace WHERE proargtypes[0]=1009 AND proargtypes[1]=26;
SELECT nspname, proname as fdwvalue,
quote_ident(nspname)||'.'||quote_ident(proname) AS schema_prefix_fdw_val
FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace
WHERE proargtypes[0]=1009 AND proargtypes[1]=26;
@@ -0,0 +1,16 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
class FDWGeneratorTestCase(BaseTestGenerator):
def runTest(self):
return []
@@ -0,0 +1,75 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from . import utils as fdw_utils
class FDWDAddTestCase(BaseTestGenerator):
""" This class will add foreign data wrappers under database node. """
scenarios = [
# Fetching default URL for foreign_data_wrapper node.
('Check FDW Node',
dict(url='/browser/foreign_data_wrapper/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schema(s) to connected database(s)
4. Add extension(s) to schema(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
extension_utils.add_extensions(cls.tester)
def runTest(self):
""" This function will add extension under 1st server of tree node. """
fdw_utils.add_fdw(self.tester)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
"""
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,79 @@
#################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from . import utils as fdw_utils
import json
class FDWDDeleteTestCase(BaseTestGenerator):
""" This class will delete foreign data wrappers under database node. """
scenarios = [
# Fetching default URL for foreign_data_wrapper node.
('Check FDW Node',
dict(url='/browser/foreign_data_wrapper/obj/'))
]
@classmethod
def setUpClass(cls):
"""
This function perform the following tasks:
1. Add and connect to the test server(s)
2. Add database(s) connected to server(s)
3. Add schemas to connected database(s)
4. Add extension(s) to schema(s)
5. Add foreign data wrapper(s) to extension(s)
:return: None
"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
extension_utils.add_extensions(cls.tester)
fdw_utils.add_fdw(cls.tester)
def runTest(self):
""" This function will delete added FDW. """
delete_respdata = fdw_utils.delete_fdw(self.tester)
self.assertTrue(delete_respdata['success'], 1)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
"""
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,83 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from . import utils as fdw_utils
class FDWDGetTestCase(BaseTestGenerator):
""" This class will add foreign data wrappers under database node. """
scenarios = [
# Fetching default URL for foreign_data_wrapper node.
('Check FDW Node',
dict(url='/browser/foreign_data_wrapper/obj/'))
]
@classmethod
def setUpClass(cls):
"""This function use to add/connect the servers and create databases"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
extension_utils.add_extensions(cls.tester)
fdw_utils.add_fdw(cls.tester)
def runTest(self):
""" This function will get added FDW. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
response = fdw_utils.verify_fdws(self.tester,
utils.SERVER_GROUP,
server_id, db_id,
fdw_id)
self.assertEquals(response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""This function deletes the added schema, database, server and parent
id file
"""
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,101 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
from pgadmin.utils.route import BaseTestGenerator
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.databases.extensions.tests import\
utils as extension_utils
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.browser.server_groups.servers.tests import utils as server_utils
from . import utils as fdw_utils
from regression.test_setup import advanced_config_data
import json
class FDWDPutTestCase(BaseTestGenerator):
""" This class will add foreign data wrappers under database node. """
scenarios = [
# Fetching default URL for foreign_data_wrapper node.
('Check FDW Node',
dict(url='/browser/foreign_data_wrapper/obj/'))
]
@classmethod
def setUpClass(cls):
"""This function use to add/connect the servers and create databases"""
# Add the server
server_utils.add_server(cls.tester)
# Connect to servers
cls.server_connect_response, cls.server_group, cls.server_ids = \
server_utils.connect_server(cls.tester)
if len(cls.server_connect_response) == 0:
raise Exception("No Server(s) connected to add the database!!!")
# Add databases to connected servers
database_utils.add_database(cls.tester, cls.server_connect_response,
cls.server_ids)
schema_utils.add_schemas(cls.tester)
extension_utils.add_extensions(cls.tester)
fdw_utils.add_fdw(cls.tester)
def runTest(self):
""" This function will update added FDW. """
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
response = fdw_utils.verify_fdws(self.tester,
utils.SERVER_GROUP,
server_id, db_id,
fdw_id)
if response.status_code == 200:
data = \
{
"description": advanced_config_data['fdw_update_data']
['comment'],
"id": fdw_id
}
put_response = self.tester.put(
self.url + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' +
str(db_id) + '/' + str(fdw_id),
data=json.dumps(data),
follow_redirects=True)
self.assertEquals(put_response.status_code, 200)
@classmethod
def tearDownClass(cls):
"""
This function deletes the added schema, database, server and parent
id file
"""
fdw_utils.delete_fdw(cls.tester)
extension_utils.delete_extension(cls.tester)
schema_utils.delete_schema(cls.tester)
database_utils.delete_database(cls.tester)
server_utils.delete_server(cls.tester)
utils.delete_parent_id_file()
@@ -0,0 +1,133 @@
# #################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# ##################################################################
import os
import pickle
import json
from regression.test_setup import advanced_config_data, pickle_path
from regression import test_utils as utils
from pgadmin.browser.server_groups.servers.tests import utils as server_utils
from pgadmin.browser.server_groups.servers.databases.tests import \
utils as database_utils
import uuid
FDW_URL = '/browser/foreign_data_wrapper/obj/'
def get_fdw_config_data(schema_name, server_connect_data):
adv_config_data = None
db_user = server_connect_data['data']['user']['name']
# Get the config data of appropriate db user
for config_test_data in advanced_config_data['fdw_credentials']:
if db_user == config_test_data['owner']:
adv_config_data = config_test_data
data = {
"fdwacl": adv_config_data['acl'],
"fdwhan": "{0}.{1}".format(schema_name, adv_config_data['handler']),
"fdwoptions": adv_config_data['options'],
"fdwowner": adv_config_data['owner'],
"fdwvalue": "{0}.{1}".format(schema_name, adv_config_data['validator']),
"name": "fdw_{}".format(str(uuid.uuid4())[1:4])
}
return data
def add_fdw(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
schema_info_dict = all_id["scid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
db_con = database_utils.verify_database(tester, utils.SERVER_GROUP,
server_id, db_id)
if db_con['data']["connected"]:
server_connect_response = server_utils.verify_server(
tester, utils.SERVER_GROUP, server_id)
schema_name = schema_info_dict[int(server_id)][1]
data = get_fdw_config_data(schema_name,
server_connect_response)
response = tester.post(FDW_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' + str(db_id) + '/',
data=json.dumps(data),
content_type='html/json')
assert response.status_code == 200
response_data = json.loads(response.data.decode('utf-8'))
write_fdw_info(response_data, server_id)
def write_fdw_info(response_data, server_id):
"""
This function writes the sequence id into parent_id.pkl
:param response_data: FDW add response data
:type response_data: dict
:param server_id: server id
:type server_id: str
:return: None
"""
fdw_id = response_data['node']['_id']
pickle_id_dict = utils.get_pickle_id_dict()
if os.path.isfile(pickle_path):
existing_server_id = open(pickle_path, 'rb')
tol_server_id = pickle.load(existing_server_id)
pickle_id_dict = tol_server_id
if 'fid' in pickle_id_dict:
if pickle_id_dict['fid']:
# Add the FDW_id as value in dict
pickle_id_dict["fid"][0].update({server_id: fdw_id})
else:
# Create new dict with server_id and FDW_id
pickle_id_dict["fid"].append({server_id: fdw_id})
fdw_output = open(pickle_path, 'wb')
pickle.dump(pickle_id_dict, fdw_output)
fdw_output.close()
def verify_fdws(tester, server_group, server_id, db_id, fdw_id):
response = tester.get(FDW_URL + str(server_group) + '/' +
str(server_id) + '/' + str(db_id) +
'/' + str(fdw_id),
content_type='html/json')
return response
def delete_fdw(tester):
all_id = utils.get_ids()
server_ids = all_id["sid"]
db_ids_dict = all_id["did"][0]
fdw_ids_dict = all_id["fid"][0]
for server_id in server_ids:
db_id = db_ids_dict[int(server_id)]
fdw_id = fdw_ids_dict[server_id]
response = verify_fdws(tester,
utils.SERVER_GROUP,
server_id, db_id,
fdw_id)
if response.status_code == 200:
delete_response = tester.delete(
FDW_URL + str(utils.SERVER_GROUP) + '/' +
str(server_id) + '/' +
str(db_id) + '/' + str(fdw_id),
follow_redirects=True)
delete_respdata = json.loads(delete_response.data.decode())
return delete_respdata
@@ -9,20 +9,20 @@
"""Implements Language Node"""
import json
import simplejson as json
from functools import wraps
import pgadmin.browser.server_groups.servers.databases as databases
from flask import render_template, make_response, request
from flask.ext.babel import gettext
from flask import render_template, make_response, request, jsonify
from flask_babel import gettext
from pgadmin.browser.collection import CollectionNodeModule
from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
parse_priv_to_db
from pgadmin.browser.utils import PGChildNodeView
from pgadmin.utils.ajax import make_json_response, \
make_response as ajax_response, internal_server_error
from pgadmin.utils.ajax import precondition_required
from pgadmin.utils.ajax import make_json_response, internal_server_error, \
make_response as ajax_response
from pgadmin.utils.driver import get_driver
from pgadmin.utils.ajax import gone
from config import PG_DEFAULT_DRIVER
@@ -133,6 +133,12 @@ class LanguageView(PGChildNodeView):
* update(gid, sid, did, lid)
- This function will update the data for the selected language node
* create(gid, sid, did)
- This function will create the new language node
* delete(gid, sid, did, lid)
- This function will delete the selected language node
* msql(gid, sid, did, lid)
- This function is used to return modified SQL for the selected language node
@@ -142,6 +148,9 @@ class LanguageView(PGChildNodeView):
* get_functions(gid, sid, did)
- This function returns the handler and inline functions for the selected language node
* get_templates(gid, sid, did)
- This function returns language templates.
* sql(gid, sid, did, lid):
- This function will generate sql to show it in sql pane for the selected language node.
@@ -165,8 +174,8 @@ class LanguageView(PGChildNodeView):
operations = dict({
'obj': [
{'get': 'properties', 'put': 'update'},
{'get': 'list'}
{'get': 'properties', 'delete': 'delete', 'put': 'update'},
{'get': 'list', 'post': 'create'}
],
'nodes': [{'get': 'node'}, {'get': 'nodes'}],
'sql': [{'get': 'sql'}],
@@ -175,7 +184,9 @@ class LanguageView(PGChildNodeView):
'dependency': [{'get': 'dependencies'}],
'dependent': [{'get': 'dependents'}],
'module.js': [{}, {}, {'get': 'module_js'}],
'get_functions': [{}, {'get': 'get_functions'}]
'get_functions': [{}, {'get': 'get_functions'}],
'get_templates': [{}, {'get': 'get_templates'}],
'delete': [{'delete': 'delete'}]
})
def _init_(self, **kwargs):
@@ -218,18 +229,8 @@ class LanguageView(PGChildNodeView):
self.driver = get_driver(PG_DEFAULT_DRIVER)
self.manager = self.driver.connection_manager(kwargs['sid'])
self.conn = self.manager.connection(did=kwargs['did'])
# If DB not connected then return error to browser
if not self.conn.connected():
return precondition_required(
gettext(
"Connection to the server has been lost!"
)
)
ver = self.manager.version
# we will set template path for sql scripts
if ver >= 90300:
# Set the template path for the SQL scripts
if self.manager.version >= 90300:
self.template_path = 'languages/sql/9.3_plus'
else:
self.template_path = 'languages/sql/9.1_plus'
@@ -289,6 +290,36 @@ class LanguageView(PGChildNodeView):
status=200
)
@check_precondition
def node(self, gid, sid, did, lid):
"""
This function will fetch properties of the language nodes.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
lid: Language ID
"""
sql = render_template("/".join([self.template_path, 'properties.sql']),
lid=lid)
status, result = self.conn.execute_2darray(sql)
if not status:
return internal_server_error(errormsg=result)
for row in result['rows']:
return make_json_response(
data=self.blueprint.generate_browser_node(
row['oid'],
did,
row['name'],
icon="icon-language"
),
status=200
)
return gone(gettext("Could not find the specified language."))
@check_precondition
def properties(self, gid, sid, did, lid):
"""
@@ -306,6 +337,11 @@ class LanguageView(PGChildNodeView):
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the language information.")
)
sql = render_template("/".join([self.template_path, 'acl.sql']), lid=lid)
status, result = self.conn.execute_dict(sql)
if not status:
@@ -314,7 +350,7 @@ class LanguageView(PGChildNodeView):
# if no acl found then by default add public
if res['rows'][0]['acl'] is None:
res['rows'][0]['lanacl'] = dict()
res['rows'][0]['lanacl']['grantee'] = 'public'
res['rows'][0]['lanacl']['grantee'] = 'PUBLIC'
res['rows'][0]['lanacl']['grantor'] = res['rows'][0]['lanowner']
res['rows'][0]['lanacl']['privileges'] = [{'privilege_type': 'U', 'privilege': True, 'with_grant': False}]
else:
@@ -325,6 +361,18 @@ class LanguageView(PGChildNodeView):
else:
res['rows'][0][row['deftype']] = [priv]
seclabels = []
if 'seclabels' in res['rows'][0] and res['rows'][0]['seclabels'] is not None:
import re
for sec in res['rows'][0]['seclabels']:
sec = re.search(r'([^=]+)=(.*$)', sec)
seclabels.append({
'provider': sec.group(1),
'label': sec.group(2)
})
res['rows'][0]['seclabels'] = seclabels
return ajax_response(
response=res['rows'][0],
status=200
@@ -341,37 +389,129 @@ class LanguageView(PGChildNodeView):
did: Database ID
lid: Language ID
"""
data = request.form if request.form else json.loads(request.data.decode())
sql = self.get_sql(data, lid)
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
try:
if sql and sql.strip('\n') and sql.strip(' '):
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
sql, name = self.get_sql(data, lid)
sql = sql.strip('\n').strip(' ')
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
return make_json_response(
success=1,
info="Language updated",
data={
'id': lid,
'did': did,
'sid': sid,
'gid': gid
}
return jsonify(
node=self.blueprint.generate_browser_node(
lid,
did,
name,
icon="icon-%s" % self.node_type
)
else:
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@check_precondition
def create(self, gid, sid, did):
"""
This function will create the language object
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
"""
required_args = [
'name'
]
data = request.form if request.form else json.loads(
request.data, encoding='utf-8'
)
for arg in required_args:
if arg not in data:
return make_json_response(
success=1,
info="Nothing to update",
data={
'id': lid,
'did': did,
'sid': sid,
'gid': gid
}
status=410,
success=0,
errormsg=gettext(
"Could not find the required parameter (%s)." % arg
)
)
try:
if 'lanacl' in data:
data['lanacl'] = parse_priv_to_db(data['lanacl'], ['U'])
sql = render_template("/".join([self.template_path, 'create.sql']),
data=data, 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']),
lanname=data['name'], conn=self.conn)
status, r_set = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=r_set)
for row in r_set['rows']:
return jsonify(
node=self.blueprint.generate_browser_node(
row['oid'],
did,
row['name'],
icon='icon-language'
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@check_precondition
def delete(self, gid, sid, did, lid):
"""
This function will drop the language object
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
lid: Language ID
"""
if self.cmd == 'delete':
# This is a cascade operation
cascade = True
else:
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)
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)
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,
}
)
except Exception as e:
return internal_server_error(errormsg=str(e))
@@ -389,21 +529,20 @@ class LanguageView(PGChildNodeView):
data = {}
for k, v in request.args.items():
try:
data[k] = json.loads(v)
data[k] = json.loads(v, encoding='utf-8')
except ValueError:
data[k] = v
try:
sql, name = self.get_sql(data, lid)
if sql == '':
sql = "--modified SQL"
sql = self.get_sql(data, lid)
if sql and sql.strip('\n') and sql.strip(' '):
return make_json_response(
data=sql,
status=200
)
else:
return make_json_response(
data='-- Modified SQL --',
status=200
)
)
except Exception as e:
return internal_server_error(errormsg=str(e))
def get_sql(self, data, lid=None):
"""
@@ -416,32 +555,43 @@ class LanguageView(PGChildNodeView):
required_args = [
'name', 'lanowner', 'description'
]
try:
sql = ''
if lid is not None:
sql = render_template("/".join([self.template_path, 'properties.sql']), lid=lid)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
for key in ['lanacl']:
if key in data and data[key] is not None:
if 'added' in data[key]:
data[key]['added'] = parse_priv_to_db(data[key]['added'])
if 'changed' in data[key]:
data[key]['changed'] = parse_priv_to_db(data[key]['changed'])
if 'deleted' in data[key]:
data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'])
if lid is not None:
sql = render_template("/".join([self.template_path, 'properties.sql']), lid=lid)
status, res = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=res)
if len(res['rows']) == 0:
return gone(
gettext("Couldnot find the language information.")
)
for key in ['lanacl']:
if key in data and data[key] is not None:
if 'added' in data[key]:
data[key]['added'] = parse_priv_to_db(data[key]['added'], ["U"])
if 'changed' in data[key]:
data[key]['changed'] = parse_priv_to_db(data[key]['changed'], ["U"])
if 'deleted' in data[key]:
data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'], ["U"])
old_data = res['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
sql = render_template("/".join([self.template_path, 'update.sql']), data=data,
o_data=old_data, conn=self.conn)
return sql.strip('\n'), data['name'] if 'name' in data else old_data['name']
else:
if 'lanacl' in data:
data['lanacl'] = parse_priv_to_db(data['lanacl'], ["U"])
sql = render_template("/".join([self.template_path, 'create.sql']),
data=data, conn=self.conn)
return sql.strip('\n'), data['name']
old_data = res['rows'][0]
for arg in required_args:
if arg not in data:
data[arg] = old_data[arg]
sql = render_template("/".join([self.template_path, 'update.sql']), data=data,
o_data=old_data, conn=self.conn)
return sql
except Exception as e:
return internal_server_error(errormsg=str(e))
@check_precondition
def get_functions(self, gid, sid, did):
@@ -462,6 +612,25 @@ class LanguageView(PGChildNodeView):
status=200
)
@check_precondition
def get_templates(self, gid, sid, did):
"""
This function returns the language template.
Args:
gid: Server Group ID
sid: Server ID
did: Database ID
"""
sql = render_template("/".join([self.template_path, 'templates.sql']))
status, result = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=result)
return make_json_response(
data=result['rows'],
status=200
)
@check_precondition
def sql(self, gid, sid, did, lid):
"""
@@ -480,9 +649,33 @@ class LanguageView(PGChildNodeView):
# Making copy of output for future use
old_data = dict(res['rows'][0])
sql = render_template("/".join([self.template_path, 'acl.sql']), lid=lid)
status, result = self.conn.execute_dict(sql)
if not status:
return internal_server_error(errormsg=result)
for row in result['rows']:
priv = parse_priv_from_db(row)
if row['deftype'] in old_data:
old_data[row['deftype']].append(priv)
else:
old_data[row['deftype']] = [priv]
seclabels = []
if 'seclabels' in old_data and old_data['seclabels'] is not None:
import re
for sec in old_data['seclabels']:
sec = re.search(r'([^=]+)=(.*$)', sec)
seclabels.append({
'provider': sec.group(1),
'label': sec.group(2)
})
old_data['seclabels'] = seclabels
sql = render_template("/".join([self.template_path, 'sqlpane.sql']), data=old_data, conn=self.conn)
return ajax_response(response=sql)
return ajax_response(response=sql.strip('\n'))
@check_precondition
def dependents(self, gid, sid, did, lid):
@@ -23,6 +23,8 @@ define(
sqlCreateHelp: 'sql-createlanguage.html',
label: '{{ _('Language') }}',
hasSQL: true,
canDrop: true,
canDropCascade: true,
hasDepends: true,
Init: function() {
@@ -31,6 +33,25 @@ define(
return;
this.initialized = true;
// Add context menus for language
pgBrowser.add_menus([{
name: 'create_language_on_database', node: 'database', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 4, label: '{{ _('Language...') }}',
icon: 'wcTabIcon icon-language', data: {action: 'create'}
},{
name: 'create_language_on_coll', node: 'coll-language', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 4, label: '{{ _('Language...') }}',
icon: 'wcTabIcon icon-language', data: {action: 'create'}
},{
name: 'create_language', node: 'language', module: this,
applies: ['object', 'context'], callback: 'show_obj_properties',
category: 'create', priority: 4, label: '{{ _('Language...') }}',
icon: 'wcTabIcon icon-language', data: {action: 'create'}
}]);
},
// Define the model for language node
@@ -40,20 +61,59 @@ define(
lanowner: undefined,
comment: undefined,
lanacl: [],
seclabels:[]
seclabels:[],
trusted: true,
lanproc: undefined,
laninl: undefined,
lanval: undefined,
is_template: false,
template_list: []
},
// Default values!
initialize: function(attrs, args) {
var isNew = (_.size(attrs) === 0);
if (isNew) {
var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
this.set({'lanowner': userInfo.name}, {silent: true});
}
pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
},
// Define the schema for the language node
schema: [{
id: 'name', label: '{{ _('Name') }}', cell: 'string',
type: 'text',
id: 'name', label: '{{ _('Name') }}', type: 'text',
control: 'node-ajax-options', mode: ['properties', 'create', 'edit'],
url:'get_templates', select2: { allowClear: false, tags: true, multiple: false },
transform: function(data, cell) {
var res = [],
control = cell || this,
label = control.model.get('name');
if (!control.model.isNew()) {
res.push({label: label, value: label});
}
else {
var tmp_list = [];
if (data && _.isArray(data)) {
_.each(data, function(d) {
res.push({label: d.tmplname, value: d.tmplname});
tmp_list.push(d.tmplname);
})
}
this.model.set({'template_list': tmp_list});
}
return res;
}
},{
id: 'oid', label:'{{ _('OID') }}', cell: 'string', mode: ['properties'],
type: 'text', disabled: true
},{
id: 'lanowner', label:'{{ _('Owner') }}', type: 'text',
control: Backform.NodeListByNameControl, node: 'role',
mode: ['edit', 'properties'], select2: { allowClear: false }
mode: ['edit', 'properties', 'create'], select2: { allowClear: false }
},{
id: 'acl', label: '{{ _('Privileges') }}', type: 'text',
group: '{{ _('Security') }}', mode: ['properties'], disabled: true
@@ -67,13 +127,22 @@ define(
'onColor': 'success', 'offColor': 'primary',
'size': 'small'
},
group: 'Definition', mode: ['edit', 'properties'],
group: 'Definition', mode: ['edit', 'properties', 'create'], deps: ['name'],
disabled: function(m) {
return !(m.isNew());
if (m.isNew()) {
if (m.get('template_list').indexOf(m.get('name')) == -1) {
m.set({'is_template': false});
return false;
}
else
m.set({'is_template': true});
}
return true;
}
},{
id: 'lanproc', label:'{{ _('Handler Function') }}', type: 'text', control: 'node-ajax-options',
group: 'Definition', mode: ['edit', 'properties'], url:'get_functions',
group: 'Definition', mode: ['edit', 'properties', 'create'], url:'get_functions',
deps: ['name'], first_empty: false,
/* This function is used to populate the handler function
* for the selected language node. It will check if the property
* type is 'handler' then push the data into the result array.
@@ -81,19 +150,24 @@ define(
transform: function(data) {
var res = [];
if (data && _.isArray(data)) {
_.each(data, function(d) {
if (d.prop_type == 'handler') {
res.push({label: d.label, value: d.label});
}
})
_.each(data, function(d) {
if (d.prop_type == 'handler') {
res.push({label: d.label, value: d.label});
}
})
}
return res;
}, disabled: function(m) {
return !(m.isNew());
if (m.isNew()) {
if (m.get('template_list').indexOf(m.get('name')) == -1)
return false;
}
return true;
}
},{
id: 'laninl', label:'{{ _('Inline Function') }}', type: 'text', control: 'node-ajax-options',
group: 'Definition', mode: ['edit', 'properties'], url:'get_functions',
group: 'Definition', mode: ['edit', 'properties', 'create'], url:'get_functions',
deps: ['name'], first_empty: false,
/* This function is used to populate the inline function
* for the selected language node. It will check if the property
* type is 'inline' then push the data into the result array.
@@ -101,19 +175,24 @@ define(
transform: function(data) {
var res = [];
if (data && _.isArray(data)) {
_.each(data, function(d) {
if (d.prop_type == 'inline') {
res.push({label: d.label, value: d.label});
}
})
_.each(data, function(d) {
if (d.prop_type == 'inline') {
res.push({label: d.label, value: d.label});
}
})
}
return res;
}, disabled: function(m) {
return !(m.isNew());
if (m.isNew()) {
if (m.get('template_list').indexOf(m.get('name')) == -1)
return false;
}
return true;
}
},{
id: 'lanval', label:'{{ _('Validator Function') }}', type: 'text', control: 'node-ajax-options',
group: 'Definition', mode: ['edit', 'properties'], url:'get_functions',
group: 'Definition', mode: ['edit', 'properties', 'create'], url:'get_functions',
deps: ['name'],
/* This function is used to populate the validator function
* for the selected language node. It will check if the property
* type is 'validator' then push the data into the result array.
@@ -129,16 +208,20 @@ define(
}
return res;
}, disabled: function(m) {
return !(m.isNew());
if (m.isNew()) {
if (m.get('template_list').indexOf(m.get('name')) == -1)
return false;
}
return true;
}
}, pgBrowser.SecurityGroupUnderSchema, {
id: 'lanacl', label: '{{ _('Privileges') }}', type: 'collection',
group: 'security', control: 'unique-col-collection', mode: ['edit'],
group: 'security', control: 'unique-col-collection', mode: ['edit', 'create'],
model: pgBrowser.Node.PrivilegeRoleModel.extend({
privileges: ['U']
}), canAdd: true, canDelete: true, uniqueCol : ['grantee']
},{
id: 'seclabels', label: '{{ _('Security Labels') }}', mode: ['edit'],
id: 'seclabels', label: '{{ _('Security Labels') }}', mode: ['edit', 'create'],
model: pgBrowser.SecLabelModel, editable: false,
type: 'collection', group: 'security', min_version: 90200,
canAdd: true, canEdit: false, canDelete: true,
@@ -160,6 +243,20 @@ define(
} else {
this.errorModel.unset('name');
}
// If predefined template is selected then no need to validate it.
if (!this.get('is_template')) {
var handler_func = this.get('lanproc');
if (_.isUndefined(handler_func) || _.isNull(handler_func) ||
String(handler_func).replace(/^\s+|\s+$/g, '') == '') {
var msg = '{{ _('Handler Function cannot be empty') }}';
this.errorModel.set('lanproc', msg);
return msg;
} else {
this.errorModel.unset('lanproc');
}
}
return null;
}
})
Loaded 100 of 782 files, more files were not shown because too many files have changed in this diff. Show more