├── .gitignore ├── LICENSE.md ├── README.md ├── dirigible ├── .gitignore ├── dirigible │ ├── __init__.py │ ├── settings.py │ ├── test_utils.py │ ├── urls.py │ └── wsgi.py ├── featured_sheet │ ├── __init__.py │ ├── admin.py │ ├── models.py │ ├── templates │ │ └── featured_sheets.html │ ├── tests │ │ ├── __init__.py │ │ └── test_models.py │ └── views.py ├── feedback │ ├── __init__.py │ ├── models.py │ ├── tests │ │ ├── __init__.py │ │ └── test_views.py │ ├── urls.py │ └── views.py ├── fts │ ├── __init__.py │ ├── screendumps │ │ └── placeholder │ └── tests │ │ ├── __init__.py │ │ ├── functionaltest.py │ │ ├── test_2521_CodeEditor.py │ │ ├── test_2525_LoginLogout.py │ │ ├── test_2528_CreateEditSheet.py │ │ ├── test_2529_HighlightErrorsInCells.py │ │ ├── test_2531_DifficultStuffInCells.py │ │ ├── test_2532_LambdasInCells.py │ │ ├── test_2533_Numpy.py │ │ ├── test_2534_JsonWorksheets.py │ │ ├── test_2535_RunWorksheetSerial.py │ │ ├── test_2536_ParallelFormulaExecution.py │ │ ├── test_2537_ErrorsInConsole.py │ │ ├── test_2538_ShowStdoutInConsole.py │ │ ├── test_2540_FrontPage.py │ │ ├── test_2544_403_404_and_500_pages.py │ │ ├── test_2545_PageResizeBehaviour.py │ │ ├── test_2546_ListSheetsOnDashboard.py │ │ ├── test_2547_EnterDataQuickly.py │ │ ├── test_2548_UserCode.py │ │ ├── test_2549_InterruptedRecalculations.py │ │ ├── test_2550_EditableSheetName.py │ │ ├── test_2554_SlicingInFormulae.py │ │ ├── test_2556_BrokenUserCode.py │ │ ├── test_2557_ClickAwaySavesUsercode.py │ │ ├── test_2558_MoreCellsByDefault.py │ │ ├── test_2559_FitEditorToCells.py │ │ ├── test_2562_ErrorInCellShouldBeClearedByConstants.py │ │ ├── test_2565_JSONAPIAuth.py │ │ ├── test_2571_DocumentationAndBlogLinks.py │ │ ├── test_2577_SaveColumnWidths.py │ │ ├── test_2581_FormulaBar.py │ │ ├── test_2582_ReferencingEmptyCell.py │ │ ├── test_2592_Cut_Copy_Paste_Within_Dirigible.py │ │ ├── test_2595_Spinner.py │ │ ├── test_2597_CapRecalcTime.py │ │ ├── test_2601_UndefinedShouldBeAvailableToUsercode.py │ │ ├── test_2602_SheetPageShouldDisplayBeforeFirstRecalcComplete.py │ │ ├── test_2603_WorksheetsMayOnlyContainCells.py │ │ ├── test_2616_RootPageIsDashboard.py │ │ ├── test_2621_CanSaveSheetsWithLotsOfFormulae.py │ │ ├── test_2622_CellRanges.py │ │ ├── test_2631_BlogRedirect.py │ │ ├── test_2633_CursorKeysMoveAroundGrid.py │ │ ├── test_2635_SheetNameSelectedOnEdit.py │ │ ├── test_2639_SciPy_and_MpMath.py │ │ ├── test_2642_RecalcTimesInConsole.py │ │ ├── test_2644_AdminOmniscience.py │ │ ├── test_2650_UsercodeSandbox.py │ │ ├── test_2651_SaveSheetNameOnBlur.py │ │ ├── test_2652_CommitCellOnBlur.py │ │ ├── test_2653_UsernameFocusedOnLoginPage.py │ │ ├── test_2654_CtrlSSavesUsercode.py │ │ ├── test_2678_GlobalStateNotShared.py │ │ ├── test_2682_CellAccessUsingA1.py │ │ ├── test_2685_ChangePassword.py │ │ ├── test_2689_DontClearCellEditorWhenRecalcsHitClient.py │ │ ├── test_2690_FocusShouldStartInGrid.py │ │ ├── test_2691_AllowSettingValuesFromUsercodeBeforeLoadConstants.py │ │ ├── test_2701_NameResolutionWorks.py │ │ ├── test_2702_HttpsInChrootJail.py │ │ ├── test_2704_OldStyleClassesInTheGrid.py │ │ ├── test_2711_ImportExcel.py │ │ ├── test_2712_ImportCSV.py │ │ ├── test_2726_FormulaBarTextSelection.py │ │ ├── test_2734_ClearCells.py │ │ ├── test_2735_CtrlKeysArePassedOnToBrowser.py │ │ ├── test_2741_Xlrd.py │ │ ├── test_2749_DisallowArbitraryKeysForWorksheet.py │ │ ├── test_2751_UsefulModules.py │ │ ├── test_2758_LoadGridDataOnDemand.py │ │ ├── test_2762_PythonConversion.py │ │ ├── test_2770_ClearDependentCellErrors.py │ │ ├── test_2774_ExportCSV.py │ │ ├── test_2781_FormulaAndFormattedValueMustBeStrings.py │ │ ├── test_2787_SignUp.py │ │ ├── test_2789_ErrorConsoleHTMLEscape.py │ │ ├── test_2795_Rewrite_Formulae_during_Cut_and_Paste.py │ │ ├── test_2799_FillDownDuringPaste.py │ │ ├── test_2812_CutCopyPasteInEditMode.py │ │ ├── test_2814_PublicWorksheets.py │ │ ├── test_2828_GridShouldNotStealFocusOnRecalc.py │ │ ├── test_2839_CutCopyPasteButtons.py │ │ ├── test_2844_CantMakeRowHeaderActive.py │ │ ├── test_2848_WorksheetBounds.py │ │ ├── test_2862_CopyAndPasteFormulaWithErrors.py │ │ ├── test_2872_CellTooltips.py │ │ ├── test_2873_IE_Warning.py │ │ ├── test_2884_FeedbackForm.py │ │ └── test_data │ │ ├── T2711-badly-named-png.xls │ │ ├── T2711-import-excel.xls │ │ ├── csv_file.csv │ │ ├── excel_generated_csv.csv │ │ ├── expected_csv_file.csv │ │ ├── expected_unicode_csv.csv │ │ ├── import_csv_button.png │ │ ├── japanese.csv │ │ └── public_sheet_csv_file.csv ├── info_pages │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ ├── non_logged_in_front_page.html │ │ ├── oss.html │ │ └── video.html │ ├── tests │ │ ├── __init__.py │ │ └── test_views.py │ └── views.py ├── manage.py ├── registration │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── locale │ │ ├── ar │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── bg │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── de │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── el │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── en │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── es │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── es_AR │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── fr │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── he │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── it │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── ja │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── nl │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── pl │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── pt_BR │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── ru │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── sr │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── sv │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ ├── zh_CN │ │ │ └── LC_MESSAGES │ │ │ │ ├── django.mo │ │ │ │ └── django.po │ │ └── zh_TW │ │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── cleanupregistration.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── shared │ ├── __init__.py │ ├── models.py │ ├── static │ │ ├── ace │ │ │ ├── ace-uncompressed.js │ │ │ ├── ace.js │ │ │ ├── cockpit-uncompressed.js │ │ │ ├── cockpit.js │ │ │ ├── mode-python.js │ │ │ └── worker-javascript.js │ │ ├── dirigible │ │ │ ├── examples │ │ │ │ └── pricelist-json-api-demo.html │ │ │ ├── images │ │ │ │ ├── about-goal-planning-screenshot.png │ │ │ │ ├── error.gif │ │ │ │ ├── find-out-more.png │ │ │ │ ├── logo_transparent_289x66.png │ │ │ │ ├── logo_transparent_490x112.png │ │ │ │ ├── spinner-small-blue-bg.gif │ │ │ │ ├── spinner-small-white-bg.gif │ │ │ │ ├── spinner.gif │ │ │ │ ├── took-0.01s-screenshot.png │ │ │ │ ├── toolbar │ │ │ │ │ ├── api_button_disabled.pdn │ │ │ │ │ ├── copy_button.png │ │ │ │ │ ├── cut_button.png │ │ │ │ │ ├── export_button.png │ │ │ │ │ ├── import_button.png │ │ │ │ │ ├── paste_button.png │ │ │ │ │ ├── recalc_button.png │ │ │ │ │ └── security_button.png │ │ │ │ ├── usercode_error_indicator.png │ │ │ │ └── watch-a-video.png │ │ │ ├── scripts │ │ │ │ ├── cell_editor.js │ │ │ │ ├── console_view.js │ │ │ │ ├── dialogs.js │ │ │ │ ├── editor_commands.js │ │ │ │ ├── feedback_dialog.js │ │ │ │ ├── grid_commands.js │ │ │ │ ├── grid_content_converter.js │ │ │ │ ├── grid_interaction_handler.js │ │ │ │ ├── grid_remote_model.js │ │ │ │ ├── grid_view.js │ │ │ │ ├── htmlescape.js │ │ │ │ ├── keyboard_cellrange_selector.js │ │ │ │ ├── page_commands.js │ │ │ │ ├── page_interaction_handler.js │ │ │ │ ├── page_view.js │ │ │ │ ├── security_settings.js │ │ │ │ ├── selection_model.js │ │ │ │ ├── sheet_page_utils.js │ │ │ │ ├── toolbar_interaction_handler.js │ │ │ │ └── usercode_view.js │ │ │ ├── styles │ │ │ │ ├── base.css │ │ │ │ ├── coming_soon_page.css │ │ │ │ ├── contact.css │ │ │ │ ├── error.css │ │ │ │ ├── index.css │ │ │ │ ├── info_page.css │ │ │ │ ├── login.css │ │ │ │ ├── non_sheet_page.css │ │ │ │ ├── pricing.css │ │ │ │ ├── registration.css │ │ │ │ ├── sheet_page.css │ │ │ │ ├── user_page.css │ │ │ │ └── video.css │ │ │ └── tests │ │ │ │ ├── cell_editor_test.html │ │ │ │ ├── console_view_test.html │ │ │ │ ├── dialogs_test.html │ │ │ │ ├── editor_commands_test.html │ │ │ │ ├── feedback_dialog_test.html │ │ │ │ ├── grid_commands_test.html │ │ │ │ ├── grid_content_converter_test.html │ │ │ │ ├── grid_interaction_handler_test.html │ │ │ │ ├── grid_remote_model_test.html │ │ │ │ ├── grid_view_test.html │ │ │ │ ├── htmlescape_test.html │ │ │ │ ├── jsmock.js │ │ │ │ ├── logger.css │ │ │ │ ├── page_commands_test.html │ │ │ │ ├── page_interaction_handler_test.html │ │ │ │ ├── page_view_test.html │ │ │ │ ├── security_settings_test.html │ │ │ │ ├── selection_model_test.html │ │ │ │ ├── sheet_page_utils_test.html │ │ │ │ ├── test_utils.js │ │ │ │ ├── test_utils_test.html │ │ │ │ ├── testlogger.css │ │ │ │ ├── toolbar_interaction_handler_test.html │ │ │ │ ├── usercode_view_test.html │ │ │ │ ├── yuirunner.js │ │ │ │ └── yuitest │ │ │ │ └── yuitest-combo.js │ │ ├── jquery │ │ │ ├── images │ │ │ │ ├── ui-bg_flat_0_009ee0_40x100.png │ │ │ │ ├── ui-bg_flat_0_444444_40x100.png │ │ │ │ ├── ui-bg_flat_0_aaaaaa_40x100.png │ │ │ │ ├── ui-bg_flat_0_ffffff_40x100.png │ │ │ │ ├── ui-icons_009ee0_256x240.png │ │ │ │ ├── ui-icons_ff0e00_256x240.png │ │ │ │ └── ui-icons_ffffff_256x240.png │ │ │ ├── jeip.js │ │ │ ├── jquery-1.5.2.min.js │ │ │ ├── jquery-ui-1.8.10.custom.css │ │ │ ├── jquery-ui-1.8.10.custom.min.js │ │ │ ├── jquery.ajaxq-0.0.1.js │ │ │ ├── jquery.caret.1.02.min.js │ │ │ ├── jquery.cookie.js │ │ │ └── jquery.event.drag-2.0.min.js │ │ ├── json │ │ │ └── json2.js │ │ ├── robots.txt │ │ ├── slickgrid │ │ │ ├── MIT-LICENSE.txt │ │ │ ├── slick.cellrangedecorator.js │ │ │ ├── slick.cellrangeselector.js │ │ │ ├── slick.cellselectionmodel.js │ │ │ ├── slick.core.js │ │ │ ├── slick.editors.js │ │ │ ├── slick.grid.css │ │ │ └── slick.grid.js │ │ └── splitter │ │ │ ├── img │ │ │ ├── hgrabber.png │ │ │ └── vgrabber.png │ │ │ └── splitter.js │ ├── templates │ │ ├── 403.html │ │ ├── 404.html │ │ ├── 500.html │ │ ├── base.html │ │ ├── error_page.html │ │ ├── footer_links_include.html │ │ ├── header_links_include.html │ │ ├── info_page.html │ │ └── non_sheet_page_small_logo.html │ ├── tests │ │ ├── __init__.py │ │ └── test_views.py │ └── views.py ├── sheet │ ├── __init__.py │ ├── admin.py │ ├── calculate.py │ ├── cell.py │ ├── cell_range.py │ ├── clipboard.py │ ├── dependency_graph.py │ ├── dirigible_datetime.py │ ├── errors.py │ ├── eval_constant.py │ ├── forms.py │ ├── formula_interpreter.py │ ├── importer.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── parser │ │ ├── __init__.py │ │ ├── fl_cell_range_parse_node.py │ │ ├── fl_cell_reference_parse_node.py │ │ ├── fl_column_reference_parse_node.py │ │ ├── fl_named_column_reference_parse_node.py │ │ ├── fl_named_row_reference_parse_node.py │ │ ├── fl_reference_parse_node.py │ │ ├── fl_row_reference_parse_node.py │ │ ├── grammar.py │ │ ├── parse_node.py │ │ ├── parse_node_constructors.py │ │ ├── parser.py │ │ ├── parsetab.py │ │ └── tokens.py │ ├── rewrite_formula_offset_cell_references.py │ ├── sheet.py │ ├── templates │ │ ├── export_csv_error.html │ │ ├── import_csv_error.html │ │ ├── import_xls_error.html │ │ └── sheet_page.html │ ├── tests │ │ ├── __init__.py │ │ ├── parser │ │ │ ├── __init__.py │ │ │ ├── test_fl_cell_range_parse_node.py │ │ │ ├── test_fl_cell_reference_parse_node.py │ │ │ ├── test_fl_coloumn_reference_parse_node.py │ │ │ ├── test_fl_named_column_reference_parse_node.py │ │ │ ├── test_fl_named_row_reference_parse_node.py │ │ │ ├── test_fl_reference_parse_node.py │ │ │ ├── test_fl_row_reference_parse_node.py │ │ │ ├── test_parse_node.py │ │ │ ├── test_parse_node_constructors.py │ │ │ └── test_parser.py │ │ ├── test_calculate.py │ │ ├── test_cell.py │ │ ├── test_cell_range.py │ │ ├── test_clipboard.py │ │ ├── test_dependency_graph.py │ │ ├── test_dirigible_datetime.py │ │ ├── test_errors.py │ │ ├── test_eval_constant.py │ │ ├── test_forms.py │ │ ├── test_formula_interpreter.py │ │ ├── test_importer.py │ │ ├── test_rewrite_formula_offset_cell_references.py │ │ ├── test_sheet.py │ │ ├── test_ui_jsonifier.py │ │ ├── test_views.py │ │ ├── test_views_api_0_1.py │ │ ├── test_worksheet.py │ │ └── utils │ │ │ ├── __init__.py │ │ │ ├── test_cell_name_utils.py │ │ │ ├── test_interruptable_thread.py │ │ │ └── test_string_utils.py │ ├── ui_jsonifier.py │ ├── urls.py │ ├── urls_api_0_1.py │ ├── utils │ │ ├── __init__.py │ │ ├── cell_name_utils.py │ │ ├── interruptable_thread.py │ │ └── string_utils.py │ ├── views.py │ ├── views_api_0_1.py │ └── worksheet.py └── user │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ ├── models.py │ ├── signup_urls.py │ ├── templates │ ├── login.html │ ├── registration │ │ ├── activate.html │ │ ├── activation_email.txt │ │ ├── activation_email_subject.txt │ │ ├── registration_complete.html │ │ └── registration_form.html │ ├── user_page.html │ └── welcome_email.txt │ ├── tests │ ├── __init__.py │ ├── test_forms.py │ ├── test_models.py │ └── test_views.py │ ├── urls.py │ └── views.py ├── documentation ├── .gitignore ├── BeautifulSoup.py ├── Makefile ├── _static │ └── file-to-force-git-to-keep-empty-dir ├── _templates │ └── layout.html ├── builtins.rst ├── conf.py ├── dirigible-theme │ ├── layout.html │ ├── static │ │ └── dirigible-style.css │ └── theme.conf ├── fl-python-differences.rst ├── import_export.rst ├── import_export_excel_garbled_csv.png ├── import_export_excel_hooray.png ├── import_export_excel_import_text_choose_comma.png ├── import_export_excel_import_text_menu.png ├── import_export_export_icon.jpg ├── import_export_export_icon.png ├── import_export_import_button.png ├── import_export_import_csv_options.jpg ├── import_export_import_dialog.png ├── import_export_import_excel_data_import_from_text_button.png ├── import_export_import_icon.jpg ├── import_export_import_icon.png ├── index.rst ├── json_api.rst ├── overview.rst ├── public_sheets.rst ├── python-modules.rst ├── security-dialog-button.png ├── security-dialog-json.png ├── security-dialog-public.png ├── spreadsheet-functions.rsl ├── spreadsheet-functions.rst ├── talk.md ├── talk_example_sheets.json ├── talk_screenshots │ ├── 01_constants.png │ ├── 02a_simple_python_formula.png │ ├── 02b_simple_python_formula_works.png │ ├── 03a_formula_error.png │ ├── 03b_formula_error_handled.png │ ├── 04a_cell_reference.png │ ├── 04b_cell_reference_works.png │ ├── 05a_circular_dependency.png │ ├── 05b_circular_dependency_handled.png │ ├── 06_dependency_graph.png │ ├── 07a_custom_function_in_usercode.png │ ├── 07b_custom_function_works.png │ ├── 08_accessing_constants_in_usercode.png │ └── 09_accessing_formula_results.png ├── toolbar.PNG ├── tutorial-01-after-basic-setup.png ├── tutorial-01-dashboard.png ├── tutorial-01-error-in-grid.png ├── tutorial-01-error-in-output-console.png ├── tutorial-01-gross-price-1.png ├── tutorial-01-gross-price-2.png ├── tutorial-01-gross-price-3.png ├── tutorial-01-gross-price-4.png ├── tutorial-01-gross-price-5.png ├── tutorial-01-net-prices.png ├── tutorial-01-products.png ├── tutorial-01-sheet-page.png ├── tutorial-02-defining-functions.png ├── tutorial-02-using-cell-values-in-usercode-error.png ├── tutorial-02-using-variables-in-cell-formulae-error-message.png ├── tutorial-02-using-variables-in-cell-formulae-error.png ├── tutorial-02-using-variables-in-cell-formulae.png ├── tutorial-03-after-csv-import.png ├── tutorial-03-all-done.png ├── tutorial-03-before-numpy.png ├── tutorial-03-bubble-array.png ├── tutorial-03-csv-import-button.png ├── tutorial-03-eccentric-anomalies.png ├── tutorial-03-first-array-formula.png ├── tutorial-03-mean-anomalies.png ├── tutorial-03-numpy-arange.png ├── tutorial-04-access-numerical-data.png ├── tutorial-04-after-csv-import.png ├── tutorial-04-all-planets.png ├── tutorial-04-all-values-one-planet-refactored.png ├── tutorial-04-all-values-one-planet.png ├── tutorial-04-empty-lists.png ├── tutorial-04-first-formula.png ├── tutorial-04-listified-orbital-data.png ├── tutorial-04-no-t-values-yet.png ├── tutorial01.rst ├── tutorial02.rst ├── tutorial03.rst ├── tutorial04.rst └── windows-mouse-pointer.png └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | python/dirigible/dirigible.db 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Resolver Systems Ltd, PythonAnywhere LLP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /dirigible/.gitignore: -------------------------------------------------------------------------------- 1 | db.sqlite3 2 | fts/screendumps 3 | -------------------------------------------------------------------------------- /dirigible/dirigible/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/dirigible/__init__.py -------------------------------------------------------------------------------- /dirigible/dirigible/urls.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.conf.urls import patterns, include, url 4 | from django.contrib import admin 5 | from django.conf import settings 6 | from django.views.generic import TemplateView 7 | 8 | from info_pages.views import front_page_view, info_page_view 9 | from sheet.views import new_sheet 10 | 11 | 12 | urlpatterns = patterns( 13 | '', 14 | 15 | url( 16 | r'^$', 17 | front_page_view, 18 | name='front_page' 19 | ), 20 | 21 | url( 22 | r'^(?Poss|video)/', 23 | info_page_view, 24 | name="info_page" 25 | ), 26 | 27 | url( 28 | # If you change this, don't forget to change the LOGIN_URL in settings.py 29 | # Here be dragons. The settings .py one has no trailing slash and needs to 30 | # stay that way. Changing either of these will stop it from working in Chrome 31 | # and in Firefox if you press ENTER to login. 32 | r'^login/', 33 | 'django.contrib.auth.views.login', 34 | {'template_name': 'login.html'}, 35 | name="login" 36 | ), 37 | 38 | url( 39 | r'^logout$', 40 | 'django.contrib.auth.views.logout', 41 | {'next_page': settings.LOGIN_URL}, 42 | name="logout" 43 | ), 44 | 45 | url( 46 | r'^new_sheet$', 47 | new_sheet, 48 | name="new_sheet" 49 | ), 50 | 51 | url( 52 | r'^user/', 53 | include('user.urls') 54 | ), 55 | 56 | url( 57 | r'^signup/', 58 | include('user.signup_urls') 59 | ), 60 | 61 | 62 | url( 63 | r'^feedback/', 64 | include('feedback.urls') 65 | ), 66 | 67 | 68 | url( 69 | r'^featured_sheets/$', 70 | TemplateView.as_view(template_name='featured_sheets.html'),#, context={'sheets': FeaturedSheet.objects.all}), 71 | name='featured_sheets' 72 | ), 73 | 74 | url(r'^admin/', include(admin.site.urls)), 75 | 76 | ) 77 | -------------------------------------------------------------------------------- /dirigible/dirigible/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for dirigible project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dirigible.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /dirigible/featured_sheet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/featured_sheet/__init__.py -------------------------------------------------------------------------------- /dirigible/featured_sheet/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | from featured_sheet.models import FeaturedSheet 5 | from django.contrib import admin 6 | 7 | admin.site.register(FeaturedSheet) 8 | 9 | -------------------------------------------------------------------------------- /dirigible/featured_sheet/models.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.db import models 6 | 7 | from sheet.models import Sheet 8 | 9 | class FeaturedSheet(models.Model): 10 | sheet = models.ForeignKey(Sheet) 11 | description = models.TextField() 12 | more_info_url = models.CharField(max_length=1024, default='', blank=True) 13 | 14 | def __unicode__(self): 15 | return 'Feature: %s' % (self.sheet.name,) 16 | -------------------------------------------------------------------------------- /dirigible/featured_sheet/templates/featured_sheets.html: -------------------------------------------------------------------------------- 1 | {% extends "info_page.html" %} 2 | 3 | {% block title %} 4 | Featured Sheets: Dirigible 5 | {% endblock %} 6 | 7 | {% block head %} 8 | {{ block.super }} 9 | 10 | {% endblock %} 11 | 12 | {% block middle %} 13 | 14 |

Featured sheets

15 | 16 | {% for sheet in sheets %} 17 |
18 | 31 | 34 | 35 |
36 | 37 | {% endfor %} 38 | 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /dirigible/featured_sheet/tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dirigible/featured_sheet/tests/test_models.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | from django.contrib.auth.models import User 5 | 6 | from dirigible.test_utils import ResolverTestCase 7 | from sheet.models import Sheet 8 | 9 | from featured_sheet.models import FeaturedSheet 10 | 11 | 12 | class TestFeaturedSheetModel(ResolverTestCase): 13 | 14 | def test_can_construct_without_more_info_url(self): 15 | user = User(username='featurer') 16 | user.save() 17 | sheet = Sheet(owner=user, name='sheet to feature') 18 | sheet.save() 19 | 20 | description = 'twas brillig and the slithy toves' 21 | fs = FeaturedSheet(sheet=sheet, description=description) 22 | fs.save() 23 | 24 | self.assertEquals(fs.sheet, sheet) 25 | self.assertEquals(fs.description, description) 26 | self.assertEquals(fs.more_info_url, '') 27 | 28 | 29 | def test_can_construct_with_more_info_url(self): 30 | user = User(username='chattyfeaturer') 31 | user.save() 32 | sheet = Sheet(owner=user, name='sheet to feature') 33 | sheet.save() 34 | 35 | description = 'twas brillig and the slithy toves' 36 | more_info_url = 'http://far.away/' 37 | fs = FeaturedSheet(sheet=sheet, description=description, more_info_url=more_info_url) 38 | fs.save() 39 | 40 | self.assertEquals(fs.sheet, sheet) 41 | self.assertEquals(fs.description, description) 42 | self.assertEquals(fs.more_info_url, more_info_url) 43 | 44 | 45 | def test_unicode(self): 46 | user = User(username='printyfeaturer') 47 | user.save() 48 | sheet = Sheet(owner=user, name='sheet to feature') 49 | sheet.save() 50 | 51 | description = 'twas brillig and the slithy toves' 52 | more_info_url = 'http://far.away/' 53 | fs = FeaturedSheet(sheet=sheet, description=description, more_info_url=more_info_url) 54 | fs.save() 55 | 56 | self.assertEquals(unicode(fs), u'Feature: %s' % (sheet.name,)) 57 | 58 | -------------------------------------------------------------------------------- /dirigible/featured_sheet/views.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | -------------------------------------------------------------------------------- /dirigible/feedback/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/feedback/__init__.py -------------------------------------------------------------------------------- /dirigible/feedback/models.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.db import models 6 | -------------------------------------------------------------------------------- /dirigible/feedback/tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dirigible/feedback/tests/test_views.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from mock import patch 6 | from textwrap import dedent 7 | 8 | from django.conf import settings 9 | from django.http import HttpRequest, HttpResponse 10 | 11 | from dirigible.test_utils import ResolverTestCase 12 | 13 | from feedback.views import submit 14 | 15 | 16 | class SubmitTest(ResolverTestCase): 17 | 18 | @patch('feedback.views.send_mail') 19 | def test_submit_with_message_and_email_address_and_username_sends_admin_email_with_all_three(self, mock_send_mail): 20 | request = HttpRequest() 21 | request.POST["message"] = "a test message" 22 | request.POST["email_address"] = "a test email address" 23 | request.POST["username"] = "a test username" 24 | request.META['HTTP_REFERER'] = 'a test page' 25 | 26 | response = submit(request) 27 | 28 | self.assertTrue(isinstance(response, HttpResponse)) 29 | self.assertEquals(response.content, "OK") 30 | 31 | self.assertCalledOnce( 32 | mock_send_mail, 33 | "User feedback from Dirigible", 34 | dedent(""" 35 | Username: a test username 36 | Email address: a test email address 37 | Page: a test page 38 | 39 | Message: 40 | a test message 41 | """), 42 | settings.SERVER_EMAIL, 43 | [settings.FEEDBACK_EMAIL] 44 | ) 45 | -------------------------------------------------------------------------------- /dirigible/feedback/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.conf.urls import * 6 | 7 | from feedback.views import submit 8 | 9 | 10 | urlpatterns = patterns('', 11 | 12 | url( 13 | r'^submit/$', 14 | submit, 15 | name="feedback_submit" 16 | ), 17 | 18 | ) 19 | -------------------------------------------------------------------------------- /dirigible/feedback/views.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | from django.core.mail import send_mail 8 | from django.http import HttpResponse 9 | from django.conf import settings 10 | 11 | 12 | 13 | def submit(request): 14 | send_mail( 15 | "User feedback from Dirigible", 16 | dedent(""" 17 | Username: %s 18 | Email address: %s 19 | Page: %s 20 | 21 | Message: 22 | %s 23 | """) % ( 24 | request.POST["username"], request.POST["email_address"], 25 | request.META['HTTP_REFERER'], request.POST["message"] 26 | ), 27 | settings.SERVER_EMAIL, 28 | [settings.FEEDBACK_EMAIL] 29 | ) 30 | return HttpResponse("OK") 31 | -------------------------------------------------------------------------------- /dirigible/fts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/fts/__init__.py -------------------------------------------------------------------------------- /dirigible/fts/screendumps/placeholder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/fts/screendumps/placeholder -------------------------------------------------------------------------------- /dirigible/fts/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/fts/tests/__init__.py -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2529_HighlightErrorsInCells.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2529_HighlightErrorsInCells(FunctionalTest): 9 | 10 | def test_highlight_errors_in_cells(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # * He enters "=lambda x:x + 1" in A1. 15 | self.enter_cell_text(2, 3, '=lambda x:x + 1') 16 | 17 | # * and notes that there is an error marked for that cell 18 | self.assert_cell_has_error( 19 | 2, 3, 20 | "FormulaError: Error in formula at position 10: unexpected ':'" 21 | ) 22 | 23 | # * He then enters '=my_value' in A2 24 | self.enter_cell_text(4, 5, '=my_value') 25 | 26 | # * and notes that A2 complains that my_value is not defined 27 | self.assert_cell_has_error(4, 5, "NameError: name 'my_value' is not defined") 28 | 29 | # * He fixes his errors and notes that the error markers go away 30 | self.enter_cell_text(2, 3, '=lambda x->x + 1') 31 | self.enter_cell_text(4, 5, '=10') 32 | self.wait_for_cell_value(4, 5, '10') 33 | 34 | self.assert_cell_has_no_error(2, 3) 35 | self.assert_cell_has_no_error(4, 5) 36 | 37 | 38 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2532_LambdasInCells.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | import re 6 | import time 7 | from urlparse import urlparse 8 | from textwrap import dedent 9 | 10 | from browser_settings import SERVER_IP 11 | 12 | from functionaltest import FunctionalTest, snapshot_on_error 13 | 14 | 15 | class Test_2532_LambdasInCells(FunctionalTest): 16 | 17 | @snapshot_on_error 18 | def test_lambda_in_cell(self): 19 | # * Harold logs in to Dirigible and creates a new sheet 20 | self.login_and_create_new_sheet() 21 | 22 | # * He enters "=lambda x -> x + 1" in A1. 23 | self.enter_cell_text(1, 1, '=lambda x -> x + 1') 24 | 25 | # * He enters =A1(5) into B1 26 | self.enter_cell_text(2, 1, '=A1(5)') 27 | 28 | # * and notes that the value in B1 is 6 29 | self.wait_for_cell_value(2, 1, '6') 30 | 31 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2547_EnterDataQuickly.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | import time 6 | 7 | from functionaltest import FunctionalTest, snapshot_on_error 8 | 9 | 10 | class Test_2547_EnterDataQuickly(FunctionalTest): 11 | 12 | @snapshot_on_error 13 | def test_enter_data_quickly(self): 14 | # * Harold logs in and creates a new sheet 15 | self.login_and_create_new_sheet() 16 | 17 | # * He enters 20 numbers quickly 18 | for row in range(1, 21): 19 | self.enter_cell_text_unhumanized(1, row, "%s" % (row,)) 20 | 21 | # * He checks that they are all there. 22 | for row in range(1, 21): 23 | self.wait_for_cell_value(1, row, "%s" % (row,)) 24 | 25 | 26 | @snapshot_on_error 27 | def test_enter_data_quickly_in_batches(self): 28 | # Running the above test made it clear that it was significantly more 29 | # likely to drop the last number than any others, so we added the below 30 | # to increase the likelihood of that particular failure mode. 31 | 32 | # * Harold logs in and creates a new sheet 33 | self.login_and_create_new_sheet() 34 | 35 | # * He enters 20 numbers quickly, taking a brief rest every five numbers 36 | for row in range(1, 21): 37 | self.enter_cell_text_unhumanized(1, row, "%s" % (row,)) 38 | if row % 5 == 0: 39 | time.sleep(2) 40 | 41 | # * He checks that they are all there. 42 | for row in range(1, 21): 43 | self.wait_for_cell_value(1, row, "%s" % (row,)) 44 | 45 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2549_InterruptedRecalculations.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | import time 6 | from textwrap import dedent 7 | 8 | from functionaltest import FunctionalTest, snapshot_on_error 9 | 10 | 11 | class Test_2549_InterruptedRecalculations(FunctionalTest): 12 | 13 | @snapshot_on_error 14 | def test_interrupted_recalc_coming_back_handled_correctly(self): 15 | # Harold logs on and creates a sheet 16 | self.login_and_create_new_sheet() 17 | 18 | # Harold enters a long-running recalc that finishes by 19 | # setting a value in the grid 20 | self.enter_usercode(dedent(''' 21 | import time 22 | time.sleep(20) 23 | worksheet[1, 5].value = 1 24 | ''')) 25 | 26 | # While it is running, he realises that there is a shorter 27 | # algorithm, so he tries that instead. 28 | time.sleep(1) 29 | self.enter_usercode(dedent(''' 30 | worksheet[1, 5].value = 2 31 | ''')) 32 | 33 | # Even though the initial longer-running recalc finishes 34 | # after the second one, the result he sees is from the 35 | # second one; he concludes that the results of the first 36 | # one must have been thrown away. 37 | self.wait_for_cell_value(1, 5, '2') 38 | 39 | # To confirm this to himself, he waits until the first 40 | # recalc has absolutely definitely finished, and confirms 41 | # that its results never appear in the grid. 42 | time.sleep(30) 43 | self.wait_for_cell_value(1, 5, '2') 44 | 45 | # He then refreshes his web page to make sure that the 46 | # correct version is current in the database. 47 | self.refresh_sheet_page() 48 | self.wait_for_grid_to_appear() 49 | self.wait_for_cell_value(1, 5, '2') 50 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2554_SlicingInFormulae.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2554_SlicingInFormulae(FunctionalTest): 9 | 10 | def test_formulas_work_properly(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # Harold REALLY likes slicing things. We should ensure that 15 | # he can indulge his axe-murderer side 16 | self.enter_cell_text(1, 1, '=[0, 10, 20, 30, 40]') 17 | self.enter_cell_text(1, 2, '=A1[1->3]') 18 | self.wait_for_cell_value(1, 2, '[10, 20]') 19 | 20 | self.enter_cell_text(2, 1, 'a string that really needs to be sliced') 21 | self.enter_cell_text(2, 2, '=B1[->10]') 22 | self.wait_for_cell_value(2, 2, 'a string t') 23 | 24 | self.enter_cell_text(3, 2, '=B1[9->]') 25 | self.wait_for_cell_value(3, 2, 'that really needs to be sliced') 26 | 27 | self.enter_cell_text(4, 2, '=B1[-8->]') 28 | self.wait_for_cell_value(4, 2, 'e sliced') 29 | 30 | self.enter_cell_text(5, 2, '=B1[4->->3]') 31 | self.wait_for_cell_value(5, 2, 'rgh ayesoele') 32 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2557_ClickAwaySavesUsercode.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2557_ClickAwaySavesUsercode(FunctionalTest): 9 | 10 | def test_blur_on_edit_textarea_saves_usercode(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # * He enters some usercode that sets A1 to 4 15 | self.selenium.get_eval('window.editor.focus()') 16 | self.enter_usercode('worksheet[1, 1].value = 4') 17 | 18 | # * He does something to cause a blur event on the editarea textbox 19 | ## We use fire_event because Selenium does not fire the full event stack 20 | ## for clicks. If this changes in future Selenium versions, it would be nice 21 | ## to expand this test to check behaviour for clicking on cells, links and 22 | ## edit sheet name 23 | self.selenium.get_eval("window.editor.blur()") 24 | 25 | # * ... and notes that A1 contains 4 26 | self.wait_for_cell_value(1, 1, '4') 27 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2558_MoreCellsByDefault.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | 6 | from functionaltest import FunctionalTest 7 | 8 | 9 | class Test_2558_MoreCellsByDefault(FunctionalTest): 10 | 11 | def test_more_cells_by_default(self): 12 | # * Harold logs in and creates a new sheet 13 | self.login_and_create_new_sheet() 14 | 15 | # * He notes that the sheet he is presented with has 16 | # an elegant sufficiency of rows and columns (1000 rows and 702 17 | # (up to ZZ) columns) 18 | self.scroll_cell_row_into_view(1, 1000) 19 | 20 | row_list_xpath = '//div[contains(@class, "slick-row") and @row=999]' 21 | row_count = self.selenium.get_xpath_count(row_list_xpath) 22 | self.assertEquals(row_count, 1, 'fewer than 1000 rows') 23 | 24 | self.scroll_cell_row_into_view(52, 1) 25 | column_list_xpath = '//div[@class="slick-cell c52"]' 26 | 27 | column_count = self.selenium.get_xpath_count(column_list_xpath) 28 | self.assertTrue(column_count >= 1, 'fewer than AZ columns') 29 | 30 | self.scroll_cell_row_into_view(1, 1) 31 | # * The last cell on the grid accepts input as expected 32 | self.enter_cell_text(52, 1000, 'end of the sheet') 33 | 34 | # * He notes that, since he just typed on the bottom row, 35 | # the cell stays in edit mode, so he has to click away 36 | # to finish the edit 37 | self.click_on_cell(51, 999) 38 | self.wait_for_cell_value(52, 1000, 'end of the sheet') 39 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2559_FitEditorToCells.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | 6 | from functionaltest import FunctionalTest 7 | 8 | 9 | class Test_2559_FitEditorToCells(FunctionalTest): 10 | 11 | def test_editor_fits_cells(self): 12 | # * Harold logs in and creates a new sheet 13 | self.login_and_create_new_sheet() 14 | 15 | cell_locator = self.get_cell_locator(3, 3) 16 | cell_width = self.selenium.get_element_width(cell_locator) 17 | cell_height = self.selenium.get_element_height(cell_locator) 18 | 19 | # * He edits a cell and notes that the editor fits the 20 | # cell exactly 21 | self.open_cell_for_editing(3, 3) 22 | editor_locator = 'css=input.editor-text' 23 | editor_width = self.selenium.get_element_width(editor_locator) 24 | editor_height = self.selenium.get_element_height(editor_locator) 25 | 26 | self.assertTrue(cell_width - editor_width <= 6, "cell width: %d, editor width: %d" % (cell_width, editor_width)) 27 | self.assertTrue(cell_height - editor_height <= 6, "cell height: %d, editor height: %d" % (cell_height, editor_height)) 28 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2562_ErrorInCellShouldBeClearedByConstants.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class test_2562_ErrorInCellShouldBeClearedByConstants(FunctionalTest): 9 | 10 | def test_ConstantsClearCellErrors(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # * He enters and error in A1. 15 | self.enter_cell_text(1, 1, '=1/0') 16 | self.assert_cell_has_error(1, 1, "ZeroDivisionError: division by zero") 17 | 18 | # * when he overwrites the cell content with a constant 19 | # the error disappears 20 | self.enter_cell_text(1, 1, '123') 21 | self.assert_cell_has_no_error(1, 1) 22 | 23 | # hooray! 24 | 25 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2571_DocumentationAndBlogLinks.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from urlparse import urljoin 6 | 7 | 8 | from functionaltest import FunctionalTest, Url 9 | 10 | 11 | class Test_2571_Documentation(FunctionalTest): 12 | 13 | def get_link_href(self, link_id): 14 | url = self.selenium.get_attribute("id=%s@href" % (link_id,)) 15 | return urljoin(Url.ROOT, url) 16 | 17 | 18 | def test_main_documentation_page_exists(self): 19 | # * Harold goes to the Dirigible front page. 20 | self.go_to_url(Url.ROOT) 21 | 22 | # * He sees that there is a link to a blog, and notes down where it goes to. 23 | blog_url = self.get_link_href("id_blog_link") 24 | 25 | # * He goes to url http://IP/documentation 26 | self.go_to_url(Url.DOCUMENTATION) 27 | 28 | # * He gets back a page, not a 404, the title of which which contains the words 'Dirigible' and 'documentation' 29 | title = self.browser.title.lower() 30 | self.assertTrue("documentation" in title) 31 | self.assertTrue("dirigible" in title) 32 | 33 | # * He logs in and creates a sheet, and notices that there is a link to the 34 | # same documentation page. 35 | self.login_and_create_new_sheet() 36 | self.assertEquals(self.get_link_href("id_help_link"), Url.DOCUMENTATION) 37 | 38 | # ...and that there is also a link to the same blog. 39 | self.assertEquals(self.get_link_href("id_blog_link"), blog_url) 40 | 41 | # * He follows the link to his dashboard. 42 | self.click_link('id_account_link') 43 | 44 | # * He sees the same help and blog links there, and confirms they go to the same places. 45 | self.assertEquals(self.get_link_href("id_help_link"), Url.DOCUMENTATION) 46 | self.assertEquals(self.get_link_href("id_blog_link"), blog_url) -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2582_ReferencingEmptyCell.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | 6 | from functionaltest import FunctionalTest 7 | 8 | 9 | class Test_2582_ReferencingEmptyCell(FunctionalTest): 10 | 11 | def test_referencing_empty_cell(self): 12 | # Harold logs in and creates a new sheet. 13 | self.login_and_create_new_sheet() 14 | 15 | # He enters "=A1" into B1 16 | self.enter_cell_text(2, 1, '=A1') 17 | 18 | # He sees that B1 is in its "I have no value but my formula is '=A1'" state. 19 | self.wait_for_cell_shown_formula(2, 1, '=A1') 20 | 21 | # Cell A1 is blank. 22 | self.wait_for_cell_value(1, 1, '') 23 | self.wait_for_spinner_to_stop() 24 | 25 | # There is nothing in the error console. 26 | self.assertTrue( 27 | self.get_console_content().startswith('Took'), 28 | "Unexpected error console content:\n%r" % (self.get_console_content(),) 29 | ) 30 | 31 | # He adds code to set the value of C1 to the end of the user code. 32 | self.append_usercode("worksheet[3, 1].value = 23") 33 | 34 | # He notes that it is being executed. 35 | self.wait_for_cell_value(3, 1, '23') 36 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2595_Spinner.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | 6 | from functionaltest import FunctionalTest 7 | 8 | 9 | class Test_2595_Throbber(FunctionalTest): 10 | 11 | def test_spinner_appears_during_recalcs(self): 12 | # * Harold likes to know when dirigible is working hard on his calculations 13 | 14 | # * He logs in and creates a new sheet 15 | self.login_and_create_new_sheet() 16 | 17 | # * When the grid has appeared, the spinner might be visible, but it disappears 18 | # rapidly as the initial empty recalc completes. 19 | self.wait_for_spinner_to_stop() 20 | 21 | # * and enters some hard-working user-code 22 | self.append_usercode('import time\ntime.sleep(20)\nworksheet[1,1].value="ready"') 23 | 24 | # * He spots the spinner on the page 25 | self.wait_for(self.is_spinner_visible, 26 | lambda : 'spinner not present', 27 | timeout_seconds = 5) 28 | 29 | # * When the recalc is done, he sees the spinner go away 30 | self.wait_for_cell_value(1, 1, 'ready', timeout_seconds=25) 31 | 32 | self.assertTrue(self.is_element_present('css=#id_spinner_image.hidden')) 33 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2597_CapRecalcTime.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | 6 | from functionaltest import FunctionalTest 7 | 8 | 9 | class Test_2597_CapRecalcTime(FunctionalTest): 10 | 11 | def test_recalcs_get_stopped(self): 12 | # * Harold doesn't want to waste money on recalculations where he created an 13 | # infinite loop. Dirigible helpfully kills any recalculations that are 14 | # taking too long. The default (set in the database per sheet) is 60sec. 15 | 16 | # * He logs in and creates a new sheet 17 | self.login_and_create_new_sheet() 18 | 19 | # * and enters some ill-advised user-code 20 | self.append_usercode('while True: pass\n\n#some stuff') 21 | fn_call_line = self.get_usercode().split('\n').index('while True: pass') 22 | 23 | # * He notes that after a minute, there is a message in the console 24 | # telling him that his recalculation timed out. 25 | self.wait_for_console_content( 26 | 'TimeoutException: \n User code line 10', 27 | timeout_seconds=59 28 | ) 29 | 30 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2601_UndefinedShouldBeAvailableToUsercode.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2601_UndefinedShouldBeAvailableToUsercode(FunctionalTest): 9 | 10 | def test_use_undefined(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # * He uses undefined in A1. 15 | self.enter_cell_text(1, 1, '=A2==undefined') 16 | self.wait_for_cell_value(1, 1, 'True') 17 | self.assert_cell_has_no_error(1, 1) 18 | 19 | # * he uses undefined in usercode 20 | self.append_usercode('worksheet[1,3].value = worksheet[1,4].value==undefined') 21 | self.wait_for_cell_value(1, 3, 'True') 22 | self.assertTrue(self.get_console_content().startswith('Took')) 23 | 24 | # hooray! 25 | 26 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2602_SheetPageShouldDisplayBeforeFirstRecalcComplete.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | from functionaltest import FunctionalTest, Url 8 | 9 | 10 | class Test_2602_SheetPageShouldDisplayBeforeFirstRecalcComplete(FunctionalTest): 11 | 12 | def test_sheet_page_displays_fast(self): 13 | # * Harold logs in to Dirigible and creates a new sheet 14 | self.login_and_create_new_sheet() 15 | sheet_page = self.browser.current_url 16 | 17 | # * He sets it up so that it takes 30 seconds to recalc. 18 | self.enter_cell_text(1, 2, '=123') 19 | self.enter_usercode(dedent(""" 20 | import time 21 | time.sleep(30.0) 22 | load_constants(worksheet) 23 | evaluate_formulae(worksheet) 24 | worksheet[2,2].value = 'usercode completed' 25 | """)) 26 | self.wait_for_cell_value(2, 2, 'usercode completed', timeout_seconds=35) 27 | self.wait_for_cell_value(1, 2, '123') 28 | 29 | # * He navigates to a different page. 30 | self.go_to_url(Url.ROOT) 31 | 32 | # * He goes back to the sheet page. 33 | self.go_to_url(sheet_page) 34 | 35 | # * He notes that the grid takes less than 10 seconds to appear. 36 | self.wait_for_grid_to_appear(timeout_seconds=10) 37 | 38 | # * Once the grid is loaded, but before the recalc has completed, it 39 | # displays the results of his formulae and usercode-only values 40 | # even before the recalc is complete 41 | self.wait_for_cell_value(1, 2, '123', timeout_seconds=35) 42 | self.wait_for_cell_value(2, 2, 'usercode completed') 43 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2603_WorksheetsMayOnlyContainCells.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | from textwrap import dedent 5 | 6 | from functionaltest import FunctionalTest 7 | 8 | 9 | class test_2603_WorksheetsMayOnlyContainCells(FunctionalTest): 10 | 11 | def test_setting_worksheet_location_to_non_cell_raises(self): 12 | # * Harold logs in to Dirigible and creates a new sheet 13 | self.login_and_create_new_sheet() 14 | 15 | # * He enters usercode which sets a worksheet location to a non-cell 16 | self.append_usercode('worksheet[1,1] = 123') 17 | error_line = len(self.get_usercode().split('\n')) 18 | expected = dedent(''' 19 | TypeError: Worksheet locations must be Cell objects 20 | User code line %d''' % (error_line,))[1:] 21 | self.wait_for_console_content(expected) 22 | 23 | # hooray! 24 | 25 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2616_RootPageIsDashboard.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from urlparse import urlparse, urlunparse 6 | 7 | from functionaltest import FunctionalTest 8 | 9 | 10 | class Test_2616_RootPageIsDashboard(FunctionalTest): 11 | 12 | # The bulk of this was tested by changing the existing functional tests to reflect the new 13 | # behaviour; we just have new stuff in this file. 14 | 15 | def test_old_url_redirects(self): 16 | # Harold has some old bookmarks to pages that have moved as part of this story. 17 | # He is delighted to find they still work. 18 | self.assert_redirects('/static/dirigible/about.html', '/about/') 19 | self.assert_redirects('/static/dirigible/contact.html', '/contact/') 20 | self.assert_redirects('/static/dirigible/pricing.html', '/pricing/') 21 | self.assert_redirects('/static/dirigible/privacy.html', '/privacy/') 22 | self.assert_redirects('/static/dirigible/terms.html', '/terms/') 23 | self.assert_redirects('/static/dirigible/video.html', '/video/') 24 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2621_CanSaveSheetsWithLotsOfFormulae.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | from functionaltest import FunctionalTest, snapshot_on_error 8 | 9 | 10 | class Test_2621_CanSaveSheetsWithLotsOfFormulae(FunctionalTest): 11 | 12 | @snapshot_on_error 13 | def test_can_save_lots_of_formulae(self): 14 | # * Harold logs in to Dirigible and creates a new sheet 15 | self.login_and_create_new_sheet() 16 | sheet_page = self.browser.current_url 17 | 18 | # * Typing tirelessly, he enters moderately-sized formulae in 19 | # 23 columns across 400 rows 20 | cell_value = "a" * 5 21 | self.enter_usercode(dedent(""" 22 | for col in range(1, 45): 23 | for row in range(1, 401): 24 | worksheet[col, row].formula = %r 25 | """ % cell_value)) 26 | self.wait_for_cell_shown_formula(1, 1, cell_value, timeout_seconds=30) 27 | self.enter_usercode(dedent(""" 28 | load_constants(worksheet) 29 | evaluate_formulae(worksheet) 30 | """)) 31 | 32 | # * He looks at the grid and sees that the data appear to be 33 | # there 34 | self.wait_for_cell_value(1, 1, cell_value, timeout_seconds=30) 35 | self.wait_for_cell_value(1, 10, cell_value) 36 | self.wait_for_cell_value(10, 1, cell_value) 37 | self.wait_for_cell_value(10, 10, cell_value) 38 | self.wait_for_cell_value(5, 5, cell_value) 39 | 40 | # * He goes back to his dashboard. 41 | self.go_to_url(sheet_page) 42 | 43 | # * He returns to the grid, and is pleased to see that the data 44 | # are still there. 45 | self.wait_for_cell_value(1, 1, cell_value, timeout_seconds=30) 46 | self.wait_for_cell_value(1, 10, cell_value) 47 | self.wait_for_cell_value(10, 1, cell_value) 48 | self.wait_for_cell_value(10, 10, cell_value) 49 | self.wait_for_cell_value(5, 5, cell_value) 50 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2635_SheetNameSelectedOnEdit.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | try: 6 | import unittest2 as unittest 7 | except ImportError: 8 | import unittest 9 | 10 | from functionaltest import FunctionalTest 11 | import key_codes 12 | 13 | 14 | class Test_2635_SheetNameSelectedOnEdit(FunctionalTest): 15 | 16 | def test_click_on_sheet_name(self): 17 | # * Harold logs in and creates a new sheet 18 | self.login_and_create_new_sheet() 19 | 20 | # * He clicks on the sheet name and enters a new one 21 | self.selenium.click('id=id_sheet_name') 22 | self.wait_for( 23 | lambda: self.is_element_present('id=edit-id_sheet_name'), 24 | lambda: 'editable sheetname to appear') 25 | self.human_key_press(key_codes.LETTER_A) 26 | self.human_key_press(key_codes.LETTER_B) 27 | self.wait_for( 28 | lambda: self.selenium.get_value('id=edit-id_sheet_name') == 'ab', 29 | lambda: 'sheetname to change') 30 | 31 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2639_SciPy_and_MpMath.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2639_SciPy_and_MpMath(FunctionalTest): 9 | 10 | def test_can_use_scipy_norm(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # He imports scipy 15 | self.prepend_usercode("from scipy.stats import norm") 16 | 17 | # and accesses the cumulative normal distribution) 18 | # function from a number of cells, and gets the expected results 19 | self.enter_cell_text(1, 1, '=norm.cdf(-1000)') 20 | self.wait_for_cell_value(1, 1, '0.0') 21 | 22 | self.enter_cell_text(1, 2, '=norm.cdf(0)') 23 | self.wait_for_cell_value(1, 2, '0.5') 24 | 25 | self.enter_cell_text(1, 3, '=norm.cdf(1000)') 26 | self.wait_for_cell_value(1, 3, '1.0') 27 | 28 | 29 | def test_can_use_mpmath(self): 30 | # * Harold logs in to Dirigible and creates a new sheet 31 | self.login_and_create_new_sheet() 32 | 33 | # He imports mpmath 34 | self.prepend_usercode("import mpmath") 35 | 36 | # and accesses the mpmath sin function 37 | self.enter_cell_text(2, 2, '=mpmath.libmp.BACKEND') 38 | self.enter_cell_text(1, 1, '=mpmath.sin(1)') 39 | self.wait_for_cell_value(1, 1, "0.841470984807897") 40 | self.wait_for_cell_value(2, 2, "gmpy") 41 | 42 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2642_RecalcTimesInConsole.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | try: 6 | import unittest2 as unittest 7 | except ImportError: 8 | import unittest 9 | 10 | import re 11 | from functionaltest import FunctionalTest 12 | 13 | class Test_2642_RecalcTimesInConsole(FunctionalTest): 14 | 15 | def test_recalc_time_appears_in_console(self): 16 | # * Harold logs in and creates a new sheet 17 | self.login_and_create_new_sheet() 18 | 19 | # * He enters some usercode 20 | self.append_usercode('worksheet[1, 1].value = 10') 21 | 22 | # * He notes that the console tells him how long the recalc took 23 | self.wait_for_cell_value(1, 1, '10') 24 | output = self.get_console_content() 25 | recalc_time_report_re = re.compile('Took \d+\.\d\d+s') 26 | found_time_report = recalc_time_report_re.search(output) 27 | self.assertTrue(found_time_report) 28 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2653_UsernameFocusedOnLoginPage.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | # Check added to T2525 6 | 7 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2654_CtrlSSavesUsercode.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from __future__ import with_statement 6 | 7 | from functionaltest import FunctionalTest 8 | import key_codes 9 | 10 | class Test_2654_CtrlSSavesUsercode(FunctionalTest): 11 | 12 | def test_ctrl_s_saves_usercode(self): 13 | # * Harold logs in and creates a new sheet 14 | self.login_and_create_new_sheet() 15 | 16 | # * He enters some usercode 17 | self.selenium.get_eval('window.editor.focus()') 18 | self.enter_usercode('worksheet[1, 1].value = 5', commit_change=False) 19 | 20 | # * and presses Ctrl-S 21 | with self.key_down(key_codes.CTRL): 22 | self.human_key_press(key_codes.LETTER_S) 23 | 24 | # * His usercode gets saved and a recalculation starts 25 | #self.wait_for_cell_value(1, 1, '5') 26 | 27 | # He types a few more characters, which should appear in the usercode 28 | # box, because the save-as dialog didn't appear to capture them 29 | self.human_key_press(key_codes.LETTER_A) 30 | self.human_key_press(key_codes.LETTER_A) 31 | self.human_key_press(key_codes.LETTER_A) 32 | 33 | ## NB enter_usercode leaves the cursor at the end of the editor. 34 | self.wait_for_usercode_editor_content( 35 | 'worksheet[1, 1].value = 5aaa' 36 | ) 37 | 38 | 39 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2682_CellAccessUsingA1.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | from functionaltest import FunctionalTest 8 | 9 | 10 | class Test_2682_CellAccessUsingA1(FunctionalTest): 11 | 12 | def test_cell_access(self): 13 | # * Harold logs in to Dirigible and creates a new sheet 14 | self.login_and_create_new_sheet() 15 | 16 | # * he uses some convenient ways to find cells in a worksheet object 17 | self.enter_usercode(dedent(''' 18 | worksheet[1, 2].value = 'a2' 19 | worksheet["B", 4].value = 'b4' 20 | worksheet["C6"].value = 'c6' 21 | worksheet.D8.value = 'd8' 22 | ''')) 23 | # * which seems to work as he expects 24 | self.wait_for_cell_value(1, 2, 'a2') 25 | self.wait_for_cell_value(2, 4, 'b4') 26 | self.wait_for_cell_value(3, 6, 'c6') 27 | self.wait_for_cell_value(4, 8, 'd8') 28 | 29 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2690_FocusShouldStartInGrid.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | import key_codes 7 | 8 | 9 | class test_2690_FocusShouldStartInGrid(FunctionalTest): 10 | 11 | def test_focus(self): 12 | # * Harold logs in to Dirigible and creates a new sheet 13 | self.login_and_create_new_sheet() 14 | 15 | # * The focus is initially in A1 16 | # (not in text editor where it is annoying since clicking on 17 | # grid then causes unwanted recalcs. Especially annoying after 18 | # loading a large sheet) 19 | 20 | # Hence, Harold starts typing... 21 | self.human_key_press(key_codes.NUMBER_1) 22 | self.human_key_press(key_codes.NUMBER_2) 23 | self.human_key_press(key_codes.NUMBER_3) 24 | 25 | # * The formula he typed appears in cell A1's edit box 26 | self.wait_for_cell_to_enter_edit_mode(1, 1) 27 | self.wait_for_cell_editor_content('123') 28 | 29 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2691_AllowSettingValuesFromUsercodeBeforeLoadConstants.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | try: 8 | import unittest2 as unittest 9 | except ImportError: 10 | import unittest 11 | 12 | from functionaltest import FunctionalTest 13 | 14 | 15 | class Test_2691_AllowSettingValuesFromUsercodeBeforeLoadConstants(FunctionalTest): 16 | 17 | def test_load_constants_doesnt_trash_cells(self): 18 | # * Harold logs in and creates a new sheet 19 | self.login_and_create_new_sheet() 20 | 21 | # * He adds stuff to the start of the usercode to set a value on a cell, before load_constants 22 | # (which previously erroneously cleared cells with no set formula) 23 | self.prepend_usercode(dedent(""" 24 | worksheet[1, 1].value = 'wibble' 25 | """)) 26 | 27 | # * When the recalc is completed, the cell shows the value 28 | self.wait_for_cell_value(1, 1, 'wibble') -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2701_NameResolutionWorks.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | from functionaltest import FunctionalTest 8 | 9 | 10 | class Test_2701_NameResolutionWorks(FunctionalTest): 11 | 12 | def test_name_resolution(self): 13 | # * Harold logs in to Dirigible and creates a new sheet 14 | self.login_and_create_new_sheet() 15 | 16 | # * He writes some usercode that does something to test 17 | # name resolution. 18 | self.enter_usercode(dedent(""" 19 | from urllib2 import urlopen, URLError 20 | try: 21 | connection = urlopen("http://www.google.com/") 22 | worksheet[1, 1].value = len(connection.read()) 23 | connection.close() 24 | except URLError, e: 25 | worksheet[1, 1].value = e.reason[1] 26 | except Exception, e: 27 | worksheet[1, 1].value = str(e) 28 | """)) 29 | 30 | # * It runs OK. 31 | self.wait_for_spinner_to_stop() 32 | size = int(self.get_cell_text(1, 1)) 33 | self.assertTrue(size > 0) 34 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2702_HttpsInChrootJail.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | from functionaltest import FunctionalTest 8 | 9 | 10 | class Test_2702_HttpsInChrootJail(FunctionalTest): 11 | 12 | def test_numpy_tutorial_create_arrays(self): 13 | # * Harold wants to access https URLs from his Dirigible sheets 14 | # * He logs in and creates a new sheet 15 | self.login_and_create_new_sheet() 16 | 17 | # * .. and adds a get for a https URL in an S3 bucket 18 | self.prepend_usercode(dedent(''' 19 | from urllib2 import urlopen 20 | connection = urlopen('https://s3.amazonaws.com/oecd-data/oecd-data.csv') 21 | content = connection.read() 22 | worksheet.A1.value = 'Canada' in content 23 | ''')) 24 | 25 | # * and notes that he gets the expected content 26 | self.wait_for_cell_value(1, 1, 'True') 27 | 28 | 29 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2704_OldStyleClassesInTheGrid.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | import re 6 | from textwrap import dedent 7 | 8 | from functionaltest import FunctionalTest 9 | 10 | 11 | class Test_2704_OldStyleClassesInTheGrid(FunctionalTest): 12 | 13 | def test_put_old_style_classes_in_grid(self): 14 | # * Harold logs in to Dirigible and creates a new sheet 15 | self.login_and_create_new_sheet() 16 | 17 | # * He defines an old-style class in the usercode 18 | self.enter_usercode(dedent(''' 19 | class Basilisk(): 20 | pass 21 | load_constants(worksheet) 22 | evaluate_formulae(worksheet) 23 | ''')) 24 | 25 | # * He puts an instance of it in the grid 26 | self.enter_cell_text(1, 1, "=Basilisk()") 27 | 28 | # * The formatted value is something sane. 29 | self.wait_for_cell_value(1, 1, re.compile(r'<__builtin__.Basilisk instance at 0x.*>')) 30 | 31 | # * He references the class itself from another cell, just for kicks. 32 | self.enter_cell_text(1, 2, "=Basilisk") 33 | 34 | # * The formatted value is equally sane. 35 | self.wait_for_cell_value(1, 2, '__builtin__.Basilisk') 36 | 37 | # * He refreshes the page. 38 | self.selenium.refresh() 39 | self.wait_for_grid_to_appear() 40 | 41 | # * Everything still looks OK. 42 | self.wait_for_cell_value(1, 1, re.compile(r'<__builtin__.Basilisk instance at 0x.*>')) 43 | self.wait_for_cell_value(1, 2, '__builtin__.Basilisk') 44 | 45 | # * Disappointed at not being about to break Dirigible, he goes out to take 46 | # potshots at the Goodyear blimp with a shotgun. 47 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2726_FormulaBarTextSelection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | from key_codes import LEFT, LETTER_S 8 | 9 | 10 | class Test_2726_FormulaBarSelectionDefect(FunctionalTest): 11 | 12 | def test_can_select_text_in_formula_bar(self): 13 | # * Harold logs in to Dirigible and creates a new sheet 14 | self.login_and_create_new_sheet() 15 | 16 | long_string = '1234567890' * 10 17 | self.enter_cell_text(1, 1, long_string) 18 | self.wait_for_cell_value(1, 1, long_string) 19 | self.click_on_cell(1, 1) 20 | 21 | self.selenium.click('id=id_formula_bar') 22 | self.human_key_press(LEFT) 23 | self.human_key_press(LEFT) 24 | self.human_key_press(LEFT) 25 | self.human_key_press(LEFT) 26 | self.human_key_press(LEFT) 27 | self.human_key_press(LEFT) 28 | 29 | self.selenium.click('id=id_formula_bar') 30 | 31 | self.human_key_press(LETTER_S) 32 | self.human_key_press(LETTER_S) 33 | self.assertTrue( 34 | self.selenium.get_value( 35 | self.get_formula_bar_locator()).endswith( 36 | '1234ss567890' 37 | ) 38 | ) 39 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2735_CtrlKeysArePassedOnToBrowser.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from __future__ import with_statement 6 | 7 | from functionaltest import FunctionalTest 8 | import key_codes 9 | 10 | 11 | class Test_2735_CtrlKeysArePassedToBrowser(FunctionalTest): 12 | 13 | def test_ctrl_t(self): 14 | # * Harold logs in to Dirigible and creates a new sheet 15 | self.login_and_create_new_sheet() 16 | 17 | # * He clicks the grid then types ctrl-t 18 | self.click_on_cell(1, 1) 19 | with self.key_down(key_codes.CTRL): 20 | self.human_key_press(key_codes.LETTER_T) 21 | 22 | # * A new tab has been opened. NB no way to test this 23 | # was found after quite a while investigating. 24 | # * Harold switches back 25 | self.selenium.select_window('null') 26 | 27 | # * The current cell is not being edited. 28 | self.assert_cell_is_current_but_not_editing(1, 1) 29 | # * The current cell does not contain a 't' 30 | self.assertEquals('', self.get_cell_text(1, 1)) 31 | 32 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2741_Xlrd.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2741_Xlrd(FunctionalTest): 9 | 10 | def test_can_use_xlrd(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # He imports xlrd 15 | self.prepend_usercode("import xlrd") 16 | self.append_usercode("worksheet.A1.value = 4") 17 | 18 | # * and it works 19 | self.wait_for_cell_value(1, 1, '4') 20 | 21 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2749_DisallowArbitraryKeysForWorksheet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2749_DisallowArbitraryKeysForWorksheet(FunctionalTest): 9 | 10 | def test_cant_use_silly_worksheet_keys(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # * He writes usercode to use a non-cell-reference key with the worksheet 15 | self.append_usercode("worksheet['fred'].value = 23") 16 | 17 | # * He gets an error 18 | self.wait_for_console_content("KeyError: 'fred' is not a valid cell location") 19 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2751_UsefulModules.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | from functionaltest import FunctionalTest 8 | 9 | 10 | class test_2751_UsefulModules(FunctionalTest): 11 | 12 | def test_can_import_useful_modules(self): 13 | # * Harold logs in to Dirigible and creates a new sheet 14 | self.login_and_create_new_sheet() 15 | 16 | # He writes some usercode that imports a bunch of modules 17 | # and then sets a cell to a value to prove that it worked. 18 | self.prepend_usercode(dedent(""" 19 | ## pycrypto 20 | from Crypto.Hash import MD5 21 | ## sqlalchemy 22 | from sqlalchemy import * 23 | ## lxml 24 | from lxml import * 25 | ## rdflib 26 | from rdflib.graph import Graph 27 | ## geopy 28 | from geopy import * 29 | ## BeautifulSoup 30 | from BeautifulSoup import * 31 | ## mechanize 32 | from mechanize import * 33 | 34 | worksheet.A1.value = 21 35 | """)) 36 | 37 | # The cell has the right value 38 | self.wait_for_cell_value(1, 1, "21") -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2758_LoadGridDataOnDemand.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from __future__ import with_statement 6 | 7 | from functionaltest import FunctionalTest 8 | import key_codes 9 | 10 | from textwrap import dedent 11 | 12 | 13 | class Test_2758_LoadGridDataOnDemand(FunctionalTest): 14 | 15 | def test_grid_data_loaded_on_demand_for_rows(self): 16 | # * Harold logs in to Dirigible and creates a new sheet 17 | self.login_and_create_new_sheet() 18 | 19 | # * Using usercode, he puts a blob of data far down and to 20 | # the right in the sheet. 21 | self.append_usercode(dedent(""" 22 | for col in range(1, 53): 23 | for row in range(600, 1001): 24 | worksheet[col, row].value = "(%s, %s)" % (col, row) 25 | """)) 26 | 27 | # * He waits for the recalc to complete. 28 | self.wait_for_spinner_to_stop() 29 | # He makes sure the grid has focus for the subsequent keypress 30 | self.click_on_cell(1, 1) 31 | 32 | # * then types Ctrl-End 33 | with self.key_down(key_codes.CTRL): 34 | self.human_key_press(key_codes.END) 35 | 36 | # * The grid scrolls down and to the right, but the cells are initially 37 | # empty 38 | self.wait_for_cell_to_be_visible(52, 1000) 39 | self.wait_for_cell_value(52, 1000, '') 40 | 41 | # * A "Buffering" window appears, relatively briefly 42 | self.wait_for_element_visibility("id=id_buffering_message", True) 43 | 44 | # * When the "buffering" window disappears, the data is there. 45 | self.wait_for_element_visibility("id=id_buffering_message", False) 46 | self.wait_for_cell_value(52, 1000, '(52, 1000)') 47 | 48 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2762_PythonConversion.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2762_PythonConversion(FunctionalTest): 9 | 10 | def test_formulas_work_properly(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # the following formula was causing errors 15 | self.enter_cell_text(1, 1, '10') 16 | self.enter_cell_text(1, 2, '=[x * A1 for x in range(5)]') 17 | self.wait_for_cell_value(1, 2, '[0, 10, 20, 30, 40]') 18 | 19 | # as was this one 20 | self.enter_cell_text(2, 1, '10') 21 | self.enter_cell_text(2, 2, '={1 -> B1}[1]') 22 | self.wait_for_cell_value(2, 2, '10') 23 | 24 | # and this 25 | self.enter_cell_text(3, 1, '10') 26 | self.enter_cell_text(3, 2, '=(lambda x -> C1 * x)(2)') 27 | self.wait_for_cell_value(3, 2, '20') 28 | 29 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2770_ClearDependentCellErrors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2770_ClearDependentCellErrors(FunctionalTest): 9 | 10 | def test_dependent_cell_errors_are_cleared(self): 11 | # * Harold logs in to Dirigible and creates a new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # * He enters "=1/0" into A1 15 | self.enter_cell_text(1, 1, '=1/0') 16 | self.wait_for_spinner_to_stop() 17 | # * He enters "=A1 + 1" into A2 18 | self.enter_cell_text(1, 2, '=A1 + 1') 19 | self.wait_for_spinner_to_stop() 20 | # * He enters "=A2 + 1" into A3 21 | self.enter_cell_text(1, 3, '=A2 + 1') 22 | self.wait_for_spinner_to_stop() 23 | 24 | # * He confirms that both A1 and A2 have errors 25 | self.assert_cell_has_error(1, 1, 'ZeroDivisionError: division by zero') 26 | self.assert_cell_has_error( 27 | 1, 2, 28 | "TypeError: unsupported operand type(s) for +: 'Undefined' and 'int'" 29 | ) 30 | self.assert_cell_has_error( 31 | 1, 3, 32 | "TypeError: unsupported operand type(s) for +: 'Undefined' and 'int'" 33 | ) 34 | 35 | # * He changes A1 to "=1" 36 | self.enter_cell_text(1, 1, '=1') 37 | 38 | # * A1's error will clear. 39 | self.wait_for_cell_value(1, 1, '1') 40 | # * A2's should too 41 | self.wait_for_cell_value(1, 2, '2') 42 | # * and A3's should too 43 | self.wait_for_cell_value(1, 3, '3') 44 | 45 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2781_FormulaAndFormattedValueMustBeStrings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | from functionaltest import FunctionalTest 8 | 9 | 10 | class Test_2781_FormulaAndFormattedValueMustBeStrings(FunctionalTest): 11 | 12 | def test_str_formula_and_formatted_value(self): 13 | # * Harold wants to be perverse and do strange and unnatural things 14 | # to cells. 15 | # * He logs in and creates a new sheet 16 | self.login_and_create_new_sheet() 17 | 18 | # * He sets the formula to an object that he made 19 | self.enter_usercode(dedent(''' 20 | class HaroldsObject(object): 21 | pass 22 | 23 | worksheet.A1.formula = HaroldsObject() 24 | ''')) 25 | 26 | # * Dirigible complains. 27 | self.wait_for_console_content( 28 | 'TypeError: cell formula must be str or unicode\n User code line 5' 29 | ) 30 | 31 | # * He tries to molest the formatted_value instead 32 | self.enter_usercode(dedent(''' 33 | class HaroldsObject(object): 34 | pass 35 | 36 | worksheet.A1.formatted_value = HaroldsObject() 37 | ''')) 38 | 39 | self.wait_for_console_content( 40 | 'TypeError: cell formatted_value must be str or unicode\n User code line 5' 41 | ) 42 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2789_ErrorConsoleHTMLEscape.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2789_ErrorConsoleHTMLEscape(FunctionalTest): 9 | 10 | def test_error_console_output_is_escaped(self): 11 | # Harold logs in to Dirigible and creates a new sheet and puts some super-sekrit data in it. 12 | self.login_and_create_new_sheet() 13 | 14 | # * He enters usercode that writes HTML to the error console 15 | self.append_usercode("print 'not bold'") 16 | 17 | # * and notes that the result is correctly escaped 18 | self.wait_for_console_content("not bold") 19 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2828_GridShouldNotStealFocusOnRecalc.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from textwrap import dedent 6 | 7 | import key_codes 8 | from functionaltest import FunctionalTest, snapshot_on_error 9 | 10 | 11 | class Test_2828_GridShouldNotStealFocusOnRecalc(FunctionalTest): 12 | 13 | @snapshot_on_error 14 | def test_recalc_doesnt_steal_focus_from_usercode(self): 15 | # Harold logs on and creates a sheet 16 | self.login_and_create_new_sheet() 17 | 18 | # Harold enters some usercode that takes a while to recalc 19 | # setting a value in the grid 20 | self.enter_usercode(dedent(''' 21 | import time 22 | time.sleep(5) 23 | ''')) 24 | 25 | # while the recalc is running he types something in the editor 26 | #self.selenium.focus('id=id_usercode') 27 | self.selenium.get_eval('window.editor.focus()') 28 | self.human_key_press(key_codes.LETTER_A) 29 | 30 | # When the recalc comes back, he types some more text, and notes with 31 | # satifaction that the grid has not stolen the focus 32 | self.wait_for_spinner_to_stop() 33 | self.human_key_press(key_codes.LETTER_A) 34 | 35 | self.wait_for( 36 | lambda : 'aa' in self.get_usercode(), 37 | lambda : "Usercode editor didn't contain aa::\n%s" % ( 38 | self.get_usercode(), 39 | ), 40 | ) 41 | 42 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2839_CutCopyPasteButtons.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2839_CutCopyPasteButtons(FunctionalTest): 9 | 10 | def test_buttons_exist_and_are_clickable(self): 11 | # * Harold logs in to Dirigible and creates a nice shiny new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # * He sees that toolbar buttons exist for cut, copy, paste 15 | self.wait_for_element_visibility('id=id_cut_button', True) 16 | self.wait_for_element_visibility('id=id_copy_button', True) 17 | self.wait_for_element_visibility('id=id_paste_button', True) 18 | 19 | # * He enters a simple cell value 20 | self.enter_cell_text(1, 1, "Sausage") 21 | 22 | # * He cuts it using the toolbar button, and it dissapears 23 | self.click_on_cell(1, 1) 24 | self.selenium.click('id=id_cut_button') 25 | self.wait_for_cell_value(1, 1, '') 26 | 27 | # * He pastes it elsewhere, he confirms it appears there 28 | self.click_on_cell(2, 1) 29 | self.selenium.click('id=id_paste_button') 30 | self.wait_for_cell_value(2, 1, 'Sausage') 31 | 32 | # * He copies it and pastes elsewhere 33 | self.click_on_cell(2, 1) 34 | self.selenium.click('id=id_copy_button') 35 | self.click_on_cell(3, 1) 36 | self.selenium.click('id=id_paste_button') 37 | # * He makes sure the data is visible in both locations 38 | self.wait_for_cell_value(3, 1, 'Sausage') 39 | self.wait_for_cell_value(2, 1, 'Sausage') 40 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2844_CantMakeRowHeaderActive.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2844_CantMakeRowHeaderActive(FunctionalTest): 9 | 10 | def test_cant_make_row_header_active(self): 11 | ## Of course, this FT will have to go when we can select whole rows/cols. 12 | 13 | # * Harold logs in to Dirigible and creates a nice shiny new sheet 14 | self.login_and_create_new_sheet() 15 | 16 | # * He notes that A1 is the active cell 17 | self.wait_for_cell_to_become_active(1, 1) 18 | 19 | # * He clicks on the row header (column 0) for row 3. 20 | self.click_on_cell(0, 3) 21 | 22 | # * He notes that A1 is still the active cell 23 | self.wait_for_cell_to_become_active(1, 1) 24 | 25 | # * He does a small mouse drag within the header (column 0) 26 | self.mouse_drag((0, 3), (0, 4)) 27 | 28 | # * He notes that A1 is still the active cell 29 | self.wait_for_cell_to_become_active(1, 1) 30 | 31 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2848_WorksheetBounds.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | 8 | class Test_2848_WorksheetBounds(FunctionalTest): 9 | 10 | def test_access_worksheet_bounds(self): 11 | # * Harold logs in to Dirigible and creates a nice shiny new sheet 12 | self.login_and_create_new_sheet() 13 | 14 | # He enters some data 15 | self.enter_cell_text(4, 2, "Top right") 16 | self.enter_cell_text(2, 10, "Bottom left") 17 | 18 | # He writes some usercode to access the bounds of the worksheet 19 | self.append_usercode("worksheet[3, 5].value = worksheet.bounds") 20 | self.append_usercode("worksheet[3, 6].value = worksheet.bounds.bottom") 21 | 22 | # ...and is delighted to discover it works! 23 | self.wait_for_cell_value(3, 5, "(2, 2, 4, 10)") 24 | self.wait_for_cell_value(3, 6, "10") 25 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2862_CopyAndPasteFormulaWithErrors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | import time 6 | from textwrap import dedent 7 | 8 | import key_codes 9 | from functionaltest import FunctionalTest, snapshot_on_error 10 | 11 | 12 | class Test_2892_CopyAndPasteFormulaWithErrors(FunctionalTest): 13 | 14 | @snapshot_on_error 15 | def test_recalc_doesnt_steal_focus_from_usercode(self): 16 | # Harold logs on and creates a sheet 17 | self.login_and_create_new_sheet() 18 | 19 | # Harold enters a formula with a sweary error in it 20 | self.enter_cell_text(1, 1, '=:!&@#&**%!') 21 | self.wait_for_spinner_to_stop() 22 | 23 | # He then tries to spread the joy by cut & pasting it 24 | self.copy_range((1, 1), (1, 1)) 25 | self.paste_range((1, 2), (1, 2)) 26 | 27 | # He sees the invalid formula in both cells now 28 | 29 | self.wait_for_cell_to_contain_formula(1, 1, '=:!&@#&**%!') 30 | self.wait_for_cell_to_contain_formula(1, 2, '=:!&@#&**%!') 31 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2872_CellTooltips.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | import time 6 | from textwrap import dedent 7 | 8 | import key_codes 9 | from functionaltest import FunctionalTest, snapshot_on_error 10 | 11 | 12 | class Test_2892_CellTooltips(FunctionalTest): 13 | 14 | @snapshot_on_error 15 | def test_formulae_are_shown_in_tooltips(self): 16 | # Harold logs on and creates a sheet 17 | self.login_and_create_new_sheet() 18 | 19 | # Harold enters a formula that won't evaluate 20 | self.enter_cell_text(1, 1, '=A2') 21 | self.wait_for_spinner_to_stop() 22 | 23 | # He then sees that a tooltip is set on the cell that would show up if 24 | # he hovered over it 25 | 26 | tooltip = self.selenium.get_attribute( 27 | self.get_cell_shown_formula_locator(1, 1) + '@title' 28 | ) 29 | 30 | self.assertEquals(tooltip, '=A2') 31 | 32 | @snapshot_on_error 33 | def test_formatted_values_are_shown_in_tooltips(self): 34 | # Harold logs on and creates a sheet 35 | self.login_and_create_new_sheet() 36 | 37 | # Harold enters a piece of text into a cell. Harold knows the basics of 38 | # spreadsheets... 39 | self.enter_cell_text(1, 1, 'Maude') 40 | self.wait_for_spinner_to_stop() 41 | 42 | # He then sees that a tooltip is set on the cell that would show up if 43 | # he hovered over it 44 | 45 | tooltip = self.selenium.get_attribute( 46 | self.get_cell_formatted_value_locator(1, 1) + '@title' 47 | ) 48 | 49 | self.assertEquals(tooltip, 'Maude') 50 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_2873_IE_Warning.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd. 2 | # All Rights Reserved 3 | # 4 | 5 | from functionaltest import FunctionalTest 6 | 7 | class Test_2873_IE_Warning(FunctionalTest): 8 | 9 | def test_IE_warnings(self): 10 | should_warning_be_visible = 'iexplore' in self.selenium.browserStartCommand 11 | 12 | # Harold goes to the Dirigible home page 13 | self.go_to_url('/') 14 | 15 | # No warning here, either way... 16 | self.wait_for_element_presence('id=id_ie_warning', False) 17 | 18 | # He clicks on the signup link 19 | self.click_link("id_signup_link") 20 | 21 | # If he's using IE, he spots a warning sign telling him he ain't 22 | # weclome round these parts. If he isn't, he doesn't. 23 | self.wait_for_element_presence('id=id_ie_warning', should_warning_be_visible) 24 | 25 | # * He then logs in and goes to a new sheet 26 | self.login_and_create_new_sheet() 27 | 28 | # If he's using IE, he spots a warning sign telling him he ain't 29 | # weclome round these parts. If he isn't, he doesn't. 30 | self.wait_for_element_presence('id=id_ie_warning', should_warning_be_visible) 31 | 32 | # He goes back to his account page and sees a warning there too 33 | self.go_to_url('/') 34 | self.wait_for_element_presence('id=id_ie_warning', should_warning_be_visible) 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/T2711-badly-named-png.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/fts/tests/test_data/T2711-badly-named-png.xls -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/T2711-import-excel.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/fts/tests/test_data/T2711-import-excel.xls -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/csv_file.csv: -------------------------------------------------------------------------------- 1 | 1,2,3 2 | a,b,c 3 | £12.95,Sacrébleu!, 4 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/excel_generated_csv.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/fts/tests/test_data/excel_generated_csv.csv -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/expected_csv_file.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/fts/tests/test_data/expected_csv_file.csv -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/expected_unicode_csv.csv: -------------------------------------------------------------------------------- 1 | ゼロウィング 2 | -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/import_csv_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/fts/tests/test_data/import_csv_button.png -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/japanese.csv: -------------------------------------------------------------------------------- 1 | 新世紀エヴァンゲリオン -------------------------------------------------------------------------------- /dirigible/fts/tests/test_data/public_sheet_csv_file.csv: -------------------------------------------------------------------------------- 1 | , 2 | , 3 | ,23 4 | ,25 5 | -------------------------------------------------------------------------------- /dirigible/info_pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/info_pages/__init__.py -------------------------------------------------------------------------------- /dirigible/info_pages/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/info_pages/migrations/__init__.py -------------------------------------------------------------------------------- /dirigible/info_pages/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /dirigible/info_pages/templates/video.html: -------------------------------------------------------------------------------- 1 | {% extends "non_sheet_page_small_logo.html" %} 2 | 3 | 4 | {% block title %} 5 | The Video: Dirigible 6 | {% endblock %} 7 | 8 | 9 | {% block head %} 10 | {{ block.super }} 11 | 12 | {% endblock %} 13 | 14 | {% block content %} 15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 | 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /dirigible/info_pages/tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dirigible/info_pages/views.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.shortcuts import render_to_response 6 | 7 | from user.views import user_dashboard 8 | 9 | 10 | def front_page_view(request): 11 | if request.user.is_authenticated(): 12 | return user_dashboard(request) 13 | else: 14 | return non_logged_in_front_page_view(request) 15 | 16 | 17 | def non_logged_in_front_page_view(request): 18 | return render_to_response('non_logged_in_front_page.html', {}) 19 | 20 | 21 | def info_page_view(request, template_name): 22 | return render_to_response( 23 | '%s.html' % (template_name,), {'user': request.user} 24 | ) 25 | -------------------------------------------------------------------------------- /dirigible/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dirigible.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /dirigible/registration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/__init__.py -------------------------------------------------------------------------------- /dirigible/registration/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from registration.models import RegistrationProfile 4 | 5 | 6 | class RegistrationAdmin(admin.ModelAdmin): 7 | list_display = ('__unicode__', 'activation_key_expired') 8 | search_fields = ('user__username', 'user__first_name') 9 | 10 | 11 | admin.site.register(RegistrationProfile, RegistrationAdmin) 12 | -------------------------------------------------------------------------------- /dirigible/registration/locale/ar/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/ar/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/bg/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/bg/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/el/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/el/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/en/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/en/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/en/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2007-09-19 19:30-0500\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: forms.py:38 20 | msgid "username" 21 | msgstr "" 22 | 23 | #: forms.py:41 24 | msgid "email address" 25 | msgstr "" 26 | 27 | #: forms.py:43 28 | msgid "password" 29 | msgstr "" 30 | 31 | #: forms.py:45 32 | msgid "password (again)" 33 | msgstr "" 34 | 35 | #: forms.py:54 36 | msgid "Usernames can only contain letters, numbers and underscores" 37 | msgstr "" 38 | 39 | #: forms.py:59 40 | msgid "This username is already taken. Please choose another." 41 | msgstr "" 42 | 43 | #: forms.py:68 44 | msgid "You must type the same password each time" 45 | msgstr "" 46 | 47 | #: forms.py:96 48 | msgid "I have read and agree to the Terms of Service" 49 | msgstr "" 50 | 51 | #: forms.py:105 52 | msgid "You must agree to the terms to register" 53 | msgstr "" 54 | 55 | #: forms.py:124 56 | msgid "" 57 | "This email address is already in use. Please supply a different email " 58 | "address." 59 | msgstr "" 60 | 61 | #: forms.py:149 62 | msgid "" 63 | "Registration using free email addresses is prohibited. Please supply a " 64 | "different email address." 65 | msgstr "" 66 | 67 | #: models.py:188 68 | msgid "user" 69 | msgstr "" 70 | 71 | #: models.py:189 72 | msgid "activation key" 73 | msgstr "" 74 | 75 | #: models.py:194 76 | msgid "registration profile" 77 | msgstr "" 78 | 79 | #: models.py:195 80 | msgid "registration profiles" 81 | msgstr "" 82 | -------------------------------------------------------------------------------- /dirigible/registration/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/es_AR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/es_AR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/he/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/he/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/it/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/it/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/ja/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/ja/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/sr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/sr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/sv/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/sv/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/zh_CN/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/zh_CN/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/locale/zh_TW/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/locale/zh_TW/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dirigible/registration/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/management/__init__.py -------------------------------------------------------------------------------- /dirigible/registration/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/management/commands/__init__.py -------------------------------------------------------------------------------- /dirigible/registration/management/commands/cleanupregistration.py: -------------------------------------------------------------------------------- 1 | """ 2 | A management command which deletes expired accounts (e.g., 3 | accounts which signed up but never activated) from the database. 4 | 5 | Calls ``RegistrationProfile.objects.delete_expired_users()``, which 6 | contains the actual logic for determining which accounts are deleted. 7 | 8 | """ 9 | 10 | from django.core.management.base import NoArgsCommand 11 | 12 | from registration.models import RegistrationProfile 13 | 14 | 15 | class Command(NoArgsCommand): 16 | help = "Delete expired user registrations from the database" 17 | 18 | def handle_noargs(self, **options): 19 | RegistrationProfile.objects.delete_expired_users() 20 | -------------------------------------------------------------------------------- /dirigible/registration/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='RegistrationProfile', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('activation_key', models.CharField(max_length=40, verbose_name='activation key')), 20 | ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, unique=True)), 21 | ], 22 | options={ 23 | 'verbose_name': 'registration profile', 24 | 'verbose_name_plural': 'registration profiles', 25 | }, 26 | bases=(models.Model,), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /dirigible/registration/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/registration/migrations/__init__.py -------------------------------------------------------------------------------- /dirigible/shared/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/__init__.py -------------------------------------------------------------------------------- /dirigible/shared/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/about-goal-planning-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/about-goal-planning-screenshot.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/error.gif -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/find-out-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/find-out-more.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/logo_transparent_289x66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/logo_transparent_289x66.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/logo_transparent_490x112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/logo_transparent_490x112.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/spinner-small-blue-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/spinner-small-blue-bg.gif -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/spinner-small-white-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/spinner-small-white-bg.gif -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/spinner.gif -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/took-0.01s-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/took-0.01s-screenshot.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/toolbar/api_button_disabled.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/toolbar/api_button_disabled.pdn -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/toolbar/copy_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/toolbar/copy_button.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/toolbar/cut_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/toolbar/cut_button.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/toolbar/export_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/toolbar/export_button.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/toolbar/import_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/toolbar/import_button.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/toolbar/paste_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/toolbar/paste_button.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/toolbar/recalc_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/toolbar/recalc_button.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/toolbar/security_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/toolbar/security_button.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/usercode_error_indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/usercode_error_indicator.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/images/watch-a-video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/dirigible/images/watch-a-video.png -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/scripts/console_view.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | // See LICENSE.md 3 | // 4 | 5 | (function($) { 6 | $.extend(true, window, { 7 | "Dirigible": { 8 | "ConsoleView": ConsoleView 9 | } 10 | }); 11 | 12 | function ConsoleView($consoleDiv) { 13 | var self = this; 14 | 15 | self.updateMetaData = function(sheetMetaData) { 16 | if ('console_text' in sheetMetaData) { 17 | $consoleDiv.html(sheetMetaData['console_text']); 18 | } else { 19 | $consoleDiv.html(''); 20 | } 21 | }; 22 | } 23 | 24 | })(jQuery); 25 | -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/scripts/editor_commands.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | // See LICENSE.md 3 | // 4 | 5 | 6 | (function($) { 7 | // register namespace 8 | $.extend(true, window, { 9 | "Dirigible": { 10 | "EditorCommands": EditorCommands 11 | } 12 | }); 13 | 14 | function EditorCommands(urls, editor) { 15 | 16 | function saveUsercode() { 17 | if (!editor.usercodeDirty) { 18 | return; 19 | } 20 | Dirigible.SheetUtils.abortOtherRecalculations(); 21 | var text = editor.getSession().getValue(); 22 | $.post( 23 | urls.setSheetUsercode, 24 | { usercode: text }, 25 | Dirigible.SheetUtils.queueRecalculation 26 | ); 27 | editor.usercodeDirty = false; 28 | } 29 | 30 | $.extend( 31 | this, 32 | { 33 | 'saveUsercode': saveUsercode 34 | } 35 | ); 36 | } 37 | 38 | })(jQuery); 39 | 40 | -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/scripts/grid_content_converter.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | // See LICENSE.md 3 | // 4 | 5 | function getGridCellFormula(grid, slickCellLocation) { 6 | var columnField = grid.getColumns()[slickCellLocation.cell].field; 7 | var rowData = grid.getDataItem(slickCellLocation.row); 8 | if (rowData !== undefined) { 9 | var cellData = rowData[columnField]; 10 | if (cellData !== undefined && cellData.formula !== undefined) { 11 | return cellData.formula; 12 | } 13 | } 14 | return ""; 15 | } 16 | 17 | 18 | function slickGridCellDataToPostParams(grid, slickCellLocation) { 19 | return { 20 | column: slickCellLocation.cell, 21 | row: slickCellLocation.row + 1, 22 | formula: grid.getCellFormula(slickCellLocation) 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/scripts/htmlescape.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | // See LICENSE.md 3 | // 4 | 5 | function htmlescape(untrusted_string) { 6 | var better_string = untrusted_string.replace(/&/g, '&'); 7 | better_string = better_string.replace(/>/g, '>'); 8 | better_string = better_string.replace(/ 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Please wait
18 | 19 | 20 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/tests/htmlescape_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
Please wait
17 | 18 | 19 | 45 | 46 |
47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/tests/testlogger.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Yahoo! Inc. All rights reserved. 3 | Code licensed under the BSD License: 4 | http://developer.yahoo.net/yui/license.txt 5 | version: 2.6.0 6 | */ 7 | .yui-log {padding-top:3em;} 8 | /* 9 | .yui-log-container {position:relative;width:60em} 10 | .yui-log .yui-log-bd {height:auto; overflow:visible;} 11 | */ 12 | .yui-log-container {width:70em} 13 | .yui-log .yui-log-bd {height:60em} 14 | .yui-log .yui-log-btns {display:none;} 15 | .yui-log .yui-log-ft .yui-log-sourcefilters {visibility:hidden;} 16 | .yui-log .yui-log-hd {display:none;} 17 | .yui-log .yui-log-ft {position:absolute;top:0em;} 18 | 19 | .pass { 20 | background-color: green; 21 | font-weight: bold; 22 | color: white; 23 | } 24 | .fail { 25 | background-color: red; 26 | font-weight: bold; 27 | color: white; 28 | } 29 | .ignore { 30 | background-color: #666; 31 | font-weight: bold; 32 | color: white; 33 | } 34 | -------------------------------------------------------------------------------- /dirigible/shared/static/dirigible/tests/yuirunner.js: -------------------------------------------------------------------------------- 1 | 2 | function escape_quotes(key, value) { 3 | if (typeof value === 'string') { 4 | // just replacing '<' seems enough to cause json encoding to 5 | // kick in for '>', etc. 6 | return value.replace(/\Cloudgen Examplet Store) 4 | * Licensed under the MIT License: 5 | * http://www.opensource.org/licenses/mit-license.php 6 | * 7 | */ 8 | (function(k,e,i,j){k.fn.caret=function(b,l){var a,c,f=this[0],d=k.browser.msie;if(typeof b==="object"&&typeof b.start==="number"&&typeof b.end==="number"){a=b.start;c=b.end}else if(typeof b==="number"&&typeof l==="number"){a=b;c=l}else if(typeof b==="string")if((a=f.value.indexOf(b))>-1)c=a+b[e];else a=null;else if(Object.prototype.toString.call(b)==="[object RegExp]"){b=b.exec(f.value);if(b!=null){a=b.index;c=a+b[0][e]}}if(typeof a!="undefined"){if(d){d=this[0].createTextRange();d.collapse(true); 9 | d.moveStart("character",a);d.moveEnd("character",c-a);d.select()}else{this[0].selectionStart=a;this[0].selectionEnd=c}this[0].focus();return this}else{if(d){c=document.selection;if(this[0].tagName.toLowerCase()!="textarea"){d=this.val();a=c[i]()[j]();a.moveEnd("character",d[e]);var g=a.text==""?d[e]:d.lastIndexOf(a.text);a=c[i]()[j]();a.moveStart("character",-d[e]);var h=a.text[e]}else{a=c[i]();c=a[j]();c.moveToElementText(this[0]);c.setEndPoint("EndToEnd",a);g=c.text[e]-a.text[e];h=g+a.text[e]}}else{g= 10 | f.selectionStart;h=f.selectionEnd}a=f.value.substring(g,h);return{start:g,end:h,text:a,replace:function(m){return f.value.substring(0,g)+m+f.value.substring(h,f.value[e])}}}}})(jQuery,"length","createRange","duplicate"); -------------------------------------------------------------------------------- /dirigible/shared/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /user/ 3 | Disallow: /feedback/submit 4 | -------------------------------------------------------------------------------- /dirigible/shared/static/slickgrid/MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Michael Leibman, http://github.com/mleibman/slickgrid 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /dirigible/shared/static/slickgrid/slick.cellrangedecorator.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | // register namespace 3 | $.extend(true, window, { 4 | "Slick": { 5 | "CellRangeDecorator": CellRangeDecorator 6 | } 7 | }); 8 | 9 | /*** 10 | * Displays an overlay on top of a given cell range. 11 | * 12 | * TODO: 13 | * Currently, it blocks mouse events to DOM nodes behind it. 14 | * Use FF and WebKit-specific "pointer-events" CSS style, or some kind of event forwarding. 15 | * Could also construct the borders separately using 4 individual DIVs. 16 | * 17 | * @param {Grid} grid 18 | * @param {Object} options 19 | */ 20 | function CellRangeDecorator(grid, options) { 21 | var _elem; 22 | var _defaults = { 23 | selectionCss: { 24 | "zIndex": "9999", 25 | "border": "2px dashed red" 26 | } 27 | }; 28 | 29 | options = $.extend(true, {}, _defaults, options); 30 | 31 | 32 | function show(range) { 33 | if (!_elem) { 34 | _elem = $("
", {css: options.selectionCss}) 35 | .css("position", "absolute") 36 | .appendTo(grid.getCanvasNode()); 37 | 38 | } 39 | 40 | var from = grid.getCellNodeBox(range.fromRow,range.fromCell); 41 | var to = grid.getCellNodeBox(range.toRow,range.toCell); 42 | 43 | _elem.css({ 44 | top: from.top - 1, 45 | left: from.left - 1, 46 | height: to.bottom - from.top - 2, 47 | width: to.right - from.left - 2 48 | }); 49 | 50 | return _elem; 51 | } 52 | 53 | function hide() { 54 | if (_elem) { 55 | _elem.remove(); 56 | _elem = null; 57 | } 58 | } 59 | 60 | $.extend(this, { 61 | "show": show, 62 | "hide": hide 63 | }); 64 | } 65 | })(jQuery); -------------------------------------------------------------------------------- /dirigible/shared/static/splitter/img/hgrabber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/splitter/img/hgrabber.png -------------------------------------------------------------------------------- /dirigible/shared/static/splitter/img/vgrabber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/shared/static/splitter/img/vgrabber.png -------------------------------------------------------------------------------- /dirigible/shared/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "error_page.html" %} 2 | 3 | {% block title %} 4 | Access Forbidden: Dirigible 5 | {% endblock %} 6 | 7 | {% block error_title %}403 Access Forbidden{% endblock %} 8 | {% block error_text %} 9 | This page is private and belongs to someone else. 10 | {% endblock %} 11 | 12 | {% block error_recovery_link %} 13 | Return to Dirigible home 14 | {% endblock %} -------------------------------------------------------------------------------- /dirigible/shared/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "error_page.html" %} 2 | 3 | {% block title %} 4 | Page not found: Dirigible 5 | {% endblock %} 6 | 7 | {% block error_title %}404 Not Found{% endblock %} 8 | {% block error_text %} 9 | The page that you seek
10 | can't be found; lost in the clouds.
11 | Please look somewhere else 12 | {% endblock %} 13 | 14 | {% block error_recovery_link %} 15 | Return to Dirigible home 16 | {% endblock %} -------------------------------------------------------------------------------- /dirigible/shared/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "error_page.html" %} 2 | 3 | {% block title %} 4 | Server error: Dirigible 5 | {% endblock %} 6 | 7 | {% block error_title %}500 Internal Server Error{% endblock %} 8 | {% block error_text %} 9 | Sorry, something has gone wrong! 10 | The Dirigible team have notified and will look into the problem ASAP. 11 | {% endblock %} 12 | 13 | {% block error_recovery_link %} 14 | Return to Dirigible home 15 | {% endblock %} -------------------------------------------------------------------------------- /dirigible/shared/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block title %}{% endblock %} 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 31 | 32 | {% block head %} 33 | {% endblock %} 34 | 35 | 36 | 37 | 38 |

39 | {% block body %} 40 | {% endblock %} 41 | 42 | 43 | -------------------------------------------------------------------------------- /dirigible/shared/templates/error_page.html: -------------------------------------------------------------------------------- 1 | {% extends "non_sheet_page_small_logo.html" %} 2 | 3 | {% block head %} 4 | {{ block.super }} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{% block error_title %}{% endblock %}

13 | 14 |

15 | {% block error_text %}{% endblock %} 16 |

17 |

18 | {% block error_recovery_link %}{% endblock %} 19 |

20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /dirigible/shared/templates/footer_links_include.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /dirigible/shared/templates/info_page.html: -------------------------------------------------------------------------------- 1 | {% extends "non_sheet_page_small_logo.html" %} 2 | 3 | {% block head %} 4 | {{ block.super }} 5 | 6 | {% endblock %} 7 | 8 | 9 | {% block content %} 10 | 11 |
12 | 13 |
14 |
15 | 16 |
17 | 18 | {% block middle %} 19 | {% endblock %} 20 | 21 |
22 | 23 |
24 |
25 | 26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /dirigible/shared/templates/non_sheet_page_small_logo.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block head %} 4 | {{ block.super }} 5 | 6 | {% endblock %} 7 | 8 | 9 | {% block body %} 10 |
11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | {% include "header_links_include.html" %} 20 |
21 | 22 | 23 | {% block content %} 24 | {% endblock %} 25 | 26 | {% include "footer_links_include.html" %} 27 | 28 |
29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /dirigible/shared/tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dirigible/shared/tests/test_views.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | try: 6 | import unittest2 as unittest 7 | except ImportError: 8 | import unittest 9 | 10 | from django.http import HttpRequest, HttpResponsePermanentRedirect 11 | 12 | from shared.views import redirect_to 13 | 14 | 15 | class TestRedirectTo(unittest.TestCase): 16 | 17 | def test_redirect_to_no_params(self): 18 | request = HttpRequest() 19 | url = "http://blog.projectdirigible.com/" 20 | response = redirect_to(request, url) 21 | self.assertTrue(isinstance(response, HttpResponsePermanentRedirect)) 22 | self.assertEquals(response.status_code, 301) 23 | self.assertEquals(response['Location'], url) 24 | 25 | 26 | def test_redirect_to_with_params(self): 27 | request = HttpRequest() 28 | request.META["QUERY_STRING"] = 'feed=rss2' 29 | url = "http://blog.projectdirigible.com/" 30 | response = redirect_to(request, url) 31 | self.assertTrue(isinstance(response, HttpResponsePermanentRedirect)) 32 | self.assertEquals(response.status_code, 301) 33 | self.assertEquals(response['Location'], url + '?feed=rss2') 34 | -------------------------------------------------------------------------------- /dirigible/shared/views.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.http import HttpResponsePermanentRedirect 6 | 7 | 8 | # Our redirect_to supports query string parameters, which 9 | # the normal Django generic one (irritatingly) does not. 10 | def redirect_to(request, url): 11 | query_string = request.META.get("QUERY_STRING") 12 | if query_string: 13 | url = "%s?%s" % (url, query_string) 14 | return HttpResponsePermanentRedirect(url) 15 | -------------------------------------------------------------------------------- /dirigible/sheet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/sheet/__init__.py -------------------------------------------------------------------------------- /dirigible/sheet/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | from .models import Sheet 5 | from django.contrib import admin 6 | 7 | 8 | class SheetAdmin(admin.ModelAdmin): 9 | list_display = ('id', 'owner', 'name') 10 | list_filter = ('owner',) 11 | 12 | admin.site.register(Sheet, SheetAdmin) 13 | 14 | -------------------------------------------------------------------------------- /dirigible/sheet/dirigible_datetime.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | import datetime 5 | 6 | class DateTime(datetime.datetime): 7 | pass 8 | -------------------------------------------------------------------------------- /dirigible/sheet/errors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from .cell import undefined 6 | from .utils.cell_name_utils import coordinates_to_cell_name 7 | 8 | 9 | class CycleError(Exception): 10 | 11 | def __init__(self, path): 12 | self.path = path 13 | 14 | 15 | def __str__(self): 16 | return ' -> '.join(coordinates_to_cell_name(*loc) for loc in self.path) 17 | 18 | 19 | def __repr__(self): 20 | return 'CycleError(%s)' % (str(self),) 21 | 22 | 23 | def __eq__(self, other): 24 | if isinstance(other, CycleError): 25 | return self.path == other.path 26 | return False 27 | 28 | 29 | def __ne__(self, other): 30 | return not self.__eq__(other) 31 | 32 | 33 | def report_cell_error(worksheet, loc, exc): 34 | worksheet[loc].value = undefined 35 | worksheet[loc].error = "%s: %s" % (exc.__class__.__name__, str(exc)) 36 | worksheet.add_console_text("%s\n Formula '%s' in %s\n" % ( 37 | worksheet[loc].error, worksheet[loc].formula, coordinates_to_cell_name(*loc))) 38 | 39 | -------------------------------------------------------------------------------- /dirigible/sheet/eval_constant.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | 6 | def eval_constant(constant): 7 | try: 8 | temp = float(constant) 9 | try: 10 | return int(constant) 11 | except ValueError: 12 | return temp 13 | except ValueError: 14 | return constant 15 | 16 | -------------------------------------------------------------------------------- /dirigible/sheet/forms.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django import forms 6 | 7 | class ImportCSVForm(forms.Form): 8 | def __init__(self, *args, **kwargs): 9 | kwargs['auto_id'] = 'id_import_form_%s' 10 | forms.Form.__init__(self, *args, **kwargs) 11 | column = forms.IntegerField(widget=forms.widgets.HiddenInput) 12 | row = forms.IntegerField(widget=forms.widgets.HiddenInput) 13 | file = forms.FileField() 14 | 15 | -------------------------------------------------------------------------------- /dirigible/sheet/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/sheet/migrations/__init__.py -------------------------------------------------------------------------------- /dirigible/sheet/models.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from .clipboard import Clipboard 6 | from .sheet import Sheet 7 | from django.contrib.auth.models import User 8 | 9 | 10 | 11 | def copy_sheet_to_user(sheet, user): 12 | sheet.id = None 13 | sheet.owner = user 14 | sheet.is_public = False 15 | sheet.save() 16 | return sheet 17 | 18 | -------------------------------------------------------------------------------- /dirigible/sheet/parser/__init__.py: -------------------------------------------------------------------------------- 1 | class FormulaError(Exception): 2 | """ 3 | This exception is used to indicate that a cell expression is badly formed. 4 | It is the equivalent of a ``SyntaxError`` in normal Python code. 5 | """ -------------------------------------------------------------------------------- /dirigible/sheet/parser/fl_cell_range_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2009 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from sheet.parser.parse_node import ParseNode 6 | 7 | class FLCellRangeParseNode(ParseNode): 8 | 9 | def __init__(self, children): 10 | assert len(children) == 3 11 | ParseNode.__init__(self, ParseNode.FL_CELL_RANGE, children) 12 | 13 | def __get_first_cell_reference(self): 14 | return self.children[0] 15 | def __set_first_cell_reference(self, cellRef): 16 | self.children[0] = cellRef 17 | 18 | first_cell_reference = property(__get_first_cell_reference, __set_first_cell_reference) 19 | 20 | def __get_second_cell_reference(self): 21 | return self.children[2] 22 | 23 | def __set_second_cell_reference(self, cellRef): 24 | self.children[2] = cellRef 25 | 26 | second_cell_reference = property(__get_second_cell_reference, __set_second_cell_reference) 27 | 28 | @property 29 | def colon(self): 30 | return self.children[1] 31 | 32 | ParseNode.register_node_type(ParseNode.FL_CELL_RANGE, FLCellRangeParseNode) 33 | -------------------------------------------------------------------------------- /dirigible/sheet/parser/fl_cell_reference_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from sheet.parser.parse_node import ParseNode 6 | from sheet.parser.fl_reference_parse_node import FLReferenceParseNode 7 | from sheet.utils.cell_name_utils import cell_name_to_coordinates, coordinates_to_cell_name 8 | 9 | class FLCellReferenceParseNode(FLReferenceParseNode): 10 | 11 | def __init__(self, children): 12 | FLReferenceParseNode.__init__(self, ParseNode.FL_CELL_REFERENCE, children) 13 | 14 | 15 | @property 16 | def colAbsolute(self): 17 | return self.localReference.startswith("$") 18 | 19 | 20 | @property 21 | def rowAbsolute(self): 22 | return '$' in self.localReference[1:] 23 | 24 | 25 | @property 26 | def plainCellName(self): 27 | return self.localReference.replace('$', '') 28 | 29 | 30 | def __getLocalReference(self): 31 | return self.children[-1] 32 | 33 | def __setLocalReference(self, cellRef): 34 | self.children[-1] = cellRef 35 | 36 | localReference = property(__getLocalReference, __setLocalReference) 37 | 38 | 39 | @property 40 | def coords(self): 41 | return cell_name_to_coordinates(self.plainCellName) 42 | 43 | 44 | def offset(self, dx, dy, move_absolute=False): 45 | (col, row) = cell_name_to_coordinates(self.plainCellName) 46 | 47 | if move_absolute or not self.colAbsolute: 48 | col += dx 49 | if move_absolute or not self.rowAbsolute: 50 | row += dy 51 | 52 | newName = coordinates_to_cell_name(col, row, colAbsolute=self.colAbsolute, rowAbsolute=self.rowAbsolute) 53 | if newName is None: 54 | newName = "#Invalid!" 55 | 56 | self.localReference = newName + self.whitespace 57 | 58 | 59 | def canonicalise(self, wsNames): 60 | self.localReference = self.localReference.upper() 61 | FLReferenceParseNode.canonicalise(self, wsNames) 62 | 63 | 64 | ParseNode.register_node_type(ParseNode.FL_CELL_REFERENCE, FLCellReferenceParseNode) 65 | -------------------------------------------------------------------------------- /dirigible/sheet/parser/fl_column_reference_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2009 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from sheet.parser.parse_node import ParseNode 6 | from sheet.parser.fl_reference_parse_node import FLReferenceParseNode 7 | from sheet.utils.cell_name_utils import column_name_to_index, column_index_to_name 8 | 9 | 10 | class FLColumnReferenceParseNode(FLReferenceParseNode): 11 | 12 | def __init__(self, children): 13 | FLReferenceParseNode.__init__(self, ParseNode.FL_COLUMN_REFERENCE, children) 14 | 15 | 16 | @property 17 | def isAbsolute(self): 18 | return self.localReference.lstrip().startswith("$") 19 | 20 | 21 | def __getPlainColumnName(self): 22 | return self.localReference.strip().replace('$', '').replace('_', '') 23 | def __setPlainColumnName(self, newName): 24 | self.children[-1] = self.localReference.replace(self.plainColumnName, newName) 25 | plainColumnName = property(__getPlainColumnName, __setPlainColumnName) 26 | 27 | 28 | @property 29 | def colIndex(self): 30 | return column_name_to_index(self.plainColumnName) 31 | 32 | @property 33 | def coords(self): 34 | return self.colIndex, 0 35 | 36 | 37 | def offset(self, count, _, moveAbsolute=False): 38 | if not moveAbsolute and self.isAbsolute: 39 | return 40 | newName = column_index_to_name(self.colIndex + count) 41 | if newName: 42 | self.plainColumnName = newName 43 | else: 44 | self.localReference = '#Invalid!' + self.whitespace 45 | 46 | def __getLocalReference(self): 47 | return self.children[-1] 48 | def __setLocalReference(self, newCol): 49 | self.children[-1] = newCol 50 | 51 | localReference = property(__getLocalReference, __setLocalReference) 52 | 53 | 54 | def canonicalise(self, wsNames): 55 | self.localReference = self.localReference.upper() 56 | FLReferenceParseNode.canonicalise(self, wsNames) 57 | 58 | ParseNode.register_node_type(ParseNode.FL_COLUMN_REFERENCE, FLColumnReferenceParseNode) 59 | -------------------------------------------------------------------------------- /dirigible/sheet/parser/fl_named_column_reference_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2009 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from sheet.parser.parse_node import ParseNode 6 | from sheet.parser.fl_reference_parse_node import FLReferenceParseNode 7 | 8 | 9 | class FLNamedColumnReferenceParseNode(FLReferenceParseNode): 10 | 11 | def __init__(self, children): 12 | FLReferenceParseNode.__init__(self, ParseNode.FL_NAMED_COLUMN_REFERENCE, children) 13 | 14 | 15 | @property 16 | def header(self): 17 | return self.children[-1].rstrip().replace("##", "#")[1:-2] 18 | 19 | 20 | ParseNode.register_node_type(ParseNode.FL_NAMED_COLUMN_REFERENCE, FLNamedColumnReferenceParseNode) 21 | -------------------------------------------------------------------------------- /dirigible/sheet/parser/fl_named_row_reference_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2009 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from sheet.parser.parse_node import ParseNode 6 | from sheet.parser.fl_reference_parse_node import FLReferenceParseNode 7 | 8 | 9 | class FLNamedRowReferenceParseNode(FLReferenceParseNode): 10 | 11 | def __init__(self, children): 12 | FLReferenceParseNode.__init__(self, ParseNode.FL_NAMED_ROW_REFERENCE, children) 13 | 14 | 15 | @property 16 | def header(self): 17 | return self.children[-1].rstrip().replace("##", "#")[2:-1] 18 | 19 | 20 | ParseNode.register_node_type(ParseNode.FL_NAMED_ROW_REFERENCE, FLNamedRowReferenceParseNode) 21 | -------------------------------------------------------------------------------- /dirigible/sheet/parser/fl_reference_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | import re 5 | 6 | from sheet.parser.parse_node import ParseNode 7 | from sheet.utils.string_utils import correct_case, get_rstripped_part 8 | 9 | 10 | _worksheetNameRegex = re.compile(r"^[A-Za-z]\w*$") 11 | def quote_fl_worksheet_name(name): 12 | if re.match(_worksheetNameRegex, name) is not None: 13 | return name 14 | return "'%s'" % name.replace("'", "''") 15 | 16 | 17 | def unquote_fl_worksheet_name(name): 18 | name = name.replace("''", "'") 19 | if name.startswith("'"): 20 | return name[1:-1] 21 | return name 22 | 23 | 24 | class FLReferenceParseNode(ParseNode): 25 | 26 | def __init__(self, nodeType, children): 27 | assert len(children) in (1, 3) 28 | ParseNode.__init__(self, nodeType, children) 29 | 30 | 31 | @property 32 | def whitespace(self): 33 | return get_rstripped_part(self.children[-1]) 34 | 35 | 36 | def __getWorksheet(self): 37 | if len(self.children) == 3: 38 | return unquote_fl_worksheet_name(self.children[0].rstrip()) 39 | return None 40 | 41 | def __setWorksheet(self, ws): 42 | if ws is None: 43 | self.children = [self.children[-1]] 44 | return 45 | 46 | ws = quote_fl_worksheet_name(ws) 47 | if self.worksheetReference: 48 | self.children[0] = ws + get_rstripped_part(self.children[0]) 49 | else: 50 | self.children = [ws, '!', self.children[-1]] 51 | 52 | worksheetReference = property(__getWorksheet, __setWorksheet) 53 | 54 | 55 | def canonicalise(self, wsNames): 56 | if self.worksheetReference: 57 | self.worksheetReference = correct_case(self.worksheetReference, wsNames) 58 | -------------------------------------------------------------------------------- /dirigible/sheet/parser/fl_row_reference_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2009 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from sheet.parser.parse_node import ParseNode 6 | from sheet.parser.fl_reference_parse_node import FLReferenceParseNode 7 | 8 | class FLRowReferenceParseNode(FLReferenceParseNode): 9 | 10 | def __init__(self, children): 11 | ParseNode.__init__(self, ParseNode.FL_ROW_REFERENCE, children) 12 | 13 | @property 14 | def isAbsolute(self): 15 | return '$' in self.children[-1] 16 | 17 | def __getPlainRowName(self): 18 | return self.children[-1][1:].strip().replace('$', '') 19 | def __setPlainRowName(self, newName): 20 | self.children[-1] = self.localReference.replace(self.plainRowName, newName) 21 | plainRowName = property(__getPlainRowName, __setPlainRowName) 22 | 23 | @property 24 | def rowIndex(self): 25 | return int(self.plainRowName) 26 | 27 | 28 | @property 29 | def coords(self): 30 | return 0, self.rowIndex 31 | 32 | 33 | def offset(self, _, count, moveAbsolute=False): 34 | if not moveAbsolute and self.isAbsolute: 35 | return 36 | newIndex = self.rowIndex + count 37 | if newIndex > 0: 38 | self.plainRowName = str(newIndex) 39 | else: 40 | self.localReference = '#Invalid!' + self.whitespace 41 | 42 | 43 | def __getLocalReference(self): 44 | return self.children[-1] 45 | def __setLocalReference(self, newRow): 46 | self.children[-1] = newRow 47 | localReference = property(__getLocalReference, __setLocalReference) 48 | 49 | 50 | ParseNode.register_node_type(ParseNode.FL_ROW_REFERENCE, FLRowReferenceParseNode) 51 | 52 | -------------------------------------------------------------------------------- /dirigible/sheet/parser/parser.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | import os 6 | from ply import lex 7 | from ply import yacc 8 | from threading import Lock 9 | 10 | from . import grammar, tokens 11 | 12 | 13 | _parser = yacc.yacc( 14 | module=grammar, 15 | outputdir=os.path.dirname(__file__), 16 | method="LALR", 17 | debug=0, 18 | tabmodule='sheet.parser.parsetab' 19 | ) 20 | _lexer = lex.lex(tokens) 21 | 22 | _parser_lock = Lock() 23 | 24 | 25 | def parse(string): 26 | _parser_lock.acquire() 27 | try: 28 | return _parser.parse(string, _lexer) 29 | finally: 30 | _parser_lock.release() 31 | 32 | -------------------------------------------------------------------------------- /dirigible/sheet/templates/export_csv_error.html: -------------------------------------------------------------------------------- 1 | {% extends "error_page.html" %} 2 | 3 | {% block title %} 4 | CSV Export Error: Dirigible 5 | {% endblock %} 6 | 7 | {% block error_title %}Could not export CSV file{% endblock %} 8 | {% block error_text %} 9 | Sorry, your spreadsheet contains characters that cannot be saved in Excel CSV format.
10 | Please try again using the international version. 11 | See the documentation for more info. 12 | {% endblock %} 13 | 14 | 15 | {% block error_recovery_link %} 16 | Return to your sheet ({{sheet.name}}) 17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /dirigible/sheet/templates/import_csv_error.html: -------------------------------------------------------------------------------- 1 | {% extends "error_page.html" %} 2 | 3 | {% block title %} 4 | CSV Import Error: Dirigible 5 | {% endblock %} 6 | 7 | {% block error_title %}Could not import CSV file{% endblock %} 8 | {% block error_text %} 9 | Sorry, the file you uploaded was not in a recognised CSV format 10 | {% endblock %} 11 | 12 | 13 | {% block error_recovery_link %} 14 | Return to your sheet ({{sheet.name}}) 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /dirigible/sheet/templates/import_xls_error.html: -------------------------------------------------------------------------------- 1 | {% extends "error_page.html" %} 2 | 3 | {% block title %} 4 | Excel Import Error: Dirigible 5 | {% endblock %} 6 | 7 | {% block error_title %}Could not import Excel file{% endblock %} 8 | {% block error_text %} 9 | Sorry, the file you uploaded was not imported 10 | {% endblock %} 11 | 12 | 13 | {% block error_recovery_link %} 14 | Return to your account page 15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /dirigible/sheet/tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dirigible/sheet/tests/parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/sheet/tests/parser/__init__.py -------------------------------------------------------------------------------- /dirigible/sheet/tests/parser/test_fl_named_column_reference_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2009 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | try: 6 | import unittest2 as unittest 7 | except ImportError: 8 | import unittest 9 | 10 | from sheet.parser.parse_node import ParseNode 11 | from sheet.parser.fl_named_column_reference_parse_node import FLNamedColumnReferenceParseNode 12 | 13 | 14 | class FLNamedColumnReferenceParseNodeTest(unittest.TestCase): 15 | 16 | def testConstruct(self): 17 | pn = FLNamedColumnReferenceParseNode(["#foo#_"]) 18 | self.assertEquals(pn.children, ["#foo#_"]) 19 | self.assertEquals(pn.type, ParseNode.FL_NAMED_COLUMN_REFERENCE) 20 | 21 | 22 | def testColumnReference(self): 23 | pn1 = FLNamedColumnReferenceParseNode(["#foo###_ "]) 24 | pn2 = FLNamedColumnReferenceParseNode(["blah", "!", "#foo###_ "]) 25 | self.assertEquals(pn1.header, "foo#") 26 | self.assertEquals(pn2.header, "foo#") 27 | 28 | 29 | def testRegisteredWithParse(self): 30 | "test registered with ParseNode" 31 | self.assertEquals(type(ParseNode.construct_node(ParseNode.FL_NAMED_COLUMN_REFERENCE, ['dfytjdky'])), 32 | FLNamedColumnReferenceParseNode, 33 | "Class is not registered with ParseNode") 34 | -------------------------------------------------------------------------------- /dirigible/sheet/tests/parser/test_fl_named_row_reference_parse_node.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2009 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | try: 6 | import unittest2 as unittest 7 | except ImportError: 8 | import unittest 9 | 10 | from sheet.parser.parse_node import ParseNode 11 | from sheet.parser.fl_named_row_reference_parse_node import FLNamedRowReferenceParseNode 12 | 13 | 14 | class FLNamedRowReferenceParseNodeTest(unittest.TestCase): 15 | 16 | def testConstruct(self): 17 | pn = FLNamedRowReferenceParseNode(["#foo#_"]) 18 | self.assertEquals(pn.children, ["#foo#_"]) 19 | self.assertEquals(pn.type, ParseNode.FL_NAMED_ROW_REFERENCE) 20 | 21 | 22 | def testRowReference(self): 23 | pn1 = FLNamedRowReferenceParseNode(["_#foo### "]) 24 | pn2 = FLNamedRowReferenceParseNode(["blah", "!", "_#foo### "]) 25 | self.assertEquals(pn1.header, "foo#") 26 | self.assertEquals(pn2.header, "foo#") 27 | 28 | 29 | def testRegisteredWithParse(self): 30 | "test registered with ParseNode" 31 | self.assertEquals(type(ParseNode.construct_node(ParseNode.FL_NAMED_ROW_REFERENCE, ['dfytjdky'])), FLNamedRowReferenceParseNode, 32 | "Class is not registered with ParseNode") 33 | 34 | -------------------------------------------------------------------------------- /dirigible/sheet/tests/test_dirigible_datetime.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | try: 6 | import unittest2 as unittest 7 | except ImportError: 8 | import unittest 9 | 10 | import datetime 11 | from sheet.dirigible_datetime import DateTime 12 | from dirigible.test_utils import ResolverTestCase 13 | 14 | class DateTimeTest(ResolverTestCase): 15 | 16 | def test_DateTime_subclasses_datetime_dot_datetime(self): 17 | self.assertTrue(isinstance( 18 | DateTime(1979, 10, 8), 19 | datetime.datetime)) 20 | -------------------------------------------------------------------------------- /dirigible/sheet/tests/test_eval_constant.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | try: 6 | import unittest2 as unittest 7 | except ImportError: 8 | import unittest 9 | 10 | from sheet.eval_constant import eval_constant 11 | 12 | 13 | class TestEvalConstant(unittest.TestCase): 14 | 15 | def test_returns_input_unchanged_in_general(self): 16 | input = 'input' 17 | self.assertEquals(id(eval_constant(input)), id(input)) 18 | 19 | 20 | def test_returns_float_for_floatlike_input(self): 21 | self.assertEquals(eval_constant('1'), 1) 22 | self.assertEquals(type(eval_constant('1')), type(1)) 23 | self.assertEquals(eval_constant('1.5'), 1.5) 24 | self.assertEquals(eval_constant('1.5e10'), 1.5e10) 25 | self.assertEquals(type(eval_constant('1.5')), type(1.5)) 26 | 27 | -------------------------------------------------------------------------------- /dirigible/sheet/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | from django import forms 5 | 6 | from dirigible.test_utils import ResolverTestCase 7 | from sheet.forms import ImportCSVForm 8 | 9 | class TestImportCSVForm(ResolverTestCase): 10 | 11 | def test_initialisation(self): 12 | import_form = ImportCSVForm() 13 | self.assertTrue(all(map(lambda args : isinstance(*args),[ 14 | (import_form.fields['column'], forms.IntegerField), 15 | (import_form.fields['row'], forms.IntegerField), 16 | (import_form.fields['file'], forms.FileField)]))) 17 | 18 | 19 | def test_hidden_fields_appear_with_correct_ids(self): 20 | import_form = ImportCSVForm() 21 | autogen_html = import_form.as_p() 22 | 23 | first_hidden_position = autogen_html.find('type="hidden"') 24 | self.assertFalse(first_hidden_position == -1) 25 | second_hidden_position = autogen_html.find('type="hidden"', first_hidden_position + 1) 26 | self.assertFalse(second_hidden_position == -1) 27 | third_hidden_position = autogen_html.find('type="hidden"', second_hidden_position + 1) 28 | self.assertEquals(third_hidden_position, -1) 29 | 30 | self.assertTrue('id="id_import_form_column"' in autogen_html) 31 | self.assertTrue('id="id_import_form_row"' in autogen_html) 32 | self.assertTrue('id="id_import_form_file"' in autogen_html) 33 | -------------------------------------------------------------------------------- /dirigible/sheet/tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/sheet/tests/utils/__init__.py -------------------------------------------------------------------------------- /dirigible/sheet/tests/utils/test_interruptable_thread.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | import sys 5 | import time 6 | 7 | try: 8 | import unittest2 as unittest 9 | except ImportError: 10 | import unittest 11 | 12 | from sheet.utils.interruptable_thread import InterruptableThread 13 | 14 | class TestInterruptableThread(unittest.TestCase): 15 | 16 | def test_interruptable_thread_is(self): 17 | def sleep_lots(): 18 | # Setting stderr to None keeps test output from 19 | # having random stack traces inserted 20 | sys.stderr = None 21 | start = time.clock() 22 | while time.clock() - start < 10: 23 | pass 24 | self.fail('thread not interrupted') 25 | 26 | it = InterruptableThread(target=sleep_lots) 27 | it.start() 28 | it.join(5) 29 | self.assertTrue(it.isAlive()) 30 | it.interrupt() 31 | time.sleep(1) 32 | self.assertFalse(it.isAlive()) 33 | -------------------------------------------------------------------------------- /dirigible/sheet/tests/utils/test_string_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2005-2010 Resolver Systems Ltd, PythonAnywhere LLP 3 | # See LICENSE.md 4 | # 5 | 6 | 7 | try: 8 | import unittest2 as unittest 9 | except ImportError: 10 | import unittest 11 | 12 | from sheet.utils.string_utils import ( 13 | correct_case, double_quote_repr_string, get_lstripped_part, get_rstripped_part 14 | ) 15 | 16 | 17 | class StringUtilsTest(unittest.TestCase): 18 | 19 | def test_get_rstripped_part(self): 20 | self.assertEquals(get_rstripped_part("foo "), " ", "get_r_stripped_part broken") 21 | self.assertEquals(get_rstripped_part(" foo "), " ", "get_r_stripped_part broken") 22 | self.assertEquals(get_rstripped_part(""), "", "get_r_stripped_part broken") 23 | 24 | 25 | def test_get_lstripped_part(self): 26 | self.assertEquals(get_lstripped_part(" foo"), " ", "get_l_stripped_part broken") 27 | self.assertEquals(get_lstripped_part("foo "), "", "get_l_stripped_part broken") 28 | self.assertEquals(get_lstripped_part(" foo "), " ", "get_l_stripped_part broken") 29 | self.assertEquals(get_lstripped_part(""), "", "get_l_stripped_part broken") 30 | 31 | 32 | def test_double_quote_repr(self): 33 | "test double_quote_repr_string" 34 | self.assertEquals(double_quote_repr_string('what\'s "this"'), '"what\'s \\"this\\""', "didn't work") 35 | self.assertEquals(double_quote_repr_string(''), '""', "didn't work") 36 | 37 | 38 | def testCorrectCase(self): 39 | self.assertEquals(correct_case("a", ["A", "B"]), "A", "case not corrected") 40 | self.assertEquals(correct_case("a", ["C", "B"]), "a", "changed despite no matches") 41 | self.assertEquals(correct_case("flippertigibbet", ["A", "B", "FlipperTiGibbet"]), "FlipperTiGibbet", "case not corrected") 42 | 43 | -------------------------------------------------------------------------------- /dirigible/sheet/ui_jsonifier.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | import simplejson as json 6 | 7 | 8 | def sheet_to_ui_json_meta_data(sheet, worksheet): 9 | result = { 10 | 'name':sheet.name, 11 | 'width':sheet.width, 12 | 'height':sheet.height, 13 | } 14 | if sheet.column_widths: 15 | result['column_widths'] = sheet.column_widths 16 | if worksheet._console_text: 17 | result['console_text'] = worksheet._console_text 18 | if worksheet._usercode_error: 19 | result['usercode_error'] = { 20 | 'message' : worksheet._usercode_error['message'], 21 | 'line' : str(worksheet._usercode_error['line']) 22 | } 23 | return json.dumps(result) 24 | 25 | 26 | def sheet_to_ui_json_grid_data(worksheet, rnge): 27 | result = {} 28 | left, topmost, right, bottom = rnge 29 | result['left'] = left 30 | result['topmost'] = topmost 31 | result['right'] = right 32 | result['bottom'] = bottom 33 | for (col, row), cell in worksheet.items(): 34 | if rnge is not None: 35 | if ( 36 | col < left or col > right or 37 | row < topmost or row > bottom 38 | ): 39 | continue 40 | cell_content = {} 41 | if cell.formula is not None: 42 | cell_content['formula'] = cell.formula 43 | if cell.formatted_value and not cell.error: 44 | cell_content['formatted_value'] = cell.formatted_value 45 | if cell.error: 46 | cell_content['error'] = cell.error 47 | if cell_content != {}: 48 | row_dict = result.setdefault(row, {}) 49 | row_dict[col] = cell_content 50 | return json.dumps(result) 51 | -------------------------------------------------------------------------------- /dirigible/sheet/urls_api_0_1.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.conf.urls import * 6 | 7 | from .views_api_0_1 import calculate_and_get_json_for_api 8 | 9 | 10 | urlpatterns = patterns('', 11 | 12 | url( 13 | r'^json/$', 14 | calculate_and_get_json_for_api, 15 | name='api_v0.1_get_sheet_json' 16 | ), 17 | ) 18 | -------------------------------------------------------------------------------- /dirigible/sheet/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/sheet/utils/__init__.py -------------------------------------------------------------------------------- /dirigible/sheet/utils/interruptable_thread.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | from ctypes import c_long, py_object, pythonapi 5 | from threading import _active, Thread 6 | 7 | class TimeoutException(Exception): 8 | pass 9 | 10 | class InterruptableThread(Thread): 11 | 12 | def _get_thread_id(self): 13 | if hasattr(self, "_thread_id"): 14 | return self._thread_id 15 | for tid, tobj in _active.items(): 16 | if tobj is self: 17 | self._thread_id = tid 18 | return tid 19 | 20 | 21 | def interrupt(self): 22 | if not self.isAlive(): 23 | return 24 | tid = self._get_thread_id() 25 | threads_affected = pythonapi.PyThreadState_SetAsyncExc(c_long(tid), py_object(TimeoutException)) 26 | if threads_affected != 1: 27 | # if it returns a number greater than one, you're in trouble, 28 | # and you should call it again with exc=NULL to revert the effect 29 | pythonapi.PyThreadState_SetAsyncExc(c_long(tid), None) 30 | raise SystemError("PyThreadState_SetAsyncExc failed") 31 | -------------------------------------------------------------------------------- /dirigible/sheet/utils/string_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2005-2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | def get_rstripped_part(string): 6 | strippedString = string.rstrip() 7 | whitespace = string[len(strippedString):] 8 | return whitespace 9 | 10 | 11 | def get_lstripped_part(string): 12 | strippedString = string.lstrip() 13 | whitespace = string[:-len(strippedString)] 14 | return whitespace 15 | 16 | 17 | def double_quote_repr_string(inString): 18 | result = repr(inString)[1:-1] 19 | result = result.replace('"', '\\"') 20 | result = result.replace("\\'", "'") 21 | return "\"%s\"" % result 22 | 23 | 24 | def correct_case(candidate, potentialMatches): 25 | for match in potentialMatches: 26 | if match.lower() == candidate.lower(): 27 | return match 28 | return candidate 29 | -------------------------------------------------------------------------------- /dirigible/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/user/__init__.py -------------------------------------------------------------------------------- /dirigible/user/admin.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.contrib import admin 6 | from django.contrib.auth.admin import UserAdmin 7 | 8 | from user.models import OneTimePad, User, UserProfile 9 | 10 | 11 | admin.site.register(OneTimePad) 12 | 13 | admin.site.unregister(User) 14 | 15 | class UserProfileInline(admin.StackedInline): 16 | model = UserProfile 17 | 18 | def has_seen_sheet_page(user): 19 | return user.get_profile().has_seen_sheet_page 20 | 21 | has_seen_sheet_page.boolean = True 22 | 23 | class MyUserAdmin(UserAdmin): 24 | inlines = [UserProfileInline] 25 | list_display = ('username', 'email', 'is_staff', 'is_active', 'date_joined', 'last_login', has_seen_sheet_page) 26 | 27 | admin.site.register(User, MyUserAdmin) 28 | 29 | -------------------------------------------------------------------------------- /dirigible/user/forms.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from registration.forms import RegistrationForm 6 | 7 | 8 | class DirigibleRegistrationForm(RegistrationForm): 9 | def __init__(self, *args, **kwargs): 10 | super(RegistrationForm, self).__init__(*args, **kwargs) 11 | self.fields['username'].error_messages['required'] = 'Please enter a username.' 12 | self.fields['email'].error_messages['required'] = 'Please enter your email address.' 13 | self.fields['email'].error_messages['invalid'] = 'Please enter a valid email address.' 14 | self.fields['password1'].error_messages['required'] = 'Please enter a password.' 15 | self.fields['password2'].error_messages['required'] = 'Please enter a password.' 16 | -------------------------------------------------------------------------------- /dirigible/user/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | import user.models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('auth', '0001_initial'), 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='OneTimePad', 19 | fields=[ 20 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 21 | ('creation_time', models.DateTimeField(auto_now_add=True)), 22 | ('guid', models.CharField(default=user.models.get_uid, max_length=72)), 23 | ], 24 | options={ 25 | }, 26 | bases=(models.Model,), 27 | ), 28 | migrations.CreateModel( 29 | name='UserProfile', 30 | fields=[ 31 | ('user', models.OneToOneField(primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), 32 | ('has_seen_sheet_page', models.BooleanField(default=False)), 33 | ], 34 | options={ 35 | }, 36 | bases=(models.Model,), 37 | ), 38 | migrations.AddField( 39 | model_name='onetimepad', 40 | name='user', 41 | field=models.ForeignKey(to=settings.AUTH_USER_MODEL), 42 | preserve_default=True, 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /dirigible/user/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/dirigible/user/migrations/__init__.py -------------------------------------------------------------------------------- /dirigible/user/models.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from uuid import uuid4 6 | 7 | from django.contrib.auth.models import AnonymousUser, User 8 | from django.db import models 9 | 10 | def get_uid(): 11 | return uuid4() 12 | 13 | class OneTimePad(models.Model): 14 | creation_time = models.DateTimeField(auto_now_add=True) 15 | user = models.ForeignKey(User) 16 | guid = models.CharField(default=get_uid, max_length=72) 17 | 18 | 19 | class UserProfile(models.Model): 20 | user = models.OneToOneField(User, primary_key=True) 21 | has_seen_sheet_page = models.BooleanField(default=False) 22 | 23 | AnonymousUser.email = None 24 | 25 | class AnonymousProfile(object): 26 | has_seen_sheet_page = True 27 | save = lambda _: None 28 | 29 | User.get_profile = lambda u: UserProfile.objects.get_or_create(user=u)[0] 30 | AnonymousUser.get_profile = lambda u: AnonymousProfile() 31 | -------------------------------------------------------------------------------- /dirigible/user/signup_urls.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.conf.urls import * 6 | from registration.views import activate 7 | 8 | from user.views import register, registration_complete 9 | 10 | 11 | urlpatterns = patterns('', 12 | 13 | url( 14 | r'^register/$', 15 | register, 16 | name="registration_register" 17 | ), 18 | 19 | url( 20 | r'^activate/(?P\w+)/$', 21 | activate, 22 | name='registration_activate' 23 | ), 24 | 25 | url( 26 | r'^register/complete/$', 27 | registration_complete, 28 | name='registration_complete' 29 | ), 30 | 31 | ) 32 | -------------------------------------------------------------------------------- /dirigible/user/templates/registration/activation_email.txt: -------------------------------------------------------------------------------- 1 | Hi, 2 | 3 | Thank you for signing up for Dirigible! 4 | 5 | Click on the following link to start using it: 6 | 7 | 8 | 9 | If you cannot click the link from your email program, please copy the URL and paste it into your web browser. 10 | 11 | If you do not want to use Dirigible, simply ignore this message and we'll not bother you again. 12 | 13 | 14 | Best Regards, 15 | -------------------------------------------------------------------------------- /dirigible/user/templates/registration/activation_email_subject.txt: -------------------------------------------------------------------------------- 1 | Dirigible Beta Sign-up -------------------------------------------------------------------------------- /dirigible/user/templates/registration/registration_complete.html: -------------------------------------------------------------------------------- 1 | {% extends "non_sheet_page_small_logo.html" %} 2 | 3 | {% block title %} 4 | Project Dirigible Beta Sign-up 5 | {% endblock %} 6 | 7 | {% block head %} 8 | {{ block.super }} 9 | 10 | {% endblock %} 11 | 12 | {% block content %} 13 |
14 | 15 |
16 |
17 |

Thank you!

18 | 19 |

20 | We have sent a confirmation email to 21 | {% if email_address %} 22 | {{ email_address }} 23 | {% else %} 24 | the email address that you provided 25 | {% endif %} 26 |

27 |

28 | Please click the link in the email to complete the sign-up. 29 |

30 | 31 |
33 |
34 | {% endblock %} 35 | 36 | -------------------------------------------------------------------------------- /dirigible/user/templates/welcome_email.txt: -------------------------------------------------------------------------------- 1 | Dear {{user.username}} 2 | 3 | Welcome to Dirigible! 4 | 5 | -------------------------------------------------------------------------------- /dirigible/user/tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dirigible/user/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from registration.forms import RegistrationForm 6 | 7 | from dirigible.test_utils import ResolverTestCase 8 | from user.forms import DirigibleRegistrationForm 9 | 10 | 11 | class DirigibleRegistrationFormTest(ResolverTestCase): 12 | 13 | def test_is_registration_form(self): 14 | form = DirigibleRegistrationForm() 15 | self.assertTrue(isinstance(form, RegistrationForm)) 16 | 17 | 18 | def test_error_messages(self): 19 | form = DirigibleRegistrationForm() 20 | self.assertEquals(form.fields['username'].error_messages['required'], "Please enter a username.") 21 | self.assertEquals(form.fields['email'].error_messages['required'], "Please enter your email address.") 22 | self.assertEquals(form.fields['email'].error_messages['invalid'], "Please enter a valid email address.") 23 | self.assertEquals(form.fields['password1'].error_messages['required'], "Please enter a password.") 24 | self.assertEquals(form.fields['password2'].error_messages['required'], "Please enter a password.") 25 | -------------------------------------------------------------------------------- /dirigible/user/tests/test_models.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | from mock import patch 5 | from django.utils import timezone 6 | 7 | from django.contrib.auth.models import AnonymousUser, User 8 | 9 | from dirigible.test_utils import ResolverTestCase 10 | from user.models import AnonymousProfile, OneTimePad, UserProfile 11 | 12 | 13 | class TestOneTimePads(ResolverTestCase): 14 | 15 | @patch('user.models.uuid4') 16 | def test_OneTimePad_init(self, mock_uuid4): 17 | user = User(username='Alice, traditionally') 18 | user.save() 19 | otp = OneTimePad(user=user) 20 | otp.save() 21 | otp = OneTimePad.objects.get(pk=otp.id) 22 | self.assertTrue((timezone.now() - otp.creation_time).seconds < 1) 23 | self.assertEquals(otp.user, user) 24 | self.assertEquals(otp.guid, unicode(mock_uuid4.return_value)) 25 | 26 | 27 | 28 | class TestUserProfiles(ResolverTestCase): 29 | 30 | def test_defaults(self): 31 | user_profile = UserProfile() 32 | self.assertEquals(user_profile.has_seen_sheet_page, False) 33 | 34 | 35 | def test_save_new_user_creates_user_profile(self): 36 | user = User(username='Kenny Ken') 37 | user.save() 38 | self.assertEquals(type(user.get_profile()), UserProfile) 39 | self.assertEquals(user.get_profile().user, user) 40 | 41 | 42 | class TestAnonymousUser(ResolverTestCase): 43 | 44 | def test_anonymous_user_has_a_profile(self): 45 | profile = AnonymousUser().get_profile() 46 | self.assertEquals(type(profile), AnonymousProfile) 47 | 48 | def test_anonymous_user_attrs(self): 49 | self.assertIsNone(AnonymousUser().email) 50 | 51 | def test_anonymous_profile_attrs(self): 52 | profile = AnonymousUser().get_profile() 53 | self.assertTrue(profile.has_seen_sheet_page) 54 | self.assertIsNone(profile.save()) 55 | 56 | -------------------------------------------------------------------------------- /dirigible/user/urls.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Resolver Systems Ltd, PythonAnywhere LLP 2 | # See LICENSE.md 3 | # 4 | 5 | from django.conf.urls import include, patterns, url 6 | 7 | from user.views import change_password, redirect_to_front_page 8 | 9 | 10 | urlpatterns = patterns( 11 | '', 12 | url( 13 | r'^[^/]+/$', 14 | redirect_to_front_page, 15 | name="user_page" 16 | ), 17 | 18 | url( 19 | r'^(?P[^/]+)/sheet/', 20 | include('sheet.urls'), 21 | ), 22 | 23 | url( 24 | r'^(?P[^/]+)/change_password/$', 25 | change_password, 26 | name="change_password" 27 | ), 28 | 29 | ) 30 | -------------------------------------------------------------------------------- /documentation/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | 3 | -------------------------------------------------------------------------------- /documentation/_static/file-to-force-git-to-keep-empty-dir: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/_static/file-to-force-git-to-keep-empty-dir -------------------------------------------------------------------------------- /documentation/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | -------------------------------------------------------------------------------- /documentation/dirigible-theme/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "default/layout.html" %} 2 | 3 | -------------------------------------------------------------------------------- /documentation/dirigible-theme/static/dirigible-style.css: -------------------------------------------------------------------------------- 1 | @import "default.css"; 2 | 3 | /* for the layout of table in 'spreadsheet-functions.html' */ 4 | 5 | td p { 6 | margin: 0px; 7 | } 8 | .line-block { 9 | margin: 0px; 10 | } 11 | 12 | 13 | /* for pygments code appearing in table in 'spreadsheet-functions.html' */ 14 | 15 | td div.highlight-python div.highlight pre { 16 | margin: 2px; 17 | padding: 4px; 18 | border-style: none; 19 | } 20 | 21 | td div.highlight-python pre { 22 | margin: 2px; 23 | padding: 4px; 24 | border-style: none; 25 | } 26 | 27 | img { 28 | margin-left: 50px; 29 | } 30 | -------------------------------------------------------------------------------- /documentation/dirigible-theme/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = default 3 | stylesheet = dirigible-style.css 4 | 5 | [options] 6 | 7 | -------------------------------------------------------------------------------- /documentation/fl-python-differences.rst: -------------------------------------------------------------------------------- 1 | Differences Between Dirigible's Formula Language and Python 2 | =========================================================== 3 | 4 | We call the syntax you use to enter formulae into the Dirigible spreadsheet grid the *formula language*. It's almost exactly the same as Python expression syntax, but has some differences for compatibility with the formula syntax used in other spreadsheet applications. (The Dirigible usercode, by contrast, is pure Python.) 5 | 6 | This page highlights the differences between the formula language and normal Python. 7 | 8 | "``:``" operator replaced by "``->``" 9 | ------------------------------------- 10 | 11 | The colon ("``:``") is reserved for future use in cell ranges, e.g. ``C3:D8``. 12 | 13 | All Python colons have been replaced by arrows ("``->``"). Lambdas, slices and dictionaries in the formula language are as follows: 14 | 15 | :: 16 | 17 | =lambda x -> x + 1 18 | =somelist[3->6] 19 | ={foo -> bar, baz -> qux} 20 | 21 | 22 | List comprehensions: ``in`` clause trailing commas disallowed 23 | ------------------------------------------------------------- 24 | 25 | Trailing commas for the Python list comprehension ``in`` clause are disallowed. For example, ``=[a for a in 1, 2,]`` is invalid because of the last comma. 26 | -------------------------------------------------------------------------------- /documentation/import_export_excel_garbled_csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_excel_garbled_csv.png -------------------------------------------------------------------------------- /documentation/import_export_excel_hooray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_excel_hooray.png -------------------------------------------------------------------------------- /documentation/import_export_excel_import_text_choose_comma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_excel_import_text_choose_comma.png -------------------------------------------------------------------------------- /documentation/import_export_excel_import_text_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_excel_import_text_menu.png -------------------------------------------------------------------------------- /documentation/import_export_export_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_export_icon.jpg -------------------------------------------------------------------------------- /documentation/import_export_export_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_export_icon.png -------------------------------------------------------------------------------- /documentation/import_export_import_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_import_button.png -------------------------------------------------------------------------------- /documentation/import_export_import_csv_options.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_import_csv_options.jpg -------------------------------------------------------------------------------- /documentation/import_export_import_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_import_dialog.png -------------------------------------------------------------------------------- /documentation/import_export_import_excel_data_import_from_text_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_import_excel_data_import_from_text_button.png -------------------------------------------------------------------------------- /documentation/import_export_import_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_import_icon.jpg -------------------------------------------------------------------------------- /documentation/import_export_import_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/import_export_import_icon.png -------------------------------------------------------------------------------- /documentation/index.rst: -------------------------------------------------------------------------------- 1 | 2 | Dirigible documentation contents 3 | ================================ 4 | 5 | Getting started 6 | --------------- 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | 11 | tutorial01.rst 12 | tutorial02.rst 13 | tutorial03.rst 14 | tutorial04.rst 15 | 16 | 17 | Reference 18 | --------- 19 | 20 | .. toctree:: 21 | :maxdepth: 1 22 | 23 | overview.rst 24 | builtins.rst 25 | spreadsheet-functions.rst 26 | fl-python-differences.rst 27 | python-modules.rst 28 | public_sheets.rst 29 | json_api.rst 30 | import_export.rst 31 | 32 | -------------------------------------------------------------------------------- /documentation/overview.rst: -------------------------------------------------------------------------------- 1 | Technical overview 2 | ================== 3 | 4 | This page explains the details of how your Dirigible models are recalculated. It assumes you're familiar with the basics of what Dirigible is and does. For a more gentle introduction, try the :doc:`tutorial01`. 5 | 6 | When you editing a Dirigible model, you can put **formulae** and **constant values** into a spreadsheet grid, and you can edit Python **usercode** over on the right. Every time you change something in the grid or modify the usercode, the model is recalculated and -- perhaps after a pause -- the updated values will appear. This page explains exactly what goes on under the hood when that happens. 7 | 8 | To recalculate the model, the server simply runs its usercode in a Python context containing a few "magic" functions and variables. The data you've entered into the spreadsheet is not touched unless you have usercode that explicitly looks at it. The default usercode is set up to manipulate the spreadsheet data so that it is processed like it would be in a traditional spreadsheet, but you could quite easily write usercode to process it in a completely different manner -- or to ignore it and simply use it as an input into your own code. 9 | 10 | The three "magic" things that make the default behaviour possible are: 11 | 12 | - The :const:`worksheet` object 13 | - The :func:`load_constants` function 14 | - The :func:`evaluate_formulae` function 15 | 16 | They are described in detail on this page: :doc:`builtins`. 17 | -------------------------------------------------------------------------------- /documentation/public_sheets.rst: -------------------------------------------------------------------------------- 1 | Making sheets public 2 | ==================== 3 | 4 | Dirigible allows you to make any of your sheets public. A public sheet can be seen and copied 5 | -- but not changed -- by anyone, including people who aren't logged in to Dirigible, 6 | so it's a great way to share your work without worrying about other people breaking things. 7 | 8 | To make a sheet public, go to the sheet page's and click the security settings button, in the 9 | toolbar above the usercode editor. 10 | 11 | .. image:: security-dialog-button.png 12 | 13 | This will display a dialog: 14 | 15 | .. image:: security-dialog-public.png 16 | 17 | Check the "Make sheet public", and click OK. 18 | 19 | Other people use the same URL to view your sheets as you do to view and change them, 20 | so if you want to send someone a link, just use the contents of your browser's address bar. 21 | -------------------------------------------------------------------------------- /documentation/python-modules.rst: -------------------------------------------------------------------------------- 1 | Batteries included: Python modules you can use 2 | ============================================== 3 | 4 | Dirigible formulae and usercode are written using Python, version 2.5. 5 | 6 | The following Python libraries are supported: 7 | 8 | * All of the built-in modules supplied with `Python 2.5 `_ 9 | * `NumPy `_, the fundamental package needed for scientific computing with Python. 10 | * `GmPy `_, provides multiprecision arithmetic 11 | * `MpMath `_, another library for multiprecision floating-point arithmetic. 12 | * The `SciPy `_ library, providing scientific tools for mathematics, science, and engineering. 13 | * `PyCrypto `_, the Python cryptography toolkit. 14 | * `SqlAlchemy `_, a Python SQL toolkit and object 15 | relational mapper. 16 | * `LXML `_, for working with XML and HTML. 17 | * `xlrd `_, for reading Excel files. 18 | * `RDFLib `_, for working with `RDF `_. 19 | * `GeoPy `_, a geocoding toolbox. 20 | * `Beautiful Soup `_, a forgiving 21 | HTML parser for real-world web pages. 22 | * `Mechanize `_, stateful 23 | programmatic web browsing. 24 | 25 | -------------------------------------------------------------------------------- /documentation/security-dialog-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/security-dialog-button.png -------------------------------------------------------------------------------- /documentation/security-dialog-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/security-dialog-json.png -------------------------------------------------------------------------------- /documentation/security-dialog-public.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/security-dialog-public.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/01_constants.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/01_constants.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/02a_simple_python_formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/02a_simple_python_formula.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/02b_simple_python_formula_works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/02b_simple_python_formula_works.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/03a_formula_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/03a_formula_error.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/03b_formula_error_handled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/03b_formula_error_handled.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/04a_cell_reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/04a_cell_reference.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/04b_cell_reference_works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/04b_cell_reference_works.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/05a_circular_dependency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/05a_circular_dependency.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/05b_circular_dependency_handled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/05b_circular_dependency_handled.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/06_dependency_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/06_dependency_graph.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/07a_custom_function_in_usercode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/07a_custom_function_in_usercode.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/07b_custom_function_works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/07b_custom_function_works.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/08_accessing_constants_in_usercode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/08_accessing_constants_in_usercode.png -------------------------------------------------------------------------------- /documentation/talk_screenshots/09_accessing_formula_results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/talk_screenshots/09_accessing_formula_results.png -------------------------------------------------------------------------------- /documentation/toolbar.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/toolbar.PNG -------------------------------------------------------------------------------- /documentation/tutorial-01-after-basic-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-after-basic-setup.png -------------------------------------------------------------------------------- /documentation/tutorial-01-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-dashboard.png -------------------------------------------------------------------------------- /documentation/tutorial-01-error-in-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-error-in-grid.png -------------------------------------------------------------------------------- /documentation/tutorial-01-error-in-output-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-error-in-output-console.png -------------------------------------------------------------------------------- /documentation/tutorial-01-gross-price-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-gross-price-1.png -------------------------------------------------------------------------------- /documentation/tutorial-01-gross-price-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-gross-price-2.png -------------------------------------------------------------------------------- /documentation/tutorial-01-gross-price-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-gross-price-3.png -------------------------------------------------------------------------------- /documentation/tutorial-01-gross-price-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-gross-price-4.png -------------------------------------------------------------------------------- /documentation/tutorial-01-gross-price-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-gross-price-5.png -------------------------------------------------------------------------------- /documentation/tutorial-01-net-prices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-net-prices.png -------------------------------------------------------------------------------- /documentation/tutorial-01-products.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-products.png -------------------------------------------------------------------------------- /documentation/tutorial-01-sheet-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-01-sheet-page.png -------------------------------------------------------------------------------- /documentation/tutorial-02-defining-functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-02-defining-functions.png -------------------------------------------------------------------------------- /documentation/tutorial-02-using-cell-values-in-usercode-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-02-using-cell-values-in-usercode-error.png -------------------------------------------------------------------------------- /documentation/tutorial-02-using-variables-in-cell-formulae-error-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-02-using-variables-in-cell-formulae-error-message.png -------------------------------------------------------------------------------- /documentation/tutorial-02-using-variables-in-cell-formulae-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-02-using-variables-in-cell-formulae-error.png -------------------------------------------------------------------------------- /documentation/tutorial-02-using-variables-in-cell-formulae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-02-using-variables-in-cell-formulae.png -------------------------------------------------------------------------------- /documentation/tutorial-03-after-csv-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-after-csv-import.png -------------------------------------------------------------------------------- /documentation/tutorial-03-all-done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-all-done.png -------------------------------------------------------------------------------- /documentation/tutorial-03-before-numpy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-before-numpy.png -------------------------------------------------------------------------------- /documentation/tutorial-03-bubble-array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-bubble-array.png -------------------------------------------------------------------------------- /documentation/tutorial-03-csv-import-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-csv-import-button.png -------------------------------------------------------------------------------- /documentation/tutorial-03-eccentric-anomalies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-eccentric-anomalies.png -------------------------------------------------------------------------------- /documentation/tutorial-03-first-array-formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-first-array-formula.png -------------------------------------------------------------------------------- /documentation/tutorial-03-mean-anomalies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-mean-anomalies.png -------------------------------------------------------------------------------- /documentation/tutorial-03-numpy-arange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-03-numpy-arange.png -------------------------------------------------------------------------------- /documentation/tutorial-04-access-numerical-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-access-numerical-data.png -------------------------------------------------------------------------------- /documentation/tutorial-04-after-csv-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-after-csv-import.png -------------------------------------------------------------------------------- /documentation/tutorial-04-all-planets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-all-planets.png -------------------------------------------------------------------------------- /documentation/tutorial-04-all-values-one-planet-refactored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-all-values-one-planet-refactored.png -------------------------------------------------------------------------------- /documentation/tutorial-04-all-values-one-planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-all-values-one-planet.png -------------------------------------------------------------------------------- /documentation/tutorial-04-empty-lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-empty-lists.png -------------------------------------------------------------------------------- /documentation/tutorial-04-first-formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-first-formula.png -------------------------------------------------------------------------------- /documentation/tutorial-04-listified-orbital-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-listified-orbital-data.png -------------------------------------------------------------------------------- /documentation/tutorial-04-no-t-values-yet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/tutorial-04-no-t-values-yet.png -------------------------------------------------------------------------------- /documentation/windows-mouse-pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pythonanywhere/dirigible-spreadsheet/c771e9a391708f3b219248bf9974e05b1582fdd0/documentation/windows-mouse-pointer.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.7 2 | argparse==1.2.1 3 | chardet==2.2.1 4 | mock==1.0.1 5 | # numpy==1.8.1 6 | ply==3.4 7 | simplejson==3.5.2 8 | wsgiref==0.1.2 9 | xlrd==0.9.3 10 | selenium 11 | --------------------------------------------------------------------------------