From 79649a6226175b930920fca55369c69a4153f794 Mon Sep 17 00:00:00 2001 From: Michael Hohn Date: Sat, 18 Dec 2021 14:58:51 -0800 Subject: [PATCH] Add treeio/ files referenced in sarif --- .../treeio/treeio/static/js/12o_super_mini.js | 8305 +++++++ data/treeio/treeio/static/js/chat.js | 561 + .../static/js/colorbox/example1/index.html | 95 + .../static/js/colorbox/example2/index.html | 95 + .../static/js/colorbox/example3/index.html | 95 + .../static/js/colorbox/example4/index.html | 95 + .../static/js/colorbox/example5/index.html | 95 + data/treeio/treeio/static/js/fileuploader.js | 1299 ++ data/treeio/treeio/static/js/hardtree.js | 1425 ++ .../demos/accordion/hoverintent.html | 148 + .../demos/dialog/modal-form.html | 157 + .../demos/droppable/photo-manager.html | 182 + .../jquery-ui-1.10.3/demos/effect/easing.html | 102 + .../demos/tabs/manipulation.html | 122 + .../js/jquery-ui-1.10.3/ui/jquery-ui.js | 15003 +++++++++++++ .../jquery-ui-1.10.3/ui/jquery.ui.button.js | 419 + .../ui/jquery.ui.datepicker.js | 2038 ++ .../ui/jquery.ui.droppable.js | 372 + .../jquery-ui-1.10.3/ui/jquery.ui.position.js | 497 + .../ui/jquery.ui.resizable.js | 968 + .../jquery-ui-1.10.3/ui/jquery.ui.slider.js | 672 + .../jquery-ui-1.10.3/ui/jquery.ui.sortable.js | 1285 ++ .../js/jquery-ui-1.10.3/ui/jquery.ui.tabs.js | 846 + .../treeio/static/js/jquery-ui-custom.js | 778 + .../static/js/jquery.ba-serializeobject.js | 31 + .../treeio/static/js/jquery.ganttView.js | 440 + .../treeio/treeio/static/js/jquery.gritter.js | 401 + .../tiny_mce/plugins/advimage/js/image.js | 464 + .../tiny_mce/plugins/advlink/js/advlink.js | 543 + .../plugins/contextmenu/editor_plugin_src.js | 163 + .../tiny_mce/plugins/emotions/js/emotions.js | 43 + .../plugins/fullpage/editor_plugin_src.js | 405 + .../plugins/fullscreen/editor_plugin_src.js | 234 + .../plugins/fullscreen/fullscreen.htm | 117 + .../plugins/inlinepopups/editor_plugin_src.js | 699 + .../plugins/layer/editor_plugin_src.js | 262 + .../plugins/legacyoutput/editor_plugin_src.js | 139 + .../plugins/lists/editor_plugin_src.js | 955 + .../plugins/media/editor_plugin_src.js | 898 + .../tiny_mce/plugins/media/js/embed.js | 73 + .../tiny_mce/plugins/media/js/media.js | 513 + .../plugins/noneditable/editor_plugin_src.js | 537 + .../plugins/paste/editor_plugin_src.js | 887 + .../plugins/preview/jscripts/embed.js | 73 + .../tiny_mce/plugins/preview/preview.html | 17 + .../plugins/save/editor_plugin_src.js | 101 + .../plugins/searchreplace/js/searchreplace.js | 152 + .../plugins/spellchecker/editor_plugin_src.js | 471 + .../plugins/style/editor_plugin_src.js | 71 + .../tiny_mce/plugins/style/js/props.js | 709 + .../plugins/tabfocus/editor_plugin_src.js | 122 + .../plugins/table/editor_plugin_src.js | 1456 ++ .../tiny_mce/plugins/table/js/cell.js | 319 + .../tiny_mce/plugins/table/js/table.js | 501 + .../plugins/template/editor_plugin_src.js | 159 + .../tiny_mce/plugins/template/js/template.js | 106 + .../plugins/visualchars/editor_plugin_src.js | 83 + .../plugins/wordcount/editor_plugin_src.js | 122 + .../plugins/xhtmlxtras/js/attributes.js | 111 + .../plugins/xhtmlxtras/js/element_common.js | 229 + .../themes/advanced/editor_template_src.js | 1490 ++ .../tiny_mce/themes/advanced/js/anchor.js | 56 + .../tiny_mce/themes/advanced/js/charmap.js | 363 + .../themes/advanced/js/color_picker.js | 345 + .../tiny_mce/themes/advanced/js/image.js | 253 + .../tiny_mce/themes/advanced/js/link.js | 159 + .../themes/simple/editor_template_src.js | 84 + .../tinymce/jscripts/tiny_mce/tiny_mce_src.js | 17942 ++++++++++++++++ .../tiny_mce/utils/editable_selects.js | 70 + .../jscripts/tiny_mce/utils/form_utils.js | 210 + .../tinymce/jscripts/tiny_mce/utils/mctabs.js | 162 + .../jscripts/tiny_mce/utils/validate.js | 252 + .../mobile/jquery.mobile.forms.ajaxform.js | 68 + .../static/mobile/jquery.mobile.scrollview.js | 803 + .../core/administration/settings_view.html | 50 + .../templates/html/core/billing/upgrade.html | 104 + .../templates/html/core/database_setup.html | 26 + data/treeio/treeio/treeio/account/ajax.py | 272 + data/treeio/treeio/treeio/account/cron.py | 88 + data/treeio/treeio/treeio/account/forms.py | 237 + .../account/south_migrations/0001_initial.py | 25 + data/treeio/treeio/treeio/account/views.py | 223 + .../changes/south_migrations/0001_initial.py | 194 + .../core/administration/api/handlers.py | 217 + .../treeio/core/administration/forms.py | 474 + .../treeio/core/administration/views.py | 848 + .../treeio/treeio/core/ajax/converter.py | 225 + data/treeio/treeio/treeio/core/api/doc.py | 280 + .../core/api/south_migrations/0001_initial.py | 152 + .../0002_auto__add_field_consumer_owner.py | 96 + data/treeio/treeio/treeio/core/api/utils.py | 346 + data/treeio/treeio/treeio/core/auth.py | 54 + .../core/contrib/messages/storage/cache.py | 98 + .../treeio/treeio/core/dashboard/views.py | 279 + data/treeio/treeio/treeio/core/db/__init__.py | 13 + data/treeio/treeio/treeio/core/db/creation.py | 77 + data/treeio/treeio/treeio/core/db/db.py | 119 + data/treeio/treeio/treeio/core/forms.py | 383 + data/treeio/treeio/treeio/core/mail.py | 522 + .../core/management/commands/installdb.py | 92 + .../core/management/commands/runcron.py | 261 + .../treeio/treeio/core/middleware/chat.py | 519 + .../treeio/treeio/core/middleware/user.py | 354 + data/treeio/treeio/treeio/core/models.py | 1544 ++ data/treeio/treeio/treeio/core/rendering.py | 246 + data/treeio/treeio/treeio/core/rss.py | 117 + data/treeio/treeio/treeio/core/sanitizer.py | 318 + .../treeio/treeio/core/search/models.py | 74 + .../treeio/treeio/treeio/core/search/views.py | 67 + ...ment__add_tag__add_revisionfield__add_r.py | 719 + .../core/south_migrations/0003_treeiocore.py | 245 + .../0004_auto__del_field_object_user.py | 228 + ...ield_group_accessentity_ptr__del_field_.py | 257 + .../0006_auto__add_configsetting.py | 242 + .../0007_auto__add_attachment.py | 258 + ...008_auto__add_field_attachment_filename.py | 244 + .../treeio/core/templatetags/modules.py | 894 + .../treeio/treeio/core/templatetags/user.py | 90 + data/treeio/treeio/treeio/core/trash/views.py | 109 + data/treeio/treeio/treeio/core/views.py | 604 + data/treeio/treeio/treeio/documents/forms.py | 200 + .../south_migrations/0001_initial.py | 187 + data/treeio/treeio/treeio/events/forms.py | 159 + data/treeio/treeio/treeio/events/rendering.py | 253 + .../events/south_migrations/0001_initial.py | 182 + .../treeio/treeio/finance/api/handlers.py | 240 + data/treeio/treeio/treeio/finance/forms.py | 782 + data/treeio/treeio/treeio/finance/models.py | 349 + .../finance/south_migrations/0001_initial.py | 331 + ...d_field_liability_value_currency__add_f.py | 393 + .../south_migrations/0003_treeiocurrency.py | 255 + data/treeio/treeio/treeio/finance/views.py | 1441 ++ .../treeio/treeio/identities/api/handlers.py | 124 + .../treeio/treeio/identities/integration.py | 248 + .../treeio/treeio/treeio/identities/models.py | 185 + .../treeio/treeio/identities/objects.py | 142 + .../south_migrations/0001_initial.py | 221 + ...02_auto__chg_field_contact_related_user.py | 158 + .../0003_related_accessentity.py | 156 + ...4_auto__del_field_contact_related_group.py | 154 + .../treeio/infrastructure/api/handlers.py | 164 + .../south_migrations/0001_initial.py | 406 + .../south_migrations/0001_initial.py | 176 + .../treeio/treeio/messaging/api/handlers.py | 190 + data/treeio/treeio/treeio/messaging/emails.py | 148 + data/treeio/treeio/treeio/messaging/forms.py | 342 + .../south_migrations/0001_initial.py | 226 + ...late__add_field_message_mlist__chg_fiel.py | 353 + .../0003_merge_emailbox_stream.py | 213 + ...essagestream_email_outgoing__del_field_.py | 222 + data/treeio/treeio/treeio/messaging/views.py | 764 + data/treeio/treeio/treeio/news/forms.py | 83 + .../news/south_migrations/0001_initial.py | 25 + data/treeio/treeio/treeio/projects/ajax.py | 31 + .../treeio/treeio/projects/identities.py | 65 + .../projects/south_migrations/0001_initial.py | 322 + .../south_migrations/0002_updaterecords.py | 236 + .../0003_auto__add_field_tasktimeslot_user.py | 210 + .../south_migrations/0004_timeslots.py | 211 + .../0005_auto__del_taskrecord.py | 208 + .../0006_auto__add_field_task_depends.py | 196 + data/treeio/treeio/treeio/projects/views.py | 1301 ++ .../reports/south_migrations/0001_initial.py | 144 + ...del_field_report_template__add_field_re.py | 174 + .../south_migrations/0003_delete_old.py | 134 + .../treeio/reports/templatetags/reports.py | 237 + data/treeio/treeio/treeio/sales/forms.py | 1095 + data/treeio/treeio/treeio/sales/models.py | 560 + ...ld_orderedproduct_tax__add_field_ordere.py | 502 + .../south_migrations/0003_treeiocurrency.py | 339 + ...auto__chg_field_orderedproduct_quantity.py | 314 + data/treeio/treeio/treeio/sales/views.py | 1604 ++ .../treeio/treeio/services/api/handlers.py | 260 + data/treeio/treeio/treeio/services/forms.py | 611 + .../treeio/treeio/services/identities.py | 67 + data/treeio/treeio/treeio/services/models.py | 370 + .../services/south_migrations/0001_initial.py | 412 + ...add_field_ticketrecord_updaterecord_ptr.py | 305 + .../south_migrations/0003_updaterecords.py | 310 + ...cord_type__del_field_ticketrecord_detai.py | 317 + data/treeio/treeio/treeio/services/views.py | 1197 ++ data/treeio/treeio/treeio_project/settings.py | 590 + 182 files changed, 105195 insertions(+) create mode 100644 data/treeio/treeio/static/js/12o_super_mini.js create mode 100644 data/treeio/treeio/static/js/chat.js create mode 100644 data/treeio/treeio/static/js/colorbox/example1/index.html create mode 100644 data/treeio/treeio/static/js/colorbox/example2/index.html create mode 100644 data/treeio/treeio/static/js/colorbox/example3/index.html create mode 100644 data/treeio/treeio/static/js/colorbox/example4/index.html create mode 100644 data/treeio/treeio/static/js/colorbox/example5/index.html create mode 100644 data/treeio/treeio/static/js/fileuploader.js create mode 100644 data/treeio/treeio/static/js/hardtree.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/demos/accordion/hoverintent.html create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/demos/dialog/modal-form.html create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/demos/droppable/photo-manager.html create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/demos/effect/easing.html create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/demos/tabs/manipulation.html create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery-ui.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery.ui.button.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery.ui.datepicker.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery.ui.droppable.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery.ui.position.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery.ui.resizable.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery.ui.slider.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery.ui.sortable.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-1.10.3/ui/jquery.ui.tabs.js create mode 100644 data/treeio/treeio/static/js/jquery-ui-custom.js create mode 100644 data/treeio/treeio/static/js/jquery.ba-serializeobject.js create mode 100644 data/treeio/treeio/static/js/jquery.ganttView.js create mode 100644 data/treeio/treeio/static/js/jquery.gritter.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/advimage/js/image.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/advlink/js/advlink.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/contextmenu/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/emotions/js/emotions.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullpage/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullscreen/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullscreen/fullscreen.htm create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/inlinepopups/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/layer/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/legacyoutput/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/lists/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/js/embed.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/js/media.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/noneditable/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/paste/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/preview/jscripts/embed.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/preview/preview.html create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/save/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/searchreplace/js/searchreplace.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/spellchecker/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/style/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/style/js/props.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/tabfocus/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/js/cell.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/js/table.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/template/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/template/js/template.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/visualchars/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/wordcount/editor_plugin_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/xhtmlxtras/js/attributes.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/xhtmlxtras/js/element_common.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/editor_template_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/anchor.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/charmap.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/color_picker.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/image.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/link.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/simple/editor_template_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/tiny_mce_src.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/utils/editable_selects.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/utils/form_utils.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/utils/mctabs.js create mode 100644 data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/utils/validate.js create mode 100644 data/treeio/treeio/static/mobile/jquery.mobile.forms.ajaxform.js create mode 100644 data/treeio/treeio/static/mobile/jquery.mobile.scrollview.js create mode 100644 data/treeio/treeio/templates/html/core/administration/settings_view.html create mode 100644 data/treeio/treeio/templates/html/core/billing/upgrade.html create mode 100644 data/treeio/treeio/templates/html/core/database_setup.html create mode 100644 data/treeio/treeio/treeio/account/ajax.py create mode 100644 data/treeio/treeio/treeio/account/cron.py create mode 100644 data/treeio/treeio/treeio/account/forms.py create mode 100644 data/treeio/treeio/treeio/account/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/account/views.py create mode 100644 data/treeio/treeio/treeio/changes/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/core/administration/api/handlers.py create mode 100644 data/treeio/treeio/treeio/core/administration/forms.py create mode 100644 data/treeio/treeio/treeio/core/administration/views.py create mode 100644 data/treeio/treeio/treeio/core/ajax/converter.py create mode 100644 data/treeio/treeio/treeio/core/api/doc.py create mode 100644 data/treeio/treeio/treeio/core/api/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/core/api/south_migrations/0002_auto__add_field_consumer_owner.py create mode 100644 data/treeio/treeio/treeio/core/api/utils.py create mode 100644 data/treeio/treeio/treeio/core/auth.py create mode 100644 data/treeio/treeio/treeio/core/contrib/messages/storage/cache.py create mode 100644 data/treeio/treeio/treeio/core/dashboard/views.py create mode 100644 data/treeio/treeio/treeio/core/db/__init__.py create mode 100644 data/treeio/treeio/treeio/core/db/creation.py create mode 100644 data/treeio/treeio/treeio/core/db/db.py create mode 100644 data/treeio/treeio/treeio/core/forms.py create mode 100644 data/treeio/treeio/treeio/core/mail.py create mode 100644 data/treeio/treeio/treeio/core/management/commands/installdb.py create mode 100644 data/treeio/treeio/treeio/core/management/commands/runcron.py create mode 100644 data/treeio/treeio/treeio/core/middleware/chat.py create mode 100644 data/treeio/treeio/treeio/core/middleware/user.py create mode 100644 data/treeio/treeio/treeio/core/models.py create mode 100644 data/treeio/treeio/treeio/core/rendering.py create mode 100644 data/treeio/treeio/treeio/core/rss.py create mode 100644 data/treeio/treeio/treeio/core/sanitizer.py create mode 100644 data/treeio/treeio/treeio/core/search/models.py create mode 100644 data/treeio/treeio/treeio/core/search/views.py create mode 100644 data/treeio/treeio/treeio/core/south_migrations/0002_auto__del_notification__add_comment__add_tag__add_revisionfield__add_r.py create mode 100644 data/treeio/treeio/treeio/core/south_migrations/0003_treeiocore.py create mode 100644 data/treeio/treeio/treeio/core/south_migrations/0004_auto__del_field_object_user.py create mode 100644 data/treeio/treeio/treeio/core/south_migrations/0005_auto__del_field_group_id__chg_field_group_accessentity_ptr__del_field_.py create mode 100644 data/treeio/treeio/treeio/core/south_migrations/0006_auto__add_configsetting.py create mode 100644 data/treeio/treeio/treeio/core/south_migrations/0007_auto__add_attachment.py create mode 100644 data/treeio/treeio/treeio/core/south_migrations/0008_auto__add_field_attachment_filename.py create mode 100644 data/treeio/treeio/treeio/core/templatetags/modules.py create mode 100644 data/treeio/treeio/treeio/core/templatetags/user.py create mode 100644 data/treeio/treeio/treeio/core/trash/views.py create mode 100644 data/treeio/treeio/treeio/core/views.py create mode 100644 data/treeio/treeio/treeio/documents/forms.py create mode 100644 data/treeio/treeio/treeio/documents/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/events/forms.py create mode 100644 data/treeio/treeio/treeio/events/rendering.py create mode 100644 data/treeio/treeio/treeio/events/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/finance/api/handlers.py create mode 100644 data/treeio/treeio/treeio/finance/forms.py create mode 100644 data/treeio/treeio/treeio/finance/models.py create mode 100644 data/treeio/treeio/treeio/finance/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/finance/south_migrations/0002_auto__add_currency__add_tax__add_field_liability_value_currency__add_f.py create mode 100644 data/treeio/treeio/treeio/finance/south_migrations/0003_treeiocurrency.py create mode 100644 data/treeio/treeio/treeio/finance/views.py create mode 100644 data/treeio/treeio/treeio/identities/api/handlers.py create mode 100644 data/treeio/treeio/treeio/identities/integration.py create mode 100644 data/treeio/treeio/treeio/identities/models.py create mode 100644 data/treeio/treeio/treeio/identities/objects.py create mode 100644 data/treeio/treeio/treeio/identities/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/identities/south_migrations/0002_auto__chg_field_contact_related_user.py create mode 100644 data/treeio/treeio/treeio/identities/south_migrations/0003_related_accessentity.py create mode 100644 data/treeio/treeio/treeio/identities/south_migrations/0004_auto__del_field_contact_related_group.py create mode 100644 data/treeio/treeio/treeio/infrastructure/api/handlers.py create mode 100644 data/treeio/treeio/treeio/infrastructure/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/knowledge/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/messaging/api/handlers.py create mode 100644 data/treeio/treeio/treeio/messaging/emails.py create mode 100644 data/treeio/treeio/treeio/messaging/forms.py create mode 100644 data/treeio/treeio/treeio/messaging/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/messaging/south_migrations/0002_auto__add_mailinglist__add_template__add_field_message_mlist__chg_fiel.py create mode 100644 data/treeio/treeio/treeio/messaging/south_migrations/0003_merge_emailbox_stream.py create mode 100644 data/treeio/treeio/treeio/messaging/south_migrations/0004_auto__del_emailbox__del_field_messagestream_email_outgoing__del_field_.py create mode 100644 data/treeio/treeio/treeio/messaging/views.py create mode 100644 data/treeio/treeio/treeio/news/forms.py create mode 100644 data/treeio/treeio/treeio/news/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/projects/ajax.py create mode 100644 data/treeio/treeio/treeio/projects/identities.py create mode 100644 data/treeio/treeio/treeio/projects/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/projects/south_migrations/0002_updaterecords.py create mode 100644 data/treeio/treeio/treeio/projects/south_migrations/0003_auto__add_field_tasktimeslot_user.py create mode 100644 data/treeio/treeio/treeio/projects/south_migrations/0004_timeslots.py create mode 100644 data/treeio/treeio/treeio/projects/south_migrations/0005_auto__del_taskrecord.py create mode 100644 data/treeio/treeio/treeio/projects/south_migrations/0006_auto__add_field_task_depends.py create mode 100644 data/treeio/treeio/treeio/projects/views.py create mode 100644 data/treeio/treeio/treeio/reports/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/reports/south_migrations/0002_auto__del_template__add_chart__del_field_report_template__add_field_re.py create mode 100644 data/treeio/treeio/treeio/reports/south_migrations/0003_delete_old.py create mode 100644 data/treeio/treeio/treeio/reports/templatetags/reports.py create mode 100644 data/treeio/treeio/treeio/sales/forms.py create mode 100644 data/treeio/treeio/treeio/sales/models.py create mode 100644 data/treeio/treeio/treeio/sales/south_migrations/0002_auto__del_updaterecord__add_field_orderedproduct_tax__add_field_ordere.py create mode 100644 data/treeio/treeio/treeio/sales/south_migrations/0003_treeiocurrency.py create mode 100644 data/treeio/treeio/treeio/sales/south_migrations/0004_auto__chg_field_orderedproduct_quantity.py create mode 100644 data/treeio/treeio/treeio/sales/views.py create mode 100644 data/treeio/treeio/treeio/services/api/handlers.py create mode 100644 data/treeio/treeio/treeio/services/forms.py create mode 100644 data/treeio/treeio/treeio/services/identities.py create mode 100644 data/treeio/treeio/treeio/services/models.py create mode 100644 data/treeio/treeio/treeio/services/south_migrations/0001_initial.py create mode 100644 data/treeio/treeio/treeio/services/south_migrations/0002_auto__add_field_ticketrecord_updaterecord_ptr.py create mode 100644 data/treeio/treeio/treeio/services/south_migrations/0003_updaterecords.py create mode 100644 data/treeio/treeio/treeio/services/south_migrations/0004_auto__del_field_ticketrecord_record_type__del_field_ticketrecord_detai.py create mode 100644 data/treeio/treeio/treeio/services/views.py create mode 100644 data/treeio/treeio/treeio_project/settings.py diff --git a/data/treeio/treeio/static/js/12o_super_mini.js b/data/treeio/treeio/static/js/12o_super_mini.js new file mode 100644 index 0000000..999caa9 --- /dev/null +++ b/data/treeio/treeio/static/js/12o_super_mini.js @@ -0,0 +1,8305 @@ +(function(a, b, c) { + function d(a) { + a = a || location.href; + return "#" + a.replace(/^[^#]*#?(.*)$/, "$1") + } + var f = "hashchange", + h = document, + n, g = a.event.special, + p = h.documentMode, + j = "on" + f in b && (p === c || p > 7); + a.fn[f] = function(a) { + return a ? this.bind(f, a) : this.trigger(f) + }; + a.fn[f].delay = 50; + g[f] = a.extend(g[f], { + setup: function() { + if (j) return false; + a(n.start) + }, + teardown: function() { + if (j) return false; + a(n.stop) + } + }); + n = function() { + function g() { + var c = d(), + j = F(q); + if (c !== q) aa(q = c, j), + a(b).trigger(f); + else if (j !== q) location.href = location.href.replace(/#.*/, "") + j; + p = setTimeout(g, a.fn[f].delay) + } + var n = {}, + p, q = d(), + w = function(a) { + return a + }, + aa = w, + F = w; + n.start = function() { + p || g() + }; + n.stop = function() { + p && clearTimeout(p); + p = c + }; + a.browser.msie && ! j && function() { + var b, c; + n.start = function() { + if (!b) c = (c = a.fn[f].src) && c + d(), + b = a('': +"");a._keyEvent=false;return I},_generateMonthYearHeader:function(a,b,c,e,f,h,i,g){var k=this._get(a,"changeMonth"),l=this._get(a,"changeYear"),u=this._get(a,"showMonthAfterYear"),j='
',o="";if(h||!k)o+=''+i[b]+"";else{i=e&&e.getFullYear()==c;var m=f&&f.getFullYear()==c;o+='"}u||(j+=o+(h||!(k&&l)?" ":""));if(h||!l)j+=''+c+"";else{g=this._get(a,"yearRange").split(":");var r=(new Date).getFullYear();i=function(s){s=s.match(/c[+-].*/)?c+parseInt(s.substring(1),10):s.match(/[+-].*/)?r+parseInt(s,10):parseInt(s,10);return isNaN(s)?r:s};b=i(g[0]);g=Math.max(b, +i(g[1]||""));b=e?Math.max(b,e.getFullYear()):b;g=f?Math.min(g,f.getFullYear()):g;for(j+='"}j+=this._get(a,"yearSuffix");if(u)j+=(h||!(k&&l)?" ":"")+o;j+="
";return j},_adjustInstDate:function(a,b,c){var e= +a.drawYear+(c=="Y"?b:0),f=a.drawMonth+(c=="M"?b:0);b=Math.min(a.selectedDay,this._getDaysInMonth(e,f))+(c=="D"?b:0);e=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(e,f,b)));a.selectedDay=e.getDate();a.drawMonth=a.selectedMonth=e.getMonth();a.drawYear=a.selectedYear=e.getFullYear();if(c=="M"||c=="Y")this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");b=c&&ba?a:b},_notifyChange:function(a){var b=this._get(a, +"onChangeMonthYear");if(b)b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){a=this._get(a,"numberOfMonths");return a==null?[1,1]:typeof a=="number"?[1,a]:a},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,e){var f=this._getNumberOfMonths(a); +c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(this._getDaysInMonth(c.getFullYear(),c.getMonth()));return this._isInRange(a,c)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min");a=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!a||b.getTime()<=a.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a, +"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker= +function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b)); +return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new L;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.5";window["DP_jQuery_"+y]=d})(jQuery); +;/* + * jQuery UI Progressbar 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + */ +(function(b,c){b.widget("ui.progressbar",{options:{value:0},min:0,max:100,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.max,"aria-valuenow":this._value()});this.valueDiv=b("
").appendTo(this.element);this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"); +this.valueDiv.remove();b.Widget.prototype.destroy.apply(this,arguments)},value:function(a){if(a===c)return this._value();this._setOption("value",a);return this},_setOption:function(a,d){if(a==="value"){this.options.value=d;this._refreshValue();this._trigger("change")}b.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;if(typeof a!=="number")a=0;return Math.min(this.max,Math.max(this.min,a))},_refreshValue:function(){var a=this.value();this.valueDiv.toggleClass("ui-corner-right", +a===this.max).width(a+"%");this.element.attr("aria-valuenow",a)}});b.extend(b.ui.progressbar,{version:"1.8.5"})})(jQuery); +;/* + * jQuery UI Effects 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/ + */ +jQuery.effects||function(f,j){function l(c){var a;if(c&&c.constructor==Array&&c.length==3)return c;if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c))return[parseInt(a[1],10),parseInt(a[2],10),parseInt(a[3],10)];if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c))return[parseInt(a[1], +16),parseInt(a[2],16),parseInt(a[3],16)];if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];if(/rgba\(0, 0, 0, 0\)/.exec(c))return m.transparent;return m[f.trim(c).toLowerCase()]}function r(c,a){var b;do{b=f.curCSS(c,a);if(b!=""&&b!="transparent"||f.nodeName(c,"body"))break;a="backgroundColor"}while(c=c.parentNode);return l(b)}function n(){var c=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle, +a={},b,d;if(c&&c.length&&c[0]&&c[c[0]])for(var e=c.length;e--;){b=c[e];if(typeof c[b]=="string"){d=b.replace(/\-(\w)/g,function(g,h){return h.toUpperCase()});a[d]=c[b]}}else for(b in c)if(typeof c[b]==="string")a[b]=c[b];return a}function o(c){var a,b;for(a in c){b=c[a];if(b==null||f.isFunction(b)||a in s||/scrollbar/.test(a)||!/color/i.test(a)&&isNaN(parseFloat(b)))delete c[a]}return c}function t(c,a){var b={_:0},d;for(d in a)if(c[d]!=a[d])b[d]=a[d];return b}function k(c,a,b,d){if(typeof c=="object"){d= +a;b=null;a=c;c=a.effect}if(f.isFunction(a)){d=a;b=null;a={}}if(typeof a=="number"||f.fx.speeds[a]){d=b;b=a;a={}}if(f.isFunction(b)){d=b;b=null}a=a||{};b=b||a.duration;b=f.fx.off?0:typeof b=="number"?b:f.fx.speeds[b]||f.fx.speeds._default;d=d||a.complete;return[c,a,b,d]}f.effects={};f.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(c,a){f.fx.step[a]=function(b){if(!b.colorInit){b.start=r(b.elem,a);b.end=l(b.end);b.colorInit= +true}b.elem.style[a]="rgb("+Math.max(Math.min(parseInt(b.pos*(b.end[0]-b.start[0])+b.start[0],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[1]-b.start[1])+b.start[1],10),255),0)+","+Math.max(Math.min(parseInt(b.pos*(b.end[2]-b.start[2])+b.start[2],10),255),0)+")"}});var m={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189, +183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255, +165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},p=["add","remove","toggle"],s={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};f.effects.animateClass=function(c,a,b,d){if(f.isFunction(b)){d=b;b=null}return this.each(function(){var e=f(this),g=e.attr("style")||" ",h=o(n.call(this)),q,u=e.attr("className");f.each(p,function(v, +i){c[i]&&e[i+"Class"](c[i])});q=o(n.call(this));e.attr("className",u);e.animate(t(h,q),a,b,function(){f.each(p,function(v,i){c[i]&&e[i+"Class"](c[i])});if(typeof e.attr("style")=="object"){e.attr("style").cssText="";e.attr("style").cssText=g}else e.attr("style",g);d&&d.apply(this,arguments)})})};f.fn.extend({_addClass:f.fn.addClass,addClass:function(c,a,b,d){return a?f.effects.animateClass.apply(this,[{add:c},a,b,d]):this._addClass(c)},_removeClass:f.fn.removeClass,removeClass:function(c,a,b,d){return a? +f.effects.animateClass.apply(this,[{remove:c},a,b,d]):this._removeClass(c)},_toggleClass:f.fn.toggleClass,toggleClass:function(c,a,b,d,e){return typeof a=="boolean"||a===j?b?f.effects.animateClass.apply(this,[a?{add:c}:{remove:c},b,d,e]):this._toggleClass(c,a):f.effects.animateClass.apply(this,[{toggle:c},a,b,d])},switchClass:function(c,a,b,d,e){return f.effects.animateClass.apply(this,[{add:a,remove:c},b,d,e])}});f.extend(f.effects,{version:"1.8.5",save:function(c,a){for(var b=0;b").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0});c.wrap(b);b=c.parent();if(c.css("position")=="static"){b.css({position:"relative"});c.css({position:"relative"})}else{f.extend(a,{position:c.css("position"),zIndex:c.css("z-index")});f.each(["top","left","bottom","right"],function(d,e){a[e]=c.css(e);if(isNaN(parseInt(a[e],10)))a[e]="auto"}); +c.css({position:"relative",top:0,left:0})}return b.css(a).show()},removeWrapper:function(c){if(c.parent().is(".ui-effects-wrapper"))return c.parent().replaceWith(c);return c},setTransition:function(c,a,b,d){d=d||{};f.each(a,function(e,g){unit=c.cssUnit(g);if(unit[0]>0)d[g]=unit[0]*b+unit[1]});return d}});f.fn.extend({effect:function(c){var a=k.apply(this,arguments);a={options:a[1],duration:a[2],callback:a[3]};var b=f.effects[c];return b&&!f.fx.off?b.call(this,a):this},_show:f.fn.show,show:function(c){if(!c|| +typeof c=="number"||f.fx.speeds[c]||!f.effects[c])return this._show.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="show";return this.effect.apply(this,a)}},_hide:f.fn.hide,hide:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||!f.effects[c])return this._hide.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="hide";return this.effect.apply(this,a)}},__toggle:f.fn.toggle,toggle:function(c){if(!c||typeof c=="number"||f.fx.speeds[c]||!f.effects[c]||typeof c== +"boolean"||f.isFunction(c))return this.__toggle.apply(this,arguments);else{var a=k.apply(this,arguments);a[1].mode="toggle";return this.effect.apply(this,a)}},cssUnit:function(c){var a=this.css(c),b=[];f.each(["em","px","%","pt"],function(d,e){if(a.indexOf(e)>0)b=[parseFloat(a),e]});return b}});f.easing.jswing=f.easing.swing;f.extend(f.easing,{def:"easeOutQuad",swing:function(c,a,b,d,e){return f.easing[f.easing.def](c,a,b,d,e)},easeInQuad:function(c,a,b,d,e){return d*(a/=e)*a+b},easeOutQuad:function(c, +a,b,d,e){return-d*(a/=e)*(a-2)+b},easeInOutQuad:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a+b;return-d/2*(--a*(a-2)-1)+b},easeInCubic:function(c,a,b,d,e){return d*(a/=e)*a*a+b},easeOutCubic:function(c,a,b,d,e){return d*((a=a/e-1)*a*a+1)+b},easeInOutCubic:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a+b;return d/2*((a-=2)*a*a+2)+b},easeInQuart:function(c,a,b,d,e){return d*(a/=e)*a*a*a+b},easeOutQuart:function(c,a,b,d,e){return-d*((a=a/e-1)*a*a*a-1)+b},easeInOutQuart:function(c,a,b,d,e){if((a/= +e/2)<1)return d/2*a*a*a*a+b;return-d/2*((a-=2)*a*a*a-2)+b},easeInQuint:function(c,a,b,d,e){return d*(a/=e)*a*a*a*a+b},easeOutQuint:function(c,a,b,d,e){return d*((a=a/e-1)*a*a*a*a+1)+b},easeInOutQuint:function(c,a,b,d,e){if((a/=e/2)<1)return d/2*a*a*a*a*a+b;return d/2*((a-=2)*a*a*a*a+2)+b},easeInSine:function(c,a,b,d,e){return-d*Math.cos(a/e*(Math.PI/2))+d+b},easeOutSine:function(c,a,b,d,e){return d*Math.sin(a/e*(Math.PI/2))+b},easeInOutSine:function(c,a,b,d,e){return-d/2*(Math.cos(Math.PI*a/e)-1)+ +b},easeInExpo:function(c,a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b},easeOutExpo:function(c,a,b,d,e){return a==e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b},easeInOutExpo:function(c,a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b},easeInCirc:function(c,a,b,d,e){return-d*(Math.sqrt(1-(a/=e)*a)-1)+b},easeOutCirc:function(c,a,b,d,e){return d*Math.sqrt(1-(a=a/e-1)*a)+b},easeInOutCirc:function(c,a,b,d,e){if((a/=e/2)<1)return-d/ +2*(Math.sqrt(1-a*a)-1)+b;return d/2*(Math.sqrt(1-(a-=2)*a)+1)+b},easeInElastic:function(c,a,b,d,e){c=1.70158;var g=0,h=d;if(a==0)return b;if((a/=e)==1)return b+d;g||(g=e*0.3);if(h").css({position:"absolute",visibility:"visible",left:-f*(h/d),top:-e*(i/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:h/d,height:i/c,left:g.left+f*(h/d)+(a.options.mode=="show"?(f-Math.floor(d/2))*(h/d):0),top:g.top+e*(i/c)+(a.options.mode=="show"?(e-Math.floor(c/2))*(i/c):0),opacity:a.options.mode=="show"?0:1}).animate({left:g.left+f*(h/d)+(a.options.mode=="show"?0:(f-Math.floor(d/2))*(h/d)),top:g.top+ +e*(i/c)+(a.options.mode=="show"?0:(e-Math.floor(c/2))*(i/c)),opacity:a.options.mode=="show"?1:0},a.duration||500);setTimeout(function(){a.options.mode=="show"?b.css({visibility:"visible"}):b.css({visibility:"visible"}).hide();a.callback&&a.callback.apply(b[0]);b.dequeue();j("div.ui-effects-explode").remove()},a.duration||500)})}})(jQuery); +;/* + * jQuery UI Effects Fade 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fade + * + * Depends: + * jquery.effects.core.js + */ +(function(b){b.effects.fade=function(a){return this.queue(function(){var c=b(this),d=b.effects.setMode(c,a.options.mode||"hide");c.animate({opacity:d},{queue:false,duration:a.duration,easing:a.options.easing,complete:function(){a.callback&&a.callback.apply(this,arguments);c.dequeue()}})})}})(jQuery); +;/* + * jQuery UI Effects Fold 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Fold + * + * Depends: + * jquery.effects.core.js + */ +(function(c){c.effects.fold=function(a){return this.queue(function(){var b=c(this),j=["position","top","left"],d=c.effects.setMode(b,a.options.mode||"hide"),g=a.options.size||15,h=!!a.options.horizFirst,k=a.duration?a.duration/2:c.fx.speeds._default/2;c.effects.save(b,j);b.show();var e=c.effects.createWrapper(b).css({overflow:"hidden"}),f=d=="show"!=h,l=f?["width","height"]:["height","width"];f=f?[e.width(),e.height()]:[e.height(),e.width()];var i=/([0-9]+)%/.exec(g);if(i)g=parseInt(i[1],10)/100* +f[d=="hide"?0:1];if(d=="show")e.css(h?{height:0,width:g}:{height:g,width:0});h={};i={};h[l[0]]=d=="show"?f[0]:g;i[l[1]]=d=="show"?f[1]:0;e.animate(h,k,a.options.easing).animate(i,k,a.options.easing,function(){d=="hide"&&b.hide();c.effects.restore(b,j);c.effects.removeWrapper(b);a.callback&&a.callback.apply(b[0],arguments);b.dequeue()})})}})(jQuery); +;/* + * jQuery UI Effects Highlight 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Highlight + * + * Depends: + * jquery.effects.core.js + */ +(function(b){b.effects.highlight=function(c){return this.queue(function(){var a=b(this),e=["backgroundImage","backgroundColor","opacity"],d=b.effects.setMode(a,c.options.mode||"show"),f={backgroundColor:a.css("backgroundColor")};if(d=="hide")f.opacity=0;b.effects.save(a,e);a.show().css({backgroundImage:"none",backgroundColor:c.options.color||"#ffff99"}).animate(f,{queue:false,duration:c.duration,easing:c.options.easing,complete:function(){d=="hide"&&a.hide();b.effects.restore(a,e);d=="show"&&!b.support.opacity&& +this.style.removeAttribute("filter");c.callback&&c.callback.apply(this,arguments);a.dequeue()}})})}})(jQuery); +;/* + * jQuery UI Effects Pulsate 1.8.5 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Effects/Pulsate + * + * Depends: + * jquery.effects.core.js + */ +(function(d){d.effects.pulsate=function(a){return this.queue(function(){var b=d(this),c=d.effects.setMode(b,a.options.mode||"show");times=(a.options.times||5)*2-1;duration=a.duration?a.duration/2:d.fx.speeds._default/2;isVisible=b.is(":visible");animateTo=0;if(!isVisible){b.css("opacity",0).show();animateTo=1}if(c=="hide"&&isVisible||c=="show"&&!isVisible)times--;for(c=0;c').appendTo(document.body).addClass(a.options.className).css({top:d.top,left:d.left,height:b.innerHeight(),width:b.innerWidth(),position:"absolute"}).animate(c,a.duration,a.options.easing,function(){f.remove();a.callback&&a.callback.apply(b[0],arguments); +b.dequeue()})})}})(jQuery); +; \ No newline at end of file diff --git a/data/treeio/treeio/static/js/jquery.ba-serializeobject.js b/data/treeio/treeio/static/js/jquery.ba-serializeobject.js new file mode 100644 index 0000000..03b905b --- /dev/null +++ b/data/treeio/treeio/static/js/jquery.ba-serializeobject.js @@ -0,0 +1,31 @@ +/*! + * jQuery serializeObject - v0.2 - 1/20/2010 + * http://benalman.com/projects/jquery-misc-plugins/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ + +// Whereas .serializeArray() serializes a form into an array, .serializeObject() +// serializes a form into an (arguably more useful) object. + +(function($,undefined){ + '$:nomunge'; // Used by YUI compressor. + + $.fn.serializeObject = function(){ + var obj = {}; + + $.each( this.serializeArray(), function(i,o){ + var n = o.name, + v = o.value; + + obj[n] = obj[n] === undefined ? v + : $.isArray( obj[n] ) ? obj[n].concat( v ) + : [ obj[n], v ]; + }); + + return obj; + }; + +})(jQuery); diff --git a/data/treeio/treeio/static/js/jquery.ganttView.js b/data/treeio/treeio/static/js/jquery.ganttView.js new file mode 100644 index 0000000..ec2aa21 --- /dev/null +++ b/data/treeio/treeio/static/js/jquery.ganttView.js @@ -0,0 +1,440 @@ +/* +jQuery.ganttView v.0.8.8 +Copyright (c) 2010 JC Grubbs - jc.grubbs@devmynd.com +MIT License Applies +*/ + +/* +Options +----------------- +showWeekends: boolean +data: object +cellWidth: number +cellHeight: number +slideWidth: number +dataUrl: string +behavior: { + clickable: boolean, + draggable: boolean, + resizable: boolean, + onClick: function, + onDrag: function, + onResize: function +} +*/ + +(function (jQuery) { + + jQuery.fn.ganttView = function () { + + var args = Array.prototype.slice.call(arguments); + + if (args.length == 1 && typeof(args[0]) == "object") { + build.call(this, args[0]); + } + + if (args.length == 2 && typeof(args[0]) == "string") { + handleMethod.call(this, args[0], args[1]); + } + }; + + function build(options) { + + var els = this; + var defaults = { + showWeekends: true, + cellWidth: 21, + cellHeight: 31, + slideWidth: 400, + vHeaderWidth: 100, + behavior: { + clickable: true, + draggable: true, + resizable: true + } + }; + + var opts = jQuery.extend(true, defaults, options); + + if (opts.data) { + build(); + } else if (opts.dataUrl) { + jQuery.getJSON(opts.dataUrl, function (data) { opts.data = data; build(); }); + } + + function build() { + + var minDays = Math.floor((opts.slideWidth / opts.cellWidth) + 5); + var startEnd = DateUtils.getBoundaryDatesFromData(opts.data, minDays); + opts.start = startEnd[0]; + opts.end = startEnd[1]; + + els.each(function () { + + var container = jQuery(this); + var div = jQuery("
", { "class": "ganttview" }); + new Chart(div, opts).render(); + container.append(div); + + var w = jQuery("div.ganttview-vtheader", container).outerWidth() + + jQuery("div.ganttview-slide-container", container).outerWidth(); + container.css("width", (w + 2) + "px"); + + new Behavior(container, opts).apply(); + }); + } + } + +$(window).resize(function () { $("#ganttChart").ganttView("setSlideWidth", $("div.module-block").width() - $('td.module-sidebar').width() - 60); }); + + function handleMethod(method, value) { + + if (method == "setSlideWidth") { + var div = $("div.ganttview", this); + var divchart = $("div#ganttChart", this); + div.each(function () { + var vtWidth = $("div.ganttview-vtheader", div).outerWidth(); + $(div).width(value + 1); + + $("div.ganttview-slide-container", this).width(value - vtWidth); + build(); + + }); + + + } + $('#ganttChart').width(value); + } + + var Chart = function(div, opts) { + + function render() { + addVtHeader(div, opts.data, opts.cellHeight); + + var slideDiv = jQuery("
", { + "class": "ganttview-slide-container", + "css": { "width": opts.slideWidth + "px" } + }); + + dates = getDates(opts.start, opts.end); + addHzHeader(slideDiv, dates, opts.cellWidth); + addGrid(slideDiv, opts.data, dates, opts.cellWidth, opts.showWeekends); + addBlockContainers(slideDiv, opts.data); + addBlocks(slideDiv, opts.data, opts.cellWidth, opts.start); + div.append(slideDiv); + applyLastClass(div.parent()); + } + + var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + // Creates a 3 dimensional array [year][month][day] of every day + // between the given start and end dates + function getDates(start, end) { + var dates = []; + dates[start.getFullYear()] = []; + dates[start.getFullYear()][start.getMonth()] = [start] + var last = start; + while (last.compareTo(end) == -1) { + var next = last.clone().addDays(1); + if (!dates[next.getFullYear()]) { dates[next.getFullYear()] = []; } + if (!dates[next.getFullYear()][next.getMonth()]) { + dates[next.getFullYear()][next.getMonth()] = []; + } + dates[next.getFullYear()][next.getMonth()].push(next); + last = next; + } + return dates; + } + + function addVtHeader(div, data, cellHeight) { + var headerDiv = jQuery("
", { "class": "ganttview-vtheader" }); + for (var i = 0; i < data.length; i++) { + var itemDiv = jQuery("
", { "class": "ganttview-vtheader-item" }); + itemDiv.append(jQuery("
", { + "class": "ganttview-vtheader-item-name", + "css": { "height": (data[i].series.length * cellHeight) + "px" } + }).append(data[i].name)); + var seriesDiv = jQuery("
", { "class": "ganttview-vtheader-series" }); + var m_start = data[i].start; + var m_end = data[i].end; + if (typeof m_start !== 'undefined' && m_start !== false && + typeof m_end !== 'undefined' && m_end !== false) { + seriesDiv.append(jQuery("
", { "class": "ganttview-vtheader-series-name" }) + .append('')); + itemDiv.append(seriesDiv); + + } + + for (var j = 0; j < data[i].series.length; j++) { + seriesDiv.append(jQuery("
", { "class": "ganttview-vtheader-series-name" }) + .append(data[i].series[j].name)); + } + itemDiv.append(seriesDiv); + headerDiv.append(itemDiv); + } + div.append(headerDiv); + } + + function addHzHeader(div, dates, cellWidth) { + var headerDiv = jQuery("
", { "class": "ganttview-hzheader" }); + var monthsDiv = jQuery("
", { "class": "ganttview-hzheader-months" }); + var daysDiv = jQuery("
", { "class": "ganttview-hzheader-days" }); + var totalW = 0; + for (var y in dates) { + for (var m in dates[y]) { + var w = dates[y][m].length * cellWidth; + totalW = totalW + w; + monthsDiv.append(jQuery("
", { + "class": "ganttview-hzheader-month", + "css": { "width": (w - 1) + "px" } + }).append(monthNames[m] + "/" + y)); + for (var d in dates[y][m]) { + daysDiv.append(jQuery("
", { "class": "ganttview-hzheader-day" }) + .append(dates[y][m][d].getDate())); + } + } + } + monthsDiv.css("width", totalW + "px"); + daysDiv.css("width", totalW + "px"); + headerDiv.append(monthsDiv).append(daysDiv); + div.append(headerDiv); + } + + function addGrid(div, data, dates, cellWidth, showWeekends) { + var gridDiv = jQuery("
", { "class": "ganttview-grid" }); + var rowDiv = jQuery("
", { "class": "ganttview-grid-row" }); + for (var y in dates) { + for (var m in dates[y]) { + for (var d in dates[y][m]) { + var cellDiv = jQuery("
", { "class": "ganttview-grid-row-cell" }); + if (DateUtils.isWeekend(dates[y][m][d]) && showWeekends) { + cellDiv.addClass("ganttview-weekend"); + } + rowDiv.append(cellDiv); + } + } + } + var w = jQuery("div.ganttview-grid-row-cell", rowDiv).length * cellWidth; + rowDiv.css("width", w + "px"); + gridDiv.css("width", w + "px"); + for (var i = 0; i < data.length; i++) { + var m_start = data[i].start; + var m_end = data[i].end; + if (typeof m_start !== 'undefined' && m_start !== false && + typeof m_end !== 'undefined' && m_end !== false) { + gridDiv.append(rowDiv.clone()); + } + for (var j = 0; j < data[i].series.length; j++) { + gridDiv.append(rowDiv.clone()); + } + } + div.append(gridDiv); + } + + function addBlockContainers(div, data) { + var blocksDiv = jQuery("
", { "class": "ganttview-blocks" }); + for (var i = 0; i < data.length; i++) { + var m_start = data[i].start; + var m_end = data[i].end; + if (typeof m_start !== 'undefined' && m_start !== false && + typeof m_end !== 'undefined' && m_end !== false) { + blocksDiv.append(jQuery("
", { "class": "ganttview-block-container" })); + } + for (var j = 0; j < data[i].series.length; j++) { + blocksDiv.append(jQuery("
", { "class": "ganttview-block-container" })); + } + } + div.append(blocksDiv); + } + + function addBlocks(div, data, cellWidth, start) { + var rows = jQuery("div.ganttview-blocks div.ganttview-block-container", div); + var rowIdx = 0; + for (var i = 0; i < data.length; i++) { + var m_start = data[i].start; + var m_end = data[i].end; + if (typeof m_start !== 'undefined' && m_start !== false && + typeof m_end !== 'undefined' && m_end !== false) { + // Milestone has start and end dates + var size = DateUtils.daysBetween(m_start, m_end) + 1; + var offset = DateUtils.daysBetween(start, m_start); + var block = jQuery("
", { + "class": "ganttview-block", + "title": data[i].label + ", " + size + " days", + "css": { + "width": ((size * cellWidth) - 9) + "px", + "margin-left": ((offset * cellWidth) + 3) + "px" + } + }); + addBlockData(block, data[i], ['']); + if (data[i].color) { + block.css("background-color", data[i].color); + } + block.append(jQuery("
", { "class": "ganttview-block-text" }).text(size)); + jQuery(rows[rowIdx]).append(block); + rowIdx = rowIdx + 1; + + } + for (var j = 0; j < data[i].series.length; j++) { + var series = data[i].series[j]; + var size = DateUtils.daysBetween(series.start, series.end) + 1; + var offset = DateUtils.daysBetween(start, series.start); + var block = jQuery("
", { + "class": "ganttview-block", + "title": series.label + ", " + size + " days", + "css": { + "width": ((size * cellWidth) - 9) + "px", + "margin-left": ((offset * cellWidth) + 3) + "px" + } + }); + addBlockData(block, data[i], series); + if (data[i].series[j].color) { + block.css("background-color", data[i].series[j].color); + } + block.append(jQuery("
", { "class": "ganttview-block-text" }).text(size)); + jQuery(rows[rowIdx]).append(block); + rowIdx = rowIdx + 1; + } + } + } + + function addBlockData(block, data, series) { + // This allows custom attributes to be added to the series data objects + // and makes them available to the 'data' argument of click, resize, and drag handlers + var blockData = { id: data.id, name: data.name }; + jQuery.extend(blockData, series); + block.data("block-data", blockData); + } + + function applyLastClass(div) { + jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child", div).addClass("last"); + jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child", div).addClass("last"); + jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child", div).addClass("last"); + } + + return { + render: render + }; + } + + var Behavior = function (div, opts) { + + function apply() { + + if (opts.behavior.clickable) { + bindBlockClick(div, opts.behavior.onClick); + } + + if (opts.behavior.resizable) { + bindBlockResize(div, opts.cellWidth, opts.start, opts.behavior.onResize); + } + + if (opts.behavior.draggable) { + bindBlockDrag(div, opts.cellWidth, opts.start, opts.behavior.onDrag); + } + } + + function bindBlockClick(div, callback) { + jQuery("div.ganttview-block", div).live("click", function () { + if (callback) { callback(jQuery(this).data("block-data")); } + }); + } + + function bindBlockResize(div, cellWidth, startDate, callback) { + jQuery("div.ganttview-block", div).resizable({ + grid: cellWidth, + handles: "e,w", + stop: function () { + var block = jQuery(this); + updateDataAndPosition(div, block, cellWidth, startDate); + if (callback) { callback(block.data("block-data")); } + } + }); + } + + function bindBlockDrag(div, cellWidth, startDate, callback) { + jQuery("div.ganttview-block", div).draggable({ + axis: "x", + grid: [cellWidth, cellWidth], + stop: function () { + var block = jQuery(this); + updateDataAndPosition(div, block, cellWidth, startDate); + if (callback) { callback(block.data("block-data")); } + } + }); + } + + function updateDataAndPosition(div, block, cellWidth, startDate) { + var container = jQuery("div.ganttview-slide-container", div); + var scroll = container.scrollLeft(); + var offset = block.offset().left - container.offset().left - 1 + scroll; + + // Set new start date + var daysFromStart = Math.round(offset / cellWidth); + var newStart = startDate.clone().addDays(daysFromStart); + block.data("block-data").start = newStart; + + // Set new end date + var width = block.outerWidth(); + var numberOfDays = Math.round(width / cellWidth) - 1; + block.data("block-data").end = newStart.clone().addDays(numberOfDays); + jQuery("div.ganttview-block-text", block).text(numberOfDays + 1); + + // Remove top and left properties to avoid incorrect block positioning, + // set position to relative to keep blocks relative to scrollbar when scrolling + block.css("top", "").css("left", "") + .css("position", "relative").css("margin-left", offset + "px"); + } + + return { + apply: apply + }; + } + + var ArrayUtils = { + + contains: function (arr, obj) { + var has = false; + for (var i = 0; i < arr.length; i++) { if (arr[i] == obj) { has = true; } } + return has; + } + }; + + var DateUtils = { + + daysBetween: function (start, end) { + if (!start || !end) { return 0; } + start = Date.parse(start); end = Date.parse(end); + if (start.getYear() == 1901 || end.getYear() == 8099) { return 0; } + var count = 0, date = start.clone(); + while (date.compareTo(end) == -1) { count = count + 1; date.addDays(1); } + return count; + }, + + isWeekend: function (date) { + return date.getDay() % 6 == 0; + }, + + getBoundaryDatesFromData: function (data, minDays) { + var minStart = new Date(); maxEnd = new Date(); + for (var i = 0; i < data.length; i++) { + for (var j = 0; j < data[i].series.length; j++) { + var start = Date.parse(data[i].series[j].start); + var end = Date.parse(data[i].series[j].end) + if (i == 0 && j == 0) { minStart = start; maxEnd = end; } + if (minStart.compareTo(start) == 1) { minStart = start; } + if (maxEnd.compareTo(end) == -1) { maxEnd = end; } + } + } + + // Insure that the width of the chart is at least the slide width to avoid empty + // whitespace to the right of the grid + if (DateUtils.daysBetween(minStart, maxEnd) < minDays) { + maxEnd = minStart.clone().addDays(minDays); + } + + return [minStart, maxEnd]; + } + }; + +})(jQuery); \ No newline at end of file diff --git a/data/treeio/treeio/static/js/jquery.gritter.js b/data/treeio/treeio/static/js/jquery.gritter.js new file mode 100644 index 0000000..811d27d --- /dev/null +++ b/data/treeio/treeio/static/js/jquery.gritter.js @@ -0,0 +1,401 @@ +/* + * Gritter for jQuery + * http://www.boedesign.com/ + * + * Copyright (c) 2009 Jordan Boesch + * Dual licensed under the MIT and GPL licenses. + * + * Date: December 1, 2009 + * Version: 1.6 + */ + +(function($){ + + /** + * Set it up as an object under the jQuery namespace + */ + $.gritter = {}; + + /** + * Set up global options that the user can over-ride + */ + $.gritter.options = { + fade_in_speed: 'medium', // how fast notifications fade in + fade_out_speed: 1000, // how fast the notices fade out + time: 6000 // hang on the screen for... + } + + /** + * Add a gritter notification to the screen + * @see Gritter#add(); + */ + $.gritter.add = function(params){ + + try { + return Gritter.add(params || {}); + } catch(e) { + + var err = 'Gritter Error: ' + e; + (typeof(console) != 'undefined' && console.error) ? + console.error(err, params) : + alert(err); + + } + + } + + /** + * Remove a gritter notification from the screen + * @see Gritter#removeSpecific(); + */ + $.gritter.remove = function(id, params){ + Gritter.removeSpecific(id, params || {}); + } + + /** + * Remove all notifications + * @see Gritter#stop(); + */ + $.gritter.removeAll = function(params){ + Gritter.stop(params || {}); + } + + /** + * Big fat Gritter object + * @constructor (not really since it's object literal) + */ + var Gritter = { + + // Public - options to over-ride with $.gritter.options in "add" + fade_in_speed: '', + fade_out_speed: '', + time: '', + + // Private - no touchy the private parts + _custom_timer: 0, + _item_count: 0, + _is_setup: 0, + _tpl_close: '
', + _tpl_item: '', + _tpl_wrap: '
', + + /** + * Add a gritter notification to the screen + * @param {Object} params The object that contains all the options for drawing the notification + * @return {Integer} The specific numeric id to that gritter notification + */ + add: function(params){ + + // We might have some issues if we don't have a title or text! + if(!params.title || !params.text){ + throw 'You need to fill out the first 2 params: "title" and "text"'; + } + + // Check the options and set them once + if(!this._is_setup){ + this._runSetup(); + } + + // Basics + var user = params.title, + text = params.text, + image = params.image || '', + sticky = params.sticky || false, + item_class = params.class_name || '', + time_alive = params.time || ''; + + this._verifyWrapper(); + + this._item_count++; + var number = this._item_count, + tmp = this._tpl_item; + + // Assign callbacks + $(['before_open', 'after_open', 'before_close', 'after_close']).each(function(i, val){ + Gritter['_' + val + '_' + number] = ($.isFunction(params[val])) ? params[val] : function(){} + }); + + // Reset + this._custom_timer = 0; + + // A custom fade time set + if(time_alive){ + this._custom_timer = time_alive; + } + + var image_str = (image != '') ? '' : '', + class_name = (image != '') ? 'gritter-with-image' : 'gritter-without-image'; + + // String replacements on the template + tmp = this._str_replace( + ['[[username]]', '[[text]]', '[[image]]', '[[number]]', '[[class_name]]', '[[item_class]]'], + [user, text, image_str, this._item_count, class_name, item_class], tmp + ); + + this['_before_open_' + number](); + $('#gritter-notice-wrapper').append(tmp); + + var item = $('#gritter-item-' + this._item_count); + + item.fadeIn(this.fade_in_speed, function(){ + Gritter['_after_open_' + number]($(this)); + }); + + if(!sticky){ + this._setFadeTimer(item, number); + } + + // Bind the hover/unhover states + $(item).bind('mouseenter mouseleave', function(event){ + if(event.type == 'mouseenter'){ + if(!sticky){ + Gritter._restoreItemIfFading($(this), number); + } + } + else { + if(!sticky){ + Gritter._setFadeTimer($(this), number); + } + } + Gritter._hoverState($(this), event.type); + }); + + return number; + + }, + + /** + * If we don't have any more gritter notifications, get rid of the wrapper using this check + * @private + * @param {Integer} unique_id The ID of the element that was just deleted, use it for a callback + * @param {Object} e The jQuery element that we're going to perform the remove() action on + */ + _countRemoveWrapper: function(unique_id, e){ + + // Remove it then run the callback function + e.remove(); + this['_after_close_' + unique_id](e); + + // Check if the wrapper is empty, if it is.. remove the wrapper + if($('.gritter-item-wrapper').length == 0){ + $('#gritter-notice-wrapper').remove(); + } + + }, + + /** + * Fade out an element after it's been on the screen for x amount of time + * @private + * @param {Object} e The jQuery element to get rid of + * @param {Integer} unique_id The id of the element to remove + * @param {Object} params An optional list of params to set fade speeds etc. + * @param {Boolean} unbind_events Unbind the mouseenter/mouseleave events if they click (X) + */ + _fade: function(e, unique_id, params, unbind_events){ + + var params = params || {}, + fade = (typeof(params.fade) != 'undefined') ? params.fade : true; + fade_out_speed = params.speed || this.fade_out_speed; + + this['_before_close_' + unique_id](e); + + // If this is true, then we are coming from clicking the (X) + if(unbind_events){ + e.unbind('mouseenter mouseleave'); + } + + // Fade it out or remove it + if(fade){ + + e.animate({ + opacity: 0 + }, fade_out_speed, function(){ + e.animate({ height: 0 }, 300, function(){ + Gritter._countRemoveWrapper(unique_id, e); + }) + }) + + } + else { + + this._countRemoveWrapper(unique_id, e); + + } + + }, + + /** + * Perform actions based on the type of bind (mouseenter, mouseleave) + * @private + * @param {Object} e The jQuery element + * @param {String} type The type of action we're performing: mouseenter or mouseleave + */ + _hoverState: function(e, type){ + + // Change the border styles and add the (X) close button when you hover + if(type == 'mouseenter'){ + + e.addClass('hover'); + var find_img = e.find('img'); + + // Insert the close button before what element + (find_img.length) ? + find_img.before(this._tpl_close) : + e.find('span').before(this._tpl_close); + + // Clicking (X) makes the perdy thing close + e.find('.gritter-close').click(function(){ + + var unique_id = e.attr('id').split('-')[2]; + Gritter.removeSpecific(unique_id, {}, e, true); + + }); + + } + // Remove the border styles and (X) close button when you mouse out + else { + + e.removeClass('hover'); + e.find('.gritter-close').remove(); + + } + + }, + + /** + * Remove a specific notification based on an ID + * @param {Integer} unique_id The ID used to delete a specific notification + * @param {Object} params A set of options passed in to determine how to get rid of it + * @param {Object} e The jQuery element that we're "fading" then removing + * @param {Boolean} unbind_events If we clicked on the (X) we set this to true to unbind mouseenter/mouseleave + */ + removeSpecific: function(unique_id, params, e, unbind_events){ + + if(!e){ + var e = $('#gritter-item-' + unique_id); + } + + // We set the fourth param to let the _fade function know to + // unbind the "mouseleave" event. Once you click (X) there's no going back! + this._fade(e, unique_id, params || {}, unbind_events); + + }, + + /** + * If the item is fading out and we hover over it, restore it! + * @private + * @param {Object} e The HTML element to remove + * @param {Integer} unique_id The ID of the element + */ + _restoreItemIfFading: function(e, unique_id){ + + clearTimeout(this['_int_id_' + unique_id]); + e.stop().css({ opacity: '' }); + + }, + + /** + * Setup the global options - only once + * @private + */ + _runSetup: function(){ + + for(opt in $.gritter.options){ + this[opt] = $.gritter.options[opt]; + } + this._is_setup = 1; + + }, + + /** + * Set the notification to fade out after a certain amount of time + * @private + * @param {Object} item The HTML element we're dealing with + * @param {Integer} unique_id The ID of the element + */ + _setFadeTimer: function(e, unique_id){ + + var timer_str = (this._custom_timer) ? this._custom_timer : this.time; + this['_int_id_' + unique_id] = setTimeout(function(){ + Gritter._fade(e, unique_id); + }, timer_str); + + }, + + /** + * Bring everything to a halt + * @param {Object} params A list of callback functions to pass when all notifications are removed + */ + stop: function(params){ + + // callbacks (if passed) + var before_close = ($.isFunction(params.before_close)) ? params.before_close : function(){}; + var after_close = ($.isFunction(params.after_close)) ? params.after_close : function(){}; + + var wrap = $('#gritter-notice-wrapper'); + before_close(wrap); + wrap.fadeOut(function(){ + $(this).remove(); + after_close(); + }); + + }, + + /** + * An extremely handy PHP function ported to JS, works well for templating + * @private + * @param {String/Array} search A list of things to search for + * @param {String/Array} replace A list of things to replace the searches with + * @return {String} sa The output + */ + _str_replace: function(search, replace, subject, count){ + + var i = 0, j = 0, temp = '', repl = '', sl = 0, fl = 0, + f = [].concat(search), + r = [].concat(replace), + s = subject, + ra = r instanceof Array, sa = s instanceof Array; + s = [].concat(s); + + if(count){ + this.window[count] = 0; + } + + for(i = 0, sl = s.length; i < sl; i++){ + + if(s[i] === ''){ + continue; + } + + for (j = 0, fl = f.length; j < fl; j++){ + + temp = s[i] + ''; + repl = ra ? (r[j] !== undefined ? r[j] : '') : r[0]; + s[i] = (temp).split(f[j]).join(repl); + + if(count && s[i] !== temp){ + this.window[count] += (temp.length-s[i].length) / f[j].length; + } + + } + } + + return sa ? s : s[0]; + + }, + + /** + * A check to make sure we have something to wrap our notices with + * @private + */ + _verifyWrapper: function(){ + + if($('#gritter-notice-wrapper').length == 0){ + $('body').append(this._tpl_wrap); + } + + } + + } + +})(jQuery); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/advimage/js/image.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/advimage/js/image.js new file mode 100644 index 0000000..02495fb --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/advimage/js/image.js @@ -0,0 +1,464 @@ +var ImageDialog = { + preInit : function() { + var url; + + tinyMCEPopup.requireLangPack(); + + if (url = tinyMCEPopup.getParam("external_image_list_url")) + document.write(''); + }, + + init : function(ed) { + var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, dom = ed.dom, n = ed.selection.getNode(), fl = tinyMCEPopup.getParam('external_image_list', 'tinyMCEImageList'); + + tinyMCEPopup.resizeToInnerSize(); + this.fillClassList('class_list'); + this.fillFileList('src_list', fl); + this.fillFileList('over_list', fl); + this.fillFileList('out_list', fl); + TinyMCE_EditableSelects.init(); + + if (n.nodeName == 'IMG') { + nl.src.value = dom.getAttrib(n, 'src'); + nl.width.value = dom.getAttrib(n, 'width'); + nl.height.value = dom.getAttrib(n, 'height'); + nl.alt.value = dom.getAttrib(n, 'alt'); + nl.title.value = dom.getAttrib(n, 'title'); + nl.vspace.value = this.getAttrib(n, 'vspace'); + nl.hspace.value = this.getAttrib(n, 'hspace'); + nl.border.value = this.getAttrib(n, 'border'); + selectByValue(f, 'align', this.getAttrib(n, 'align')); + selectByValue(f, 'class_list', dom.getAttrib(n, 'class'), true, true); + nl.style.value = dom.getAttrib(n, 'style'); + nl.id.value = dom.getAttrib(n, 'id'); + nl.dir.value = dom.getAttrib(n, 'dir'); + nl.lang.value = dom.getAttrib(n, 'lang'); + nl.usemap.value = dom.getAttrib(n, 'usemap'); + nl.longdesc.value = dom.getAttrib(n, 'longdesc'); + nl.insert.value = ed.getLang('update'); + + if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseover'))) + nl.onmouseoversrc.value = dom.getAttrib(n, 'onmouseover').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1'); + + if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseout'))) + nl.onmouseoutsrc.value = dom.getAttrib(n, 'onmouseout').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1'); + + if (ed.settings.inline_styles) { + // Move attribs to styles + if (dom.getAttrib(n, 'align')) + this.updateStyle('align'); + + if (dom.getAttrib(n, 'hspace')) + this.updateStyle('hspace'); + + if (dom.getAttrib(n, 'border')) + this.updateStyle('border'); + + if (dom.getAttrib(n, 'vspace')) + this.updateStyle('vspace'); + } + } + + // Setup browse button + document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image'); + if (isVisible('srcbrowser')) + document.getElementById('src').style.width = '260px'; + + // Setup browse button + document.getElementById('onmouseoversrccontainer').innerHTML = getBrowserHTML('overbrowser','onmouseoversrc','image','theme_advanced_image'); + if (isVisible('overbrowser')) + document.getElementById('onmouseoversrc').style.width = '260px'; + + // Setup browse button + document.getElementById('onmouseoutsrccontainer').innerHTML = getBrowserHTML('outbrowser','onmouseoutsrc','image','theme_advanced_image'); + if (isVisible('outbrowser')) + document.getElementById('onmouseoutsrc').style.width = '260px'; + + // If option enabled default contrain proportions to checked + if (ed.getParam("advimage_constrain_proportions", true)) + f.constrain.checked = true; + + // Check swap image if valid data + if (nl.onmouseoversrc.value || nl.onmouseoutsrc.value) + this.setSwapImage(true); + else + this.setSwapImage(false); + + this.changeAppearance(); + this.showPreviewImage(nl.src.value, 1); + }, + + insert : function(file, title) { + var ed = tinyMCEPopup.editor, t = this, f = document.forms[0]; + + if (f.src.value === '') { + if (ed.selection.getNode().nodeName == 'IMG') { + ed.dom.remove(ed.selection.getNode()); + ed.execCommand('mceRepaint'); + } + + tinyMCEPopup.close(); + return; + } + + if (tinyMCEPopup.getParam("accessibility_warnings", 1)) { + if (!f.alt.value) { + tinyMCEPopup.confirm(tinyMCEPopup.getLang('advimage_dlg.missing_alt'), function(s) { + if (s) + t.insertAndClose(); + }); + + return; + } + } + + t.insertAndClose(); + }, + + insertAndClose : function() { + var ed = tinyMCEPopup.editor, f = document.forms[0], nl = f.elements, v, args = {}, el; + + tinyMCEPopup.restoreSelection(); + + // Fixes crash in Safari + if (tinymce.isWebKit) + ed.getWin().focus(); + + if (!ed.settings.inline_styles) { + args = { + vspace : nl.vspace.value, + hspace : nl.hspace.value, + border : nl.border.value, + align : getSelectValue(f, 'align') + }; + } else { + // Remove deprecated values + args = { + vspace : '', + hspace : '', + border : '', + align : '' + }; + } + + tinymce.extend(args, { + src : nl.src.value.replace(/ /g, '%20'), + width : nl.width.value, + height : nl.height.value, + alt : nl.alt.value, + title : nl.title.value, + 'class' : getSelectValue(f, 'class_list'), + style : nl.style.value, + id : nl.id.value, + dir : nl.dir.value, + lang : nl.lang.value, + usemap : nl.usemap.value, + longdesc : nl.longdesc.value + }); + + args.onmouseover = args.onmouseout = ''; + + if (f.onmousemovecheck.checked) { + if (nl.onmouseoversrc.value) + args.onmouseover = "this.src='" + nl.onmouseoversrc.value + "';"; + + if (nl.onmouseoutsrc.value) + args.onmouseout = "this.src='" + nl.onmouseoutsrc.value + "';"; + } + + el = ed.selection.getNode(); + + if (el && el.nodeName == 'IMG') { + ed.dom.setAttribs(el, args); + } else { + tinymce.each(args, function(value, name) { + if (value === "") { + delete args[name]; + } + }); + + ed.execCommand('mceInsertContent', false, tinyMCEPopup.editor.dom.createHTML('img', args), {skip_undo : 1}); + ed.undoManager.add(); + } + + tinyMCEPopup.editor.execCommand('mceRepaint'); + tinyMCEPopup.editor.focus(); + tinyMCEPopup.close(); + }, + + getAttrib : function(e, at) { + var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2; + + if (ed.settings.inline_styles) { + switch (at) { + case 'align': + if (v = dom.getStyle(e, 'float')) + return v; + + if (v = dom.getStyle(e, 'vertical-align')) + return v; + + break; + + case 'hspace': + v = dom.getStyle(e, 'margin-left') + v2 = dom.getStyle(e, 'margin-right'); + + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'vspace': + v = dom.getStyle(e, 'margin-top') + v2 = dom.getStyle(e, 'margin-bottom'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'border': + v = 0; + + tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) { + sv = dom.getStyle(e, 'border-' + sv + '-width'); + + // False or not the same as prev + if (!sv || (sv != v && v !== 0)) { + v = 0; + return false; + } + + if (sv) + v = sv; + }); + + if (v) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + } + } + + if (v = dom.getAttrib(e, at)) + return v; + + return ''; + }, + + setSwapImage : function(st) { + var f = document.forms[0]; + + f.onmousemovecheck.checked = st; + setBrowserDisabled('overbrowser', !st); + setBrowserDisabled('outbrowser', !st); + + if (f.over_list) + f.over_list.disabled = !st; + + if (f.out_list) + f.out_list.disabled = !st; + + f.onmouseoversrc.disabled = !st; + f.onmouseoutsrc.disabled = !st; + }, + + fillClassList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + if (v = tinyMCEPopup.getParam('theme_advanced_styles')) { + cl = []; + + tinymce.each(v.split(';'), function(v) { + var p = v.split('='); + + cl.push({'title' : p[0], 'class' : p[1]}); + }); + } else + cl = tinyMCEPopup.editor.dom.getClasses(); + + if (cl.length > 0) { + lst.options.length = 0; + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + + tinymce.each(cl, function(o) { + lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = typeof(l) === 'function' ? l() : window[l]; + lst.options.length = 0; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + resetImageData : function() { + var f = document.forms[0]; + + f.elements.width.value = f.elements.height.value = ''; + }, + + updateImageData : function(img, st) { + var f = document.forms[0]; + + if (!st) { + f.elements.width.value = img.width; + f.elements.height.value = img.height; + } + + this.preloadImg = img; + }, + + changeAppearance : function() { + var ed = tinyMCEPopup.editor, f = document.forms[0], img = document.getElementById('alignSampleImg'); + + if (img) { + if (ed.getParam('inline_styles')) { + ed.dom.setAttrib(img, 'style', f.style.value); + } else { + img.align = f.align.value; + img.border = f.border.value; + img.hspace = f.hspace.value; + img.vspace = f.vspace.value; + } + } + }, + + changeHeight : function() { + var f = document.forms[0], tp, t = this; + + if (!f.constrain.checked || !t.preloadImg) { + return; + } + + if (f.width.value == "" || f.height.value == "") + return; + + tp = (parseInt(f.width.value) / parseInt(t.preloadImg.width)) * t.preloadImg.height; + f.height.value = tp.toFixed(0); + }, + + changeWidth : function() { + var f = document.forms[0], tp, t = this; + + if (!f.constrain.checked || !t.preloadImg) { + return; + } + + if (f.width.value == "" || f.height.value == "") + return; + + tp = (parseInt(f.height.value) / parseInt(t.preloadImg.height)) * t.preloadImg.width; + f.width.value = tp.toFixed(0); + }, + + updateStyle : function(ty) { + var dom = tinyMCEPopup.dom, b, bStyle, bColor, v, isIE = tinymce.isIE, f = document.forms[0], img = dom.create('img', {style : dom.get('style').value}); + + if (tinyMCEPopup.editor.settings.inline_styles) { + // Handle align + if (ty == 'align') { + dom.setStyle(img, 'float', ''); + dom.setStyle(img, 'vertical-align', ''); + + v = getSelectValue(f, 'align'); + if (v) { + if (v == 'left' || v == 'right') + dom.setStyle(img, 'float', v); + else + img.style.verticalAlign = v; + } + } + + // Handle border + if (ty == 'border') { + b = img.style.border ? img.style.border.split(' ') : []; + bStyle = dom.getStyle(img, 'border-style'); + bColor = dom.getStyle(img, 'border-color'); + + dom.setStyle(img, 'border', ''); + + v = f.border.value; + if (v || v == '0') { + if (v == '0') + img.style.border = isIE ? '0' : '0 none none'; + else { + var isOldIE = tinymce.isIE && (!document.documentMode || document.documentMode < 9); + + if (b.length == 3 && b[isOldIE ? 2 : 1]) + bStyle = b[isOldIE ? 2 : 1]; + else if (!bStyle || bStyle == 'none') + bStyle = 'solid'; + if (b.length == 3 && b[isIE ? 0 : 2]) + bColor = b[isOldIE ? 0 : 2]; + else if (!bColor || bColor == 'none') + bColor = 'black'; + img.style.border = v + 'px ' + bStyle + ' ' + bColor; + } + } + } + + // Handle hspace + if (ty == 'hspace') { + dom.setStyle(img, 'marginLeft', ''); + dom.setStyle(img, 'marginRight', ''); + + v = f.hspace.value; + if (v) { + img.style.marginLeft = v + 'px'; + img.style.marginRight = v + 'px'; + } + } + + // Handle vspace + if (ty == 'vspace') { + dom.setStyle(img, 'marginTop', ''); + dom.setStyle(img, 'marginBottom', ''); + + v = f.vspace.value; + if (v) { + img.style.marginTop = v + 'px'; + img.style.marginBottom = v + 'px'; + } + } + + // Merge + dom.get('style').value = dom.serializeStyle(dom.parseStyle(img.style.cssText), 'img'); + } + }, + + changeMouseMove : function() { + }, + + showPreviewImage : function(u, st) { + if (!u) { + tinyMCEPopup.dom.setHTML('prev', ''); + return; + } + + if (!st && tinyMCEPopup.getParam("advimage_update_dimensions_onchange", true)) + this.resetImageData(); + + u = tinyMCEPopup.editor.documentBaseURI.toAbsolute(u); + + if (!st) + tinyMCEPopup.dom.setHTML('prev', ''); + else + tinyMCEPopup.dom.setHTML('prev', ''); + } +}; + +ImageDialog.preInit(); +tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/advlink/js/advlink.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/advlink/js/advlink.js new file mode 100644 index 0000000..5bf8070 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/advlink/js/advlink.js @@ -0,0 +1,543 @@ +/* Functions for the advlink plugin popup */ + +tinyMCEPopup.requireLangPack(); + +var templates = { + "window.open" : "window.open('${url}','${target}','${options}')" +}; + +function preinit() { + var url; + + if (url = tinyMCEPopup.getParam("external_link_list_url")) + document.write(''); +} + +function changeClass() { + var f = document.forms[0]; + + f.classes.value = getSelectValue(f, 'classlist'); +} + +function init() { + tinyMCEPopup.resizeToInnerSize(); + + var formObj = document.forms[0]; + var inst = tinyMCEPopup.editor; + var elm = inst.selection.getNode(); + var action = "insert"; + var html; + + document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser','href','file','advlink'); + document.getElementById('popupurlbrowsercontainer').innerHTML = getBrowserHTML('popupurlbrowser','popupurl','file','advlink'); + document.getElementById('targetlistcontainer').innerHTML = getTargetListHTML('targetlist','target'); + + // Link list + html = getLinkListHTML('linklisthref','href'); + if (html == "") + document.getElementById("linklisthrefrow").style.display = 'none'; + else + document.getElementById("linklisthrefcontainer").innerHTML = html; + + // Anchor list + html = getAnchorListHTML('anchorlist','href'); + if (html == "") + document.getElementById("anchorlistrow").style.display = 'none'; + else + document.getElementById("anchorlistcontainer").innerHTML = html; + + // Resize some elements + if (isVisible('hrefbrowser')) + document.getElementById('href').style.width = '260px'; + + if (isVisible('popupurlbrowser')) + document.getElementById('popupurl').style.width = '180px'; + + elm = inst.dom.getParent(elm, "A"); + if (elm == null) { + var prospect = inst.dom.create("p", null, inst.selection.getContent()); + if (prospect.childNodes.length === 1) { + elm = prospect.firstChild; + } + } + + if (elm != null && elm.nodeName == "A") + action = "update"; + + formObj.insert.value = tinyMCEPopup.getLang(action, 'Insert', true); + + setPopupControlsDisabled(true); + + if (action == "update") { + var href = inst.dom.getAttrib(elm, 'href'); + var onclick = inst.dom.getAttrib(elm, 'onclick'); + var linkTarget = inst.dom.getAttrib(elm, 'target') ? inst.dom.getAttrib(elm, 'target') : "_self"; + + // Setup form data + setFormValue('href', href); + setFormValue('title', inst.dom.getAttrib(elm, 'title')); + setFormValue('id', inst.dom.getAttrib(elm, 'id')); + setFormValue('style', inst.dom.getAttrib(elm, "style")); + setFormValue('rel', inst.dom.getAttrib(elm, 'rel')); + setFormValue('rev', inst.dom.getAttrib(elm, 'rev')); + setFormValue('charset', inst.dom.getAttrib(elm, 'charset')); + setFormValue('hreflang', inst.dom.getAttrib(elm, 'hreflang')); + setFormValue('dir', inst.dom.getAttrib(elm, 'dir')); + setFormValue('lang', inst.dom.getAttrib(elm, 'lang')); + setFormValue('tabindex', inst.dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : "")); + setFormValue('accesskey', inst.dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : "")); + setFormValue('type', inst.dom.getAttrib(elm, 'type')); + setFormValue('onfocus', inst.dom.getAttrib(elm, 'onfocus')); + setFormValue('onblur', inst.dom.getAttrib(elm, 'onblur')); + setFormValue('onclick', onclick); + setFormValue('ondblclick', inst.dom.getAttrib(elm, 'ondblclick')); + setFormValue('onmousedown', inst.dom.getAttrib(elm, 'onmousedown')); + setFormValue('onmouseup', inst.dom.getAttrib(elm, 'onmouseup')); + setFormValue('onmouseover', inst.dom.getAttrib(elm, 'onmouseover')); + setFormValue('onmousemove', inst.dom.getAttrib(elm, 'onmousemove')); + setFormValue('onmouseout', inst.dom.getAttrib(elm, 'onmouseout')); + setFormValue('onkeypress', inst.dom.getAttrib(elm, 'onkeypress')); + setFormValue('onkeydown', inst.dom.getAttrib(elm, 'onkeydown')); + setFormValue('onkeyup', inst.dom.getAttrib(elm, 'onkeyup')); + setFormValue('target', linkTarget); + setFormValue('classes', inst.dom.getAttrib(elm, 'class')); + + // Parse onclick data + if (onclick != null && onclick.indexOf('window.open') != -1) + parseWindowOpen(onclick); + else + parseFunction(onclick); + + // Select by the values + selectByValue(formObj, 'dir', inst.dom.getAttrib(elm, 'dir')); + selectByValue(formObj, 'rel', inst.dom.getAttrib(elm, 'rel')); + selectByValue(formObj, 'rev', inst.dom.getAttrib(elm, 'rev')); + selectByValue(formObj, 'linklisthref', href); + + if (href.charAt(0) == '#') + selectByValue(formObj, 'anchorlist', href); + + addClassesToList('classlist', 'advlink_styles'); + + selectByValue(formObj, 'classlist', inst.dom.getAttrib(elm, 'class'), true); + selectByValue(formObj, 'targetlist', linkTarget, true); + } else + addClassesToList('classlist', 'advlink_styles'); +} + +function checkPrefix(n) { + if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_email'))) + n.value = 'mailto:' + n.value; + + if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_external'))) + n.value = 'http://' + n.value; +} + +function setFormValue(name, value) { + document.forms[0].elements[name].value = value; +} + +function parseWindowOpen(onclick) { + var formObj = document.forms[0]; + + // Preprocess center code + if (onclick.indexOf('return false;') != -1) { + formObj.popupreturn.checked = true; + onclick = onclick.replace('return false;', ''); + } else + formObj.popupreturn.checked = false; + + var onClickData = parseLink(onclick); + + if (onClickData != null) { + formObj.ispopup.checked = true; + setPopupControlsDisabled(false); + + var onClickWindowOptions = parseOptions(onClickData['options']); + var url = onClickData['url']; + + formObj.popupname.value = onClickData['target']; + formObj.popupurl.value = url; + formObj.popupwidth.value = getOption(onClickWindowOptions, 'width'); + formObj.popupheight.value = getOption(onClickWindowOptions, 'height'); + + formObj.popupleft.value = getOption(onClickWindowOptions, 'left'); + formObj.popuptop.value = getOption(onClickWindowOptions, 'top'); + + if (formObj.popupleft.value.indexOf('screen') != -1) + formObj.popupleft.value = "c"; + + if (formObj.popuptop.value.indexOf('screen') != -1) + formObj.popuptop.value = "c"; + + formObj.popuplocation.checked = getOption(onClickWindowOptions, 'location') == "yes"; + formObj.popupscrollbars.checked = getOption(onClickWindowOptions, 'scrollbars') == "yes"; + formObj.popupmenubar.checked = getOption(onClickWindowOptions, 'menubar') == "yes"; + formObj.popupresizable.checked = getOption(onClickWindowOptions, 'resizable') == "yes"; + formObj.popuptoolbar.checked = getOption(onClickWindowOptions, 'toolbar') == "yes"; + formObj.popupstatus.checked = getOption(onClickWindowOptions, 'status') == "yes"; + formObj.popupdependent.checked = getOption(onClickWindowOptions, 'dependent') == "yes"; + + buildOnClick(); + } +} + +function parseFunction(onclick) { + var formObj = document.forms[0]; + var onClickData = parseLink(onclick); + + // TODO: Add stuff here +} + +function getOption(opts, name) { + return typeof(opts[name]) == "undefined" ? "" : opts[name]; +} + +function setPopupControlsDisabled(state) { + var formObj = document.forms[0]; + + formObj.popupname.disabled = state; + formObj.popupurl.disabled = state; + formObj.popupwidth.disabled = state; + formObj.popupheight.disabled = state; + formObj.popupleft.disabled = state; + formObj.popuptop.disabled = state; + formObj.popuplocation.disabled = state; + formObj.popupscrollbars.disabled = state; + formObj.popupmenubar.disabled = state; + formObj.popupresizable.disabled = state; + formObj.popuptoolbar.disabled = state; + formObj.popupstatus.disabled = state; + formObj.popupreturn.disabled = state; + formObj.popupdependent.disabled = state; + + setBrowserDisabled('popupurlbrowser', state); +} + +function parseLink(link) { + link = link.replace(new RegExp(''', 'g'), "'"); + + var fnName = link.replace(new RegExp("\\s*([A-Za-z0-9\.]*)\\s*\\(.*", "gi"), "$1"); + + // Is function name a template function + var template = templates[fnName]; + if (template) { + // Build regexp + var variableNames = template.match(new RegExp("'?\\$\\{[A-Za-z0-9\.]*\\}'?", "gi")); + var regExp = "\\s*[A-Za-z0-9\.]*\\s*\\("; + var replaceStr = ""; + for (var i=0; i'); + for (var i=0; i' + name + ''; + + if ((name = nodes[i].id) != "" && !nodes[i].href) + html += ''; + } + + if (html == "") + return ""; + + html = ''; + + return html; +} + +function insertAction() { + var inst = tinyMCEPopup.editor; + var elm, elementArray, i; + + elm = inst.selection.getNode(); + checkPrefix(document.forms[0].href); + + elm = inst.dom.getParent(elm, "A"); + + // Remove element if there is no href + if (!document.forms[0].href.value) { + i = inst.selection.getBookmark(); + inst.dom.remove(elm, 1); + inst.selection.moveToBookmark(i); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + return; + } + + // Create new anchor elements + if (elm == null) { + inst.getDoc().execCommand("unlink", false, null); + tinyMCEPopup.execCommand("mceInsertLink", false, "#mce_temp_url#", {skip_undo : 1}); + + elementArray = tinymce.grep(inst.dom.select("a"), function(n) {return inst.dom.getAttrib(n, 'href') == '#mce_temp_url#';}); + for (i=0; i' + tinyMCELinkList[i][0] + ''; + + html += ''; + + return html; + + // tinyMCE.debug('-- image list start --', html, '-- image list end --'); +} + +function getTargetListHTML(elm_id, target_form_element) { + var targets = tinyMCEPopup.getParam('theme_advanced_link_targets', '').split(';'); + var html = ''; + + html += ''; + + return html; +} + +// While loading +preinit(); +tinyMCEPopup.onInit.add(init); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/contextmenu/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/contextmenu/editor_plugin_src.js new file mode 100644 index 0000000..237cbf5 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/contextmenu/editor_plugin_src.js @@ -0,0 +1,163 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var Event = tinymce.dom.Event, each = tinymce.each, DOM = tinymce.DOM; + + /** + * This plugin a context menu to TinyMCE editor instances. + * + * @class tinymce.plugins.ContextMenu + */ + tinymce.create('tinymce.plugins.ContextMenu', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @method init + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + init : function(ed) { + var t = this, showMenu, contextmenuNeverUseNative, realCtrlKey, hideMenu; + + t.editor = ed; + + contextmenuNeverUseNative = ed.settings.contextmenu_never_use_native; + + /** + * This event gets fired when the context menu is shown. + * + * @event onContextMenu + * @param {tinymce.plugins.ContextMenu} sender Plugin instance sending the event. + * @param {tinymce.ui.DropMenu} menu Drop down menu to fill with more items if needed. + */ + t.onContextMenu = new tinymce.util.Dispatcher(this); + + hideMenu = function(e) { + hide(ed, e); + }; + + showMenu = ed.onContextMenu.add(function(ed, e) { + // Block TinyMCE menu on ctrlKey and work around Safari issue + if ((realCtrlKey !== 0 ? realCtrlKey : e.ctrlKey) && !contextmenuNeverUseNative) + return; + + Event.cancel(e); + + // Select the image if it's clicked. WebKit would other wise expand the selection + if (e.target.nodeName == 'IMG') + ed.selection.select(e.target); + + t._getMenu(ed).showMenu(e.clientX || e.pageX, e.clientY || e.pageY); + Event.add(ed.getDoc(), 'click', hideMenu); + + ed.nodeChanged(); + }); + + ed.onRemove.add(function() { + if (t._menu) + t._menu.removeAll(); + }); + + function hide(ed, e) { + realCtrlKey = 0; + + // Since the contextmenu event moves + // the selection we need to store it away + if (e && e.button == 2) { + realCtrlKey = e.ctrlKey; + return; + } + + if (t._menu) { + t._menu.removeAll(); + t._menu.destroy(); + Event.remove(ed.getDoc(), 'click', hideMenu); + t._menu = null; + } + }; + + ed.onMouseDown.add(hide); + ed.onKeyDown.add(hide); + ed.onKeyDown.add(function(ed, e) { + if (e.shiftKey && !e.ctrlKey && !e.altKey && e.keyCode === 121) { + Event.cancel(e); + showMenu(ed, e); + } + }); + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @method getInfo + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Contextmenu', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + _getMenu : function(ed) { + var t = this, m = t._menu, se = ed.selection, col = se.isCollapsed(), el = se.getNode() || ed.getBody(), am, p; + + if (m) { + m.removeAll(); + m.destroy(); + } + + p = DOM.getPos(ed.getContentAreaContainer()); + + m = ed.controlManager.createDropMenu('contextmenu', { + offset_x : p.x + ed.getParam('contextmenu_offset_x', 0), + offset_y : p.y + ed.getParam('contextmenu_offset_y', 0), + constrain : 1, + keyboard_focus: true + }); + + t._menu = m; + + m.add({title : 'advanced.cut_desc', icon : 'cut', cmd : 'Cut'}).setDisabled(col); + m.add({title : 'advanced.copy_desc', icon : 'copy', cmd : 'Copy'}).setDisabled(col); + m.add({title : 'advanced.paste_desc', icon : 'paste', cmd : 'Paste'}); + + if ((el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) || !col) { + m.addSeparator(); + m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); + m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); + } + + m.addSeparator(); + m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); + + m.addSeparator(); + am = m.addMenu({title : 'contextmenu.align'}); + am.add({title : 'contextmenu.left', icon : 'justifyleft', cmd : 'JustifyLeft'}); + am.add({title : 'contextmenu.center', icon : 'justifycenter', cmd : 'JustifyCenter'}); + am.add({title : 'contextmenu.right', icon : 'justifyright', cmd : 'JustifyRight'}); + am.add({title : 'contextmenu.full', icon : 'justifyfull', cmd : 'JustifyFull'}); + + t.onContextMenu.dispatch(t, m, el, col); + + return m; + } + }); + + // Register plugin + tinymce.PluginManager.add('contextmenu', tinymce.plugins.ContextMenu); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/emotions/js/emotions.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/emotions/js/emotions.js new file mode 100644 index 0000000..f73516c --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/emotions/js/emotions.js @@ -0,0 +1,43 @@ +tinyMCEPopup.requireLangPack(); + +var EmotionsDialog = { + addKeyboardNavigation: function(){ + var tableElm, cells, settings; + + cells = tinyMCEPopup.dom.select("a.emoticon_link", "emoticon_table"); + + settings ={ + root: "emoticon_table", + items: cells + }; + cells[0].tabindex=0; + tinyMCEPopup.dom.addClass(cells[0], "mceFocus"); + if (tinymce.isGecko) { + cells[0].focus(); + } else { + setTimeout(function(){ + cells[0].focus(); + }, 100); + } + tinyMCEPopup.editor.windowManager.createInstance('tinymce.ui.KeyboardNavigation', settings, tinyMCEPopup.dom); + }, + init : function(ed) { + tinyMCEPopup.resizeToInnerSize(); + this.addKeyboardNavigation(); + }, + + insert : function(file, title) { + var ed = tinyMCEPopup.editor, dom = ed.dom; + + tinyMCEPopup.execCommand('mceInsertContent', false, dom.createHTML('img', { + src : tinyMCEPopup.getWindowArg('plugin_url') + '/img/' + file, + alt : ed.getLang(title), + title : ed.getLang(title), + border : 0 + })); + + tinyMCEPopup.close(); + } +}; + +tinyMCEPopup.onInit.add(EmotionsDialog.init, EmotionsDialog); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullpage/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullpage/editor_plugin_src.js new file mode 100644 index 0000000..8b49c44 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullpage/editor_plugin_src.js @@ -0,0 +1,405 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each, Node = tinymce.html.Node; + + tinymce.create('tinymce.plugins.FullPagePlugin', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceFullPageProperties', function() { + ed.windowManager.open({ + file : url + '/fullpage.htm', + width : 430 + parseInt(ed.getLang('fullpage.delta_width', 0)), + height : 495 + parseInt(ed.getLang('fullpage.delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + data : t._htmlToData() + }); + }); + + // Register buttons + ed.addButton('fullpage', {title : 'fullpage.desc', cmd : 'mceFullPageProperties'}); + + ed.onBeforeSetContent.add(t._setContent, t); + ed.onGetContent.add(t._getContent, t); + }, + + getInfo : function() { + return { + longname : 'Fullpage', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private plugin internal methods + + _htmlToData : function() { + var headerFragment = this._parseHeader(), data = {}, nodes, elm, matches, editor = this.editor; + + function getAttr(elm, name) { + var value = elm.attr(name); + + return value || ''; + }; + + // Default some values + data.fontface = editor.getParam("fullpage_default_fontface", ""); + data.fontsize = editor.getParam("fullpage_default_fontsize", ""); + + // Parse XML PI + elm = headerFragment.firstChild; + if (elm.type == 7) { + data.xml_pi = true; + matches = /encoding="([^"]+)"/.exec(elm.value); + if (matches) + data.docencoding = matches[1]; + } + + // Parse doctype + elm = headerFragment.getAll('#doctype')[0]; + if (elm) + data.doctype = '"; + + // Parse title element + elm = headerFragment.getAll('title')[0]; + if (elm && elm.firstChild) { + data.metatitle = elm.firstChild.value; + } + + // Parse meta elements + each(headerFragment.getAll('meta'), function(meta) { + var name = meta.attr('name'), httpEquiv = meta.attr('http-equiv'), matches; + + if (name) + data['meta' + name.toLowerCase()] = meta.attr('content'); + else if (httpEquiv == "Content-Type") { + matches = /charset\s*=\s*(.*)\s*/gi.exec(meta.attr('content')); + + if (matches) + data.docencoding = matches[1]; + } + }); + + // Parse html attribs + elm = headerFragment.getAll('html')[0]; + if (elm) + data.langcode = getAttr(elm, 'lang') || getAttr(elm, 'xml:lang'); + + // Parse stylesheet + elm = headerFragment.getAll('link')[0]; + if (elm && elm.attr('rel') == 'stylesheet') + data.stylesheet = elm.attr('href'); + + // Parse body parts + elm = headerFragment.getAll('body')[0]; + if (elm) { + data.langdir = getAttr(elm, 'dir'); + data.style = getAttr(elm, 'style'); + data.visited_color = getAttr(elm, 'vlink'); + data.link_color = getAttr(elm, 'link'); + data.active_color = getAttr(elm, 'alink'); + } + + return data; + }, + + _dataToHtml : function(data) { + var headerFragment, headElement, html, elm, value, dom = this.editor.dom; + + function setAttr(elm, name, value) { + elm.attr(name, value ? value : undefined); + }; + + function addHeadNode(node) { + if (headElement.firstChild) + headElement.insert(node, headElement.firstChild); + else + headElement.append(node); + }; + + headerFragment = this._parseHeader(); + headElement = headerFragment.getAll('head')[0]; + if (!headElement) { + elm = headerFragment.getAll('html')[0]; + headElement = new Node('head', 1); + + if (elm.firstChild) + elm.insert(headElement, elm.firstChild, true); + else + elm.append(headElement); + } + + // Add/update/remove XML-PI + elm = headerFragment.firstChild; + if (data.xml_pi) { + value = 'version="1.0"'; + + if (data.docencoding) + value += ' encoding="' + data.docencoding + '"'; + + if (elm.type != 7) { + elm = new Node('xml', 7); + headerFragment.insert(elm, headerFragment.firstChild, true); + } + + elm.value = value; + } else if (elm && elm.type == 7) + elm.remove(); + + // Add/update/remove doctype + elm = headerFragment.getAll('#doctype')[0]; + if (data.doctype) { + if (!elm) { + elm = new Node('#doctype', 10); + + if (data.xml_pi) + headerFragment.insert(elm, headerFragment.firstChild); + else + addHeadNode(elm); + } + + elm.value = data.doctype.substring(9, data.doctype.length - 1); + } else if (elm) + elm.remove(); + + // Add/update/remove title + elm = headerFragment.getAll('title')[0]; + if (data.metatitle) { + if (!elm) { + elm = new Node('title', 1); + elm.append(new Node('#text', 3)).value = data.metatitle; + addHeadNode(elm); + } + } + + // Add meta encoding + if (data.docencoding) { + elm = null; + each(headerFragment.getAll('meta'), function(meta) { + if (meta.attr('http-equiv') == 'Content-Type') + elm = meta; + }); + + if (!elm) { + elm = new Node('meta', 1); + elm.attr('http-equiv', 'Content-Type'); + elm.shortEnded = true; + addHeadNode(elm); + } + + elm.attr('content', 'text/html; charset=' + data.docencoding); + } + + // Add/update/remove meta + each('keywords,description,author,copyright,robots'.split(','), function(name) { + var nodes = headerFragment.getAll('meta'), i, meta, value = data['meta' + name]; + + for (i = 0; i < nodes.length; i++) { + meta = nodes[i]; + + if (meta.attr('name') == name) { + if (value) + meta.attr('content', value); + else + meta.remove(); + + return; + } + } + + if (value) { + elm = new Node('meta', 1); + elm.attr('name', name); + elm.attr('content', value); + elm.shortEnded = true; + + addHeadNode(elm); + } + }); + + // Add/update/delete link + elm = headerFragment.getAll('link')[0]; + if (elm && elm.attr('rel') == 'stylesheet') { + if (data.stylesheet) + elm.attr('href', data.stylesheet); + else + elm.remove(); + } else if (data.stylesheet) { + elm = new Node('link', 1); + elm.attr({ + rel : 'stylesheet', + text : 'text/css', + href : data.stylesheet + }); + elm.shortEnded = true; + + addHeadNode(elm); + } + + // Update body attributes + elm = headerFragment.getAll('body')[0]; + if (elm) { + setAttr(elm, 'dir', data.langdir); + setAttr(elm, 'style', data.style); + setAttr(elm, 'vlink', data.visited_color); + setAttr(elm, 'link', data.link_color); + setAttr(elm, 'alink', data.active_color); + + // Update iframe body as well + dom.setAttribs(this.editor.getBody(), { + style : data.style, + dir : data.dir, + vLink : data.visited_color, + link : data.link_color, + aLink : data.active_color + }); + } + + // Set html attributes + elm = headerFragment.getAll('html')[0]; + if (elm) { + setAttr(elm, 'lang', data.langcode); + setAttr(elm, 'xml:lang', data.langcode); + } + + // Serialize header fragment and crop away body part + html = new tinymce.html.Serializer({ + validate: false, + indent: true, + apply_source_formatting : true, + indent_before: 'head,html,body,meta,title,script,link,style', + indent_after: 'head,html,body,meta,title,script,link,style' + }).serialize(headerFragment); + + this.head = html.substring(0, html.indexOf('')); + }, + + _parseHeader : function() { + // Parse the contents with a DOM parser + return new tinymce.html.DomParser({ + validate: false, + root_name: '#document' + }).parse(this.head); + }, + + _setContent : function(ed, o) { + var self = this, startPos, endPos, content = o.content, headerFragment, styles = '', dom = self.editor.dom, elm; + + function low(s) { + return s.replace(/<\/?[A-Z]+/g, function(a) { + return a.toLowerCase(); + }) + }; + + // Ignore raw updated if we already have a head, this will fix issues with undo/redo keeping the head/foot separate + if (o.format == 'raw' && self.head) + return; + + if (o.source_view && ed.getParam('fullpage_hide_in_source_view')) + return; + + // Parse out head, body and footer + content = content.replace(/<(\/?)BODY/gi, '<$1body'); + startPos = content.indexOf('', startPos); + self.head = low(content.substring(0, startPos + 1)); + + endPos = content.indexOf('\n'; + + header += editor.getParam('fullpage_default_doctype', ''); + header += '\n\n\n'; + + if (value = editor.getParam('fullpage_default_title')) + header += '' + value + '\n'; + + if (value = editor.getParam('fullpage_default_encoding')) + header += '\n'; + + if (value = editor.getParam('fullpage_default_font_family')) + styles += 'font-family: ' + value + ';'; + + if (value = editor.getParam('fullpage_default_font_size')) + styles += 'font-size: ' + value + ';'; + + if (value = editor.getParam('fullpage_default_text_color')) + styles += 'color: ' + value + ';'; + + header += '\n\n'; + + return header; + }, + + _getContent : function(ed, o) { + var self = this; + + if (!o.source_view || !ed.getParam('fullpage_hide_in_source_view')) + o.content = tinymce.trim(self.head) + '\n' + tinymce.trim(o.content) + '\n' + tinymce.trim(self.foot); + } + }); + + // Register plugin + tinymce.PluginManager.add('fullpage', tinymce.plugins.FullPagePlugin); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullscreen/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullscreen/editor_plugin_src.js new file mode 100644 index 0000000..a1f39a0 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullscreen/editor_plugin_src.js @@ -0,0 +1,234 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM; + + // State Transfer function + var transferState = function(oldEditor, newEditor, bookmark) { + var transferColorButtonState = function(swapme) { + var c = oldEditor.controlManager.get(swapme); + var newC = newEditor.controlManager.get(swapme); + + if (c && newC) { + newC.displayColor(c.value); + } + + }; + + transferColorButtonState('forecolor'); + transferColorButtonState('backcolor'); + newEditor.setContent(oldEditor.getContent({format : 'raw'}), {format : 'raw'}); + newEditor.selection.moveToBookmark(bookmark); + + if (oldEditor.plugins.spellchecker && newEditor.plugins.spellchecker) { + newEditor.plugins.spellchecker.setLanguage(oldEditor.plugins.spellchecker.selectedLang); + } + }; + + tinymce.create('tinymce.plugins.FullScreenPlugin', { + init : function(ed, url) { + var t = this, s = {}, de = DOM.doc.documentElement, vp, fullscreen_overflow, fullscreen_html_overflow, fullscreen_scrollx, fullscreen_scrolly, posCss, bookmark; + + // Register commands + ed.addCommand('mceFullScreen', function() { + var win, oed; + + if (ed.getParam('fullscreen_is_enabled')) { + if (ed.getParam('fullscreen_new_window')) + closeFullscreen(); // Call to close in fullscreen.htm + else { + DOM.win.setTimeout(function() { + var fullscreenEditor = ed; + + // find the editor that opened this one, execute restore function there + var originalEditor = tinyMCE.get(fullscreenEditor.getParam('fullscreen_editor_id')); + originalEditor.plugins.fullscreen.saveState(fullscreenEditor); + + tinyMCE.remove(fullscreenEditor); + }, 10); + } + + return; + } + + if (ed.getParam('fullscreen_new_window')) { + t.fullscreenSettings = { + bookmark: ed.selection.getBookmark() + }; + win = DOM.win.open(url + "/fullscreen.htm", "mceFullScreenPopup", "fullscreen=yes,menubar=no,toolbar=no,scrollbars=no,resizable=yes,left=0,top=0,width=" + screen.availWidth + ",height=" + screen.availHeight); + try { + win.resizeTo(screen.availWidth, screen.availHeight); + } catch (e) { + // Ignore + } + } else { + fullscreen_overflow = DOM.getStyle(DOM.doc.body, 'overflow', 1) || 'auto'; + fullscreen_html_overflow = DOM.getStyle(de, 'overflow', 1); + vp = DOM.getViewPort(); + fullscreen_scrollx = vp.x; + fullscreen_scrolly = vp.y; + + // Fixes an Opera bug where the scrollbars doesn't reappear + if (tinymce.isOpera && fullscreen_overflow == 'visible') + fullscreen_overflow = 'auto'; + + // Fixes an IE bug where horizontal scrollbars would appear + if (tinymce.isIE && fullscreen_overflow == 'scroll') + fullscreen_overflow = 'auto'; + + // Fixes an IE bug where the scrollbars doesn't reappear + if (tinymce.isIE && (fullscreen_html_overflow == 'visible' || fullscreen_html_overflow == 'scroll')) + fullscreen_html_overflow = 'auto'; + + if (fullscreen_overflow == '0px') + fullscreen_overflow = ''; + + DOM.setStyle(DOM.doc.body, 'overflow', 'hidden'); + de.style.overflow = 'hidden'; //Fix for IE6/7 + vp = DOM.getViewPort(); + DOM.win.scrollTo(0, 0); + + if (tinymce.isIE) + vp.h -= 1; + + // Use fixed position if it exists + if (tinymce.isIE6 || document.compatMode == 'BackCompat') + posCss = 'absolute;top:' + vp.y; + else + posCss = 'fixed;top:0'; + + n = DOM.add(DOM.doc.body, 'div', { + id : 'mce_fullscreen_container', + style : 'position:' + posCss + ';left:0;width:' + vp.w + 'px;height:' + vp.h + 'px;z-index:200000;'}); + DOM.add(n, 'div', {id : 'mce_fullscreen'}); + + tinymce.each(ed.settings, function(v, n) { + s[n] = v; + }); + + s.id = 'mce_fullscreen'; + s.width = n.clientWidth; + s.height = n.clientHeight - 15; + s.fullscreen_is_enabled = true; + s.fullscreen_editor_id = ed.id; + s.theme_advanced_resizing = false; + s.save_onsavecallback = function() { + ed.setContent(tinyMCE.get(s.id).getContent()); + ed.execCommand('mceSave'); + }; + + tinymce.each(ed.getParam('fullscreen_settings'), function(v, k) { + s[k] = v; + }); + + t.fullscreenSettings = { + bookmark: ed.selection.getBookmark(), + fullscreen_overflow: fullscreen_overflow, + fullscreen_html_overflow: fullscreen_html_overflow, + fullscreen_scrollx: fullscreen_scrollx, + fullscreen_scrolly: fullscreen_scrolly + }; + + if (s.theme_advanced_toolbar_location === 'external') + s.theme_advanced_toolbar_location = 'top'; + + tinyMCE.oldSettings = tinyMCE.settings; // Store old settings, the Editor constructor overwrites them + t.fullscreenEditor = new tinymce.Editor('mce_fullscreen', s); + t.fullscreenEditor.onInit.add(function() { + t.loadState(t.fullscreenEditor); + }); + + t.fullscreenEditor.render(); + + t.fullscreenElement = new tinymce.dom.Element('mce_fullscreen_container'); + t.fullscreenElement.update(); + //document.body.overflow = 'hidden'; + + t.resizeFunc = tinymce.dom.Event.add(DOM.win, 'resize', function() { + var vp = tinymce.DOM.getViewPort(), fed = t.fullscreenEditor, outerSize, innerSize; + + // Get outer/inner size to get a delta size that can be used to calc the new iframe size + outerSize = fed.dom.getSize(fed.getContainer().getElementsByTagName('table')[0]); + innerSize = fed.dom.getSize(fed.getContainer().getElementsByTagName('iframe')[0]); + + fed.theme.resizeTo(vp.w - outerSize.w + innerSize.w, vp.h - outerSize.h + innerSize.h); + }); + } + }); + + // Register buttons + ed.addButton('fullscreen', {title : 'fullscreen.desc', cmd : 'mceFullScreen'}); + + ed.onNodeChange.add(function(ed, cm) { + cm.setActive('fullscreen', ed.getParam('fullscreen_is_enabled')); + }); + + // fullscreenEditor is a param here because in window mode we don't create it + t.loadState = function(fullscreenEditor) { + if (!(fullscreenEditor && t.fullscreenSettings)) { + throw "No fullscreen editor to load to"; + } + + transferState(ed, fullscreenEditor, t.fullscreenSettings.bookmark); + fullscreenEditor.focus(); + + }; + + // fullscreenEditor is a param here because in window mode we don't create it + t.saveState = function(fullscreenEditor) { + if (!(fullscreenEditor && t.fullscreenSettings)) { + throw "No fullscreen editor to restore from"; + } + var settings = t.fullscreenSettings; + + transferState(fullscreenEditor, ed, fullscreenEditor.selection.getBookmark()); + + // cleanup only required if window mode isn't used + if (!ed.getParam('fullscreen_new_window')) { + tinymce.dom.Event.remove(DOM.win, 'resize', t.resizeFunc); + delete t.resizeFunc; + + DOM.remove('mce_fullscreen_container'); + + DOM.doc.documentElement.style.overflow = settings.fullscreen_html_overflow; + DOM.setStyle(DOM.doc.body, 'overflow', settings.fullscreen_overflow); + DOM.win.scrollTo(settings.fullscreen_scrollx, settings.fullscreen_scrolly); + } + tinyMCE.settings = tinyMCE.oldSettings; // Restore old settings + + // clear variables + delete tinyMCE.oldSettings; + delete t.fullscreenEditor; + delete t.fullscreenElement; + delete t.fullscreenSettings; + + // allow the fullscreen editor to be removed before restoring focus and selection + DOM.win.setTimeout(function() { + ed.selection.moveToBookmark(bookmark); + ed.focus(); + }, 10); + }; + }, + + getInfo : function() { + return { + longname : 'Fullscreen', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('fullscreen', tinymce.plugins.FullScreenPlugin); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullscreen/fullscreen.htm b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullscreen/fullscreen.htm new file mode 100644 index 0000000..35dcb0a --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/fullscreen/fullscreen.htm @@ -0,0 +1,117 @@ + + + + + + + + + +
+ +
+ + + + + diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/inlinepopups/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/inlinepopups/editor_plugin_src.js new file mode 100644 index 0000000..5171845 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/inlinepopups/editor_plugin_src.js @@ -0,0 +1,699 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM, Element = tinymce.dom.Element, Event = tinymce.dom.Event, each = tinymce.each, is = tinymce.is; + + tinymce.create('tinymce.plugins.InlinePopups', { + init : function(ed, url) { + // Replace window manager + ed.onBeforeRenderUI.add(function() { + ed.windowManager = new tinymce.InlineWindowManager(ed); + DOM.loadCSS(url + '/skins/' + (ed.settings.inlinepopups_skin || 'clearlooks2') + "/window.css"); + }); + }, + + getInfo : function() { + return { + longname : 'InlinePopups', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + tinymce.create('tinymce.InlineWindowManager:tinymce.WindowManager', { + InlineWindowManager : function(ed) { + var t = this; + + t.parent(ed); + t.zIndex = 300000; + t.count = 0; + t.windows = {}; + }, + + open : function(f, p) { + var t = this, id, opt = '', ed = t.editor, dw = 0, dh = 0, vp, po, mdf, clf, we, w, u, parentWindow; + + f = f || {}; + p = p || {}; + + // Run native windows + if (!f.inline) + return t.parent(f, p); + + parentWindow = t._frontWindow(); + if (parentWindow && DOM.get(parentWindow.id + '_ifr')) { + parentWindow.focussedElement = DOM.get(parentWindow.id + '_ifr').contentWindow.document.activeElement; + } + + // Only store selection if the type is a normal window + if (!f.type) + t.bookmark = ed.selection.getBookmark(1); + + id = DOM.uniqueId("mce_inlinepopups_"); // Use a prefix so this can't conflict with other ids + vp = DOM.getViewPort(); + f.width = parseInt(f.width || 320); + f.height = parseInt(f.height || 240) + (tinymce.isIE ? 8 : 0); + f.min_width = parseInt(f.min_width || 150); + f.min_height = parseInt(f.min_height || 100); + f.max_width = parseInt(f.max_width || 2000); + f.max_height = parseInt(f.max_height || 2000); + f.left = f.left || Math.round(Math.max(vp.x, vp.x + (vp.w / 2.0) - (f.width / 2.0))); + f.top = f.top || Math.round(Math.max(vp.y, vp.y + (vp.h / 2.0) - (f.height / 2.0))); + f.movable = f.resizable = true; + p.mce_width = f.width; + p.mce_height = f.height; + p.mce_inline = true; + p.mce_window_id = id; + p.mce_auto_focus = f.auto_focus; + + // Transpose +// po = DOM.getPos(ed.getContainer()); +// f.left -= po.x; +// f.top -= po.y; + + t.features = f; + t.params = p; + t.onOpen.dispatch(t, f, p); + + if (f.type) { + opt += ' mceModal'; + + if (f.type) + opt += ' mce' + f.type.substring(0, 1).toUpperCase() + f.type.substring(1); + + f.resizable = false; + } + + if (f.statusbar) + opt += ' mceStatusbar'; + + if (f.resizable) + opt += ' mceResizable'; + + if (f.minimizable) + opt += ' mceMinimizable'; + + if (f.maximizable) + opt += ' mceMaximizable'; + + if (f.movable) + opt += ' mceMovable'; + + // Create DOM objects + t._addAll(DOM.doc.body, + ['div', {id : id, role : 'dialog', 'aria-labelledby': f.type ? id + '_content' : id + '_title', 'class' : (ed.settings.inlinepopups_skin || 'clearlooks2') + (tinymce.isIE && window.getSelection ? ' ie9' : ''), style : 'width:100px;height:100px'}, + ['div', {id : id + '_wrapper', 'class' : 'mceWrapper' + opt}, + ['div', {id : id + '_top', 'class' : 'mceTop'}, + ['div', {'class' : 'mceLeft'}], + ['div', {'class' : 'mceCenter'}], + ['div', {'class' : 'mceRight'}], + ['span', {id : id + '_title'}, f.title || ''] + ], + + ['div', {id : id + '_middle', 'class' : 'mceMiddle'}, + ['div', {id : id + '_left', 'class' : 'mceLeft', tabindex : '0'}], + ['span', {id : id + '_content'}], + ['div', {id : id + '_right', 'class' : 'mceRight', tabindex : '0'}] + ], + + ['div', {id : id + '_bottom', 'class' : 'mceBottom'}, + ['div', {'class' : 'mceLeft'}], + ['div', {'class' : 'mceCenter'}], + ['div', {'class' : 'mceRight'}], + ['span', {id : id + '_status'}, 'Content'] + ], + + ['a', {'class' : 'mceMove', tabindex : '-1', href : 'javascript:;'}], + ['a', {'class' : 'mceMin', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceMax', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceMed', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceClose', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {id : id + '_resize_n', 'class' : 'mceResize mceResizeN', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_s', 'class' : 'mceResize mceResizeS', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_w', 'class' : 'mceResize mceResizeW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_e', 'class' : 'mceResize mceResizeE', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_nw', 'class' : 'mceResize mceResizeNW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_ne', 'class' : 'mceResize mceResizeNE', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_sw', 'class' : 'mceResize mceResizeSW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_se', 'class' : 'mceResize mceResizeSE', tabindex : '-1', href : 'javascript:;'}] + ] + ] + ); + + DOM.setStyles(id, {top : -10000, left : -10000}); + + // Fix gecko rendering bug, where the editors iframe messed with window contents + if (tinymce.isGecko) + DOM.setStyle(id, 'overflow', 'auto'); + + // Measure borders + if (!f.type) { + dw += DOM.get(id + '_left').clientWidth; + dw += DOM.get(id + '_right').clientWidth; + dh += DOM.get(id + '_top').clientHeight; + dh += DOM.get(id + '_bottom').clientHeight; + } + + // Resize window + DOM.setStyles(id, {top : f.top, left : f.left, width : f.width + dw, height : f.height + dh}); + + u = f.url || f.file; + if (u) { + if (tinymce.relaxedDomain) + u += (u.indexOf('?') == -1 ? '?' : '&') + 'mce_rdomain=' + tinymce.relaxedDomain; + + u = tinymce._addVer(u); + } + + if (!f.type) { + DOM.add(id + '_content', 'iframe', {id : id + '_ifr', src : 'javascript:""', frameBorder : 0, style : 'border:0;width:10px;height:10px'}); + DOM.setStyles(id + '_ifr', {width : f.width, height : f.height}); + DOM.setAttrib(id + '_ifr', 'src', u); + } else { + DOM.add(id + '_wrapper', 'a', {id : id + '_ok', 'class' : 'mceButton mceOk', href : 'javascript:;', onmousedown : 'return false;'}, 'Ok'); + + if (f.type == 'confirm') + DOM.add(id + '_wrapper', 'a', {'class' : 'mceButton mceCancel', href : 'javascript:;', onmousedown : 'return false;'}, 'Cancel'); + + DOM.add(id + '_middle', 'div', {'class' : 'mceIcon'}); + DOM.setHTML(id + '_content', f.content.replace('\n', '
')); + + Event.add(id, 'keyup', function(evt) { + var VK_ESCAPE = 27; + if (evt.keyCode === VK_ESCAPE) { + f.button_func(false); + return Event.cancel(evt); + } + }); + + Event.add(id, 'keydown', function(evt) { + var cancelButton, VK_TAB = 9; + if (evt.keyCode === VK_TAB) { + cancelButton = DOM.select('a.mceCancel', id + '_wrapper')[0]; + if (cancelButton && cancelButton !== evt.target) { + cancelButton.focus(); + } else { + DOM.get(id + '_ok').focus(); + } + return Event.cancel(evt); + } + }); + } + + // Register events + mdf = Event.add(id, 'mousedown', function(e) { + var n = e.target, w, vp; + + w = t.windows[id]; + t.focus(id); + + if (n.nodeName == 'A' || n.nodeName == 'a') { + if (n.className == 'mceClose') { + t.close(null, id); + return Event.cancel(e); + } else if (n.className == 'mceMax') { + w.oldPos = w.element.getXY(); + w.oldSize = w.element.getSize(); + + vp = DOM.getViewPort(); + + // Reduce viewport size to avoid scrollbars + vp.w -= 2; + vp.h -= 2; + + w.element.moveTo(vp.x, vp.y); + w.element.resizeTo(vp.w, vp.h); + DOM.setStyles(id + '_ifr', {width : vp.w - w.deltaWidth, height : vp.h - w.deltaHeight}); + DOM.addClass(id + '_wrapper', 'mceMaximized'); + } else if (n.className == 'mceMed') { + // Reset to old size + w.element.moveTo(w.oldPos.x, w.oldPos.y); + w.element.resizeTo(w.oldSize.w, w.oldSize.h); + w.iframeElement.resizeTo(w.oldSize.w - w.deltaWidth, w.oldSize.h - w.deltaHeight); + + DOM.removeClass(id + '_wrapper', 'mceMaximized'); + } else if (n.className == 'mceMove') + return t._startDrag(id, e, n.className); + else if (DOM.hasClass(n, 'mceResize')) + return t._startDrag(id, e, n.className.substring(13)); + } + }); + + clf = Event.add(id, 'click', function(e) { + var n = e.target; + + t.focus(id); + + if (n.nodeName == 'A' || n.nodeName == 'a') { + switch (n.className) { + case 'mceClose': + t.close(null, id); + return Event.cancel(e); + + case 'mceButton mceOk': + case 'mceButton mceCancel': + f.button_func(n.className == 'mceButton mceOk'); + return Event.cancel(e); + } + } + }); + + // Make sure the tab order loops within the dialog. + Event.add([id + '_left', id + '_right'], 'focus', function(evt) { + var iframe = DOM.get(id + '_ifr'); + if (iframe) { + var body = iframe.contentWindow.document.body; + var focusable = DOM.select(':input:enabled,*[tabindex=0]', body); + if (evt.target.id === (id + '_left')) { + focusable[focusable.length - 1].focus(); + } else { + focusable[0].focus(); + } + } else { + DOM.get(id + '_ok').focus(); + } + }); + + // Add window + w = t.windows[id] = { + id : id, + mousedown_func : mdf, + click_func : clf, + element : new Element(id, {blocker : 1, container : ed.getContainer()}), + iframeElement : new Element(id + '_ifr'), + features : f, + deltaWidth : dw, + deltaHeight : dh + }; + + w.iframeElement.on('focus', function() { + t.focus(id); + }); + + // Setup blocker + if (t.count == 0 && t.editor.getParam('dialog_type', 'modal') == 'modal') { + DOM.add(DOM.doc.body, 'div', { + id : 'mceModalBlocker', + 'class' : (t.editor.settings.inlinepopups_skin || 'clearlooks2') + '_modalBlocker', + style : {zIndex : t.zIndex - 1} + }); + + DOM.show('mceModalBlocker'); // Reduces flicker in IE + DOM.setAttrib(DOM.doc.body, 'aria-hidden', 'true'); + } else + DOM.setStyle('mceModalBlocker', 'z-index', t.zIndex - 1); + + if (tinymce.isIE6 || /Firefox\/2\./.test(navigator.userAgent) || (tinymce.isIE && !DOM.boxModel)) + DOM.setStyles('mceModalBlocker', {position : 'absolute', left : vp.x, top : vp.y, width : vp.w - 2, height : vp.h - 2}); + + DOM.setAttrib(id, 'aria-hidden', 'false'); + t.focus(id); + t._fixIELayout(id, 1); + + // Focus ok button + if (DOM.get(id + '_ok')) + DOM.get(id + '_ok').focus(); + t.count++; + + return w; + }, + + focus : function(id) { + var t = this, w; + + if (w = t.windows[id]) { + w.zIndex = this.zIndex++; + w.element.setStyle('zIndex', w.zIndex); + w.element.update(); + + id = id + '_wrapper'; + DOM.removeClass(t.lastId, 'mceFocus'); + DOM.addClass(id, 'mceFocus'); + t.lastId = id; + + if (w.focussedElement) { + w.focussedElement.focus(); + } else if (DOM.get(id + '_ok')) { + DOM.get(w.id + '_ok').focus(); + } else if (DOM.get(w.id + '_ifr')) { + DOM.get(w.id + '_ifr').focus(); + } + } + }, + + _addAll : function(te, ne) { + var i, n, t = this, dom = tinymce.DOM; + + if (is(ne, 'string')) + te.appendChild(dom.doc.createTextNode(ne)); + else if (ne.length) { + te = te.appendChild(dom.create(ne[0], ne[1])); + + for (i=2; i ix) { + fw = w; + ix = w.zIndex; + } + }); + return fw; + }, + + setTitle : function(w, ti) { + var e; + + w = this._findId(w); + + if (e = DOM.get(w + '_title')) + e.innerHTML = DOM.encode(ti); + }, + + alert : function(txt, cb, s) { + var t = this, w; + + w = t.open({ + title : t, + type : 'alert', + button_func : function(s) { + if (cb) + cb.call(s || t, s); + + t.close(null, w.id); + }, + content : DOM.encode(t.editor.getLang(txt, txt)), + inline : 1, + width : 400, + height : 130 + }); + }, + + confirm : function(txt, cb, s) { + var t = this, w; + + w = t.open({ + title : t, + type : 'confirm', + button_func : function(s) { + if (cb) + cb.call(s || t, s); + + t.close(null, w.id); + }, + content : DOM.encode(t.editor.getLang(txt, txt)), + inline : 1, + width : 400, + height : 130 + }); + }, + + // Internal functions + + _findId : function(w) { + var t = this; + + if (typeof(w) == 'string') + return w; + + each(t.windows, function(wo) { + var ifr = DOM.get(wo.id + '_ifr'); + + if (ifr && w == ifr.contentWindow) { + w = wo.id; + return false; + } + }); + + return w; + }, + + _fixIELayout : function(id, s) { + var w, img; + + if (!tinymce.isIE6) + return; + + // Fixes the bug where hover flickers and does odd things in IE6 + each(['n','s','w','e','nw','ne','sw','se'], function(v) { + var e = DOM.get(id + '_resize_' + v); + + DOM.setStyles(e, { + width : s ? e.clientWidth : '', + height : s ? e.clientHeight : '', + cursor : DOM.getStyle(e, 'cursor', 1) + }); + + DOM.setStyle(id + "_bottom", 'bottom', '-1px'); + + e = 0; + }); + + // Fixes graphics glitch + if (w = this.windows[id]) { + // Fixes rendering bug after resize + w.element.hide(); + w.element.show(); + + // Forced a repaint of the window + //DOM.get(id).style.filter = ''; + + // IE has a bug where images used in CSS won't get loaded + // sometimes when the cache in the browser is disabled + // This fix tries to solve it by loading the images using the image object + each(DOM.select('div,a', id), function(e, i) { + if (e.currentStyle.backgroundImage != 'none') { + img = new Image(); + img.src = e.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/, '$1'); + } + }); + + DOM.get(id).style.filter = ''; + } + } + }); + + // Register plugin + tinymce.PluginManager.add('inlinepopups', tinymce.plugins.InlinePopups); +})(); + diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/layer/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/layer/editor_plugin_src.js new file mode 100644 index 0000000..d31978b --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/layer/editor_plugin_src.js @@ -0,0 +1,262 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + function findParentLayer(node) { + do { + if (node.className && node.className.indexOf('mceItemLayer') != -1) { + return node; + } + } while (node = node.parentNode); + }; + + tinymce.create('tinymce.plugins.Layer', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceInsertLayer', t._insertLayer, t); + + ed.addCommand('mceMoveForward', function() { + t._move(1); + }); + + ed.addCommand('mceMoveBackward', function() { + t._move(-1); + }); + + ed.addCommand('mceMakeAbsolute', function() { + t._toggleAbsolute(); + }); + + // Register buttons + ed.addButton('moveforward', {title : 'layer.forward_desc', cmd : 'mceMoveForward'}); + ed.addButton('movebackward', {title : 'layer.backward_desc', cmd : 'mceMoveBackward'}); + ed.addButton('absolute', {title : 'layer.absolute_desc', cmd : 'mceMakeAbsolute'}); + ed.addButton('insertlayer', {title : 'layer.insertlayer_desc', cmd : 'mceInsertLayer'}); + + ed.onInit.add(function() { + var dom = ed.dom; + + if (tinymce.isIE) + ed.getDoc().execCommand('2D-Position', false, true); + }); + + // Remove serialized styles when selecting a layer since it might be changed by a drag operation + ed.onMouseUp.add(function(ed, e) { + var layer = findParentLayer(e.target); + + if (layer) { + ed.dom.setAttrib(layer, 'data-mce-style', ''); + } + }); + + // Fixes edit focus issues with layers on Gecko + // This will enable designMode while inside a layer and disable it when outside + ed.onMouseDown.add(function(ed, e) { + var node = e.target, doc = ed.getDoc(), parent; + + if (tinymce.isGecko) { + if (findParentLayer(node)) { + if (doc.designMode !== 'on') { + doc.designMode = 'on'; + + // Repaint caret + node = doc.body; + parent = node.parentNode; + parent.removeChild(node); + parent.appendChild(node); + } + } else if (doc.designMode == 'on') { + doc.designMode = 'off'; + } + } + }); + + ed.onNodeChange.add(t._nodeChange, t); + ed.onVisualAid.add(t._visualAid, t); + }, + + getInfo : function() { + return { + longname : 'Layer', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + _nodeChange : function(ed, cm, n) { + var le, p; + + le = this._getParentLayer(n); + p = ed.dom.getParent(n, 'DIV,P,IMG'); + + if (!p) { + cm.setDisabled('absolute', 1); + cm.setDisabled('moveforward', 1); + cm.setDisabled('movebackward', 1); + } else { + cm.setDisabled('absolute', 0); + cm.setDisabled('moveforward', !le); + cm.setDisabled('movebackward', !le); + cm.setActive('absolute', le && le.style.position.toLowerCase() == "absolute"); + } + }, + + // Private methods + + _visualAid : function(ed, e, s) { + var dom = ed.dom; + + tinymce.each(dom.select('div,p', e), function(e) { + if (/^(absolute|relative|fixed)$/i.test(e.style.position)) { + if (s) + dom.addClass(e, 'mceItemVisualAid'); + else + dom.removeClass(e, 'mceItemVisualAid'); + + dom.addClass(e, 'mceItemLayer'); + } + }); + }, + + _move : function(d) { + var ed = this.editor, i, z = [], le = this._getParentLayer(ed.selection.getNode()), ci = -1, fi = -1, nl; + + nl = []; + tinymce.walk(ed.getBody(), function(n) { + if (n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position)) + nl.push(n); + }, 'childNodes'); + + // Find z-indexes + for (i=0; i -1) { + nl[ci].style.zIndex = z[fi]; + nl[fi].style.zIndex = z[ci]; + } else { + if (z[ci] > 0) + nl[ci].style.zIndex = z[ci] - 1; + } + } else { + // Move forward + + // Try find a higher one + for (i=0; i z[ci]) { + fi = i; + break; + } + } + + if (fi > -1) { + nl[ci].style.zIndex = z[fi]; + nl[fi].style.zIndex = z[ci]; + } else + nl[ci].style.zIndex = z[ci] + 1; + } + + ed.execCommand('mceRepaint'); + }, + + _getParentLayer : function(n) { + return this.editor.dom.getParent(n, function(n) { + return n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position); + }); + }, + + _insertLayer : function() { + var ed = this.editor, dom = ed.dom, p = dom.getPos(dom.getParent(ed.selection.getNode(), '*')), body = ed.getBody(); + + ed.dom.add(body, 'div', { + style : { + position : 'absolute', + left : p.x, + top : (p.y > 20 ? p.y : 20), + width : 100, + height : 100 + }, + 'class' : 'mceItemVisualAid mceItemLayer' + }, ed.selection.getContent() || ed.getLang('layer.content')); + + // Workaround for IE where it messes up the JS engine if you insert a layer on IE 6,7 + if (tinymce.isIE) + dom.setHTML(body, body.innerHTML); + }, + + _toggleAbsolute : function() { + var ed = this.editor, le = this._getParentLayer(ed.selection.getNode()); + + if (!le) + le = ed.dom.getParent(ed.selection.getNode(), 'DIV,P,IMG'); + + if (le) { + if (le.style.position.toLowerCase() == "absolute") { + ed.dom.setStyles(le, { + position : '', + left : '', + top : '', + width : '', + height : '' + }); + + ed.dom.removeClass(le, 'mceItemVisualAid'); + ed.dom.removeClass(le, 'mceItemLayer'); + } else { + if (le.style.left == "") + le.style.left = 20 + 'px'; + + if (le.style.top == "") + le.style.top = 20 + 'px'; + + if (le.style.width == "") + le.style.width = le.width ? (le.width + 'px') : '100px'; + + if (le.style.height == "") + le.style.height = le.height ? (le.height + 'px') : '100px'; + + le.style.position = "absolute"; + + ed.dom.setAttrib(le, 'data-mce-style', ''); + ed.addVisual(ed.getBody()); + } + + ed.execCommand('mceRepaint'); + ed.nodeChanged(); + } + } + }); + + // Register plugin + tinymce.PluginManager.add('layer', tinymce.plugins.Layer); +})(); \ No newline at end of file diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/legacyoutput/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/legacyoutput/editor_plugin_src.js new file mode 100644 index 0000000..349bf80 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/legacyoutput/editor_plugin_src.js @@ -0,0 +1,139 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + * + * This plugin will force TinyMCE to produce deprecated legacy output such as font elements, u elements, align + * attributes and so forth. There are a few cases where these old items might be needed for example in email applications or with Flash + * + * However you should NOT use this plugin if you are building some system that produces web contents such as a CMS. All these elements are + * not apart of the newer specifications for HTML and XHTML. + */ + +(function(tinymce) { + // Override inline_styles setting to force TinyMCE to produce deprecated contents + tinymce.onAddEditor.addToTop(function(tinymce, editor) { + editor.settings.inline_styles = false; + }); + + // Create the legacy ouput plugin + tinymce.create('tinymce.plugins.LegacyOutput', { + init : function(editor) { + editor.onInit.add(function() { + var alignElements = 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', + fontSizes = tinymce.explode(editor.settings.font_size_style_values), + schema = editor.schema; + + // Override some internal formats to produce legacy elements and attributes + editor.formatter.register({ + // Change alignment formats to use the deprecated align attribute + alignleft : {selector : alignElements, attributes : {align : 'left'}}, + aligncenter : {selector : alignElements, attributes : {align : 'center'}}, + alignright : {selector : alignElements, attributes : {align : 'right'}}, + alignfull : {selector : alignElements, attributes : {align : 'justify'}}, + + // Change the basic formatting elements to use deprecated element types + bold : [ + {inline : 'b', remove : 'all'}, + {inline : 'strong', remove : 'all'}, + {inline : 'span', styles : {fontWeight : 'bold'}} + ], + italic : [ + {inline : 'i', remove : 'all'}, + {inline : 'em', remove : 'all'}, + {inline : 'span', styles : {fontStyle : 'italic'}} + ], + underline : [ + {inline : 'u', remove : 'all'}, + {inline : 'span', styles : {textDecoration : 'underline'}, exact : true} + ], + strikethrough : [ + {inline : 'strike', remove : 'all'}, + {inline : 'span', styles : {textDecoration: 'line-through'}, exact : true} + ], + + // Change font size and font family to use the deprecated font element + fontname : {inline : 'font', attributes : {face : '%value'}}, + fontsize : { + inline : 'font', + attributes : { + size : function(vars) { + return tinymce.inArray(fontSizes, vars.value) + 1; + } + } + }, + + // Setup font elements for colors as well + forecolor : {inline : 'font', attributes : {color : '%value'}}, + hilitecolor : {inline : 'font', styles : {backgroundColor : '%value'}} + }); + + // Check that deprecated elements are allowed if not add them + tinymce.each('b,i,u,strike'.split(','), function(name) { + schema.addValidElements(name + '[*]'); + }); + + // Add font element if it's missing + if (!schema.getElementRule("font")) + schema.addValidElements("font[face|size|color|style]"); + + // Add the missing and depreacted align attribute for the serialization engine + tinymce.each(alignElements.split(','), function(name) { + var rule = schema.getElementRule(name), found; + + if (rule) { + if (!rule.attributes.align) { + rule.attributes.align = {}; + rule.attributesOrder.push('align'); + } + } + }); + + // Listen for the onNodeChange event so that we can do special logic for the font size and font name drop boxes + editor.onNodeChange.add(function(editor, control_manager) { + var control, fontElm, fontName, fontSize; + + // Find font element get it's name and size + fontElm = editor.dom.getParent(editor.selection.getNode(), 'font'); + if (fontElm) { + fontName = fontElm.face; + fontSize = fontElm.size; + } + + // Select/unselect the font name in droplist + if (control = control_manager.get('fontselect')) { + control.select(function(value) { + return value == fontName; + }); + } + + // Select/unselect the font size in droplist + if (control = control_manager.get('fontsizeselect')) { + control.select(function(value) { + var index = tinymce.inArray(fontSizes, value.fontSize); + + return index + 1 == fontSize; + }); + } + }); + }); + }, + + getInfo : function() { + return { + longname : 'LegacyOutput', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/legacyoutput', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('legacyoutput', tinymce.plugins.LegacyOutput); +})(tinymce); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/lists/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/lists/editor_plugin_src.js new file mode 100644 index 0000000..1000ef7 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/lists/editor_plugin_src.js @@ -0,0 +1,955 @@ +/** + * editor_plugin_src.js + * + * Copyright 2011, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each, Event = tinymce.dom.Event, bookmark; + + // Skips text nodes that only contain whitespace since they aren't semantically important. + function skipWhitespaceNodes(e, next) { + while (e && (e.nodeType === 8 || (e.nodeType === 3 && /^[ \t\n\r]*$/.test(e.nodeValue)))) { + e = next(e); + } + return e; + } + + function skipWhitespaceNodesBackwards(e) { + return skipWhitespaceNodes(e, function(e) { + return e.previousSibling; + }); + } + + function skipWhitespaceNodesForwards(e) { + return skipWhitespaceNodes(e, function(e) { + return e.nextSibling; + }); + } + + function hasParentInList(ed, e, list) { + return ed.dom.getParent(e, function(p) { + return tinymce.inArray(list, p) !== -1; + }); + } + + function isList(e) { + return e && (e.tagName === 'OL' || e.tagName === 'UL'); + } + + function splitNestedLists(element, dom) { + var tmp, nested, wrapItem; + tmp = skipWhitespaceNodesBackwards(element.lastChild); + while (isList(tmp)) { + nested = tmp; + tmp = skipWhitespaceNodesBackwards(nested.previousSibling); + } + if (nested) { + wrapItem = dom.create('li', { style: 'list-style-type: none;'}); + dom.split(element, nested); + dom.insertAfter(wrapItem, nested); + wrapItem.appendChild(nested); + wrapItem.appendChild(nested); + element = wrapItem.previousSibling; + } + return element; + } + + function attemptMergeWithAdjacent(e, allowDifferentListStyles, mergeParagraphs) { + e = attemptMergeWithPrevious(e, allowDifferentListStyles, mergeParagraphs); + return attemptMergeWithNext(e, allowDifferentListStyles, mergeParagraphs); + } + + function attemptMergeWithPrevious(e, allowDifferentListStyles, mergeParagraphs) { + var prev = skipWhitespaceNodesBackwards(e.previousSibling); + if (prev) { + return attemptMerge(prev, e, allowDifferentListStyles ? prev : false, mergeParagraphs); + } else { + return e; + } + } + + function attemptMergeWithNext(e, allowDifferentListStyles, mergeParagraphs) { + var next = skipWhitespaceNodesForwards(e.nextSibling); + if (next) { + return attemptMerge(e, next, allowDifferentListStyles ? next : false, mergeParagraphs); + } else { + return e; + } + } + + function attemptMerge(e1, e2, differentStylesMasterElement, mergeParagraphs) { + if (canMerge(e1, e2, !!differentStylesMasterElement, mergeParagraphs)) { + return merge(e1, e2, differentStylesMasterElement); + } else if (e1 && e1.tagName === 'LI' && isList(e2)) { + // Fix invalidly nested lists. + e1.appendChild(e2); + } + return e2; + } + + function canMerge(e1, e2, allowDifferentListStyles, mergeParagraphs) { + if (!e1 || !e2) { + return false; + } else if (e1.tagName === 'LI' && e2.tagName === 'LI') { + return e2.style.listStyleType === 'none' || containsOnlyAList(e2); + } else if (isList(e1)) { + return (e1.tagName === e2.tagName && (allowDifferentListStyles || e1.style.listStyleType === e2.style.listStyleType)) || isListForIndent(e2); + } else return mergeParagraphs && e1.tagName === 'P' && e2.tagName === 'P'; + } + + function isListForIndent(e) { + var firstLI = skipWhitespaceNodesForwards(e.firstChild), lastLI = skipWhitespaceNodesBackwards(e.lastChild); + return firstLI && lastLI && isList(e) && firstLI === lastLI && (isList(firstLI) || firstLI.style.listStyleType === 'none' || containsOnlyAList(firstLI)); + } + + function containsOnlyAList(e) { + var firstChild = skipWhitespaceNodesForwards(e.firstChild), lastChild = skipWhitespaceNodesBackwards(e.lastChild); + return firstChild && lastChild && firstChild === lastChild && isList(firstChild); + } + + function merge(e1, e2, masterElement) { + var lastOriginal = skipWhitespaceNodesBackwards(e1.lastChild), firstNew = skipWhitespaceNodesForwards(e2.firstChild); + if (e1.tagName === 'P') { + e1.appendChild(e1.ownerDocument.createElement('br')); + } + while (e2.firstChild) { + e1.appendChild(e2.firstChild); + } + if (masterElement) { + e1.style.listStyleType = masterElement.style.listStyleType; + } + e2.parentNode.removeChild(e2); + attemptMerge(lastOriginal, firstNew, false); + return e1; + } + + function findItemToOperateOn(e, dom) { + var item; + if (!dom.is(e, 'li,ol,ul')) { + item = dom.getParent(e, 'li'); + if (item) { + e = item; + } + } + return e; + } + + tinymce.create('tinymce.plugins.Lists', { + init: function(ed) { + var LIST_TABBING = 'TABBING'; + var LIST_EMPTY_ITEM = 'EMPTY'; + var LIST_ESCAPE = 'ESCAPE'; + var LIST_PARAGRAPH = 'PARAGRAPH'; + var LIST_UNKNOWN = 'UNKNOWN'; + var state = LIST_UNKNOWN; + + function isTabInList(e) { + // Don't indent on Ctrl+Tab or Alt+Tab + return e.keyCode === tinymce.VK.TAB && !(e.altKey || e.ctrlKey) && + (ed.queryCommandState('InsertUnorderedList') || ed.queryCommandState('InsertOrderedList')); + } + + function isOnLastListItem() { + var li = getLi(); + var grandParent = li.parentNode.parentNode; + var isLastItem = li.parentNode.lastChild === li; + return isLastItem && !isNestedList(grandParent) && isEmptyListItem(li); + } + + function isNestedList(grandParent) { + if (isList(grandParent)) { + return grandParent.parentNode && grandParent.parentNode.tagName === 'LI'; + } else { + return grandParent.tagName === 'LI'; + } + } + + function isInEmptyListItem() { + return ed.selection.isCollapsed() && isEmptyListItem(getLi()); + } + + function getLi() { + var n = ed.selection.getStart(); + // Get start will return BR if the LI only contains a BR or an empty element as we use these to fix caret position + return ((n.tagName == 'BR' || n.tagName == '') && n.parentNode.tagName == 'LI') ? n.parentNode : n; + } + + function isEmptyListItem(li) { + var numChildren = li.childNodes.length; + if (li.tagName === 'LI') { + return numChildren == 0 ? true : numChildren == 1 && (li.firstChild.tagName == '' || li.firstChild.tagName == 'BR' || isEmptyIE9Li(li)); + } + return false; + } + + function isEmptyIE9Li(li) { + // only consider this to be last item if there is no list item content or that content is nbsp or space since IE9 creates these + var lis = tinymce.grep(li.parentNode.childNodes, function(n) {return n.tagName == 'LI'}); + var isLastLi = li == lis[lis.length - 1]; + var child = li.firstChild; + return tinymce.isIE9 && isLastLi && (child.nodeValue == String.fromCharCode(160) || child.nodeValue == String.fromCharCode(32)); + } + + function isEnter(e) { + return e.keyCode === tinymce.VK.ENTER; + } + + function isEnterWithoutShift(e) { + return isEnter(e) && !e.shiftKey; + } + + function getListKeyState(e) { + if (isTabInList(e)) { + return LIST_TABBING; + } else if (isEnterWithoutShift(e) && isOnLastListItem()) { + // Returns LIST_UNKNOWN since breaking out of lists is handled by the EnterKey.js logic now + //return LIST_ESCAPE; + return LIST_UNKNOWN; + } else if (isEnterWithoutShift(e) && isInEmptyListItem()) { + return LIST_EMPTY_ITEM; + } else { + return LIST_UNKNOWN; + } + } + + function cancelDefaultEvents(ed, e) { + // list escape is done manually using outdent as it does not create paragraphs correctly in td's + if (state == LIST_TABBING || state == LIST_EMPTY_ITEM || tinymce.isGecko && state == LIST_ESCAPE) { + Event.cancel(e); + } + } + + function isCursorAtEndOfContainer() { + var range = ed.selection.getRng(true); + var startContainer = range.startContainer; + if (startContainer.nodeType == 3) { + var value = startContainer.nodeValue; + if (tinymce.isIE9 && value.length > 1 && value.charCodeAt(value.length-1) == 32) { + // IE9 places a space on the end of the text in some cases so ignore last char + return (range.endOffset == value.length-1); + } else { + return (range.endOffset == value.length); + } + } else if (startContainer.nodeType == 1) { + return range.endOffset == startContainer.childNodes.length; + } + return false; + } + + /* + If we are at the end of a list item surrounded with an element, pressing enter should create a + new list item instead without splitting the element e.g. don't want to create new P or H1 tag + */ + function isEndOfListItem() { + var node = ed.selection.getNode(); + var validElements = 'h1,h2,h3,h4,h5,h6,p,div'; + var isLastParagraphOfLi = ed.dom.is(node, validElements) && node.parentNode.tagName === 'LI' && node.parentNode.lastChild === node; + return ed.selection.isCollapsed() && isLastParagraphOfLi && isCursorAtEndOfContainer(); + } + + // Creates a new list item after the current selection's list item parent + function createNewLi(ed, e) { + if (isEnterWithoutShift(e) && isEndOfListItem()) { + var node = ed.selection.getNode(); + var li = ed.dom.create("li"); + var parentLi = ed.dom.getParent(node, 'li'); + ed.dom.insertAfter(li, parentLi); + + // Move caret to new list element. + if (tinymce.isIE6 || tinymce.isIE7 || tinyMCE.isIE8) { + // Removed this line since it would create an odd < > tag and placing the caret inside an empty LI is handled and should be handled by the selection logic + //li.appendChild(ed.dom.create(" ")); // IE needs an element within the bullet point + ed.selection.setCursorLocation(li, 1); + } else { + ed.selection.setCursorLocation(li, 0); + } + e.preventDefault(); + } + } + + function imageJoiningListItem(ed, e) { + var prevSibling; + + if (!tinymce.isGecko) + return; + + var n = ed.selection.getStart(); + if (e.keyCode != tinymce.VK.BACKSPACE || n.tagName !== 'IMG') + return; + + function lastLI(node) { + var child = node.firstChild; + var li = null; + do { + if (!child) + break; + + if (child.tagName === 'LI') + li = child; + } while (child = child.nextSibling); + + return li; + } + + function addChildren(parentNode, destination) { + while (parentNode.childNodes.length > 0) + destination.appendChild(parentNode.childNodes[0]); + } + + // Check if there is a previous sibling + prevSibling = n.parentNode.previousSibling; + if (!prevSibling) + return; + + var ul; + if (prevSibling.tagName === 'UL' || prevSibling.tagName === 'OL') + ul = prevSibling; + else if (prevSibling.previousSibling && (prevSibling.previousSibling.tagName === 'UL' || prevSibling.previousSibling.tagName === 'OL')) + ul = prevSibling.previousSibling; + else + return; + + var li = lastLI(ul); + + // move the caret to the end of the list item + var rng = ed.dom.createRng(); + rng.setStart(li, 1); + rng.setEnd(li, 1); + ed.selection.setRng(rng); + ed.selection.collapse(true); + + // save a bookmark at the end of the list item + var bookmark = ed.selection.getBookmark(); + + // copy the image an its text to the list item + var clone = n.parentNode.cloneNode(true); + if (clone.tagName === 'P' || clone.tagName === 'DIV') + addChildren(clone, li); + else + li.appendChild(clone); + + // remove the old copy of the image + n.parentNode.parentNode.removeChild(n.parentNode); + + // move the caret where we saved the bookmark + ed.selection.moveToBookmark(bookmark); + } + + // fix the cursor position to ensure it is correct in IE + function setCursorPositionToOriginalLi(li) { + var list = ed.dom.getParent(li, 'ol,ul'); + if (list != null) { + var lastLi = list.lastChild; + // Removed this line since IE9 would report an DOM character error and placing the caret inside an empty LI is handled and should be handled by the selection logic + //lastLi.appendChild(ed.getDoc().createElement('')); + ed.selection.setCursorLocation(lastLi, 0); + } + } + + this.ed = ed; + ed.addCommand('Indent', this.indent, this); + ed.addCommand('Outdent', this.outdent, this); + ed.addCommand('InsertUnorderedList', function() { + this.applyList('UL', 'OL'); + }, this); + ed.addCommand('InsertOrderedList', function() { + this.applyList('OL', 'UL'); + }, this); + + ed.onInit.add(function() { + ed.editorCommands.addCommands({ + 'outdent': function() { + var sel = ed.selection, dom = ed.dom; + + function hasStyleIndent(n) { + n = dom.getParent(n, dom.isBlock); + return n && (parseInt(ed.dom.getStyle(n, 'margin-left') || 0, 10) + parseInt(ed.dom.getStyle(n, 'padding-left') || 0, 10)) > 0; + } + + return hasStyleIndent(sel.getStart()) || hasStyleIndent(sel.getEnd()) || ed.queryCommandState('InsertOrderedList') || ed.queryCommandState('InsertUnorderedList'); + } + }, 'state'); + }); + + ed.onKeyUp.add(function(ed, e) { + if (state == LIST_TABBING) { + ed.execCommand(e.shiftKey ? 'Outdent' : 'Indent', true, null); + state = LIST_UNKNOWN; + return Event.cancel(e); + } else if (state == LIST_EMPTY_ITEM) { + var li = getLi(); + var shouldOutdent = ed.settings.list_outdent_on_enter === true || e.shiftKey; + ed.execCommand(shouldOutdent ? 'Outdent' : 'Indent', true, null); + if (tinymce.isIE) { + setCursorPositionToOriginalLi(li); + } + + return Event.cancel(e); + } else if (state == LIST_ESCAPE) { + if (tinymce.isIE6 || tinymce.isIE7 || tinymce.isIE8) { + // append a zero sized nbsp so that caret is positioned correctly in IE after escaping and applying formatting. + // if there is no text then applying formatting for e.g a H1 to the P tag immediately following list after + // escaping from it will cause the caret to be positioned on the last li instead of staying the in P tag. + var n = ed.getDoc().createTextNode('\uFEFF'); + ed.selection.getNode().appendChild(n); + } else if (tinymce.isIE9 || tinymce.isGecko) { + // IE9 does not escape the list so we use outdent to do this and cancel the default behaviour + // Gecko does not create a paragraph outdenting inside a TD so default behaviour is cancelled and we outdent ourselves + ed.execCommand('Outdent'); + return Event.cancel(e); + } + } + }); + + function fixListItem(parent, reference) { + // a zero-sized non-breaking space is placed in the empty list item so that the nested list is + // displayed on the below line instead of next to it + var n = ed.getDoc().createTextNode('\uFEFF'); + parent.insertBefore(n, reference); + ed.selection.setCursorLocation(n, 0); + // repaint to remove rendering artifact. only visible when creating new list + ed.execCommand('mceRepaint'); + } + + function fixIndentedListItemForGecko(ed, e) { + if (isEnter(e)) { + var li = getLi(); + if (li) { + var parent = li.parentNode; + var grandParent = parent && parent.parentNode; + if (grandParent && grandParent.nodeName == 'LI' && grandParent.firstChild == parent && li == parent.firstChild) { + fixListItem(grandParent, parent); + } + } + } + } + + function fixIndentedListItemForIE8(ed, e) { + if (isEnter(e)) { + var li = getLi(); + if (ed.dom.select('ul li', li).length === 1) { + var list = li.firstChild; + fixListItem(li, list); + } + } + } + + function fixDeletingFirstCharOfList(ed, e) { + function listElements(li) { + var elements = []; + var walker = new tinymce.dom.TreeWalker(li.firstChild, li); + for (var node = walker.current(); node; node = walker.next()) { + if (ed.dom.is(node, 'ol,ul,li')) { + elements.push(node); + } + } + return elements; + } + + if (e.keyCode == tinymce.VK.BACKSPACE) { + var li = getLi(); + if (li) { + var list = ed.dom.getParent(li, 'ol,ul'), + rng = ed.selection.getRng(); + if (list && list.firstChild === li && rng.startOffset == 0) { + var elements = listElements(li); + elements.unshift(li); + ed.execCommand("Outdent", false, elements); + ed.undoManager.add(); + return Event.cancel(e); + } + } + } + } + + function fixDeletingEmptyLiInWebkit(ed, e) { + var li = getLi(); + if (e.keyCode === tinymce.VK.BACKSPACE && ed.dom.is(li, 'li') && li.parentNode.firstChild!==li) { + if (ed.dom.select('ul,ol', li).length === 1) { + var prevLi = li.previousSibling; + ed.dom.remove(ed.dom.select('br', li)); + ed.dom.remove(li, true); + var textNodes = tinymce.grep(prevLi.childNodes, function(n){ return n.nodeType === 3 }); + if (textNodes.length === 1) { + var textNode = textNodes[0]; + ed.selection.setCursorLocation(textNode, textNode.length); + } + ed.undoManager.add(); + return Event.cancel(e); + } + } + } + + ed.onKeyDown.add(function(_, e) { state = getListKeyState(e); }); + ed.onKeyDown.add(cancelDefaultEvents); + ed.onKeyDown.add(imageJoiningListItem); + ed.onKeyDown.add(createNewLi); + + if (tinymce.isGecko) { + ed.onKeyUp.add(fixIndentedListItemForGecko); + } + if (tinymce.isIE8) { + ed.onKeyUp.add(fixIndentedListItemForIE8); + } + if (tinymce.isGecko || tinymce.isWebKit) { + ed.onKeyDown.add(fixDeletingFirstCharOfList); + } + if (tinymce.isWebKit) { + ed.onKeyDown.add(fixDeletingEmptyLiInWebkit); + } + }, + + applyList: function(targetListType, oppositeListType) { + var t = this, ed = t.ed, dom = ed.dom, applied = [], hasSameType = false, hasOppositeType = false, hasNonList = false, actions, + selectedBlocks = ed.selection.getSelectedBlocks(); + + function cleanupBr(e) { + if (e && e.tagName === 'BR') { + dom.remove(e); + } + } + + function makeList(element) { + var list = dom.create(targetListType), li; + + function adjustIndentForNewList(element) { + // If there's a margin-left, outdent one level to account for the extra list margin. + if (element.style.marginLeft || element.style.paddingLeft) { + t.adjustPaddingFunction(false)(element); + } + } + + if (element.tagName === 'LI') { + // No change required. + } else if (element.tagName === 'P' || element.tagName === 'DIV' || element.tagName === 'BODY') { + processBrs(element, function(startSection, br) { + doWrapList(startSection, br, element.tagName === 'BODY' ? null : startSection.parentNode); + li = startSection.parentNode; + adjustIndentForNewList(li); + cleanupBr(br); + }); + if (li) { + if (li.tagName === 'LI' && (element.tagName === 'P' || selectedBlocks.length > 1)) { + dom.split(li.parentNode.parentNode, li.parentNode); + } + attemptMergeWithAdjacent(li.parentNode, true); + } + return; + } else { + // Put the list around the element. + li = dom.create('li'); + dom.insertAfter(li, element); + li.appendChild(element); + adjustIndentForNewList(element); + element = li; + } + dom.insertAfter(list, element); + list.appendChild(element); + attemptMergeWithAdjacent(list, true); + applied.push(element); + } + + function doWrapList(start, end, template) { + var li, n = start, tmp; + while (!dom.isBlock(start.parentNode) && start.parentNode !== dom.getRoot()) { + start = dom.split(start.parentNode, start.previousSibling); + start = start.nextSibling; + n = start; + } + if (template) { + li = template.cloneNode(true); + start.parentNode.insertBefore(li, start); + while (li.firstChild) dom.remove(li.firstChild); + li = dom.rename(li, 'li'); + } else { + li = dom.create('li'); + start.parentNode.insertBefore(li, start); + } + while (n && n != end) { + tmp = n.nextSibling; + li.appendChild(n); + n = tmp; + } + if (li.childNodes.length === 0) { + li.innerHTML = '
'; + } + makeList(li); + } + + function processBrs(element, callback) { + var startSection, previousBR, END_TO_START = 3, START_TO_END = 1, + breakElements = 'br,ul,ol,p,div,h1,h2,h3,h4,h5,h6,table,blockquote,address,pre,form,center,dl'; + + function isAnyPartSelected(start, end) { + var r = dom.createRng(), sel; + bookmark.keep = true; + ed.selection.moveToBookmark(bookmark); + bookmark.keep = false; + sel = ed.selection.getRng(true); + if (!end) { + end = start.parentNode.lastChild; + } + r.setStartBefore(start); + r.setEndAfter(end); + return !(r.compareBoundaryPoints(END_TO_START, sel) > 0 || r.compareBoundaryPoints(START_TO_END, sel) <= 0); + } + + function nextLeaf(br) { + if (br.nextSibling) + return br.nextSibling; + if (!dom.isBlock(br.parentNode) && br.parentNode !== dom.getRoot()) + return nextLeaf(br.parentNode); + } + + // Split on BRs within the range and process those. + startSection = element.firstChild; + // First mark the BRs that have any part of the previous section selected. + var trailingContentSelected = false; + each(dom.select(breakElements, element), function(br) { + if (br.hasAttribute && br.hasAttribute('_mce_bogus')) { + return true; // Skip the bogus Brs that are put in to appease Firefox and Safari. + } + if (isAnyPartSelected(startSection, br)) { + dom.addClass(br, '_mce_tagged_br'); + startSection = nextLeaf(br); + } + }); + trailingContentSelected = (startSection && isAnyPartSelected(startSection, undefined)); + startSection = element.firstChild; + each(dom.select(breakElements, element), function(br) { + // Got a section from start to br. + var tmp = nextLeaf(br); + if (br.hasAttribute && br.hasAttribute('_mce_bogus')) { + return true; // Skip the bogus Brs that are put in to appease Firefox and Safari. + } + if (dom.hasClass(br, '_mce_tagged_br')) { + callback(startSection, br, previousBR); + previousBR = null; + } else { + previousBR = br; + } + startSection = tmp; + }); + if (trailingContentSelected) { + callback(startSection, undefined, previousBR); + } + } + + function wrapList(element) { + processBrs(element, function(startSection, br, previousBR) { + // Need to indent this part + doWrapList(startSection, br); + cleanupBr(br); + cleanupBr(previousBR); + }); + } + + function changeList(element) { + if (tinymce.inArray(applied, element) !== -1) { + return; + } + if (element.parentNode.tagName === oppositeListType) { + dom.split(element.parentNode, element); + makeList(element); + attemptMergeWithNext(element.parentNode, false); + } + applied.push(element); + } + + function convertListItemToParagraph(element) { + var child, nextChild, mergedElement, splitLast; + if (tinymce.inArray(applied, element) !== -1) { + return; + } + element = splitNestedLists(element, dom); + while (dom.is(element.parentNode, 'ol,ul,li')) { + dom.split(element.parentNode, element); + } + // Push the original element we have from the selection, not the renamed one. + applied.push(element); + element = dom.rename(element, 'p'); + mergedElement = attemptMergeWithAdjacent(element, false, ed.settings.force_br_newlines); + if (mergedElement === element) { + // Now split out any block elements that can't be contained within a P. + // Manually iterate to ensure we handle modifications correctly (doesn't work with tinymce.each) + child = element.firstChild; + while (child) { + if (dom.isBlock(child)) { + child = dom.split(child.parentNode, child); + splitLast = true; + nextChild = child.nextSibling && child.nextSibling.firstChild; + } else { + nextChild = child.nextSibling; + if (splitLast && child.tagName === 'BR') { + dom.remove(child); + } + splitLast = false; + } + child = nextChild; + } + } + } + + each(selectedBlocks, function(e) { + e = findItemToOperateOn(e, dom); + if (e.tagName === oppositeListType || (e.tagName === 'LI' && e.parentNode.tagName === oppositeListType)) { + hasOppositeType = true; + } else if (e.tagName === targetListType || (e.tagName === 'LI' && e.parentNode.tagName === targetListType)) { + hasSameType = true; + } else { + hasNonList = true; + } + }); + + if (hasNonList &&!hasSameType || hasOppositeType || selectedBlocks.length === 0) { + actions = { + 'LI': changeList, + 'H1': makeList, + 'H2': makeList, + 'H3': makeList, + 'H4': makeList, + 'H5': makeList, + 'H6': makeList, + 'P': makeList, + 'BODY': makeList, + 'DIV': selectedBlocks.length > 1 ? makeList : wrapList, + defaultAction: wrapList, + elements: this.selectedBlocks() + }; + } else { + actions = { + defaultAction: convertListItemToParagraph, + elements: this.selectedBlocks(), + processEvenIfEmpty: true + }; + } + this.process(actions); + }, + + indent: function() { + var ed = this.ed, dom = ed.dom, indented = []; + + function createWrapItem(element) { + var wrapItem = dom.create('li', { style: 'list-style-type: none;'}); + dom.insertAfter(wrapItem, element); + return wrapItem; + } + + function createWrapList(element) { + var wrapItem = createWrapItem(element), + list = dom.getParent(element, 'ol,ul'), + listType = list.tagName, + listStyle = dom.getStyle(list, 'list-style-type'), + attrs = {}, + wrapList; + if (listStyle !== '') { + attrs.style = 'list-style-type: ' + listStyle + ';'; + } + wrapList = dom.create(listType, attrs); + wrapItem.appendChild(wrapList); + return wrapList; + } + + function indentLI(element) { + if (!hasParentInList(ed, element, indented)) { + element = splitNestedLists(element, dom); + var wrapList = createWrapList(element); + wrapList.appendChild(element); + attemptMergeWithAdjacent(wrapList.parentNode, false); + attemptMergeWithAdjacent(wrapList, false); + indented.push(element); + } + } + + this.process({ + 'LI': indentLI, + defaultAction: this.adjustPaddingFunction(true), + elements: this.selectedBlocks() + }); + + }, + + outdent: function(ui, elements) { + var t = this, ed = t.ed, dom = ed.dom, outdented = []; + + function outdentLI(element) { + var listElement, targetParent, align; + if (!hasParentInList(ed, element, outdented)) { + if (dom.getStyle(element, 'margin-left') !== '' || dom.getStyle(element, 'padding-left') !== '') { + return t.adjustPaddingFunction(false)(element); + } + align = dom.getStyle(element, 'text-align', true); + if (align === 'center' || align === 'right') { + dom.setStyle(element, 'text-align', 'left'); + return; + } + element = splitNestedLists(element, dom); + listElement = element.parentNode; + targetParent = element.parentNode.parentNode; + if (targetParent.tagName === 'P') { + dom.split(targetParent, element.parentNode); + } else { + dom.split(listElement, element); + if (targetParent.tagName === 'LI') { + // Nested list, need to split the LI and go back out to the OL/UL element. + dom.split(targetParent, element); + } else if (!dom.is(targetParent, 'ol,ul')) { + dom.rename(element, 'p'); + } + } + outdented.push(element); + } + } + + var listElements = elements && tinymce.is(elements, 'array') ? elements : this.selectedBlocks(); + this.process({ + 'LI': outdentLI, + defaultAction: this.adjustPaddingFunction(false), + elements: listElements + }); + + each(outdented, attemptMergeWithAdjacent); + }, + + process: function(actions) { + var t = this, sel = t.ed.selection, dom = t.ed.dom, selectedBlocks, r; + + function isEmptyElement(element) { + var excludeBrsAndBookmarks = tinymce.grep(element.childNodes, function(n) { + return !(n.nodeName === 'BR' || n.nodeName === 'SPAN' && dom.getAttrib(n, 'data-mce-type') == 'bookmark' + || n.nodeType == 3 && (n.nodeValue == String.fromCharCode(160) || n.nodeValue == '')); + }); + return excludeBrsAndBookmarks.length === 0; + } + + function processElement(element) { + dom.removeClass(element, '_mce_act_on'); + if (!element || element.nodeType !== 1 || ! actions.processEvenIfEmpty && selectedBlocks.length > 1 && isEmptyElement(element)) { + return; + } + element = findItemToOperateOn(element, dom); + var action = actions[element.tagName]; + if (!action) { + action = actions.defaultAction; + } + action(element); + } + + function recurse(element) { + t.splitSafeEach(element.childNodes, processElement, true); + } + + function brAtEdgeOfSelection(container, offset) { + return offset >= 0 && container.hasChildNodes() && offset < container.childNodes.length && + container.childNodes[offset].tagName === 'BR'; + } + + function isInTable() { + var n = sel.getNode(); + var p = dom.getParent(n, 'td'); + return p !== null; + } + + selectedBlocks = actions.elements; + + r = sel.getRng(true); + if (!r.collapsed) { + if (brAtEdgeOfSelection(r.endContainer, r.endOffset - 1)) { + r.setEnd(r.endContainer, r.endOffset - 1); + sel.setRng(r); + } + if (brAtEdgeOfSelection(r.startContainer, r.startOffset)) { + r.setStart(r.startContainer, r.startOffset + 1); + sel.setRng(r); + } + } + + + if (tinymce.isIE8) { + // append a zero sized nbsp so that caret is restored correctly using bookmark + var s = t.ed.selection.getNode(); + if (s.tagName === 'LI' && !(s.parentNode.lastChild === s)) { + var i = t.ed.getDoc().createTextNode('\uFEFF'); + s.appendChild(i); + } + } + + bookmark = sel.getBookmark(); + actions.OL = actions.UL = recurse; + t.splitSafeEach(selectedBlocks, processElement); + sel.moveToBookmark(bookmark); + bookmark = null; + + // we avoid doing repaint in a table as this will move the caret out of the table in Firefox 3.6 + if (!isInTable()) { + // Avoids table or image handles being left behind in Firefox. + t.ed.execCommand('mceRepaint'); + } + }, + + splitSafeEach: function(elements, f, forceClassBase) { + if (forceClassBase || + (tinymce.isGecko && + (/Firefox\/[12]\.[0-9]/.test(navigator.userAgent) || + /Firefox\/3\.[0-4]/.test(navigator.userAgent)))) { + this.classBasedEach(elements, f); + } else { + each(elements, f); + } + }, + + classBasedEach: function(elements, f) { + var dom = this.ed.dom, nodes, element; + // Mark nodes + each(elements, function(element) { + dom.addClass(element, '_mce_act_on'); + }); + nodes = dom.select('._mce_act_on'); + while (nodes.length > 0) { + element = nodes.shift(); + dom.removeClass(element, '_mce_act_on'); + f(element); + nodes = dom.select('._mce_act_on'); + } + }, + + adjustPaddingFunction: function(isIndent) { + var indentAmount, indentUnits, ed = this.ed; + indentAmount = ed.settings.indentation; + indentUnits = /[a-z%]+/i.exec(indentAmount); + indentAmount = parseInt(indentAmount, 10); + return function(element) { + var currentIndent, newIndentAmount; + currentIndent = parseInt(ed.dom.getStyle(element, 'margin-left') || 0, 10) + parseInt(ed.dom.getStyle(element, 'padding-left') || 0, 10); + if (isIndent) { + newIndentAmount = currentIndent + indentAmount; + } else { + newIndentAmount = currentIndent - indentAmount; + } + ed.dom.setStyle(element, 'padding-left', ''); + ed.dom.setStyle(element, 'margin-left', newIndentAmount > 0 ? newIndentAmount + indentUnits : ''); + }; + }, + + selectedBlocks: function() { + var ed = this.ed, selectedBlocks = ed.selection.getSelectedBlocks(); + return selectedBlocks.length == 0 ? [ ed.dom.getRoot() ] : selectedBlocks; + }, + + getInfo: function() { + return { + longname : 'Lists', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/lists', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + tinymce.PluginManager.add("lists", tinymce.plugins.Lists); +}()); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/editor_plugin_src.js new file mode 100644 index 0000000..2ca33ac --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/editor_plugin_src.js @@ -0,0 +1,898 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var rootAttributes = tinymce.explode('id,name,width,height,style,align,class,hspace,vspace,bgcolor,type'), excludedAttrs = tinymce.makeMap(rootAttributes.join(',')), Node = tinymce.html.Node, + mediaTypes, scriptRegExp, JSON = tinymce.util.JSON, mimeTypes; + + // Media types supported by this plugin + mediaTypes = [ + // Type, clsid:s, mime types, codebase + ["Flash", "d27cdb6e-ae6d-11cf-96b8-444553540000", "application/x-shockwave-flash", "http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"], + ["ShockWave", "166b1bca-3f9c-11cf-8075-444553540000", "application/x-director", "http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0"], + ["WindowsMedia", "6bf52a52-394a-11d3-b153-00c04f79faa6,22d6f312-b0f6-11d0-94ab-0080c74c7e95,05589fa1-c356-11ce-bf01-00aa0055595a", "application/x-mplayer2", "http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701"], + ["QuickTime", "02bf25d5-8c17-4b23-bc80-d3488abddc6b", "video/quicktime", "http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0"], + ["RealMedia", "cfcdaa03-8be4-11cf-b84b-0020afbbccfa", "audio/x-pn-realaudio-plugin", "http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"], + ["Java", "8ad9c840-044e-11d1-b3e9-00805f499d93", "application/x-java-applet", "http://java.sun.com/products/plugin/autodl/jinstall-1_5_0-windows-i586.cab#Version=1,5,0,0"], + ["Silverlight", "dfeaf541-f3e1-4c24-acac-99c30715084a", "application/x-silverlight-2"], + ["Iframe"], + ["Video"], + ["EmbeddedAudio"], + ["Audio"] + ]; + + function normalizeSize(size) { + return typeof(size) == "string" ? size.replace(/[^0-9%]/g, '') : size; + } + + function toArray(obj) { + var undef, out, i; + + if (obj && !obj.splice) { + out = []; + + for (i = 0; true; i++) { + if (obj[i]) + out[i] = obj[i]; + else + break; + } + + return out; + } + + return obj; + }; + + tinymce.create('tinymce.plugins.MediaPlugin', { + init : function(ed, url) { + var self = this, lookup = {}, i, y, item, name; + + function isMediaImg(node) { + return node && node.nodeName === 'IMG' && ed.dom.hasClass(node, 'mceItemMedia'); + }; + + self.editor = ed; + self.url = url; + + // Parse media types into a lookup table + scriptRegExp = ''; + for (i = 0; i < mediaTypes.length; i++) { + name = mediaTypes[i][0]; + + item = { + name : name, + clsids : tinymce.explode(mediaTypes[i][1] || ''), + mimes : tinymce.explode(mediaTypes[i][2] || ''), + codebase : mediaTypes[i][3] + }; + + for (y = 0; y < item.clsids.length; y++) + lookup['clsid:' + item.clsids[y]] = item; + + for (y = 0; y < item.mimes.length; y++) + lookup[item.mimes[y]] = item; + + lookup['mceItem' + name] = item; + lookup[name.toLowerCase()] = item; + + scriptRegExp += (scriptRegExp ? '|' : '') + name; + } + + // Handle the media_types setting + tinymce.each(ed.getParam("media_types", + "video=mp4,m4v,ogv,webm;" + + "silverlight=xap;" + + "flash=swf,flv;" + + "shockwave=dcr;" + + "quicktime=mov,qt,mpg,mpeg;" + + "shockwave=dcr;" + + "windowsmedia=avi,wmv,wm,asf,asx,wmx,wvx;" + + "realmedia=rm,ra,ram;" + + "java=jar;" + + "audio=mp3,ogg" + ).split(';'), function(item) { + var i, extensions, type; + + item = item.split(/=/); + extensions = tinymce.explode(item[1].toLowerCase()); + for (i = 0; i < extensions.length; i++) { + type = lookup[item[0].toLowerCase()]; + + if (type) + lookup[extensions[i]] = type; + } + }); + + scriptRegExp = new RegExp('write(' + scriptRegExp + ')\\(([^)]+)\\)'); + self.lookup = lookup; + + ed.onPreInit.add(function() { + // Allow video elements + ed.schema.addValidElements('object[id|style|width|height|classid|codebase|*],param[name|value],embed[id|style|width|height|type|src|*],video[*],audio[*],source[*]'); + + // Convert video elements to image placeholder + ed.parser.addNodeFilter('object,embed,video,audio,script,iframe', function(nodes) { + var i = nodes.length; + + while (i--) + self.objectToImg(nodes[i]); + }); + + // Convert image placeholders to video elements + ed.serializer.addNodeFilter('img', function(nodes, name, args) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + if ((node.attr('class') || '').indexOf('mceItemMedia') !== -1) + self.imgToObject(node, args); + } + }); + }); + + ed.onInit.add(function() { + // Display "media" instead of "img" in element path + if (ed.theme && ed.theme.onResolveName) { + ed.theme.onResolveName.add(function(theme, path_object) { + if (path_object.name === 'img' && ed.dom.hasClass(path_object.node, 'mceItemMedia')) + path_object.name = 'media'; + }); + } + + // Add contect menu if it's loaded + if (ed && ed.plugins.contextmenu) { + ed.plugins.contextmenu.onContextMenu.add(function(plugin, menu, element) { + if (element.nodeName === 'IMG' && element.className.indexOf('mceItemMedia') !== -1) + menu.add({title : 'media.edit', icon : 'media', cmd : 'mceMedia'}); + }); + } + }); + + // Register commands + ed.addCommand('mceMedia', function() { + var data, img; + + img = ed.selection.getNode(); + if (isMediaImg(img)) { + data = ed.dom.getAttrib(img, 'data-mce-json'); + if (data) { + data = JSON.parse(data); + + // Add some extra properties to the data object + tinymce.each(rootAttributes, function(name) { + var value = ed.dom.getAttrib(img, name); + + if (value) + data[name] = value; + }); + + data.type = self.getType(img.className).name.toLowerCase(); + } + } + + if (!data) { + data = { + type : 'flash', + video: {sources:[]}, + params: {} + }; + } + + ed.windowManager.open({ + file : url + '/media.htm', + width : 430 + parseInt(ed.getLang('media.delta_width', 0)), + height : 500 + parseInt(ed.getLang('media.delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + data : data + }); + }); + + // Register buttons + ed.addButton('media', {title : 'media.desc', cmd : 'mceMedia'}); + + // Update media selection status + ed.onNodeChange.add(function(ed, cm, node) { + cm.setActive('media', isMediaImg(node)); + }); + }, + + convertUrl : function(url, force_absolute) { + var self = this, editor = self.editor, settings = editor.settings, + urlConverter = settings.url_converter, + urlConverterScope = settings.url_converter_scope || self; + + if (!url) + return url; + + if (force_absolute) + return editor.documentBaseURI.toAbsolute(url); + + return urlConverter.call(urlConverterScope, url, 'src', 'object'); + }, + + getInfo : function() { + return { + longname : 'Media', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/media', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + /** + * Converts the JSON data object to an img node. + */ + dataToImg : function(data, force_absolute) { + var self = this, editor = self.editor, baseUri = editor.documentBaseURI, sources, attrs, img, i; + + data.params.src = self.convertUrl(data.params.src, force_absolute); + + attrs = data.video.attrs; + if (attrs) + attrs.src = self.convertUrl(attrs.src, force_absolute); + + if (attrs) + attrs.poster = self.convertUrl(attrs.poster, force_absolute); + + sources = toArray(data.video.sources); + if (sources) { + for (i = 0; i < sources.length; i++) + sources[i].src = self.convertUrl(sources[i].src, force_absolute); + } + + img = self.editor.dom.create('img', { + id : data.id, + style : data.style, + align : data.align, + hspace : data.hspace, + vspace : data.vspace, + src : self.editor.theme.url + '/img/trans.gif', + 'class' : 'mceItemMedia mceItem' + self.getType(data.type).name, + 'data-mce-json' : JSON.serialize(data, "'") + }); + + img.width = data.width = normalizeSize(data.width || (data.type == 'audio' ? "300" : "320")); + img.height = data.height = normalizeSize(data.height || (data.type == 'audio' ? "32" : "240")); + + return img; + }, + + /** + * Converts the JSON data object to a HTML string. + */ + dataToHtml : function(data, force_absolute) { + return this.editor.serializer.serialize(this.dataToImg(data, force_absolute), {forced_root_block : '', force_absolute : force_absolute}); + }, + + /** + * Converts the JSON data object to a HTML string. + */ + htmlToData : function(html) { + var fragment, img, data; + + data = { + type : 'flash', + video: {sources:[]}, + params: {} + }; + + fragment = this.editor.parser.parse(html); + img = fragment.getAll('img')[0]; + + if (img) { + data = JSON.parse(img.attr('data-mce-json')); + data.type = this.getType(img.attr('class')).name.toLowerCase(); + + // Add some extra properties to the data object + tinymce.each(rootAttributes, function(name) { + var value = img.attr(name); + + if (value) + data[name] = value; + }); + } + + return data; + }, + + /** + * Get type item by extension, class, clsid or mime type. + * + * @method getType + * @param {String} value Value to get type item by. + * @return {Object} Type item object or undefined. + */ + getType : function(value) { + var i, values, typeItem; + + // Find type by checking the classes + values = tinymce.explode(value, ' '); + for (i = 0; i < values.length; i++) { + typeItem = this.lookup[values[i]]; + + if (typeItem) + return typeItem; + } + }, + + /** + * Converts a tinymce.html.Node image element to video/object/embed. + */ + imgToObject : function(node, args) { + var self = this, editor = self.editor, video, object, embed, iframe, name, value, data, + source, sources, params, param, typeItem, i, item, mp4Source, replacement, + posterSrc, style, audio; + + // Adds the flash player + function addPlayer(video_src, poster_src) { + var baseUri, flashVars, flashVarsOutput, params, flashPlayer; + + flashPlayer = editor.getParam('flash_video_player_url', self.convertUrl(self.url + '/moxieplayer.swf')); + if (flashPlayer) { + baseUri = editor.documentBaseURI; + data.params.src = flashPlayer; + + // Convert the movie url to absolute urls + if (editor.getParam('flash_video_player_absvideourl', true)) { + video_src = baseUri.toAbsolute(video_src || '', true); + poster_src = baseUri.toAbsolute(poster_src || '', true); + } + + // Generate flash vars + flashVarsOutput = ''; + flashVars = editor.getParam('flash_video_player_flashvars', {url : '$url', poster : '$poster'}); + tinymce.each(flashVars, function(value, name) { + // Replace $url and $poster variables in flashvars value + value = value.replace(/\$url/, video_src || ''); + value = value.replace(/\$poster/, poster_src || ''); + + if (value.length > 0) + flashVarsOutput += (flashVarsOutput ? '&' : '') + name + '=' + escape(value); + }); + + if (flashVarsOutput.length) + data.params.flashvars = flashVarsOutput; + + params = editor.getParam('flash_video_player_params', { + allowfullscreen: true, + allowscriptaccess: true + }); + + tinymce.each(params, function(value, name) { + data.params[name] = "" + value; + }); + } + }; + + data = node.attr('data-mce-json'); + if (!data) + return; + + data = JSON.parse(data); + typeItem = this.getType(node.attr('class')); + + style = node.attr('data-mce-style'); + if (!style) { + style = node.attr('style'); + + if (style) + style = editor.dom.serializeStyle(editor.dom.parseStyle(style, 'img')); + } + + // Use node width/height to override the data width/height when the placeholder is resized + data.width = node.attr('width') || data.width; + data.height = node.attr('height') || data.height; + + // Handle iframe + if (typeItem.name === 'Iframe') { + replacement = new Node('iframe', 1); + + tinymce.each(rootAttributes, function(name) { + var value = node.attr(name); + + if (name == 'class' && value) + value = value.replace(/mceItem.+ ?/g, ''); + + if (value && value.length > 0) + replacement.attr(name, value); + }); + + for (name in data.params) + replacement.attr(name, data.params[name]); + + replacement.attr({ + style: style, + src: data.params.src + }); + + node.replace(replacement); + + return; + } + + // Handle scripts + if (this.editor.settings.media_use_script) { + replacement = new Node('script', 1).attr('type', 'text/javascript'); + + value = new Node('#text', 3); + value.value = 'write' + typeItem.name + '(' + JSON.serialize(tinymce.extend(data.params, { + width: node.attr('width'), + height: node.attr('height') + })) + ');'; + + replacement.append(value); + node.replace(replacement); + + return; + } + + // Add HTML5 video element + if (typeItem.name === 'Video' && data.video.sources[0]) { + // Create new object element + video = new Node('video', 1).attr(tinymce.extend({ + id : node.attr('id'), + width: normalizeSize(node.attr('width')), + height: normalizeSize(node.attr('height')), + style : style + }, data.video.attrs)); + + // Get poster source and use that for flash fallback + if (data.video.attrs) + posterSrc = data.video.attrs.poster; + + sources = data.video.sources = toArray(data.video.sources); + for (i = 0; i < sources.length; i++) { + if (/\.mp4$/.test(sources[i].src)) + mp4Source = sources[i].src; + } + + if (!sources[0].type) { + video.attr('src', sources[0].src); + sources.splice(0, 1); + } + + for (i = 0; i < sources.length; i++) { + source = new Node('source', 1).attr(sources[i]); + source.shortEnded = true; + video.append(source); + } + + // Create flash fallback for video if we have a mp4 source + if (mp4Source) { + addPlayer(mp4Source, posterSrc); + typeItem = self.getType('flash'); + } else + data.params.src = ''; + } + + // Add HTML5 audio element + if (typeItem.name === 'Audio' && data.video.sources[0]) { + // Create new object element + audio = new Node('audio', 1).attr(tinymce.extend({ + id : node.attr('id'), + width: normalizeSize(node.attr('width')), + height: normalizeSize(node.attr('height')), + style : style + }, data.video.attrs)); + + // Get poster source and use that for flash fallback + if (data.video.attrs) + posterSrc = data.video.attrs.poster; + + sources = data.video.sources = toArray(data.video.sources); + if (!sources[0].type) { + audio.attr('src', sources[0].src); + sources.splice(0, 1); + } + + for (i = 0; i < sources.length; i++) { + source = new Node('source', 1).attr(sources[i]); + source.shortEnded = true; + audio.append(source); + } + + data.params.src = ''; + } + + if (typeItem.name === 'EmbeddedAudio') { + embed = new Node('embed', 1); + embed.shortEnded = true; + embed.attr({ + id: node.attr('id'), + width: normalizeSize(node.attr('width')), + height: normalizeSize(node.attr('height')), + style : style, + type: node.attr('type') + }); + + for (name in data.params) + embed.attr(name, data.params[name]); + + tinymce.each(rootAttributes, function(name) { + if (data[name] && name != 'type') + embed.attr(name, data[name]); + }); + + data.params.src = ''; + } + + // Do we have a params src then we can generate object + if (data.params.src) { + // Is flv movie add player for it + if (/\.flv$/i.test(data.params.src)) + addPlayer(data.params.src, ''); + + if (args && args.force_absolute) + data.params.src = editor.documentBaseURI.toAbsolute(data.params.src); + + // Create new object element + object = new Node('object', 1).attr({ + id : node.attr('id'), + width: normalizeSize(node.attr('width')), + height: normalizeSize(node.attr('height')), + style : style + }); + + tinymce.each(rootAttributes, function(name) { + var value = data[name]; + + if (name == 'class' && value) + value = value.replace(/mceItem.+ ?/g, ''); + + if (value && name != 'type') + object.attr(name, value); + }); + + // Add params + for (name in data.params) { + param = new Node('param', 1); + param.shortEnded = true; + value = data.params[name]; + + // Windows media needs to use url instead of src for the media URL + if (name === 'src' && typeItem.name === 'WindowsMedia') + name = 'url'; + + param.attr({name: name, value: value}); + object.append(param); + } + + // Setup add type and classid if strict is disabled + if (this.editor.getParam('media_strict', true)) { + object.attr({ + data: data.params.src, + type: typeItem.mimes[0] + }); + } else { + object.attr({ + classid: "clsid:" + typeItem.clsids[0], + codebase: typeItem.codebase + }); + + embed = new Node('embed', 1); + embed.shortEnded = true; + embed.attr({ + id: node.attr('id'), + width: normalizeSize(node.attr('width')), + height: normalizeSize(node.attr('height')), + style : style, + type: typeItem.mimes[0] + }); + + for (name in data.params) + embed.attr(name, data.params[name]); + + tinymce.each(rootAttributes, function(name) { + if (data[name] && name != 'type') + embed.attr(name, data[name]); + }); + + object.append(embed); + } + + // Insert raw HTML + if (data.object_html) { + value = new Node('#text', 3); + value.raw = true; + value.value = data.object_html; + object.append(value); + } + + // Append object to video element if it exists + if (video) + video.append(object); + } + + if (video) { + // Insert raw HTML + if (data.video_html) { + value = new Node('#text', 3); + value.raw = true; + value.value = data.video_html; + video.append(value); + } + } + + if (audio) { + // Insert raw HTML + if (data.video_html) { + value = new Node('#text', 3); + value.raw = true; + value.value = data.video_html; + audio.append(value); + } + } + + var n = video || audio || object || embed; + if (n) + node.replace(n); + else + node.remove(); + }, + + /** + * Converts a tinymce.html.Node video/object/embed to an img element. + * + * The video/object/embed will be converted into an image placeholder with a JSON data attribute like this: + * + * + * The JSON structure will be like this: + * {'params':{'flashvars':'something','quality':'high','src':'someurl'}, 'video':{'sources':[{src: 'someurl', type: 'video/mp4'}]}} + */ + objectToImg : function(node) { + var object, embed, video, iframe, img, name, id, width, height, style, i, html, + param, params, source, sources, data, type, lookup = this.lookup, + matches, attrs, urlConverter = this.editor.settings.url_converter, + urlConverterScope = this.editor.settings.url_converter_scope, + hspace, vspace, align, bgcolor; + + function getInnerHTML(node) { + return new tinymce.html.Serializer({ + inner: true, + validate: false + }).serialize(node); + }; + + function lookupAttribute(o, attr) { + return lookup[(o.attr(attr) || '').toLowerCase()]; + } + + function lookupExtension(src) { + var ext = src.replace(/^.*\.([^.]+)$/, '$1'); + return lookup[ext.toLowerCase() || '']; + } + + // If node isn't in document + if (!node.parent) + return; + + // Handle media scripts + if (node.name === 'script') { + if (node.firstChild) + matches = scriptRegExp.exec(node.firstChild.value); + + if (!matches) + return; + + type = matches[1]; + data = {video : {}, params : JSON.parse(matches[2])}; + width = data.params.width; + height = data.params.height; + } + + // Setup data objects + data = data || { + video : {}, + params : {} + }; + + // Setup new image object + img = new Node('img', 1); + img.attr({ + src : this.editor.theme.url + '/img/trans.gif' + }); + + // Video element + name = node.name; + if (name === 'video' || name == 'audio') { + video = node; + object = node.getAll('object')[0]; + embed = node.getAll('embed')[0]; + width = video.attr('width'); + height = video.attr('height'); + id = video.attr('id'); + data.video = {attrs : {}, sources : []}; + + // Get all video attributes + attrs = data.video.attrs; + for (name in video.attributes.map) + attrs[name] = video.attributes.map[name]; + + source = node.attr('src'); + if (source) + data.video.sources.push({src : urlConverter.call(urlConverterScope, source, 'src', node.name)}); + + // Get all sources + sources = video.getAll("source"); + for (i = 0; i < sources.length; i++) { + source = sources[i].remove(); + + data.video.sources.push({ + src: urlConverter.call(urlConverterScope, source.attr('src'), 'src', 'source'), + type: source.attr('type'), + media: source.attr('media') + }); + } + + // Convert the poster URL + if (attrs.poster) + attrs.poster = urlConverter.call(urlConverterScope, attrs.poster, 'poster', node.name); + } + + // Object element + if (node.name === 'object') { + object = node; + embed = node.getAll('embed')[0]; + } + + // Embed element + if (node.name === 'embed') + embed = node; + + // Iframe element + if (node.name === 'iframe') { + iframe = node; + type = 'Iframe'; + } + + if (object) { + // Get width/height + width = width || object.attr('width'); + height = height || object.attr('height'); + style = style || object.attr('style'); + id = id || object.attr('id'); + hspace = hspace || object.attr('hspace'); + vspace = vspace || object.attr('vspace'); + align = align || object.attr('align'); + bgcolor = bgcolor || object.attr('bgcolor'); + data.name = object.attr('name'); + + // Get all object params + params = object.getAll("param"); + for (i = 0; i < params.length; i++) { + param = params[i]; + name = param.remove().attr('name'); + + if (!excludedAttrs[name]) + data.params[name] = param.attr('value'); + } + + data.params.src = data.params.src || object.attr('data'); + } + + if (embed) { + // Get width/height + width = width || embed.attr('width'); + height = height || embed.attr('height'); + style = style || embed.attr('style'); + id = id || embed.attr('id'); + hspace = hspace || embed.attr('hspace'); + vspace = vspace || embed.attr('vspace'); + align = align || embed.attr('align'); + bgcolor = bgcolor || embed.attr('bgcolor'); + + // Get all embed attributes + for (name in embed.attributes.map) { + if (!excludedAttrs[name] && !data.params[name]) + data.params[name] = embed.attributes.map[name]; + } + } + + if (iframe) { + // Get width/height + width = normalizeSize(iframe.attr('width')); + height = normalizeSize(iframe.attr('height')); + style = style || iframe.attr('style'); + id = iframe.attr('id'); + hspace = iframe.attr('hspace'); + vspace = iframe.attr('vspace'); + align = iframe.attr('align'); + bgcolor = iframe.attr('bgcolor'); + + tinymce.each(rootAttributes, function(name) { + img.attr(name, iframe.attr(name)); + }); + + // Get all iframe attributes + for (name in iframe.attributes.map) { + if (!excludedAttrs[name] && !data.params[name]) + data.params[name] = iframe.attributes.map[name]; + } + } + + // Use src not movie + if (data.params.movie) { + data.params.src = data.params.src || data.params.movie; + delete data.params.movie; + } + + // Convert the URL to relative/absolute depending on configuration + if (data.params.src) + data.params.src = urlConverter.call(urlConverterScope, data.params.src, 'src', 'object'); + + if (video) { + if (node.name === 'video') + type = lookup.video.name; + else if (node.name === 'audio') + type = lookup.audio.name; + } + + if (object && !type) + type = (lookupAttribute(object, 'clsid') || lookupAttribute(object, 'classid') || lookupAttribute(object, 'type') || {}).name; + + if (embed && !type) + type = (lookupAttribute(embed, 'type') || lookupExtension(data.params.src) || {}).name; + + // for embedded audio we preserve the original specified type + if (embed && type == 'EmbeddedAudio') { + data.params.type = embed.attr('type'); + } + + // Replace the video/object/embed element with a placeholder image containing the data + node.replace(img); + + // Remove embed + if (embed) + embed.remove(); + + // Serialize the inner HTML of the object element + if (object) { + html = getInnerHTML(object.remove()); + + if (html) + data.object_html = html; + } + + // Serialize the inner HTML of the video element + if (video) { + html = getInnerHTML(video.remove()); + + if (html) + data.video_html = html; + } + + data.hspace = hspace; + data.vspace = vspace; + data.align = align; + data.bgcolor = bgcolor; + + // Set width/height of placeholder + img.attr({ + id : id, + 'class' : 'mceItemMedia mceItem' + (type || 'Flash'), + style : style, + width : width || (node.name == 'audio' ? "300" : "320"), + height : height || (node.name == 'audio' ? "32" : "240"), + hspace : hspace, + vspace : vspace, + align : align, + bgcolor : bgcolor, + "data-mce-json" : JSON.serialize(data, "'") + }); + } + }); + + // Register plugin + tinymce.PluginManager.add('media', tinymce.plugins.MediaPlugin); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/js/embed.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/js/embed.js new file mode 100644 index 0000000..6fe25de --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/media/js/embed.js @@ -0,0 +1,73 @@ +/** + * This script contains embed functions for common plugins. This scripts are complety free to use for any purpose. + */ + +function writeFlash(p) { + writeEmbed( + 'D27CDB6E-AE6D-11cf-96B8-444553540000', + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0', + 'application/x-shockwave-flash', + p + ); +} + +function writeShockWave(p) { + writeEmbed( + '166B1BCA-3F9C-11CF-8075-444553540000', + 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0', + 'application/x-director', + p + ); +} + +function writeQuickTime(p) { + writeEmbed( + '02BF25D5-8C17-4B23-BC80-D3488ABDDC6B', + 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0', + 'video/quicktime', + p + ); +} + +function writeRealMedia(p) { + writeEmbed( + 'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA', + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0', + 'audio/x-pn-realaudio-plugin', + p + ); +} + +function writeWindowsMedia(p) { + p.url = p.src; + writeEmbed( + '6BF52A52-394A-11D3-B153-00C04F79FAA6', + 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701', + 'application/x-mplayer2', + p + ); +} + +function writeEmbed(cls, cb, mt, p) { + var h = '', n; + + h += ''; + + h += ''); + + function get(id) { + return document.getElementById(id); + } + + function clone(obj) { + var i, len, copy, attr; + + if (null == obj || "object" != typeof obj) + return obj; + + // Handle Array + if ('length' in obj) { + copy = []; + + for (i = 0, len = obj.length; i < len; ++i) { + copy[i] = clone(obj[i]); + } + + return copy; + } + + // Handle Object + copy = {}; + for (attr in obj) { + if (obj.hasOwnProperty(attr)) + copy[attr] = clone(obj[attr]); + } + + return copy; + } + + function getVal(id) { + var elm = get(id); + + if (elm.nodeName == "SELECT") + return elm.options[elm.selectedIndex].value; + + if (elm.type == "checkbox") + return elm.checked; + + return elm.value; + } + + function setVal(id, value, name) { + if (typeof(value) != 'undefined' && value != null) { + var elm = get(id); + + if (elm.nodeName == "SELECT") + selectByValue(document.forms[0], id, value); + else if (elm.type == "checkbox") { + if (typeof(value) == 'string') { + value = value.toLowerCase(); + value = (!name && value === 'true') || (name && value === name.toLowerCase()); + } + elm.checked = !!value; + } else + elm.value = value; + } + } + + window.Media = { + init : function() { + var html, editor, self = this; + + self.editor = editor = tinyMCEPopup.editor; + + // Setup file browsers and color pickers + get('filebrowsercontainer').innerHTML = getBrowserHTML('filebrowser','src','media','media'); + get('qtsrcfilebrowsercontainer').innerHTML = getBrowserHTML('qtsrcfilebrowser','quicktime_qtsrc','media','media'); + get('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); + get('video_altsource1_filebrowser').innerHTML = getBrowserHTML('video_filebrowser_altsource1','video_altsource1','media','media'); + get('video_altsource2_filebrowser').innerHTML = getBrowserHTML('video_filebrowser_altsource2','video_altsource2','media','media'); + get('audio_altsource1_filebrowser').innerHTML = getBrowserHTML('audio_filebrowser_altsource1','audio_altsource1','media','media'); + get('audio_altsource2_filebrowser').innerHTML = getBrowserHTML('audio_filebrowser_altsource2','audio_altsource2','media','media'); + get('video_poster_filebrowser').innerHTML = getBrowserHTML('filebrowser_poster','video_poster','image','media'); + + html = self.getMediaListHTML('medialist', 'src', 'media', 'media'); + if (html == "") + get("linklistrow").style.display = 'none'; + else + get("linklistcontainer").innerHTML = html; + + if (isVisible('filebrowser')) + get('src').style.width = '230px'; + + if (isVisible('video_filebrowser_altsource1')) + get('video_altsource1').style.width = '220px'; + + if (isVisible('video_filebrowser_altsource2')) + get('video_altsource2').style.width = '220px'; + + if (isVisible('audio_filebrowser_altsource1')) + get('audio_altsource1').style.width = '220px'; + + if (isVisible('audio_filebrowser_altsource2')) + get('audio_altsource2').style.width = '220px'; + + if (isVisible('filebrowser_poster')) + get('video_poster').style.width = '220px'; + + editor.dom.setOuterHTML(get('media_type'), self.getMediaTypeHTML(editor)); + + self.setDefaultDialogSettings(editor); + self.data = clone(tinyMCEPopup.getWindowArg('data')); + self.dataToForm(); + self.preview(); + + updateColor('bgcolor_pick', 'bgcolor'); + }, + + insert : function() { + var editor = tinyMCEPopup.editor; + + this.formToData(); + editor.execCommand('mceRepaint'); + tinyMCEPopup.restoreSelection(); + editor.selection.setNode(editor.plugins.media.dataToImg(this.data)); + tinyMCEPopup.close(); + }, + + preview : function() { + get('prev').innerHTML = this.editor.plugins.media.dataToHtml(this.data, true); + }, + + moveStates : function(to_form, field) { + var data = this.data, editor = this.editor, + mediaPlugin = editor.plugins.media, ext, src, typeInfo, defaultStates, src; + + defaultStates = { + // QuickTime + quicktime_autoplay : true, + quicktime_controller : true, + + // Flash + flash_play : true, + flash_loop : true, + flash_menu : true, + + // WindowsMedia + windowsmedia_autostart : true, + windowsmedia_enablecontextmenu : true, + windowsmedia_invokeurls : true, + + // RealMedia + realmedia_autogotourl : true, + realmedia_imagestatus : true + }; + + function parseQueryParams(str) { + var out = {}; + + if (str) { + tinymce.each(str.split('&'), function(item) { + var parts = item.split('='); + + out[unescape(parts[0])] = unescape(parts[1]); + }); + } + + return out; + }; + + function setOptions(type, names) { + var i, name, formItemName, value, list; + + if (type == data.type || type == 'global') { + names = tinymce.explode(names); + for (i = 0; i < names.length; i++) { + name = names[i]; + formItemName = type == 'global' ? name : type + '_' + name; + + if (type == 'global') + list = data; + else if (type == 'video' || type == 'audio') { + list = data.video.attrs; + + if (!list && !to_form) + data.video.attrs = list = {}; + } else + list = data.params; + + if (list) { + if (to_form) { + setVal(formItemName, list[name], type == 'video' || type == 'audio' ? name : ''); + } else { + delete list[name]; + + value = getVal(formItemName); + if ((type == 'video' || type == 'audio') && value === true) + value = name; + + if (defaultStates[formItemName]) { + if (value !== defaultStates[formItemName]) { + value = "" + value; + list[name] = value; + } + } else if (value) { + value = "" + value; + list[name] = value; + } + } + } + } + } + } + + if (!to_form) { + data.type = get('media_type').options[get('media_type').selectedIndex].value; + data.width = getVal('width'); + data.height = getVal('height'); + + // Switch type based on extension + src = getVal('src'); + if (field == 'src') { + ext = src.replace(/^.*\.([^.]+)$/, '$1'); + if (typeInfo = mediaPlugin.getType(ext)) + data.type = typeInfo.name.toLowerCase(); + + setVal('media_type', data.type); + } + + if (data.type == "video" || data.type == "audio") { + if (!data.video.sources) + data.video.sources = []; + + data.video.sources[0] = {src: getVal('src')}; + } + } + + // Hide all fieldsets and show the one active + get('video_options').style.display = 'none'; + get('audio_options').style.display = 'none'; + get('flash_options').style.display = 'none'; + get('quicktime_options').style.display = 'none'; + get('shockwave_options').style.display = 'none'; + get('windowsmedia_options').style.display = 'none'; + get('realmedia_options').style.display = 'none'; + get('embeddedaudio_options').style.display = 'none'; + + if (get(data.type + '_options')) + get(data.type + '_options').style.display = 'block'; + + setVal('media_type', data.type); + + setOptions('flash', 'play,loop,menu,swliveconnect,quality,scale,salign,wmode,base,flashvars'); + setOptions('quicktime', 'loop,autoplay,cache,controller,correction,enablejavascript,kioskmode,autohref,playeveryframe,targetcache,scale,starttime,endtime,target,qtsrcchokespeed,volume,qtsrc'); + setOptions('shockwave', 'sound,progress,autostart,swliveconnect,swvolume,swstretchstyle,swstretchhalign,swstretchvalign'); + setOptions('windowsmedia', 'autostart,enabled,enablecontextmenu,fullscreen,invokeurls,mute,stretchtofit,windowlessvideo,balance,baseurl,captioningid,currentmarker,currentposition,defaultframe,playcount,rate,uimode,volume'); + setOptions('realmedia', 'autostart,loop,autogotourl,center,imagestatus,maintainaspect,nojava,prefetch,shuffle,console,controls,numloop,scriptcallbacks'); + setOptions('video', 'poster,autoplay,loop,muted,preload,controls'); + setOptions('audio', 'autoplay,loop,preload,controls'); + setOptions('embeddedaudio', 'autoplay,loop,controls'); + setOptions('global', 'id,name,vspace,hspace,bgcolor,align,width,height'); + + if (to_form) { + if (data.type == 'video') { + if (data.video.sources[0]) + setVal('src', data.video.sources[0].src); + + src = data.video.sources[1]; + if (src) + setVal('video_altsource1', src.src); + + src = data.video.sources[2]; + if (src) + setVal('video_altsource2', src.src); + } else if (data.type == 'audio') { + if (data.video.sources[0]) + setVal('src', data.video.sources[0].src); + + src = data.video.sources[1]; + if (src) + setVal('audio_altsource1', src.src); + + src = data.video.sources[2]; + if (src) + setVal('audio_altsource2', src.src); + } else { + // Check flash vars + if (data.type == 'flash') { + tinymce.each(editor.getParam('flash_video_player_flashvars', {url : '$url', poster : '$poster'}), function(value, name) { + if (value == '$url') + data.params.src = parseQueryParams(data.params.flashvars)[name] || data.params.src || ''; + }); + } + + setVal('src', data.params.src); + } + } else { + src = getVal("src"); + + // YouTube Embed + if (src.match(/youtube\.com\/embed\/\w+/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + setVal('src', src); + setVal('media_type', data.type); + } else { + // YouTube *NEW* + if (src.match(/youtu\.be\/[a-z1-9.-_]+/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://www.youtube.com/embed/' + src.match(/youtu.be\/([a-z1-9.-_]+)/)[1]; + setVal('src', src); + setVal('media_type', data.type); + } + + // YouTube + if (src.match(/youtube\.com(.+)v=([^&]+)/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://www.youtube.com/embed/' + src.match(/v=([^&]+)/)[1]; + setVal('src', src); + setVal('media_type', data.type); + } + } + + // Google video + if (src.match(/video\.google\.com(.+)docid=([^&]+)/)) { + data.width = 425; + data.height = 326; + data.type = 'flash'; + src = 'http://video.google.com/googleplayer.swf?docId=' + src.match(/docid=([^&]+)/)[1] + '&hl=en'; + setVal('src', src); + setVal('media_type', data.type); + } + + // Vimeo + if (src.match(/vimeo\.com\/([0-9]+)/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://player.vimeo.com/video/' + src.match(/vimeo.com\/([0-9]+)/)[1]; + setVal('src', src); + setVal('media_type', data.type); + } + + // stream.cz + if (src.match(/stream\.cz\/((?!object).)*\/([0-9]+)/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://www.stream.cz/object/' + src.match(/stream.cz\/[^/]+\/([0-9]+)/)[1]; + setVal('src', src); + setVal('media_type', data.type); + } + + // Google maps + if (src.match(/maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://maps.google.com/maps/ms?msid=' + src.match(/msid=(.+)/)[1] + "&output=embed"; + setVal('src', src); + setVal('media_type', data.type); + } + + if (data.type == 'video') { + if (!data.video.sources) + data.video.sources = []; + + data.video.sources[0] = {src : src}; + + src = getVal("video_altsource1"); + if (src) + data.video.sources[1] = {src : src}; + + src = getVal("video_altsource2"); + if (src) + data.video.sources[2] = {src : src}; + } else if (data.type == 'audio') { + if (!data.video.sources) + data.video.sources = []; + + data.video.sources[0] = {src : src}; + + src = getVal("audio_altsource1"); + if (src) + data.video.sources[1] = {src : src}; + + src = getVal("audio_altsource2"); + if (src) + data.video.sources[2] = {src : src}; + } else + data.params.src = src; + + // Set default size + setVal('width', data.width || (data.type == 'audio' ? 300 : 320)); + setVal('height', data.height || (data.type == 'audio' ? 32 : 240)); + } + }, + + dataToForm : function() { + this.moveStates(true); + }, + + formToData : function(field) { + if (field == "width" || field == "height") + this.changeSize(field); + + if (field == 'source') { + this.moveStates(false, field); + setVal('source', this.editor.plugins.media.dataToHtml(this.data)); + this.panel = 'source'; + } else { + if (this.panel == 'source') { + this.data = clone(this.editor.plugins.media.htmlToData(getVal('source'))); + this.dataToForm(); + this.panel = ''; + } + + this.moveStates(false, field); + this.preview(); + } + }, + + beforeResize : function() { + this.width = parseInt(getVal('width') || (this.data.type == 'audio' ? "300" : "320"), 10); + this.height = parseInt(getVal('height') || (this.data.type == 'audio' ? "32" : "240"), 10); + }, + + changeSize : function(type) { + var width, height, scale, size; + + if (get('constrain').checked) { + width = parseInt(getVal('width') || (this.data.type == 'audio' ? "300" : "320"), 10); + height = parseInt(getVal('height') || (this.data.type == 'audio' ? "32" : "240"), 10); + + if (type == 'width') { + this.height = Math.round((width / this.width) * height); + setVal('height', this.height); + } else { + this.width = Math.round((height / this.height) * width); + setVal('width', this.width); + } + } + }, + + getMediaListHTML : function() { + if (typeof(tinyMCEMediaList) != "undefined" && tinyMCEMediaList.length > 0) { + var html = ""; + + html += ''; + + return html; + } + + return ""; + }, + + getMediaTypeHTML : function(editor) { + function option(media_type, element) { + if (!editor.schema.getElementRule(element || media_type)) { + return ''; + } + + return '' + } + + var html = ""; + + html += ''; + return html; + }, + + setDefaultDialogSettings : function(editor) { + var defaultDialogSettings = editor.getParam("media_dialog_defaults", {}); + tinymce.each(defaultDialogSettings, function(v, k) { + setVal(k, v); + }); + } + }; + + tinyMCEPopup.requireLangPack(); + tinyMCEPopup.onInit.add(function() { + Media.init(); + }); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/noneditable/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/noneditable/editor_plugin_src.js new file mode 100644 index 0000000..35c0cea --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/noneditable/editor_plugin_src.js @@ -0,0 +1,537 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var TreeWalker = tinymce.dom.TreeWalker; + var externalName = 'contenteditable', internalName = 'data-mce-' + externalName; + var VK = tinymce.VK; + + function handleContentEditableSelection(ed) { + var dom = ed.dom, selection = ed.selection, invisibleChar, caretContainerId = 'mce_noneditablecaret', invisibleChar = '\uFEFF'; + + // Returns the content editable state of a node "true/false" or null + function getContentEditable(node) { + var contentEditable; + + // Ignore non elements + if (node.nodeType === 1) { + // Check for fake content editable + contentEditable = node.getAttribute(internalName); + if (contentEditable && contentEditable !== "inherit") { + return contentEditable; + } + + // Check for real content editable + contentEditable = node.contentEditable; + if (contentEditable !== "inherit") { + return contentEditable; + } + } + + return null; + }; + + // Returns the noneditable parent or null if there is a editable before it or if it wasn't found + function getNonEditableParent(node) { + var state; + + while (node) { + state = getContentEditable(node); + if (state) { + return state === "false" ? node : null; + } + + node = node.parentNode; + } + }; + + // Get caret container parent for the specified node + function getParentCaretContainer(node) { + while (node) { + if (node.id === caretContainerId) { + return node; + } + + node = node.parentNode; + } + }; + + // Finds the first text node in the specified node + function findFirstTextNode(node) { + var walker; + + if (node) { + walker = new TreeWalker(node, node); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType === 3) { + return node; + } + } + } + }; + + // Insert caret container before/after target or expand selection to include block + function insertCaretContainerOrExpandToBlock(target, before) { + var caretContainer, rng; + + // Select block + if (getContentEditable(target) === "false") { + if (dom.isBlock(target)) { + selection.select(target); + return; + } + } + + rng = dom.createRng(); + + if (getContentEditable(target) === "true") { + if (!target.firstChild) { + target.appendChild(ed.getDoc().createTextNode('\u00a0')); + } + + target = target.firstChild; + before = true; + } + + //caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style:'border: 1px solid red'}, invisibleChar); + caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true}, invisibleChar); + + if (before) { + target.parentNode.insertBefore(caretContainer, target); + } else { + dom.insertAfter(caretContainer, target); + } + + rng.setStart(caretContainer.firstChild, 1); + rng.collapse(true); + selection.setRng(rng); + + return caretContainer; + }; + + // Removes any caret container except the one we might be in + function removeCaretContainer(caretContainer) { + var child, currentCaretContainer, lastContainer; + + if (caretContainer) { + rng = selection.getRng(true); + rng.setStartBefore(caretContainer); + rng.setEndBefore(caretContainer); + + child = findFirstTextNode(caretContainer); + if (child && child.nodeValue.charAt(0) == invisibleChar) { + child = child.deleteData(0, 1); + } + + dom.remove(caretContainer, true); + + selection.setRng(rng); + } else { + currentCaretContainer = getParentCaretContainer(selection.getStart()); + while ((caretContainer = dom.get(caretContainerId)) && caretContainer !== lastContainer) { + if (currentCaretContainer !== caretContainer) { + child = findFirstTextNode(caretContainer); + if (child && child.nodeValue.charAt(0) == invisibleChar) { + child = child.deleteData(0, 1); + } + + dom.remove(caretContainer, true); + } + + lastContainer = caretContainer; + } + } + }; + + // Modifies the selection to include contentEditable false elements or insert caret containers + function moveSelection() { + var nonEditableStart, nonEditableEnd, isCollapsed, rng, element; + + // Checks if there is any contents to the left/right side of caret returns the noneditable element or any editable element if it finds one inside + function hasSideContent(element, left) { + var container, offset, walker, node, len; + + container = rng.startContainer; + offset = rng.startOffset; + + // If endpoint is in middle of text node then expand to beginning/end of element + if (container.nodeType == 3) { + len = container.nodeValue.length; + if ((offset > 0 && offset < len) || (left ? offset == len : offset == 0)) { + return; + } + } else { + // Can we resolve the node by index + if (offset < container.childNodes.length) { + // Browser represents caret position as the offset at the start of an element. When moving right + // this is the element we are moving into so we consider our container to be child node at offset-1 + var pos = !left && offset > 0 ? offset-1 : offset; + container = container.childNodes[pos]; + if (container.hasChildNodes()) { + container = container.firstChild; + } + } else { + // If not then the caret is at the last position in it's container and the caret container should be inserted after the noneditable element + return !left ? element : null; + } + } + + // Walk left/right to look for contents + walker = new TreeWalker(container, element); + while (node = walker[left ? 'prev' : 'next']()) { + if (node.nodeType === 3 && node.nodeValue.length > 0) { + return; + } else if (getContentEditable(node) === "true") { + // Found contentEditable=true element return this one to we can move the caret inside it + return node; + } + } + + return element; + }; + + // Remove any existing caret containers + removeCaretContainer(); + + // Get noneditable start/end elements + isCollapsed = selection.isCollapsed(); + nonEditableStart = getNonEditableParent(selection.getStart()); + nonEditableEnd = getNonEditableParent(selection.getEnd()); + + // Is any fo the range endpoints noneditable + if (nonEditableStart || nonEditableEnd) { + rng = selection.getRng(true); + + // If it's a caret selection then look left/right to see if we need to move the caret out side or expand + if (isCollapsed) { + nonEditableStart = nonEditableStart || nonEditableEnd; + var start = selection.getStart(); + if (element = hasSideContent(nonEditableStart, true)) { + // We have no contents to the left of the caret then insert a caret container before the noneditable element + insertCaretContainerOrExpandToBlock(element, true); + } else if (element = hasSideContent(nonEditableStart, false)) { + // We have no contents to the right of the caret then insert a caret container after the noneditable element + insertCaretContainerOrExpandToBlock(element, false); + } else { + // We are in the middle of a noneditable so expand to select it + selection.select(nonEditableStart); + } + } else { + rng = selection.getRng(true); + + // Expand selection to include start non editable element + if (nonEditableStart) { + rng.setStartBefore(nonEditableStart); + } + + // Expand selection to include end non editable element + if (nonEditableEnd) { + rng.setEndAfter(nonEditableEnd); + } + + selection.setRng(rng); + } + } + }; + + function handleKey(ed, e) { + var keyCode = e.keyCode, nonEditableParent, caretContainer, startElement, endElement; + + function getNonEmptyTextNodeSibling(node, prev) { + while (node = node[prev ? 'previousSibling' : 'nextSibling']) { + if (node.nodeType !== 3 || node.nodeValue.length > 0) { + return node; + } + } + }; + + function positionCaretOnElement(element, start) { + selection.select(element); + selection.collapse(start); + } + + function canDelete(backspace) { + var rng, container, offset, nonEditableParent; + + function removeNodeIfNotParent(node) { + var parent = container; + + while (parent) { + if (parent === node) { + return; + } + + parent = parent.parentNode; + } + + dom.remove(node); + moveSelection(); + } + + function isNextPrevTreeNodeNonEditable() { + var node, walker, nonEmptyElements = ed.schema.getNonEmptyElements(); + + walker = new tinymce.dom.TreeWalker(container, ed.getBody()); + while (node = (backspace ? walker.prev() : walker.next())) { + // Found IMG/INPUT etc + if (nonEmptyElements[node.nodeName.toLowerCase()]) { + break; + } + + // Found text node with contents + if (node.nodeType === 3 && tinymce.trim(node.nodeValue).length > 0) { + break; + } + + // Found non editable node + if (getContentEditable(node) === "false") { + removeNodeIfNotParent(node); + return true; + } + } + + // Check if the content node is within a non editable parent + if (getNonEditableParent(node)) { + return true; + } + + return false; + } + + if (selection.isCollapsed()) { + rng = selection.getRng(true); + container = rng.startContainer; + offset = rng.startOffset; + container = getParentCaretContainer(container) || container; + + // Is in noneditable parent + if (nonEditableParent = getNonEditableParent(container)) { + removeNodeIfNotParent(nonEditableParent); + return false; + } + + // Check if the caret is in the middle of a text node + if (container.nodeType == 3 && (backspace ? offset > 0 : offset < container.nodeValue.length)) { + return true; + } + + // Resolve container index + if (container.nodeType == 1) { + container = container.childNodes[offset] || container; + } + + // Check if previous or next tree node is non editable then block the event + if (isNextPrevTreeNodeNonEditable()) { + return false; + } + } + + return true; + } + + startElement = selection.getStart() + endElement = selection.getEnd(); + + // Disable all key presses in contentEditable=false except delete or backspace + nonEditableParent = getNonEditableParent(startElement) || getNonEditableParent(endElement); + if (nonEditableParent && (keyCode < 112 || keyCode > 124) && keyCode != VK.DELETE && keyCode != VK.BACKSPACE) { + // Is Ctrl+c, Ctrl+v or Ctrl+x then use default browser behavior + if ((tinymce.isMac ? e.metaKey : e.ctrlKey) && (keyCode == 67 || keyCode == 88 || keyCode == 86)) { + return; + } + + e.preventDefault(); + + // Arrow left/right select the element and collapse left/right + if (keyCode == VK.LEFT || keyCode == VK.RIGHT) { + var left = keyCode == VK.LEFT; + // If a block element find previous or next element to position the caret + if (ed.dom.isBlock(nonEditableParent)) { + var targetElement = left ? nonEditableParent.previousSibling : nonEditableParent.nextSibling; + var walker = new TreeWalker(targetElement, targetElement); + var caretElement = left ? walker.prev() : walker.next(); + positionCaretOnElement(caretElement, !left); + } else { + positionCaretOnElement(nonEditableParent, left); + } + } + } else { + // Is arrow left/right, backspace or delete + if (keyCode == VK.LEFT || keyCode == VK.RIGHT || keyCode == VK.BACKSPACE || keyCode == VK.DELETE) { + caretContainer = getParentCaretContainer(startElement); + if (caretContainer) { + // Arrow left or backspace + if (keyCode == VK.LEFT || keyCode == VK.BACKSPACE) { + nonEditableParent = getNonEmptyTextNodeSibling(caretContainer, true); + + if (nonEditableParent && getContentEditable(nonEditableParent) === "false") { + e.preventDefault(); + + if (keyCode == VK.LEFT) { + positionCaretOnElement(nonEditableParent, true); + } else { + dom.remove(nonEditableParent); + return; + } + } else { + removeCaretContainer(caretContainer); + } + } + + // Arrow right or delete + if (keyCode == VK.RIGHT || keyCode == VK.DELETE) { + nonEditableParent = getNonEmptyTextNodeSibling(caretContainer); + + if (nonEditableParent && getContentEditable(nonEditableParent) === "false") { + e.preventDefault(); + + if (keyCode == VK.RIGHT) { + positionCaretOnElement(nonEditableParent, false); + } else { + dom.remove(nonEditableParent); + return; + } + } else { + removeCaretContainer(caretContainer); + } + } + } + + if ((keyCode == VK.BACKSPACE || keyCode == VK.DELETE) && !canDelete(keyCode == VK.BACKSPACE)) { + e.preventDefault(); + return false; + } + } + } + }; + + ed.onMouseDown.addToTop(function(ed, e) { + var node = ed.selection.getNode(); + + if (getContentEditable(node) === "false" && node == e.target) { + // Expand selection on mouse down we can't block the default event since it's used for drag/drop + moveSelection(); + } + }); + + ed.onMouseUp.addToTop(moveSelection); + ed.onKeyDown.addToTop(handleKey); + ed.onKeyUp.addToTop(moveSelection); + }; + + tinymce.create('tinymce.plugins.NonEditablePlugin', { + init : function(ed, url) { + var editClass, nonEditClass, nonEditableRegExps; + + // Converts configured regexps to noneditable span items + function convertRegExpsToNonEditable(ed, args) { + var i = nonEditableRegExps.length, content = args.content, cls = tinymce.trim(nonEditClass); + + // Don't replace the variables when raw is used for example on undo/redo + if (args.format == "raw") { + return; + } + + while (i--) { + content = content.replace(nonEditableRegExps[i], function(match) { + var args = arguments, index = args[args.length - 2]; + + // Is value inside an attribute then don't replace + if (index > 0 && content.charAt(index - 1) == '"') { + return match; + } + + return '' + ed.dom.encode(typeof(args[1]) === "string" ? args[1] : args[0]) + ''; + }); + } + + args.content = content; + }; + + editClass = " " + tinymce.trim(ed.getParam("noneditable_editable_class", "mceEditable")) + " "; + nonEditClass = " " + tinymce.trim(ed.getParam("noneditable_noneditable_class", "mceNonEditable")) + " "; + + // Setup noneditable regexps array + nonEditableRegExps = ed.getParam("noneditable_regexp"); + if (nonEditableRegExps && !nonEditableRegExps.length) { + nonEditableRegExps = [nonEditableRegExps]; + } + + ed.onPreInit.add(function() { + handleContentEditableSelection(ed); + + if (nonEditableRegExps) { + ed.selection.onBeforeSetContent.add(convertRegExpsToNonEditable); + ed.onBeforeSetContent.add(convertRegExpsToNonEditable); + } + + // Apply contentEditable true/false on elements with the noneditable/editable classes + ed.parser.addAttributeFilter('class', function(nodes) { + var i = nodes.length, className, node; + + while (i--) { + node = nodes[i]; + className = " " + node.attr("class") + " "; + + if (className.indexOf(editClass) !== -1) { + node.attr(internalName, "true"); + } else if (className.indexOf(nonEditClass) !== -1) { + node.attr(internalName, "false"); + } + } + }); + + // Remove internal name + ed.serializer.addAttributeFilter(internalName, function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (nonEditableRegExps && node.attr('data-mce-content')) { + node.name = "#text"; + node.type = 3; + node.raw = true; + node.value = node.attr('data-mce-content'); + } else { + node.attr(externalName, null); + node.attr(internalName, null); + } + } + }); + + // Convert external name into internal name + ed.parser.addAttributeFilter(externalName, function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.attr(internalName, node.attr(externalName)); + node.attr(externalName, null); + } + }); + }); + }, + + getInfo : function() { + return { + longname : 'Non editable elements', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('noneditable', tinymce.plugins.NonEditablePlugin); +})(); \ No newline at end of file diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/paste/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/paste/editor_plugin_src.js new file mode 100644 index 0000000..23ac039 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/paste/editor_plugin_src.js @@ -0,0 +1,887 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each, + defs = { + paste_auto_cleanup_on_paste : true, + paste_enable_default_filters : true, + paste_block_drop : false, + paste_retain_style_properties : "none", + paste_strip_class_attributes : "mso", + paste_remove_spans : false, + paste_remove_styles : false, + paste_remove_styles_if_webkit : true, + paste_convert_middot_lists : true, + paste_convert_headers_to_strong : false, + paste_dialog_width : "450", + paste_dialog_height : "400", + paste_max_consecutive_linebreaks: 2, + paste_text_use_dialog : false, + paste_text_sticky : false, + paste_text_sticky_default : false, + paste_text_notifyalways : false, + paste_text_linebreaktype : "combined", + paste_text_replacements : [ + [/\u2026/g, "..."], + [/[\x93\x94\u201c\u201d]/g, '"'], + [/[\x60\x91\x92\u2018\u2019]/g, "'"] + ] + }; + + function getParam(ed, name) { + return ed.getParam(name, defs[name]); + } + + tinymce.create('tinymce.plugins.PastePlugin', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + t.url = url; + + // Setup plugin events + t.onPreProcess = new tinymce.util.Dispatcher(t); + t.onPostProcess = new tinymce.util.Dispatcher(t); + + // Register default handlers + t.onPreProcess.add(t._preProcess); + t.onPostProcess.add(t._postProcess); + + // Register optional preprocess handler + t.onPreProcess.add(function(pl, o) { + ed.execCallback('paste_preprocess', pl, o); + }); + + // Register optional postprocess + t.onPostProcess.add(function(pl, o) { + ed.execCallback('paste_postprocess', pl, o); + }); + + ed.onKeyDown.addToTop(function(ed, e) { + // Block ctrl+v from adding an undo level since the default logic in tinymce.Editor will add that + if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) + return false; // Stop other listeners + }); + + // Initialize plain text flag + ed.pasteAsPlainText = getParam(ed, 'paste_text_sticky_default'); + + // This function executes the process handlers and inserts the contents + // force_rich overrides plain text mode set by user, important for pasting with execCommand + function process(o, force_rich) { + var dom = ed.dom, rng; + + // Execute pre process handlers + t.onPreProcess.dispatch(t, o); + + // Create DOM structure + o.node = dom.create('div', 0, o.content); + + // If pasting inside the same element and the contents is only one block + // remove the block and keep the text since Firefox will copy parts of pre and h1-h6 as a pre element + if (tinymce.isGecko) { + rng = ed.selection.getRng(true); + if (rng.startContainer == rng.endContainer && rng.startContainer.nodeType == 3) { + // Is only one block node and it doesn't contain word stuff + if (o.node.childNodes.length === 1 && /^(p|h[1-6]|pre)$/i.test(o.node.firstChild.nodeName) && o.content.indexOf('__MCE_ITEM__') === -1) + dom.remove(o.node.firstChild, true); + } + } + + // Execute post process handlers + t.onPostProcess.dispatch(t, o); + + // Serialize content + o.content = ed.serializer.serialize(o.node, {getInner : 1, forced_root_block : ''}); + + // Plain text option active? + if ((!force_rich) && (ed.pasteAsPlainText)) { + t._insertPlainText(o.content); + + if (!getParam(ed, "paste_text_sticky")) { + ed.pasteAsPlainText = false; + ed.controlManager.setActive("pastetext", false); + } + } else { + t._insert(o.content); + } + } + + // Add command for external usage + ed.addCommand('mceInsertClipboardContent', function(u, o) { + process(o, true); + }); + + if (!getParam(ed, "paste_text_use_dialog")) { + ed.addCommand('mcePasteText', function(u, v) { + var cookie = tinymce.util.Cookie; + + ed.pasteAsPlainText = !ed.pasteAsPlainText; + ed.controlManager.setActive('pastetext', ed.pasteAsPlainText); + + if ((ed.pasteAsPlainText) && (!cookie.get("tinymcePasteText"))) { + if (getParam(ed, "paste_text_sticky")) { + ed.windowManager.alert(ed.translate('paste.plaintext_mode_sticky')); + } else { + ed.windowManager.alert(ed.translate('paste.plaintext_mode')); + } + + if (!getParam(ed, "paste_text_notifyalways")) { + cookie.set("tinymcePasteText", "1", new Date(new Date().getFullYear() + 1, 12, 31)) + } + } + }); + } + + ed.addButton('pastetext', {title: 'paste.paste_text_desc', cmd: 'mcePasteText'}); + ed.addButton('selectall', {title: 'paste.selectall_desc', cmd: 'selectall'}); + + // This function grabs the contents from the clipboard by adding a + // hidden div and placing the caret inside it and after the browser paste + // is done it grabs that contents and processes that + function grabContent(e) { + var n, or, rng, oldRng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY, textContent; + + // Check if browser supports direct plaintext access + if (e.clipboardData || dom.doc.dataTransfer) { + textContent = (e.clipboardData || dom.doc.dataTransfer).getData('Text'); + + if (ed.pasteAsPlainText) { + e.preventDefault(); + process({content : dom.encode(textContent).replace(/\r?\n/g, '
')}); + return; + } + } + + if (dom.get('_mcePaste')) + return; + + // Create container to paste into + n = dom.add(body, 'div', {id : '_mcePaste', 'class' : 'mcePaste', 'data-mce-bogus' : '1'}, '\uFEFF\uFEFF'); + + // If contentEditable mode we need to find out the position of the closest element + if (body != ed.getDoc().body) + posY = dom.getPos(ed.selection.getStart(), body).y; + else + posY = body.scrollTop + dom.getViewPort(ed.getWin()).y; + + // Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles + // If also needs to be in view on IE or the paste would fail + dom.setStyles(n, { + position : 'absolute', + left : tinymce.isGecko ? -40 : 0, // Need to move it out of site on Gecko since it will othewise display a ghost resize rect for the div + top : posY - 25, + width : 1, + height : 1, + overflow : 'hidden' + }); + + if (tinymce.isIE) { + // Store away the old range + oldRng = sel.getRng(); + + // Select the container + rng = dom.doc.body.createTextRange(); + rng.moveToElementText(n); + rng.execCommand('Paste'); + + // Remove container + dom.remove(n); + + // Check if the contents was changed, if it wasn't then clipboard extraction failed probably due + // to IE security settings so we pass the junk though better than nothing right + if (n.innerHTML === '\uFEFF\uFEFF') { + ed.execCommand('mcePasteWord'); + e.preventDefault(); + return; + } + + // Restore the old range and clear the contents before pasting + sel.setRng(oldRng); + sel.setContent(''); + + // For some odd reason we need to detach the the mceInsertContent call from the paste event + // It's like IE has a reference to the parent element that you paste in and the selection gets messed up + // when it tries to restore the selection + setTimeout(function() { + // Process contents + process({content : n.innerHTML}); + }, 0); + + // Block the real paste event + return tinymce.dom.Event.cancel(e); + } else { + function block(e) { + e.preventDefault(); + }; + + // Block mousedown and click to prevent selection change + dom.bind(ed.getDoc(), 'mousedown', block); + dom.bind(ed.getDoc(), 'keydown', block); + + or = ed.selection.getRng(); + + // Move select contents inside DIV + n = n.firstChild; + rng = ed.getDoc().createRange(); + rng.setStart(n, 0); + rng.setEnd(n, 2); + sel.setRng(rng); + + // Wait a while and grab the pasted contents + window.setTimeout(function() { + var h = '', nl; + + // Paste divs duplicated in paste divs seems to happen when you paste plain text so lets first look for that broken behavior in WebKit + if (!dom.select('div.mcePaste > div.mcePaste').length) { + nl = dom.select('div.mcePaste'); + + // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string + each(nl, function(n) { + var child = n.firstChild; + + // WebKit inserts a DIV container with lots of odd styles + if (child && child.nodeName == 'DIV' && child.style.marginTop && child.style.backgroundColor) { + dom.remove(child, 1); + } + + // Remove apply style spans + each(dom.select('span.Apple-style-span', n), function(n) { + dom.remove(n, 1); + }); + + // Remove bogus br elements + each(dom.select('br[data-mce-bogus]', n), function(n) { + dom.remove(n); + }); + + // WebKit will make a copy of the DIV for each line of plain text pasted and insert them into the DIV + if (n.parentNode.className != 'mcePaste') + h += n.innerHTML; + }); + } else { + // Found WebKit weirdness so force the content into paragraphs this seems to happen when you paste plain text from Nodepad etc + // So this logic will replace double enter with paragraphs and single enter with br so it kind of looks the same + h = '

' + dom.encode(textContent).replace(/\r?\n\r?\n/g, '

').replace(/\r?\n/g, '
') + '

'; + } + + // Remove the nodes + each(dom.select('div.mcePaste'), function(n) { + dom.remove(n); + }); + + // Restore the old selection + if (or) + sel.setRng(or); + + process({content : h}); + + // Unblock events ones we got the contents + dom.unbind(ed.getDoc(), 'mousedown', block); + dom.unbind(ed.getDoc(), 'keydown', block); + }, 0); + } + } + + // Check if we should use the new auto process method + if (getParam(ed, "paste_auto_cleanup_on_paste")) { + // Is it's Opera or older FF use key handler + if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) { + ed.onKeyDown.addToTop(function(ed, e) { + if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) + grabContent(e); + }); + } else { + // Grab contents on paste event on Gecko and WebKit + ed.onPaste.addToTop(function(ed, e) { + return grabContent(e); + }); + } + } + + ed.onInit.add(function() { + ed.controlManager.setActive("pastetext", ed.pasteAsPlainText); + + // Block all drag/drop events + if (getParam(ed, "paste_block_drop")) { + ed.dom.bind(ed.getBody(), ['dragend', 'dragover', 'draggesture', 'dragdrop', 'drop', 'drag'], function(e) { + e.preventDefault(); + e.stopPropagation(); + + return false; + }); + } + }); + + // Add legacy support + t._legacySupport(); + }, + + getInfo : function() { + return { + longname : 'Paste text/word', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + _preProcess : function(pl, o) { + var ed = this.editor, + h = o.content, + grep = tinymce.grep, + explode = tinymce.explode, + trim = tinymce.trim, + len, stripClass; + + //console.log('Before preprocess:' + o.content); + + function process(items) { + each(items, function(v) { + // Remove or replace + if (v.constructor == RegExp) + h = h.replace(v, ''); + else + h = h.replace(v[0], v[1]); + }); + } + + if (ed.settings.paste_enable_default_filters == false) { + return; + } + + // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser + if (tinymce.isIE && document.documentMode >= 9 && /<(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)/.test(o.content)) { + // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser + process([[/(?:
 [\s\r\n]+|
)*(<\/?(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)[^>]*>)(?:
 [\s\r\n]+|
)*/g, '$1']]); + + // IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break + process([ + [/

/g, '

'], // Replace multiple BR elements with uppercase BR to keep them intact + [/
/g, ' '], // Replace single br elements with space since they are word wrap BR:s + [/

/g, '
'] // Replace back the double brs but into a single BR + ]); + } + + // Detect Word content and process it more aggressive + if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) || o.wordContent) { + o.wordContent = true; // Mark the pasted contents as word specific content + //console.log('Word contents detected.'); + + // Process away some basic content + process([ + /^\s*( )+/gi, //   entities at the start of contents + /( |]*>)+\s*$/gi //   entities at the end of contents + ]); + + if (getParam(ed, "paste_convert_headers_to_strong")) { + h = h.replace(/

]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "

$1

"); + } + + if (getParam(ed, "paste_convert_middot_lists")) { + process([ + [//gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker + [/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'], // Convert mso-list and symbol spans to item markers + [/(]+(?:MsoListParagraph)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol paragraphs to item markers (FF) + ]); + } + + process([ + // Word comments like conditional comments etc + //gi, + + // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content, MS Office namespaced tags, and a few other tags + /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, + + // Convert into for line-though + [/<(\/?)s>/gi, "<$1strike>"], + + // Replace nsbp entites to char since it's easier to handle + [/ /gi, "\u00a0"] + ]); + + // Remove bad attributes, with or without quotes, ensuring that attribute text is really inside a tag. + // If JavaScript had a RegExp look-behind, we could have integrated this with the last process() array and got rid of the loop. But alas, it does not, so we cannot. + do { + len = h.length; + // Don't remove the type attribute for lists so that non-default list types display correctly. + h = h.replace(/(]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); + h = h.replace(/(<(ol|ul)[^>]*\s)(?:id|name|language|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); + } while (len != h.length); + + // Remove all spans if no styles is to be retained + if (getParam(ed, "paste_retain_style_properties").replace(/^none$/i, "").length == 0) { + h = h.replace(/<\/?span[^>]*>/gi, ""); + } else { + // We're keeping styles, so at least clean them up. + // CSS Reference: http://msdn.microsoft.com/en-us/library/aa155477.aspx + + process([ + // Convert ___ to string of alternating breaking/non-breaking spaces of same length + [/([\s\u00a0]*)<\/span>/gi, + function(str, spaces) { + return (spaces.length > 0)? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ""; + } + ], + + // Examine all styles: delete junk, transform some, and keep the rest + [/(<[a-z][^>]*)\sstyle="([^"]*)"/gi, + function(str, tag, style) { + var n = [], + i = 0, + s = explode(trim(style).replace(/"/gi, "'"), ";"); + + // Examine each style definition within the tag's style attribute + each(s, function(v) { + var name, value, + parts = explode(v, ":"); + + function ensureUnits(v) { + return v + ((v !== "0") && (/\d$/.test(v)))? "px" : ""; + } + + if (parts.length == 2) { + name = parts[0].toLowerCase(); + value = parts[1].toLowerCase(); + + // Translate certain MS Office styles into their CSS equivalents + switch (name) { + case "mso-padding-alt": + case "mso-padding-top-alt": + case "mso-padding-right-alt": + case "mso-padding-bottom-alt": + case "mso-padding-left-alt": + case "mso-margin-alt": + case "mso-margin-top-alt": + case "mso-margin-right-alt": + case "mso-margin-bottom-alt": + case "mso-margin-left-alt": + case "mso-table-layout-alt": + case "mso-height": + case "mso-width": + case "mso-vertical-align-alt": + n[i++] = name.replace(/^mso-|-alt$/g, "") + ":" + ensureUnits(value); + return; + + case "horiz-align": + n[i++] = "text-align:" + value; + return; + + case "vert-align": + n[i++] = "vertical-align:" + value; + return; + + case "font-color": + case "mso-foreground": + n[i++] = "color:" + value; + return; + + case "mso-background": + case "mso-highlight": + n[i++] = "background:" + value; + return; + + case "mso-default-height": + n[i++] = "min-height:" + ensureUnits(value); + return; + + case "mso-default-width": + n[i++] = "min-width:" + ensureUnits(value); + return; + + case "mso-padding-between-alt": + n[i++] = "border-collapse:separate;border-spacing:" + ensureUnits(value); + return; + + case "text-line-through": + if ((value == "single") || (value == "double")) { + n[i++] = "text-decoration:line-through"; + } + return; + + case "mso-zero-height": + if (value == "yes") { + n[i++] = "display:none"; + } + return; + } + + // Eliminate all MS Office style definitions that have no CSS equivalent by examining the first characters in the name + if (/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(name)) { + return; + } + + // If it reached this point, it must be a valid CSS style + n[i++] = name + ":" + parts[1]; // Lower-case name, but keep value case + } + }); + + // If style attribute contained any valid styles the re-write it; otherwise delete style attribute. + if (i > 0) { + return tag + ' style="' + n.join(';') + '"'; + } else { + return tag; + } + } + ] + ]); + } + } + + // Replace headers with + if (getParam(ed, "paste_convert_headers_to_strong")) { + process([ + [/]*>/gi, "

"], + [/<\/h[1-6][^>]*>/gi, "

"] + ]); + } + + process([ + // Copy paste from Java like Open Office will produce this junk on FF + [/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi, ''] + ]); + + // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso"). + // Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation. + stripClass = getParam(ed, "paste_strip_class_attributes"); + + if (stripClass !== "none") { + function removeClasses(match, g1) { + if (stripClass === "all") + return ''; + + var cls = grep(explode(g1.replace(/^(["'])(.*)\1$/, "$2"), " "), + function(v) { + return (/^(?!mso)/i.test(v)); + } + ); + + return cls.length ? ' class="' + cls.join(" ") + '"' : ''; + }; + + h = h.replace(/ class="([^"]+)"/gi, removeClasses); + h = h.replace(/ class=([\-\w]+)/gi, removeClasses); + } + + // Remove spans option + if (getParam(ed, "paste_remove_spans")) { + h = h.replace(/<\/?span[^>]*>/gi, ""); + } + + //console.log('After preprocess:' + h); + + o.content = h; + }, + + /** + * Various post process items. + */ + _postProcess : function(pl, o) { + var t = this, ed = t.editor, dom = ed.dom, styleProps; + + if (ed.settings.paste_enable_default_filters == false) { + return; + } + + if (o.wordContent) { + // Remove named anchors or TOC links + each(dom.select('a', o.node), function(a) { + if (!a.href || a.href.indexOf('#_Toc') != -1) + dom.remove(a, 1); + }); + + if (getParam(ed, "paste_convert_middot_lists")) { + t._convertLists(pl, o); + } + + // Process styles + styleProps = getParam(ed, "paste_retain_style_properties"); // retained properties + + // Process only if a string was specified and not equal to "all" or "*" + if ((tinymce.is(styleProps, "string")) && (styleProps !== "all") && (styleProps !== "*")) { + styleProps = tinymce.explode(styleProps.replace(/^none$/i, "")); + + // Retains some style properties + each(dom.select('*', o.node), function(el) { + var newStyle = {}, npc = 0, i, sp, sv; + + // Store a subset of the existing styles + if (styleProps) { + for (i = 0; i < styleProps.length; i++) { + sp = styleProps[i]; + sv = dom.getStyle(el, sp); + + if (sv) { + newStyle[sp] = sv; + npc++; + } + } + } + + // Remove all of the existing styles + dom.setAttrib(el, 'style', ''); + + if (styleProps && npc > 0) + dom.setStyles(el, newStyle); // Add back the stored subset of styles + else // Remove empty span tags that do not have class attributes + if (el.nodeName == 'SPAN' && !el.className) + dom.remove(el, true); + }); + } + } + + // Remove all style information or only specifically on WebKit to avoid the style bug on that browser + if (getParam(ed, "paste_remove_styles") || (getParam(ed, "paste_remove_styles_if_webkit") && tinymce.isWebKit)) { + each(dom.select('*[style]', o.node), function(el) { + el.removeAttribute('style'); + el.removeAttribute('data-mce-style'); + }); + } else { + if (tinymce.isWebKit) { + // We need to compress the styles on WebKit since if you paste it will become + // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles + each(dom.select('*', o.node), function(el) { + el.removeAttribute('data-mce-style'); + }); + } + } + }, + + /** + * Converts the most common bullet and number formats in Office into a real semantic UL/LI list. + */ + _convertLists : function(pl, o) { + var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin, levels = [], lastType, html; + + // Convert middot lists into real semantic lists + each(dom.select('p', o.node), function(p) { + var sib, val = '', type, html, idx, parents; + + // Get text node value at beginning of paragraph + for (sib = p.firstChild; sib && sib.nodeType == 3; sib = sib.nextSibling) + val += sib.nodeValue; + + val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0'); + + // Detect unordered lists look for bullets + if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*\u00a0*/.test(val)) + type = 'ul'; + + // Detect ordered lists 1., a. or ixv. + if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0+/.test(val)) + type = 'ol'; + + // Check if node value matches the list pattern: o   + if (type) { + margin = parseFloat(p.style.marginLeft || 0); + + if (margin > lastMargin) + levels.push(margin); + + if (!listElm || type != lastType) { + listElm = dom.create(type); + dom.insertAfter(listElm, p); + } else { + // Nested list element + if (margin > lastMargin) { + listElm = li.appendChild(dom.create(type)); + } else if (margin < lastMargin) { + // Find parent level based on margin value + idx = tinymce.inArray(levels, margin); + parents = dom.getParents(listElm.parentNode, type); + listElm = parents[parents.length - 1 - idx] || listElm; + } + } + + // Remove middot or number spans if they exists + each(dom.select('span', p), function(span) { + var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, ''); + + // Remove span with the middot or the number + if (type == 'ul' && /^__MCE_ITEM__[\u2022\u00b7\u00a7\u00d8o\u25CF]/.test(html)) + dom.remove(span); + else if (/^__MCE_ITEM__[\s\S]*\w+\.( |\u00a0)*\s*/.test(html)) + dom.remove(span); + }); + + html = p.innerHTML; + + // Remove middot/list items + if (type == 'ul') + html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*( |\u00a0)+\s*/, ''); + else + html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*[\w|'<'|'>']+\.( |\u00a0)+\s*/, '');; + + // Create li and add paragraph data into the new li + li = listElm.appendChild(dom.create('li', 0, html)); + dom.remove(p); + + lastMargin = margin; + lastType = type; + } else + listElm = lastMargin = 0; // End list element + }); + + // Remove any left over makers + html = o.node.innerHTML; + if (html.indexOf('__MCE_ITEM__') != -1) + o.node.innerHTML = html.replace(/__MCE_ITEM__/g, ''); + }, + + /** + * Inserts the specified contents at the caret position. + */ + _insert : function(h, skip_undo) { + var ed = this.editor, r = ed.selection.getRng(); + + // First delete the contents seems to work better on WebKit when the selection spans multiple list items or multiple table cells. + if (!ed.selection.isCollapsed() && r.startContainer != r.endContainer) + ed.getDoc().execCommand('Delete', false, null); + + ed.execCommand('mceInsertContent', false, h, {skip_undo : skip_undo}); + }, + + /** + * Instead of the old plain text method which tried to re-create a paste operation, the + * new approach adds a plain text mode toggle switch that changes the behavior of paste. + * This function is passed the same input that the regular paste plugin produces. + * It performs additional scrubbing and produces (and inserts) the plain text. + * This approach leverages all of the great existing functionality in the paste + * plugin, and requires minimal changes to add the new functionality. + * Speednet - June 2009 + */ + _insertPlainText : function(content) { + var ed = this.editor, + linebr = getParam(ed, "paste_text_linebreaktype"), + rl = getParam(ed, "paste_text_replacements"), + is = tinymce.is; + + function process(items) { + each(items, function(v) { + if (v.constructor == RegExp) + content = content.replace(v, ""); + else + content = content.replace(v[0], v[1]); + }); + }; + + if ((typeof(content) === "string") && (content.length > 0)) { + // If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line + if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(content)) { + process([ + /[\n\r]+/g + ]); + } else { + // Otherwise just get rid of carriage returns (only need linefeeds) + process([ + /\r+/g + ]); + } + + process([ + [/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi, "\n\n"], // Block tags get a blank line after them + [/]*>|<\/tr>/gi, "\n"], // Single linebreak for
tags and table rows + [/<\/t[dh]>\s*]*>/gi, "\t"], // Table cells get tabs betweem them + /<[a-z!\/?][^>]*>/gi, // Delete all remaining tags + [/ /gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*) + [/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"] // Cool little RegExp deletes whitespace around linebreak chars. + ]); + + var maxLinebreaks = Number(getParam(ed, "paste_max_consecutive_linebreaks")); + if (maxLinebreaks > -1) { + var maxLinebreaksRegex = new RegExp("\n{" + (maxLinebreaks + 1) + ",}", "g"); + var linebreakReplacement = ""; + + while (linebreakReplacement.length < maxLinebreaks) { + linebreakReplacement += "\n"; + } + + process([ + [maxLinebreaksRegex, linebreakReplacement] // Limit max consecutive linebreaks + ]); + } + + content = ed.dom.decode(tinymce.html.Entities.encodeRaw(content)); + + // Perform default or custom replacements + if (is(rl, "array")) { + process(rl); + } else if (is(rl, "string")) { + process(new RegExp(rl, "gi")); + } + + // Treat paragraphs as specified in the config + if (linebr == "none") { + // Convert all line breaks to space + process([ + [/\n+/g, " "] + ]); + } else if (linebr == "br") { + // Convert all line breaks to
+ process([ + [/\n/g, "
"] + ]); + } else if (linebr == "p") { + // Convert all line breaks to

...

+ process([ + [/\n+/g, "

"], + [/^(.*<\/p>)(

)$/, '

$1'] + ]); + } else { + // defaults to "combined" + // Convert single line breaks to
and double line breaks to

...

+ process([ + [/\n\n/g, "

"], + [/^(.*<\/p>)(

)$/, '

$1'], + [/\n/g, "
"] + ]); + } + + ed.execCommand('mceInsertContent', false, content); + } + }, + + /** + * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine. + */ + _legacySupport : function() { + var t = this, ed = t.editor; + + // Register command(s) for backwards compatibility + ed.addCommand("mcePasteWord", function() { + ed.windowManager.open({ + file: t.url + "/pasteword.htm", + width: parseInt(getParam(ed, "paste_dialog_width")), + height: parseInt(getParam(ed, "paste_dialog_height")), + inline: 1 + }); + }); + + if (getParam(ed, "paste_text_use_dialog")) { + ed.addCommand("mcePasteText", function() { + ed.windowManager.open({ + file : t.url + "/pastetext.htm", + width: parseInt(getParam(ed, "paste_dialog_width")), + height: parseInt(getParam(ed, "paste_dialog_height")), + inline : 1 + }); + }); + } + + // Register button for backwards compatibility + ed.addButton("pasteword", {title : "paste.paste_word_desc", cmd : "mcePasteWord"}); + } + }); + + // Register plugin + tinymce.PluginManager.add("paste", tinymce.plugins.PastePlugin); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/preview/jscripts/embed.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/preview/jscripts/embed.js new file mode 100644 index 0000000..6fe25de --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/preview/jscripts/embed.js @@ -0,0 +1,73 @@ +/** + * This script contains embed functions for common plugins. This scripts are complety free to use for any purpose. + */ + +function writeFlash(p) { + writeEmbed( + 'D27CDB6E-AE6D-11cf-96B8-444553540000', + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0', + 'application/x-shockwave-flash', + p + ); +} + +function writeShockWave(p) { + writeEmbed( + '166B1BCA-3F9C-11CF-8075-444553540000', + 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0', + 'application/x-director', + p + ); +} + +function writeQuickTime(p) { + writeEmbed( + '02BF25D5-8C17-4B23-BC80-D3488ABDDC6B', + 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0', + 'video/quicktime', + p + ); +} + +function writeRealMedia(p) { + writeEmbed( + 'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA', + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0', + 'audio/x-pn-realaudio-plugin', + p + ); +} + +function writeWindowsMedia(p) { + p.url = p.src; + writeEmbed( + '6BF52A52-394A-11D3-B153-00C04F79FAA6', + 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701', + 'application/x-mplayer2', + p + ); +} + +function writeEmbed(cls, cb, mt, p) { + var h = '', n; + + h += ''; + + h += ' + + + + + +{#preview.preview_desc} + + + + + diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/save/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/save/editor_plugin_src.js new file mode 100644 index 0000000..5ab6491 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/save/editor_plugin_src.js @@ -0,0 +1,101 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.Save', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceSave', t._save, t); + ed.addCommand('mceCancel', t._cancel, t); + + // Register buttons + ed.addButton('save', {title : 'save.save_desc', cmd : 'mceSave'}); + ed.addButton('cancel', {title : 'save.cancel_desc', cmd : 'mceCancel'}); + + ed.onNodeChange.add(t._nodeChange, t); + ed.addShortcut('ctrl+s', ed.getLang('save.save_desc'), 'mceSave'); + }, + + getInfo : function() { + return { + longname : 'Save', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/save', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + _nodeChange : function(ed, cm, n) { + var ed = this.editor; + + if (ed.getParam('save_enablewhendirty')) { + cm.setDisabled('save', !ed.isDirty()); + cm.setDisabled('cancel', !ed.isDirty()); + } + }, + + // Private methods + + _save : function() { + var ed = this.editor, formObj, os, i, elementId; + + formObj = tinymce.DOM.get(ed.id).form || tinymce.DOM.getParent(ed.id, 'form'); + + if (ed.getParam("save_enablewhendirty") && !ed.isDirty()) + return; + + tinyMCE.triggerSave(); + + // Use callback instead + if (os = ed.getParam("save_onsavecallback")) { + if (ed.execCallback('save_onsavecallback', ed)) { + ed.startContent = tinymce.trim(ed.getContent({format : 'raw'})); + ed.nodeChanged(); + } + + return; + } + + if (formObj) { + ed.isNotDirty = true; + + if (formObj.onsubmit == null || formObj.onsubmit() != false) + formObj.submit(); + + ed.nodeChanged(); + } else + ed.windowManager.alert("Error: No form element found."); + }, + + _cancel : function() { + var ed = this.editor, os, h = tinymce.trim(ed.startContent); + + // Use callback instead + if (os = ed.getParam("save_oncancelcallback")) { + ed.execCallback('save_oncancelcallback', ed); + return; + } + + ed.setContent(h); + ed.undoManager.clear(); + ed.nodeChanged(); + } + }); + + // Register plugin + tinymce.PluginManager.add('save', tinymce.plugins.Save); +})(); \ No newline at end of file diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/searchreplace/js/searchreplace.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/searchreplace/js/searchreplace.js new file mode 100644 index 0000000..85c542e --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/searchreplace/js/searchreplace.js @@ -0,0 +1,152 @@ +tinyMCEPopup.requireLangPack(); + +var SearchReplaceDialog = { + init : function(ed) { + var t = this, f = document.forms[0], m = tinyMCEPopup.getWindowArg("mode"); + + t.switchMode(m); + + f[m + '_panel_searchstring'].value = tinyMCEPopup.getWindowArg("search_string"); + + // Focus input field + f[m + '_panel_searchstring'].focus(); + + mcTabs.onChange.add(function(tab_id, panel_id) { + t.switchMode(tab_id.substring(0, tab_id.indexOf('_'))); + }); + + }, + + switchMode : function(m) { + var f, lm = this.lastMode; + + if (lm != m) { + f = document.forms[0]; + + if (lm) { + f[m + '_panel_searchstring'].value = f[lm + '_panel_searchstring'].value; + f[m + '_panel_backwardsu'].checked = f[lm + '_panel_backwardsu'].checked; + f[m + '_panel_backwardsd'].checked = f[lm + '_panel_backwardsd'].checked; + f[m + '_panel_casesensitivebox'].checked = f[lm + '_panel_casesensitivebox'].checked; + } + + mcTabs.displayTab(m + '_tab', m + '_panel'); + document.getElementById("replaceBtn").style.display = (m == "replace") ? "inline" : "none"; + document.getElementById("replaceAllBtn").style.display = (m == "replace") ? "inline" : "none"; + this.lastMode = m; + } + }, + + searchNext : function(a) { + var ed = tinyMCEPopup.editor, se = ed.selection, r = se.getRng(), f, m = this.lastMode, s, b, fl = 0, w = ed.getWin(), wm = ed.windowManager, fo = 0; + + if (tinymce.isIE11 && !window.find) { + ed.windowManager.alert("This feature is not available in IE 11+. Upgrade TinyMCE to 4.x to get this functionallity back."); + return; + } + + // Get input + f = document.forms[0]; + s = f[m + '_panel_searchstring'].value; + b = f[m + '_panel_backwardsu'].checked; + ca = f[m + '_panel_casesensitivebox'].checked; + rs = f['replace_panel_replacestring'].value; + + if (tinymce.isIE) { + r = ed.getDoc().selection.createRange(); + } + + if (s == '') + return; + + function fix() { + // Correct Firefox graphics glitches + // TODO: Verify if this is actually needed any more, maybe it was for very old FF versions? + r = se.getRng().cloneRange(); + ed.getDoc().execCommand('SelectAll', false, null); + se.setRng(r); + }; + + function replace() { + ed.selection.setContent(rs); // Needs to be duplicated due to selection bug in IE + }; + + // IE flags + if (ca) + fl = fl | 4; + + switch (a) { + case 'all': + // Move caret to beginning of text + ed.execCommand('SelectAll'); + ed.selection.collapse(true); + + if (tinymce.isIE) { + ed.focus(); + r = ed.getDoc().selection.createRange(); + + while (r.findText(s, b ? -1 : 1, fl)) { + r.scrollIntoView(); + r.select(); + replace(); + fo = 1; + + if (b) { + r.moveEnd("character", -(rs.length)); // Otherwise will loop forever + } else { + // to avoid looping for ever in MSIE 9/10 when just + // changing the case + r.moveStart("character", rs.length); + } + } + + tinyMCEPopup.storeSelection(); + } else { + while (w.find(s, ca, b, false, false, false, false)) { + replace(); + fo = 1; + } + } + + if (fo) + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.allreplaced')); + else + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + + return; + + case 'current': + if (!ed.selection.isCollapsed()) + replace(); + + break; + } + + se.collapse(b); + r = se.getRng(); + + // Whats the point + if (!s) + return; + + if (tinymce.isIE) { + ed.focus(); + r = ed.getDoc().selection.createRange(); + + if (r.findText(s, b ? -1 : 1, fl)) { + r.scrollIntoView(); + r.select(); + } else + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + + tinyMCEPopup.storeSelection(); + } else { + if (!w.find(s, ca, b, false, false, false, false)) + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + else + fix(); + } + } +}; + +tinyMCEPopup.onInit.add(SearchReplaceDialog.init, SearchReplaceDialog); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/spellchecker/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/spellchecker/editor_plugin_src.js new file mode 100644 index 0000000..4081181 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/spellchecker/editor_plugin_src.js @@ -0,0 +1,471 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var JSONRequest = tinymce.util.JSONRequest, each = tinymce.each, DOM = tinymce.DOM; + + tinymce.create('tinymce.plugins.SpellcheckerPlugin', { + getInfo : function() { + return { + longname : 'Spellchecker', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + init : function(ed, url) { + var t = this, cm; + + t.url = url; + t.editor = ed; + t.rpcUrl = ed.getParam("spellchecker_rpc_url", "{backend}"); + + if (t.rpcUrl == '{backend}') { + // Sniff if the browser supports native spellchecking (Don't know of a better way) + if (tinymce.isIE) + return; + + t.hasSupport = true; + + // Disable the context menu when spellchecking is active + ed.onContextMenu.addToTop(function(ed, e) { + if (t.active) + return false; + }); + } + + // Register commands + ed.addCommand('mceSpellCheck', function() { + if (t.rpcUrl == '{backend}') { + // Enable/disable native spellchecker + t.editor.getBody().spellcheck = t.active = !t.active; + return; + } + + if (!t.active) { + ed.setProgressState(1); + t._sendRPC('checkWords', [t.selectedLang, t._getWords()], function(r) { + if (r.length > 0) { + t.active = 1; + t._markWords(r); + ed.setProgressState(0); + ed.nodeChanged(); + } else { + ed.setProgressState(0); + + if (ed.getParam('spellchecker_report_no_misspellings', true)) + ed.windowManager.alert('spellchecker.no_mpell'); + } + }); + } else + t._done(); + }); + + if (ed.settings.content_css !== false) + ed.contentCSS.push(url + '/css/content.css'); + + ed.onClick.add(t._showMenu, t); + ed.onContextMenu.add(t._showMenu, t); + ed.onBeforeGetContent.add(function() { + if (t.active) + t._removeWords(); + }); + + ed.onNodeChange.add(function(ed, cm) { + cm.setActive('spellchecker', t.active); + }); + + ed.onSetContent.add(function() { + t._done(); + }); + + ed.onBeforeGetContent.add(function() { + t._done(); + }); + + ed.onBeforeExecCommand.add(function(ed, cmd) { + if (cmd == 'mceFullScreen') + t._done(); + }); + + // Find selected language + t.languages = {}; + each(ed.getParam('spellchecker_languages', '+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv', 'hash'), function(v, k) { + if (k.indexOf('+') === 0) { + k = k.substring(1); + t.selectedLang = v; + } + + t.languages[k] = v; + }); + }, + + createControl : function(n, cm) { + var t = this, c, ed = t.editor; + + if (n == 'spellchecker') { + // Use basic button if we use the native spellchecker + if (t.rpcUrl == '{backend}') { + // Create simple toggle button if we have native support + if (t.hasSupport) + c = cm.createButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t}); + + return c; + } + + c = cm.createSplitButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t}); + + c.onRenderMenu.add(function(c, m) { + m.add({title : 'spellchecker.langs', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + t.menuItems = {}; + each(t.languages, function(v, k) { + var o = {icon : 1}, mi; + + o.onclick = function() { + if (v == t.selectedLang) { + return; + } + t._updateMenu(mi); + t.selectedLang = v; + }; + + o.title = k; + mi = m.add(o); + mi.setSelected(v == t.selectedLang); + t.menuItems[v] = mi; + if (v == t.selectedLang) + t.selectedItem = mi; + }); + }); + + + + return c; + } + }, + + setLanguage: function(lang) { + var t = this; + + if (lang == t.selectedLang) { + // allowed + return; + } + + if (tinymce.grep(t.languages, function(v) { return v === lang; }).length === 0) { + throw "Unknown language: " + lang; + } + + t.selectedLang = lang; + + // if the menu has been shown, update it as well + if (t.menuItems) { + t._updateMenu(t.menuItems[lang]); + } + + if (t.active) { + // clear error in the old language. + t._done(); + + // Don't immediately block the UI to check spelling in the new language, this is an API not a user action. + } + }, + + // Internal functions + + _updateMenu: function(mi) { + mi.setSelected(1); + this.selectedItem.setSelected(0); + this.selectedItem = mi; + }, + + _walk : function(n, f) { + var d = this.editor.getDoc(), w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(this, n); + } else + tinymce.walk(n, f, 'childNodes'); + }, + + _getSeparators : function() { + var re = '', i, str = this.editor.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}\u201d\u201c'); + + // Build word separator regexp + for (i=0; i elements content is broken after spellchecking. + // Bug #1408: Preceding whitespace characters are removed + // @TODO: I'm not sure that both are still issues on IE9. + if (tinymce.isIE) { + // Enclose mispelled words with temporal tag + v = v.replace(rx, '$1$2'); + // Loop over the content finding mispelled words + while ((pos = v.indexOf('')) != -1) { + // Add text node for the content before the word + txt = v.substring(0, pos); + if (txt.length) { + node = doc.createTextNode(dom.decode(txt)); + elem.appendChild(node); + } + v = v.substring(pos+10); + pos = v.indexOf(''); + txt = v.substring(0, pos); + v = v.substring(pos+11); + // Add span element for the word + elem.appendChild(dom.create('span', {'class' : 'mceItemHiddenSpellWord'}, txt)); + } + // Add text node for the rest of the content + if (v.length) { + node = doc.createTextNode(dom.decode(v)); + elem.appendChild(node); + } + } else { + // Other browsers preserve whitespace characters on innerHTML usage + elem.innerHTML = v.replace(rx, '$1$2'); + } + + // Finally, replace the node with the container + dom.replace(elem, n); + } + }); + + se.setRng(r); + }, + + _showMenu : function(ed, e) { + var t = this, ed = t.editor, m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin()), wordSpan = e.target; + + e = 0; // Fixes IE memory leak + + if (!m) { + m = ed.controlManager.createDropMenu('spellcheckermenu', {'class' : 'mceNoIcons'}); + t._menu = m; + } + + if (dom.hasClass(wordSpan, 'mceItemHiddenSpellWord')) { + m.removeAll(); + m.add({title : 'spellchecker.wait', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + + t._sendRPC('getSuggestions', [t.selectedLang, dom.decode(wordSpan.innerHTML)], function(r) { + var ignoreRpc; + + m.removeAll(); + + if (r.length > 0) { + m.add({title : 'spellchecker.sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + each(r, function(v) { + m.add({title : v, onclick : function() { + dom.replace(ed.getDoc().createTextNode(v), wordSpan); + t._checkDone(); + }}); + }); + + m.addSeparator(); + } else + m.add({title : 'spellchecker.no_sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + + if (ed.getParam('show_ignore_words', true)) { + ignoreRpc = t.editor.getParam("spellchecker_enable_ignore_rpc", ''); + m.add({ + title : 'spellchecker.ignore_word', + onclick : function() { + var word = wordSpan.innerHTML; + + dom.remove(wordSpan, 1); + t._checkDone(); + + // tell the server if we need to + if (ignoreRpc) { + ed.setProgressState(1); + t._sendRPC('ignoreWord', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + } + }); + + m.add({ + title : 'spellchecker.ignore_words', + onclick : function() { + var word = wordSpan.innerHTML; + + t._removeWords(dom.decode(word)); + t._checkDone(); + + // tell the server if we need to + if (ignoreRpc) { + ed.setProgressState(1); + t._sendRPC('ignoreWords', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + } + }); + } + + if (t.editor.getParam("spellchecker_enable_learn_rpc")) { + m.add({ + title : 'spellchecker.learn_word', + onclick : function() { + var word = wordSpan.innerHTML; + + dom.remove(wordSpan, 1); + t._checkDone(); + + ed.setProgressState(1); + t._sendRPC('learnWord', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + }); + } + + m.update(); + }); + + p1 = DOM.getPos(ed.getContentAreaContainer()); + m.settings.offset_x = p1.x; + m.settings.offset_y = p1.y; + + ed.selection.select(wordSpan); + p1 = dom.getPos(wordSpan); + m.showMenu(p1.x, p1.y + wordSpan.offsetHeight - vp.y); + + return tinymce.dom.Event.cancel(e); + } else + m.hideMenu(); + }, + + _checkDone : function() { + var t = this, ed = t.editor, dom = ed.dom, o; + + each(dom.select('span'), function(n) { + if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) { + o = true; + return false; + } + }); + + if (!o) + t._done(); + }, + + _done : function() { + var t = this, la = t.active; + + if (t.active) { + t.active = 0; + t._removeWords(); + + if (t._menu) + t._menu.hideMenu(); + + if (la) + t.editor.nodeChanged(); + } + }, + + _sendRPC : function(m, p, cb) { + var t = this; + + JSONRequest.sendRPC({ + url : t.rpcUrl, + method : m, + params : p, + success : cb, + error : function(e, x) { + t.editor.setProgressState(0); + t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText)); + } + }); + } + }); + + // Register plugin + tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/style/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/style/editor_plugin_src.js new file mode 100644 index 0000000..5a2d848 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/style/editor_plugin_src.js @@ -0,0 +1,71 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.StylePlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceStyleProps', function() { + + var applyStyleToBlocks = false; + var blocks = ed.selection.getSelectedBlocks(); + var styles = []; + + if (blocks.length === 1) { + styles.push(ed.selection.getNode().style.cssText); + } + else { + tinymce.each(blocks, function(block) { + styles.push(ed.dom.getAttrib(block, 'style')); + }); + applyStyleToBlocks = true; + } + + ed.windowManager.open({ + file : url + '/props.htm', + width : 480 + parseInt(ed.getLang('style.delta_width', 0)), + height : 340 + parseInt(ed.getLang('style.delta_height', 0)), + inline : 1 + }, { + applyStyleToBlocks : applyStyleToBlocks, + plugin_url : url, + styles : styles + }); + }); + + ed.addCommand('mceSetElementStyle', function(ui, v) { + if (e = ed.selection.getNode()) { + ed.dom.setAttrib(e, 'style', v); + ed.execCommand('mceRepaint'); + } + }); + + ed.onNodeChange.add(function(ed, cm, n) { + cm.setDisabled('styleprops', n.nodeName === 'BODY'); + }); + + // Register buttons + ed.addButton('styleprops', {title : 'style.desc', cmd : 'mceStyleProps'}); + }, + + getInfo : function() { + return { + longname : 'Style', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/style', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('style', tinymce.plugins.StylePlugin); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/style/js/props.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/style/js/props.js new file mode 100644 index 0000000..853222b --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/style/js/props.js @@ -0,0 +1,709 @@ +tinyMCEPopup.requireLangPack(); + +var defaultFonts = "" + + "Arial, Helvetica, sans-serif=Arial, Helvetica, sans-serif;" + + "Times New Roman, Times, serif=Times New Roman, Times, serif;" + + "Courier New, Courier, mono=Courier New, Courier, mono;" + + "Times New Roman, Times, serif=Times New Roman, Times, serif;" + + "Georgia, Times New Roman, Times, serif=Georgia, Times New Roman, Times, serif;" + + "Verdana, Arial, Helvetica, sans-serif=Verdana, Arial, Helvetica, sans-serif;" + + "Geneva, Arial, Helvetica, sans-serif=Geneva, Arial, Helvetica, sans-serif"; + +var defaultSizes = "9;10;12;14;16;18;24;xx-small;x-small;small;medium;large;x-large;xx-large;smaller;larger"; +var defaultMeasurement = "+pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%"; +var defaultSpacingMeasurement = "pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;+ems=em;exs=ex;%"; +var defaultIndentMeasurement = "pixels=px;+points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%"; +var defaultWeight = "normal;bold;bolder;lighter;100;200;300;400;500;600;700;800;900"; +var defaultTextStyle = "normal;italic;oblique"; +var defaultVariant = "normal;small-caps"; +var defaultLineHeight = "normal"; +var defaultAttachment = "fixed;scroll"; +var defaultRepeat = "no-repeat;repeat;repeat-x;repeat-y"; +var defaultPosH = "left;center;right"; +var defaultPosV = "top;center;bottom"; +var defaultVAlign = "baseline;sub;super;top;text-top;middle;bottom;text-bottom"; +var defaultDisplay = "inline;block;list-item;run-in;compact;marker;table;inline-table;table-row-group;table-header-group;table-footer-group;table-row;table-column-group;table-column;table-cell;table-caption;none"; +var defaultBorderStyle = "none;solid;dashed;dotted;double;groove;ridge;inset;outset"; +var defaultBorderWidth = "thin;medium;thick"; +var defaultListType = "disc;circle;square;decimal;lower-roman;upper-roman;lower-alpha;upper-alpha;none"; + +function aggregateStyles(allStyles) { + var mergedStyles = {}; + + tinymce.each(allStyles, function(style) { + if (style !== '') { + var parsedStyles = tinyMCEPopup.editor.dom.parseStyle(style); + for (var name in parsedStyles) { + if (parsedStyles.hasOwnProperty(name)) { + if (mergedStyles[name] === undefined) { + mergedStyles[name] = parsedStyles[name]; + } + else if (name === 'text-decoration') { + if (mergedStyles[name].indexOf(parsedStyles[name]) === -1) { + mergedStyles[name] = mergedStyles[name] +' '+ parsedStyles[name]; + } + } + } + } + } + }); + + return mergedStyles; +} + +var applyActionIsInsert; +var existingStyles; + +function init(ed) { + var ce = document.getElementById('container'), h; + + existingStyles = aggregateStyles(tinyMCEPopup.getWindowArg('styles')); + ce.style.cssText = tinyMCEPopup.editor.dom.serializeStyle(existingStyles); + + applyActionIsInsert = ed.getParam("edit_css_style_insert_span", false); + document.getElementById('toggle_insert_span').checked = applyActionIsInsert; + + h = getBrowserHTML('background_image_browser','background_image','image','advimage'); + document.getElementById("background_image_browser").innerHTML = h; + + document.getElementById('text_color_pickcontainer').innerHTML = getColorPickerHTML('text_color_pick','text_color'); + document.getElementById('background_color_pickcontainer').innerHTML = getColorPickerHTML('background_color_pick','background_color'); + document.getElementById('border_color_top_pickcontainer').innerHTML = getColorPickerHTML('border_color_top_pick','border_color_top'); + document.getElementById('border_color_right_pickcontainer').innerHTML = getColorPickerHTML('border_color_right_pick','border_color_right'); + document.getElementById('border_color_bottom_pickcontainer').innerHTML = getColorPickerHTML('border_color_bottom_pick','border_color_bottom'); + document.getElementById('border_color_left_pickcontainer').innerHTML = getColorPickerHTML('border_color_left_pick','border_color_left'); + + fillSelect(0, 'text_font', 'style_font', defaultFonts, ';', true); + fillSelect(0, 'text_size', 'style_font_size', defaultSizes, ';', true); + fillSelect(0, 'text_size_measurement', 'style_font_size_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'text_case', 'style_text_case', "capitalize;uppercase;lowercase", ';', true); + fillSelect(0, 'text_weight', 'style_font_weight', defaultWeight, ';', true); + fillSelect(0, 'text_style', 'style_font_style', defaultTextStyle, ';', true); + fillSelect(0, 'text_variant', 'style_font_variant', defaultVariant, ';', true); + fillSelect(0, 'text_lineheight', 'style_font_line_height', defaultLineHeight, ';', true); + fillSelect(0, 'text_lineheight_measurement', 'style_font_line_height_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'background_attachment', 'style_background_attachment', defaultAttachment, ';', true); + fillSelect(0, 'background_repeat', 'style_background_repeat', defaultRepeat, ';', true); + + fillSelect(0, 'background_hpos_measurement', 'style_background_hpos_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'background_vpos_measurement', 'style_background_vpos_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'background_hpos', 'style_background_hpos', defaultPosH, ';', true); + fillSelect(0, 'background_vpos', 'style_background_vpos', defaultPosV, ';', true); + + fillSelect(0, 'block_wordspacing', 'style_wordspacing', 'normal', ';', true); + fillSelect(0, 'block_wordspacing_measurement', 'style_wordspacing_measurement', defaultSpacingMeasurement, ';', true); + fillSelect(0, 'block_letterspacing', 'style_letterspacing', 'normal', ';', true); + fillSelect(0, 'block_letterspacing_measurement', 'style_letterspacing_measurement', defaultSpacingMeasurement, ';', true); + fillSelect(0, 'block_vertical_alignment', 'style_vertical_alignment', defaultVAlign, ';', true); + fillSelect(0, 'block_text_align', 'style_text_align', "left;right;center;justify", ';', true); + fillSelect(0, 'block_whitespace', 'style_whitespace', "normal;pre;nowrap", ';', true); + fillSelect(0, 'block_display', 'style_display', defaultDisplay, ';', true); + fillSelect(0, 'block_text_indent_measurement', 'style_text_indent_measurement', defaultIndentMeasurement, ';', true); + + fillSelect(0, 'box_width_measurement', 'style_box_width_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_height_measurement', 'style_box_height_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_float', 'style_float', 'left;right;none', ';', true); + fillSelect(0, 'box_clear', 'style_clear', 'left;right;both;none', ';', true); + fillSelect(0, 'box_padding_left_measurement', 'style_padding_left_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_top_measurement', 'style_padding_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_bottom_measurement', 'style_padding_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_right_measurement', 'style_padding_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_left_measurement', 'style_margin_left_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_top_measurement', 'style_margin_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_bottom_measurement', 'style_margin_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_right_measurement', 'style_margin_right_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'border_style_top', 'style_border_style_top', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_right', 'style_border_style_right', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_bottom', 'style_border_style_bottom', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_left', 'style_border_style_left', defaultBorderStyle, ';', true); + + fillSelect(0, 'border_width_top', 'style_border_width_top', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_right', 'style_border_width_right', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_bottom', 'style_border_width_bottom', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_left', 'style_border_width_left', defaultBorderWidth, ';', true); + + fillSelect(0, 'border_width_top_measurement', 'style_border_width_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_right_measurement', 'style_border_width_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_bottom_measurement', 'style_border_width_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_left_measurement', 'style_border_width_left_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'list_type', 'style_list_type', defaultListType, ';', true); + fillSelect(0, 'list_position', 'style_list_position', "inside;outside", ';', true); + + fillSelect(0, 'positioning_type', 'style_positioning_type', "absolute;relative;static", ';', true); + fillSelect(0, 'positioning_visibility', 'style_positioning_visibility', "inherit;visible;hidden", ';', true); + + fillSelect(0, 'positioning_width_measurement', 'style_positioning_width_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_height_measurement', 'style_positioning_height_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_overflow', 'style_positioning_overflow', "visible;hidden;scroll;auto", ';', true); + + fillSelect(0, 'positioning_placement_top_measurement', 'style_positioning_placement_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_right_measurement', 'style_positioning_placement_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_bottom_measurement', 'style_positioning_placement_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_left_measurement', 'style_positioning_placement_left_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'positioning_clip_top_measurement', 'style_positioning_clip_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_right_measurement', 'style_positioning_clip_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_bottom_measurement', 'style_positioning_clip_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_left_measurement', 'style_positioning_clip_left_measurement', defaultMeasurement, ';', true); + + TinyMCE_EditableSelects.init(); + setupFormData(); + showDisabledControls(); +} + +function setupFormData() { + var ce = document.getElementById('container'), f = document.forms[0], s, b, i; + + // Setup text fields + + selectByValue(f, 'text_font', ce.style.fontFamily, true, true); + selectByValue(f, 'text_size', getNum(ce.style.fontSize), true, true); + selectByValue(f, 'text_size_measurement', getMeasurement(ce.style.fontSize)); + selectByValue(f, 'text_weight', ce.style.fontWeight, true, true); + selectByValue(f, 'text_style', ce.style.fontStyle, true, true); + selectByValue(f, 'text_lineheight', getNum(ce.style.lineHeight), true, true); + selectByValue(f, 'text_lineheight_measurement', getMeasurement(ce.style.lineHeight)); + selectByValue(f, 'text_case', ce.style.textTransform, true, true); + selectByValue(f, 'text_variant', ce.style.fontVariant, true, true); + f.text_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.color); + updateColor('text_color_pick', 'text_color'); + f.text_underline.checked = inStr(ce.style.textDecoration, 'underline'); + f.text_overline.checked = inStr(ce.style.textDecoration, 'overline'); + f.text_linethrough.checked = inStr(ce.style.textDecoration, 'line-through'); + f.text_blink.checked = inStr(ce.style.textDecoration, 'blink'); + f.text_none.checked = inStr(ce.style.textDecoration, 'none'); + updateTextDecorations(); + + // Setup background fields + + f.background_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.backgroundColor); + updateColor('background_color_pick', 'background_color'); + f.background_image.value = ce.style.backgroundImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); + selectByValue(f, 'background_repeat', ce.style.backgroundRepeat, true, true); + selectByValue(f, 'background_attachment', ce.style.backgroundAttachment, true, true); + selectByValue(f, 'background_hpos', getNum(getVal(ce.style.backgroundPosition, 0)), true, true); + selectByValue(f, 'background_hpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 0))); + selectByValue(f, 'background_vpos', getNum(getVal(ce.style.backgroundPosition, 1)), true, true); + selectByValue(f, 'background_vpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 1))); + + // Setup block fields + + selectByValue(f, 'block_wordspacing', getNum(ce.style.wordSpacing), true, true); + selectByValue(f, 'block_wordspacing_measurement', getMeasurement(ce.style.wordSpacing)); + selectByValue(f, 'block_letterspacing', getNum(ce.style.letterSpacing), true, true); + selectByValue(f, 'block_letterspacing_measurement', getMeasurement(ce.style.letterSpacing)); + selectByValue(f, 'block_vertical_alignment', ce.style.verticalAlign, true, true); + selectByValue(f, 'block_text_align', ce.style.textAlign, true, true); + f.block_text_indent.value = getNum(ce.style.textIndent); + selectByValue(f, 'block_text_indent_measurement', getMeasurement(ce.style.textIndent)); + selectByValue(f, 'block_whitespace', ce.style.whiteSpace, true, true); + selectByValue(f, 'block_display', ce.style.display, true, true); + + // Setup box fields + + f.box_width.value = getNum(ce.style.width); + selectByValue(f, 'box_width_measurement', getMeasurement(ce.style.width)); + + f.box_height.value = getNum(ce.style.height); + selectByValue(f, 'box_height_measurement', getMeasurement(ce.style.height)); + selectByValue(f, 'box_float', ce.style.cssFloat || ce.style.styleFloat, true, true); + + selectByValue(f, 'box_clear', ce.style.clear, true, true); + + setupBox(f, ce, 'box_padding', 'padding', ''); + setupBox(f, ce, 'box_margin', 'margin', ''); + + // Setup border fields + + setupBox(f, ce, 'border_style', 'border', 'Style'); + setupBox(f, ce, 'border_width', 'border', 'Width'); + setupBox(f, ce, 'border_color', 'border', 'Color'); + + updateColor('border_color_top_pick', 'border_color_top'); + updateColor('border_color_right_pick', 'border_color_right'); + updateColor('border_color_bottom_pick', 'border_color_bottom'); + updateColor('border_color_left_pick', 'border_color_left'); + + f.elements.border_color_top.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_top.value); + f.elements.border_color_right.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_right.value); + f.elements.border_color_bottom.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_bottom.value); + f.elements.border_color_left.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_left.value); + + // Setup list fields + + selectByValue(f, 'list_type', ce.style.listStyleType, true, true); + selectByValue(f, 'list_position', ce.style.listStylePosition, true, true); + f.list_bullet_image.value = ce.style.listStyleImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); + + // Setup box fields + + selectByValue(f, 'positioning_type', ce.style.position, true, true); + selectByValue(f, 'positioning_visibility', ce.style.visibility, true, true); + selectByValue(f, 'positioning_overflow', ce.style.overflow, true, true); + f.positioning_zindex.value = ce.style.zIndex ? ce.style.zIndex : ""; + + f.positioning_width.value = getNum(ce.style.width); + selectByValue(f, 'positioning_width_measurement', getMeasurement(ce.style.width)); + + f.positioning_height.value = getNum(ce.style.height); + selectByValue(f, 'positioning_height_measurement', getMeasurement(ce.style.height)); + + setupBox(f, ce, 'positioning_placement', '', '', ['top', 'right', 'bottom', 'left']); + + s = ce.style.clip.replace(new RegExp("rect\\('?([^']*)'?\\)", 'gi'), "$1"); + s = s.replace(/,/g, ' '); + + if (!hasEqualValues([getVal(s, 0), getVal(s, 1), getVal(s, 2), getVal(s, 3)])) { + f.positioning_clip_top.value = getNum(getVal(s, 0)); + selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0))); + f.positioning_clip_right.value = getNum(getVal(s, 1)); + selectByValue(f, 'positioning_clip_right_measurement', getMeasurement(getVal(s, 1))); + f.positioning_clip_bottom.value = getNum(getVal(s, 2)); + selectByValue(f, 'positioning_clip_bottom_measurement', getMeasurement(getVal(s, 2))); + f.positioning_clip_left.value = getNum(getVal(s, 3)); + selectByValue(f, 'positioning_clip_left_measurement', getMeasurement(getVal(s, 3))); + } else { + f.positioning_clip_top.value = getNum(getVal(s, 0)); + selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0))); + f.positioning_clip_right.value = f.positioning_clip_bottom.value = f.positioning_clip_left.value; + } + +// setupBox(f, ce, '', 'border', 'Color'); +} + +function getMeasurement(s) { + return s.replace(/^([0-9.]+)(.*)$/, "$2"); +} + +function getNum(s) { + if (new RegExp('^(?:[0-9.]+)(?:[a-z%]+)$', 'gi').test(s)) + return s.replace(/[^0-9.]/g, ''); + + return s; +} + +function inStr(s, n) { + return new RegExp(n, 'gi').test(s); +} + +function getVal(s, i) { + var a = s.split(' '); + + if (a.length > 1) + return a[i]; + + return ""; +} + +function setValue(f, n, v) { + if (f.elements[n].type == "text") + f.elements[n].value = v; + else + selectByValue(f, n, v, true, true); +} + +function setupBox(f, ce, fp, pr, sf, b) { + if (typeof(b) == "undefined") + b = ['Top', 'Right', 'Bottom', 'Left']; + + if (isSame(ce, pr, sf, b)) { + f.elements[fp + "_same"].checked = true; + + setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf])); + f.elements[fp + "_top"].disabled = false; + + f.elements[fp + "_right"].value = ""; + f.elements[fp + "_right"].disabled = true; + f.elements[fp + "_bottom"].value = ""; + f.elements[fp + "_bottom"].disabled = true; + f.elements[fp + "_left"].value = ""; + f.elements[fp + "_left"].disabled = true; + + if (f.elements[fp + "_top_measurement"]) { + selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf])); + f.elements[fp + "_left_measurement"].disabled = true; + f.elements[fp + "_bottom_measurement"].disabled = true; + f.elements[fp + "_right_measurement"].disabled = true; + } + } else { + f.elements[fp + "_same"].checked = false; + + setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf])); + f.elements[fp + "_top"].disabled = false; + + setValue(f, fp + "_right", getNum(ce.style[pr + b[1] + sf])); + f.elements[fp + "_right"].disabled = false; + + setValue(f, fp + "_bottom", getNum(ce.style[pr + b[2] + sf])); + f.elements[fp + "_bottom"].disabled = false; + + setValue(f, fp + "_left", getNum(ce.style[pr + b[3] + sf])); + f.elements[fp + "_left"].disabled = false; + + if (f.elements[fp + "_top_measurement"]) { + selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf])); + selectByValue(f, fp + '_right_measurement', getMeasurement(ce.style[pr + b[1] + sf])); + selectByValue(f, fp + '_bottom_measurement', getMeasurement(ce.style[pr + b[2] + sf])); + selectByValue(f, fp + '_left_measurement', getMeasurement(ce.style[pr + b[3] + sf])); + f.elements[fp + "_left_measurement"].disabled = false; + f.elements[fp + "_bottom_measurement"].disabled = false; + f.elements[fp + "_right_measurement"].disabled = false; + } + } +} + +function isSame(e, pr, sf, b) { + var a = [], i, x; + + if (typeof(b) == "undefined") + b = ['Top', 'Right', 'Bottom', 'Left']; + + if (typeof(sf) == "undefined" || sf == null) + sf = ""; + + a[0] = e.style[pr + b[0] + sf]; + a[1] = e.style[pr + b[1] + sf]; + a[2] = e.style[pr + b[2] + sf]; + a[3] = e.style[pr + b[3] + sf]; + + for (i=0; i 0 ? s.substring(1) : s; + + if (f.text_none.checked) + s = "none"; + + ce.style.textDecoration = s; + + // Build background styles + + ce.style.backgroundColor = f.background_color.value; + ce.style.backgroundImage = f.background_image.value != "" ? "url(" + f.background_image.value + ")" : ""; + ce.style.backgroundRepeat = f.background_repeat.value; + ce.style.backgroundAttachment = f.background_attachment.value; + + if (f.background_hpos.value != "") { + s = ""; + s += f.background_hpos.value + (isNum(f.background_hpos.value) ? f.background_hpos_measurement.value : "") + " "; + s += f.background_vpos.value + (isNum(f.background_vpos.value) ? f.background_vpos_measurement.value : ""); + ce.style.backgroundPosition = s; + } + + // Build block styles + + ce.style.wordSpacing = f.block_wordspacing.value + (isNum(f.block_wordspacing.value) ? f.block_wordspacing_measurement.value : ""); + ce.style.letterSpacing = f.block_letterspacing.value + (isNum(f.block_letterspacing.value) ? f.block_letterspacing_measurement.value : ""); + ce.style.verticalAlign = f.block_vertical_alignment.value; + ce.style.textAlign = f.block_text_align.value; + ce.style.textIndent = f.block_text_indent.value + (isNum(f.block_text_indent.value) ? f.block_text_indent_measurement.value : ""); + ce.style.whiteSpace = f.block_whitespace.value; + ce.style.display = f.block_display.value; + + // Build box styles + + ce.style.width = f.box_width.value + (isNum(f.box_width.value) ? f.box_width_measurement.value : ""); + ce.style.height = f.box_height.value + (isNum(f.box_height.value) ? f.box_height_measurement.value : ""); + ce.style.styleFloat = f.box_float.value; + ce.style.cssFloat = f.box_float.value; + + ce.style.clear = f.box_clear.value; + + if (!f.box_padding_same.checked) { + ce.style.paddingTop = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : ""); + ce.style.paddingRight = f.box_padding_right.value + (isNum(f.box_padding_right.value) ? f.box_padding_right_measurement.value : ""); + ce.style.paddingBottom = f.box_padding_bottom.value + (isNum(f.box_padding_bottom.value) ? f.box_padding_bottom_measurement.value : ""); + ce.style.paddingLeft = f.box_padding_left.value + (isNum(f.box_padding_left.value) ? f.box_padding_left_measurement.value : ""); + } else + ce.style.padding = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : ""); + + if (!f.box_margin_same.checked) { + ce.style.marginTop = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : ""); + ce.style.marginRight = f.box_margin_right.value + (isNum(f.box_margin_right.value) ? f.box_margin_right_measurement.value : ""); + ce.style.marginBottom = f.box_margin_bottom.value + (isNum(f.box_margin_bottom.value) ? f.box_margin_bottom_measurement.value : ""); + ce.style.marginLeft = f.box_margin_left.value + (isNum(f.box_margin_left.value) ? f.box_margin_left_measurement.value : ""); + } else + ce.style.margin = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : ""); + + // Build border styles + + if (!f.border_style_same.checked) { + ce.style.borderTopStyle = f.border_style_top.value; + ce.style.borderRightStyle = f.border_style_right.value; + ce.style.borderBottomStyle = f.border_style_bottom.value; + ce.style.borderLeftStyle = f.border_style_left.value; + } else + ce.style.borderStyle = f.border_style_top.value; + + if (!f.border_width_same.checked) { + ce.style.borderTopWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : ""); + ce.style.borderRightWidth = f.border_width_right.value + (isNum(f.border_width_right.value) ? f.border_width_right_measurement.value : ""); + ce.style.borderBottomWidth = f.border_width_bottom.value + (isNum(f.border_width_bottom.value) ? f.border_width_bottom_measurement.value : ""); + ce.style.borderLeftWidth = f.border_width_left.value + (isNum(f.border_width_left.value) ? f.border_width_left_measurement.value : ""); + } else + ce.style.borderWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : ""); + + if (!f.border_color_same.checked) { + ce.style.borderTopColor = f.border_color_top.value; + ce.style.borderRightColor = f.border_color_right.value; + ce.style.borderBottomColor = f.border_color_bottom.value; + ce.style.borderLeftColor = f.border_color_left.value; + } else + ce.style.borderColor = f.border_color_top.value; + + // Build list styles + + ce.style.listStyleType = f.list_type.value; + ce.style.listStylePosition = f.list_position.value; + ce.style.listStyleImage = f.list_bullet_image.value != "" ? "url(" + f.list_bullet_image.value + ")" : ""; + + // Build positioning styles + + ce.style.position = f.positioning_type.value; + ce.style.visibility = f.positioning_visibility.value; + + if (ce.style.width == "") + ce.style.width = f.positioning_width.value + (isNum(f.positioning_width.value) ? f.positioning_width_measurement.value : ""); + + if (ce.style.height == "") + ce.style.height = f.positioning_height.value + (isNum(f.positioning_height.value) ? f.positioning_height_measurement.value : ""); + + ce.style.zIndex = f.positioning_zindex.value; + ce.style.overflow = f.positioning_overflow.value; + + if (!f.positioning_placement_same.checked) { + ce.style.top = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : ""); + ce.style.right = f.positioning_placement_right.value + (isNum(f.positioning_placement_right.value) ? f.positioning_placement_right_measurement.value : ""); + ce.style.bottom = f.positioning_placement_bottom.value + (isNum(f.positioning_placement_bottom.value) ? f.positioning_placement_bottom_measurement.value : ""); + ce.style.left = f.positioning_placement_left.value + (isNum(f.positioning_placement_left.value) ? f.positioning_placement_left_measurement.value : ""); + } else { + s = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : ""); + ce.style.top = s; + ce.style.right = s; + ce.style.bottom = s; + ce.style.left = s; + } + + if (!f.positioning_clip_same.checked) { + s = "rect("; + s += (isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_right.value) ? f.positioning_clip_right.value + f.positioning_clip_right_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_bottom.value) ? f.positioning_clip_bottom.value + f.positioning_clip_bottom_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_left.value) ? f.positioning_clip_left.value + f.positioning_clip_left_measurement.value : "auto"); + s += ")"; + + if (s != "rect(auto auto auto auto)") + ce.style.clip = s; + } else { + s = "rect("; + t = isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto"; + s += t + " "; + s += t + " "; + s += t + " "; + s += t + ")"; + + if (s != "rect(auto auto auto auto)") + ce.style.clip = s; + } + + ce.style.cssText = ce.style.cssText; +} + +function isNum(s) { + return new RegExp('[0-9]+', 'g').test(s); +} + +function showDisabledControls() { + var f = document.forms, i, a; + + for (i=0; i 1) { + addSelectValue(f, s, p[0], p[1]); + + if (se) + selectByValue(f, s, p[1]); + } else { + addSelectValue(f, s, p[0], p[0]); + + if (se) + selectByValue(f, s, p[0]); + } + } +} + +function toggleSame(ce, pre) { + var el = document.forms[0].elements, i; + + if (ce.checked) { + el[pre + "_top"].disabled = false; + el[pre + "_right"].disabled = true; + el[pre + "_bottom"].disabled = true; + el[pre + "_left"].disabled = true; + + if (el[pre + "_top_measurement"]) { + el[pre + "_top_measurement"].disabled = false; + el[pre + "_right_measurement"].disabled = true; + el[pre + "_bottom_measurement"].disabled = true; + el[pre + "_left_measurement"].disabled = true; + } + } else { + el[pre + "_top"].disabled = false; + el[pre + "_right"].disabled = false; + el[pre + "_bottom"].disabled = false; + el[pre + "_left"].disabled = false; + + if (el[pre + "_top_measurement"]) { + el[pre + "_top_measurement"].disabled = false; + el[pre + "_right_measurement"].disabled = false; + el[pre + "_bottom_measurement"].disabled = false; + el[pre + "_left_measurement"].disabled = false; + } + } + + showDisabledControls(); +} + +function synch(fr, to) { + var f = document.forms[0]; + + f.elements[to].value = f.elements[fr].value; + + if (f.elements[fr + "_measurement"]) + selectByValue(f, to + "_measurement", f.elements[fr + "_measurement"].value); +} + +function updateTextDecorations(){ + var el = document.forms[0].elements; + + var textDecorations = ["text_underline", "text_overline", "text_linethrough", "text_blink"]; + var noneChecked = el["text_none"].checked; + tinymce.each(textDecorations, function(id) { + el[id].disabled = noneChecked; + if (noneChecked) { + el[id].checked = false; + } + }); +} + +tinyMCEPopup.onInit.add(init); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/tabfocus/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/tabfocus/editor_plugin_src.js new file mode 100644 index 0000000..94f4532 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/tabfocus/editor_plugin_src.js @@ -0,0 +1,122 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, explode = tinymce.explode; + + tinymce.create('tinymce.plugins.TabFocusPlugin', { + init : function(ed, url) { + function tabCancel(ed, e) { + if (e.keyCode === 9) + return Event.cancel(e); + } + + function tabHandler(ed, e) { + var x, i, f, el, v; + + function find(d) { + el = DOM.select(':input:enabled,*[tabindex]:not(iframe)'); + + function canSelectRecursive(e) { + return e.nodeName==="BODY" || (e.type != 'hidden' && + !(e.style.display == "none") && + !(e.style.visibility == "hidden") && canSelectRecursive(e.parentNode)); + } + function canSelectInOldIe(el) { + return el.attributes["tabIndex"].specified || el.nodeName == "INPUT" || el.nodeName == "TEXTAREA"; + } + function isOldIe() { + return tinymce.isIE6 || tinymce.isIE7; + } + function canSelect(el) { + return ((!isOldIe() || canSelectInOldIe(el))) && el.getAttribute("tabindex") != '-1' && canSelectRecursive(el); + } + + each(el, function(e, i) { + if (e.id == ed.id) { + x = i; + return false; + } + }); + if (d > 0) { + for (i = x + 1; i < el.length; i++) { + if (canSelect(el[i])) + return el[i]; + } + } else { + for (i = x - 1; i >= 0; i--) { + if (canSelect(el[i])) + return el[i]; + } + } + + return null; + } + + if (e.keyCode === 9) { + v = explode(ed.getParam('tab_focus', ed.getParam('tabfocus_elements', ':prev,:next'))); + + if (v.length == 1) { + v[1] = v[0]; + v[0] = ':prev'; + } + + // Find element to focus + if (e.shiftKey) { + if (v[0] == ':prev') + el = find(-1); + else + el = DOM.get(v[0]); + } else { + if (v[1] == ':next') + el = find(1); + else + el = DOM.get(v[1]); + } + + if (el) { + if (el.id && (ed = tinymce.get(el.id || el.name))) + ed.focus(); + else + window.setTimeout(function() { + if (!tinymce.isWebKit) + window.focus(); + el.focus(); + }, 10); + + return Event.cancel(e); + } + } + } + + ed.onKeyUp.add(tabCancel); + + if (tinymce.isGecko) { + ed.onKeyPress.add(tabHandler); + ed.onKeyDown.add(tabCancel); + } else + ed.onKeyDown.add(tabHandler); + + }, + + getInfo : function() { + return { + longname : 'Tabfocus', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('tabfocus', tinymce.plugins.TabFocusPlugin); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/editor_plugin_src.js new file mode 100644 index 0000000..0456483 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/editor_plugin_src.js @@ -0,0 +1,1456 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var each = tinymce.each; + + // Checks if the selection/caret is at the start of the specified block element + function isAtStart(rng, par) { + var doc = par.ownerDocument, rng2 = doc.createRange(), elm; + + rng2.setStartBefore(par); + rng2.setEnd(rng.endContainer, rng.endOffset); + + elm = doc.createElement('body'); + elm.appendChild(rng2.cloneContents()); + + // Check for text characters of other elements that should be treated as content + return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0; + }; + + function getSpanVal(td, name) { + return parseInt(td.getAttribute(name) || 1); + } + + /** + * Table Grid class. + */ + function TableGrid(table, dom, selection) { + var grid, startPos, endPos, selectedCell; + + buildGrid(); + selectedCell = dom.getParent(selection.getStart(), 'th,td'); + if (selectedCell) { + startPos = getPos(selectedCell); + endPos = findEndPos(); + selectedCell = getCell(startPos.x, startPos.y); + } + + function cloneNode(node, children) { + node = node.cloneNode(children); + node.removeAttribute('id'); + + return node; + } + + function buildGrid() { + var startY = 0; + + grid = []; + + each(['thead', 'tbody', 'tfoot'], function(part) { + var rows = dom.select('> ' + part + ' tr', table); + + each(rows, function(tr, y) { + y += startY; + + each(dom.select('> td, > th', tr), function(td, x) { + var x2, y2, rowspan, colspan; + + // Skip over existing cells produced by rowspan + if (grid[y]) { + while (grid[y][x]) + x++; + } + + // Get col/rowspan from cell + rowspan = getSpanVal(td, 'rowspan'); + colspan = getSpanVal(td, 'colspan'); + + // Fill out rowspan/colspan right and down + for (y2 = y; y2 < y + rowspan; y2++) { + if (!grid[y2]) + grid[y2] = []; + + for (x2 = x; x2 < x + colspan; x2++) { + grid[y2][x2] = { + part : part, + real : y2 == y && x2 == x, + elm : td, + rowspan : rowspan, + colspan : colspan + }; + } + } + }); + }); + + startY += rows.length; + }); + }; + + function getCell(x, y) { + var row; + + row = grid[y]; + if (row) + return row[x]; + }; + + function setSpanVal(td, name, val) { + if (td) { + val = parseInt(val); + + if (val === 1) + td.removeAttribute(name, 1); + else + td.setAttribute(name, val, 1); + } + } + + function isCellSelected(cell) { + return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell); + }; + + function getSelectedRows() { + var rows = []; + + each(table.rows, function(row) { + each(row.cells, function(cell) { + if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) { + rows.push(row); + return false; + } + }); + }); + + return rows; + }; + + function deleteTable() { + var rng = dom.createRng(); + + rng.setStartAfter(table); + rng.setEndAfter(table); + + selection.setRng(rng); + + dom.remove(table); + }; + + function cloneCell(cell) { + var formatNode; + + // Clone formats + tinymce.walk(cell, function(node) { + var curNode; + + if (node.nodeType == 3) { + each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) { + node = cloneNode(node, false); + + if (!formatNode) + formatNode = curNode = node; + else if (curNode) + curNode.appendChild(node); + + curNode = node; + }); + + // Add something to the inner node + if (curNode) + curNode.innerHTML = tinymce.isIE && !tinymce.isIE11 ? ' ' : '
'; + + return false; + } + }, 'childNodes'); + + cell = cloneNode(cell, false); + setSpanVal(cell, 'rowSpan', 1); + setSpanVal(cell, 'colSpan', 1); + + if (formatNode) { + cell.appendChild(formatNode); + } else { + if (!tinymce.isIE || tinymce.isIE11) + cell.innerHTML = '
'; + } + + return cell; + }; + + function cleanup() { + var rng = dom.createRng(); + + // Empty rows + each(dom.select('tr', table), function(tr) { + if (tr.cells.length == 0) + dom.remove(tr); + }); + + // Empty table + if (dom.select('tr', table).length == 0) { + rng.setStartAfter(table); + rng.setEndAfter(table); + selection.setRng(rng); + dom.remove(table); + return; + } + + // Empty header/body/footer + each(dom.select('thead,tbody,tfoot', table), function(part) { + if (part.rows.length == 0) + dom.remove(part); + }); + + // Restore selection to start position if it still exists + buildGrid(); + + // Restore the selection to the closest table position + row = grid[Math.min(grid.length - 1, startPos.y)]; + if (row) { + selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true); + selection.collapse(true); + } + }; + + function fillLeftDown(x, y, rows, cols) { + var tr, x2, r, c, cell; + + tr = grid[y][x].elm.parentNode; + for (r = 1; r <= rows; r++) { + tr = dom.getNext(tr, 'tr'); + + if (tr) { + // Loop left to find real cell + for (x2 = x; x2 >= 0; x2--) { + cell = grid[y + r][x2].elm; + + if (cell.parentNode == tr) { + // Append clones after + for (c = 1; c <= cols; c++) + dom.insertAfter(cloneCell(cell), cell); + + break; + } + } + + if (x2 == -1) { + // Insert nodes before first cell + for (c = 1; c <= cols; c++) + tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]); + } + } + } + }; + + function split() { + each(grid, function(row, y) { + each(row, function(cell, x) { + var colSpan, rowSpan, newCell, i; + + if (isCellSelected(cell)) { + cell = cell.elm; + colSpan = getSpanVal(cell, 'colspan'); + rowSpan = getSpanVal(cell, 'rowspan'); + + if (colSpan > 1 || rowSpan > 1) { + setSpanVal(cell, 'rowSpan', 1); + setSpanVal(cell, 'colSpan', 1); + + // Insert cells right + for (i = 0; i < colSpan - 1; i++) + dom.insertAfter(cloneCell(cell), cell); + + fillLeftDown(x, y, rowSpan - 1, colSpan); + } + } + }); + }); + }; + + function merge(cell, cols, rows) { + var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count; + + // Use specified cell and cols/rows + if (cell) { + pos = getPos(cell); + startX = pos.x; + startY = pos.y; + endX = startX + (cols - 1); + endY = startY + (rows - 1); + } else { + startPos = endPos = null; + + // Calculate start/end pos by checking for selected cells in grid works better with context menu + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell)) { + if (!startPos) { + startPos = {x: x, y: y}; + } + + endPos = {x: x, y: y}; + } + }); + }); + + // Use selection + startX = startPos.x; + startY = startPos.y; + endX = endPos.x; + endY = endPos.y; + } + + // Find start/end cells + startCell = getCell(startX, startY); + endCell = getCell(endX, endY); + + // Check if the cells exists and if they are of the same part for example tbody = tbody + if (startCell && endCell && startCell.part == endCell.part) { + // Split and rebuild grid + split(); + buildGrid(); + + // Set row/col span to start cell + startCell = getCell(startX, startY).elm; + setSpanVal(startCell, 'colSpan', (endX - startX) + 1); + setSpanVal(startCell, 'rowSpan', (endY - startY) + 1); + + // Remove other cells and add it's contents to the start cell + for (y = startY; y <= endY; y++) { + for (x = startX; x <= endX; x++) { + if (!grid[y] || !grid[y][x]) + continue; + + cell = grid[y][x].elm; + + if (cell != startCell) { + // Move children to startCell + children = tinymce.grep(cell.childNodes); + each(children, function(node) { + startCell.appendChild(node); + }); + + // Remove bogus nodes if there is children in the target cell + if (children.length) { + children = tinymce.grep(startCell.childNodes); + count = 0; + each(children, function(node) { + if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1) + startCell.removeChild(node); + }); + } + + // Remove cell + dom.remove(cell); + } + } + } + + // Remove empty rows etc and restore caret location + cleanup(); + } + }; + + function insertRow(before) { + var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan; + + // Find first/last row + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell)) { + cell = cell.elm; + rowElm = cell.parentNode; + newRow = cloneNode(rowElm, false); + posY = y; + + if (before) + return false; + } + }); + + if (before) + return !posY; + }); + + for (x = 0; x < grid[0].length; x++) { + // Cell not found could be because of an invalid table structure + if (!grid[posY][x]) + continue; + + cell = grid[posY][x].elm; + + if (cell != lastCell) { + if (!before) { + rowSpan = getSpanVal(cell, 'rowspan'); + if (rowSpan > 1) { + setSpanVal(cell, 'rowSpan', rowSpan + 1); + continue; + } + } else { + // Check if cell above can be expanded + if (posY > 0 && grid[posY - 1][x]) { + otherCell = grid[posY - 1][x].elm; + rowSpan = getSpanVal(otherCell, 'rowSpan'); + if (rowSpan > 1) { + setSpanVal(otherCell, 'rowSpan', rowSpan + 1); + continue; + } + } + } + + // Insert new cell into new row + newCell = cloneCell(cell); + setSpanVal(newCell, 'colSpan', cell.colSpan); + + newRow.appendChild(newCell); + + lastCell = cell; + } + } + + if (newRow.hasChildNodes()) { + if (!before) + dom.insertAfter(newRow, rowElm); + else + rowElm.parentNode.insertBefore(newRow, rowElm); + } + }; + + function insertCol(before) { + var posX, lastCell; + + // Find first/last column + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell)) { + posX = x; + + if (before) + return false; + } + }); + + if (before) + return !posX; + }); + + each(grid, function(row, y) { + var cell, rowSpan, colSpan; + + if (!row[posX]) + return; + + cell = row[posX].elm; + if (cell != lastCell) { + colSpan = getSpanVal(cell, 'colspan'); + rowSpan = getSpanVal(cell, 'rowspan'); + + if (colSpan == 1) { + if (!before) { + dom.insertAfter(cloneCell(cell), cell); + fillLeftDown(posX, y, rowSpan - 1, colSpan); + } else { + cell.parentNode.insertBefore(cloneCell(cell), cell); + fillLeftDown(posX, y, rowSpan - 1, colSpan); + } + } else + setSpanVal(cell, 'colSpan', cell.colSpan + 1); + + lastCell = cell; + } + }); + }; + + function deleteCols() { + var cols = []; + + // Get selected column indexes + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) { + each(grid, function(row) { + var cell = row[x].elm, colSpan; + + colSpan = getSpanVal(cell, 'colSpan'); + + if (colSpan > 1) + setSpanVal(cell, 'colSpan', colSpan - 1); + else + dom.remove(cell); + }); + + cols.push(x); + } + }); + }); + + cleanup(); + }; + + function deleteRows() { + var rows; + + function deleteRow(tr) { + var nextTr, pos, lastCell; + + nextTr = dom.getNext(tr, 'tr'); + + // Move down row spanned cells + each(tr.cells, function(cell) { + var rowSpan = getSpanVal(cell, 'rowSpan'); + + if (rowSpan > 1) { + setSpanVal(cell, 'rowSpan', rowSpan - 1); + pos = getPos(cell); + fillLeftDown(pos.x, pos.y, 1, 1); + } + }); + + // Delete cells + pos = getPos(tr.cells[0]); + each(grid[pos.y], function(cell) { + var rowSpan; + + cell = cell.elm; + + if (cell != lastCell) { + rowSpan = getSpanVal(cell, 'rowSpan'); + + if (rowSpan <= 1) + dom.remove(cell); + else + setSpanVal(cell, 'rowSpan', rowSpan - 1); + + lastCell = cell; + } + }); + }; + + // Get selected rows and move selection out of scope + rows = getSelectedRows(); + + // Delete all selected rows + each(rows.reverse(), function(tr) { + deleteRow(tr); + }); + + cleanup(); + }; + + function cutRows() { + var rows = getSelectedRows(); + + dom.remove(rows); + cleanup(); + + return rows; + }; + + function copyRows() { + var rows = getSelectedRows(); + + each(rows, function(row, i) { + rows[i] = cloneNode(row, true); + }); + + return rows; + }; + + function pasteRows(rows, before) { + // If we don't have any rows in the clipboard, return immediately + if(!rows) + return; + + var selectedRows = getSelectedRows(), + targetRow = selectedRows[before ? 0 : selectedRows.length - 1], + targetCellCount = targetRow.cells.length; + + // Calc target cell count + each(grid, function(row) { + var match; + + targetCellCount = 0; + each(row, function(cell, x) { + if (cell.real) + targetCellCount += cell.colspan; + + if (cell.elm.parentNode == targetRow) + match = 1; + }); + + if (match) + return false; + }); + + if (!before) + rows.reverse(); + + each(rows, function(row) { + var cellCount = row.cells.length, cell; + + // Remove col/rowspans + for (i = 0; i < cellCount; i++) { + cell = row.cells[i]; + setSpanVal(cell, 'colSpan', 1); + setSpanVal(cell, 'rowSpan', 1); + } + + // Needs more cells + for (i = cellCount; i < targetCellCount; i++) + row.appendChild(cloneCell(row.cells[cellCount - 1])); + + // Needs less cells + for (i = targetCellCount; i < cellCount; i++) + dom.remove(row.cells[i]); + + // Add before/after + if (before) + targetRow.parentNode.insertBefore(row, targetRow); + else + dom.insertAfter(row, targetRow); + }); + + // Remove current selection + dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); + }; + + function getPos(target) { + var pos; + + each(grid, function(row, y) { + each(row, function(cell, x) { + if (cell.elm == target) { + pos = {x : x, y : y}; + return false; + } + }); + + return !pos; + }); + + return pos; + }; + + function setStartCell(cell) { + startPos = getPos(cell); + }; + + function findEndPos() { + var pos, maxX, maxY; + + maxX = maxY = 0; + + each(grid, function(row, y) { + each(row, function(cell, x) { + var colSpan, rowSpan; + + if (isCellSelected(cell)) { + cell = grid[y][x]; + + if (x > maxX) + maxX = x; + + if (y > maxY) + maxY = y; + + if (cell.real) { + colSpan = cell.colspan - 1; + rowSpan = cell.rowspan - 1; + + if (colSpan) { + if (x + colSpan > maxX) + maxX = x + colSpan; + } + + if (rowSpan) { + if (y + rowSpan > maxY) + maxY = y + rowSpan; + } + } + } + }); + }); + + return {x : maxX, y : maxY}; + }; + + function setEndCell(cell) { + var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan; + + endPos = getPos(cell); + + if (startPos && endPos) { + // Get start/end positions + startX = Math.min(startPos.x, endPos.x); + startY = Math.min(startPos.y, endPos.y); + endX = Math.max(startPos.x, endPos.x); + endY = Math.max(startPos.y, endPos.y); + + // Expand end positon to include spans + maxX = endX; + maxY = endY; + + // Expand startX + for (y = startY; y <= maxY; y++) { + cell = grid[y][startX]; + + if (!cell.real) { + if (startX - (cell.colspan - 1) < startX) + startX -= cell.colspan - 1; + } + } + + // Expand startY + for (x = startX; x <= maxX; x++) { + cell = grid[startY][x]; + + if (!cell.real) { + if (startY - (cell.rowspan - 1) < startY) + startY -= cell.rowspan - 1; + } + } + + // Find max X, Y + for (y = startY; y <= endY; y++) { + for (x = startX; x <= endX; x++) { + cell = grid[y][x]; + + if (cell.real) { + colSpan = cell.colspan - 1; + rowSpan = cell.rowspan - 1; + + if (colSpan) { + if (x + colSpan > maxX) + maxX = x + colSpan; + } + + if (rowSpan) { + if (y + rowSpan > maxY) + maxY = y + rowSpan; + } + } + } + } + + // Remove current selection + dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); + + // Add new selection + for (y = startY; y <= maxY; y++) { + for (x = startX; x <= maxX; x++) { + if (grid[y][x]) + dom.addClass(grid[y][x].elm, 'mceSelected'); + } + } + } + }; + + // Expose to public + tinymce.extend(this, { + deleteTable : deleteTable, + split : split, + merge : merge, + insertRow : insertRow, + insertCol : insertCol, + deleteCols : deleteCols, + deleteRows : deleteRows, + cutRows : cutRows, + copyRows : copyRows, + pasteRows : pasteRows, + getPos : getPos, + setStartCell : setStartCell, + setEndCell : setEndCell + }); + }; + + tinymce.create('tinymce.plugins.TablePlugin', { + init : function(ed, url) { + var winMan, clipboardRows, hasCellSelection = true; // Might be selected cells on reload + + function createTableGrid(node) { + var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table'); + + if (tblElm) + return new TableGrid(tblElm, ed.dom, selection); + }; + + function cleanup() { + // Restore selection possibilities + ed.getBody().style.webkitUserSelect = ''; + + if (hasCellSelection) { + ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); + hasCellSelection = false; + } + }; + + // Register buttons + each([ + ['table', 'table.desc', 'mceInsertTable', true], + ['delete_table', 'table.del', 'mceTableDelete'], + ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'], + ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'], + ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'], + ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'], + ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'], + ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'], + ['row_props', 'table.row_desc', 'mceTableRowProps', true], + ['cell_props', 'table.cell_desc', 'mceTableCellProps', true], + ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true], + ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true] + ], function(c) { + ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]}); + }); + + // Select whole table is a table border is clicked + if (!tinymce.isIE) { + ed.onClick.add(function(ed, e) { + e = e.target; + + if (e.nodeName === 'TABLE') { + ed.selection.select(e); + ed.nodeChanged(); + } + }); + } + + ed.onPreProcess.add(function(ed, args) { + var nodes, i, node, dom = ed.dom, value; + + nodes = dom.select('table', args.node); + i = nodes.length; + while (i--) { + node = nodes[i]; + dom.setAttrib(node, 'data-mce-style', ''); + + if ((value = dom.getAttrib(node, 'width'))) { + dom.setStyle(node, 'width', value); + dom.setAttrib(node, 'width', ''); + } + + if ((value = dom.getAttrib(node, 'height'))) { + dom.setStyle(node, 'height', value); + dom.setAttrib(node, 'height', ''); + } + } + }); + + // Handle node change updates + ed.onNodeChange.add(function(ed, cm, n) { + var p; + + n = ed.selection.getStart(); + p = ed.dom.getParent(n, 'td,th,caption'); + cm.setActive('table', n.nodeName === 'TABLE' || !!p); + + // Disable table tools if we are in caption + if (p && p.nodeName === 'CAPTION') + p = 0; + + cm.setDisabled('delete_table', !p); + cm.setDisabled('delete_col', !p); + cm.setDisabled('delete_table', !p); + cm.setDisabled('delete_row', !p); + cm.setDisabled('col_after', !p); + cm.setDisabled('col_before', !p); + cm.setDisabled('row_after', !p); + cm.setDisabled('row_before', !p); + cm.setDisabled('row_props', !p); + cm.setDisabled('cell_props', !p); + cm.setDisabled('split_cells', !p); + cm.setDisabled('merge_cells', !p); + }); + + ed.onInit.add(function(ed) { + var startTable, startCell, dom = ed.dom, tableGrid; + + winMan = ed.windowManager; + + // Add cell selection logic + ed.onMouseDown.add(function(ed, e) { + if (e.button != 2) { + cleanup(); + + startCell = dom.getParent(e.target, 'td,th'); + startTable = dom.getParent(startCell, 'table'); + } + }); + + dom.bind(ed.getDoc(), 'mouseover', function(e) { + var sel, table, target = e.target; + + if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) { + table = dom.getParent(target, 'table'); + if (table == startTable) { + if (!tableGrid) { + tableGrid = createTableGrid(table); + tableGrid.setStartCell(startCell); + + ed.getBody().style.webkitUserSelect = 'none'; + } + + tableGrid.setEndCell(target); + hasCellSelection = true; + } + + // Remove current selection + sel = ed.selection.getSel(); + + try { + if (sel.removeAllRanges) + sel.removeAllRanges(); + else + sel.empty(); + } catch (ex) { + // IE9 might throw errors here + } + + e.preventDefault(); + } + }); + + ed.onMouseUp.add(function(ed, e) { + var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode; + + // Move selection to startCell + if (startCell) { + if (tableGrid) + ed.getBody().style.webkitUserSelect = ''; + + function setPoint(node, start) { + var walker = new tinymce.dom.TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); + + return; + } + } while (node = (start ? walker.next() : walker.prev())); + } + + // Try to expand text selection as much as we can only Gecko supports cell selection + selectedCells = dom.select('td.mceSelected,th.mceSelected'); + if (selectedCells.length > 0) { + rng = dom.createRng(); + node = selectedCells[0]; + endNode = selectedCells[selectedCells.length - 1]; + rng.setStartBefore(node); + rng.setEndAfter(node); + + setPoint(node, 1); + walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table')); + + do { + if (node.nodeName == 'TD' || node.nodeName == 'TH') { + if (!dom.hasClass(node, 'mceSelected')) + break; + + lastNode = node; + } + } while (node = walker.next()); + + setPoint(lastNode); + + sel.setRng(rng); + } + + ed.nodeChanged(); + startCell = tableGrid = startTable = null; + } + }); + + ed.onKeyUp.add(function(ed, e) { + cleanup(); + }); + + ed.onKeyDown.add(function (ed, e) { + fixTableCellSelection(ed); + }); + + ed.onMouseDown.add(function (ed, e) { + if (e.button != 2) { + fixTableCellSelection(ed); + } + }); + function tableCellSelected(ed, rng, n, currentCell) { + // The decision of when a table cell is selected is somewhat involved. The fact that this code is + // required is actually a pointer to the root cause of this bug. A cell is selected when the start + // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases) + // or the parent of the table (in the case of the selection containing the last cell of a table). + var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'), + tableParent, allOfCellSelected, tableCellSelection; + if (table) + tableParent = table.parentNode; + allOfCellSelected =rng.startContainer.nodeType == TEXT_NODE && + rng.startOffset == 0 && + rng.endOffset == 0 && + currentCell && + (n.nodeName=="TR" || n==tableParent); + tableCellSelection = (n.nodeName=="TD"||n.nodeName=="TH")&& !currentCell; + return allOfCellSelected || tableCellSelection; + // return false; + } + + // this nasty hack is here to work around some WebKit selection bugs. + function fixTableCellSelection(ed) { + if (!tinymce.isWebKit) + return; + + var rng = ed.selection.getRng(); + var n = ed.selection.getNode(); + var currentCell = ed.dom.getParent(rng.startContainer, 'TD,TH'); + + if (!tableCellSelected(ed, rng, n, currentCell)) + return; + if (!currentCell) { + currentCell=n; + } + + // Get the very last node inside the table cell + var end = currentCell.lastChild; + while (end.lastChild) + end = end.lastChild; + + // Select the entire table cell. Nothing outside of the table cell should be selected. + rng.setEnd(end, end.nodeValue.length); + ed.selection.setRng(rng); + } + ed.plugins.table.fixTableCellSelection=fixTableCellSelection; + + // Add context menu + if (ed && ed.plugins.contextmenu) { + ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) { + var sm, se = ed.selection, el = se.getNode() || ed.getBody(); + + if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) { + m.removeAll(); + + if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) { + m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); + m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); + m.addSeparator(); + } + + if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) { + m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); + m.addSeparator(); + } + + m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}}); + m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'}); + m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'}); + m.addSeparator(); + + // Cell menu + sm = m.addMenu({title : 'table.cell'}); + sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'}); + sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'}); + sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'}); + + // Row menu + sm = m.addMenu({title : 'table.row'}); + sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'}); + sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'}); + sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'}); + sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'}); + sm.addSeparator(); + sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'}); + sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'}); + sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows); + sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows); + + // Column menu + sm = m.addMenu({title : 'table.col'}); + sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'}); + sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'}); + sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'}); + } else + m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'}); + }); + } + + // Fix to allow navigating up and down in a table in WebKit browsers. + if (tinymce.isWebKit) { + function moveSelection(ed, e) { + var VK = tinymce.VK; + var key = e.keyCode; + + function handle(upBool, sourceNode, event) { + var siblingDirection = upBool ? 'previousSibling' : 'nextSibling'; + var currentRow = ed.dom.getParent(sourceNode, 'tr'); + var siblingRow = currentRow[siblingDirection]; + + if (siblingRow) { + moveCursorToRow(ed, sourceNode, siblingRow, upBool); + tinymce.dom.Event.cancel(event); + return true; + } else { + var tableNode = ed.dom.getParent(currentRow, 'table'); + var middleNode = currentRow.parentNode; + var parentNodeName = middleNode.nodeName.toLowerCase(); + if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) { + var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody'); + if (targetParent !== null) { + return moveToRowInTarget(upBool, targetParent, sourceNode, event); + } + } + return escapeTable(upBool, currentRow, siblingDirection, tableNode, event); + } + } + + function getTargetParent(upBool, topNode, secondNode, nodeName) { + var tbodies = ed.dom.select('>' + nodeName, topNode); + var position = tbodies.indexOf(secondNode); + if (upBool && position === 0 || !upBool && position === tbodies.length - 1) { + return getFirstHeadOrFoot(upBool, topNode); + } else if (position === -1) { + var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1; + return tbodies[topOrBottom]; + } else { + return tbodies[position + (upBool ? -1 : 1)]; + } + } + + function getFirstHeadOrFoot(upBool, parent) { + var tagName = upBool ? 'thead' : 'tfoot'; + var headOrFoot = ed.dom.select('>' + tagName, parent); + return headOrFoot.length !== 0 ? headOrFoot[0] : null; + } + + function moveToRowInTarget(upBool, targetParent, sourceNode, event) { + var targetRow = getChildForDirection(targetParent, upBool); + targetRow && moveCursorToRow(ed, sourceNode, targetRow, upBool); + tinymce.dom.Event.cancel(event); + return true; + } + + function escapeTable(upBool, currentRow, siblingDirection, table, event) { + var tableSibling = table[siblingDirection]; + if (tableSibling) { + moveCursorToStartOfElement(tableSibling); + return true; + } else { + var parentCell = ed.dom.getParent(table, 'td,th'); + if (parentCell) { + return handle(upBool, parentCell, event); + } else { + var backUpSibling = getChildForDirection(currentRow, !upBool); + moveCursorToStartOfElement(backUpSibling); + return tinymce.dom.Event.cancel(event); + } + } + } + + function getChildForDirection(parent, up) { + var child = parent && parent[up ? 'lastChild' : 'firstChild']; + // BR is not a valid table child to return in this case we return the table cell + return child && child.nodeName === 'BR' ? ed.dom.getParent(child, 'td,th') : child; + } + + function moveCursorToStartOfElement(n) { + ed.selection.setCursorLocation(n, 0); + } + + function isVerticalMovement() { + return key == VK.UP || key == VK.DOWN; + } + + function isInTable(ed) { + var node = ed.selection.getNode(); + var currentRow = ed.dom.getParent(node, 'tr'); + return currentRow !== null; + } + + function columnIndex(column) { + var colIndex = 0; + var c = column; + while (c.previousSibling) { + c = c.previousSibling; + colIndex = colIndex + getSpanVal(c, "colspan"); + } + return colIndex; + } + + function findColumn(rowElement, columnIndex) { + var c = 0; + var r = 0; + each(rowElement.children, function(cell, i) { + c = c + getSpanVal(cell, "colspan"); + r = i; + if (c > columnIndex) + return false; + }); + return r; + } + + function moveCursorToRow(ed, node, row, upBool) { + var srcColumnIndex = columnIndex(ed.dom.getParent(node, 'td,th')); + var tgtColumnIndex = findColumn(row, srcColumnIndex); + var tgtNode = row.childNodes[tgtColumnIndex]; + var rowCellTarget = getChildForDirection(tgtNode, upBool); + moveCursorToStartOfElement(rowCellTarget || tgtNode); + } + + function shouldFixCaret(preBrowserNode) { + var newNode = ed.selection.getNode(); + var newParent = ed.dom.getParent(newNode, 'td,th'); + var oldParent = ed.dom.getParent(preBrowserNode, 'td,th'); + return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent) + } + + function checkSameParentTable(nodeOne, NodeTwo) { + return ed.dom.getParent(nodeOne, 'TABLE') === ed.dom.getParent(NodeTwo, 'TABLE'); + } + + if (isVerticalMovement() && isInTable(ed)) { + var preBrowserNode = ed.selection.getNode(); + setTimeout(function() { + if (shouldFixCaret(preBrowserNode)) { + handle(!e.shiftKey && key === VK.UP, preBrowserNode, e); + } + }, 0); + } + } + + ed.onKeyDown.add(moveSelection); + } + + // Fixes an issue on Gecko where it's impossible to place the caret behind a table + // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled + function fixTableCaretPos() { + var last; + + // Skip empty text nodes form the end + for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ; + + if (last && last.nodeName == 'TABLE') { + if (ed.settings.forced_root_block) + ed.dom.add(ed.getBody(), ed.settings.forced_root_block, null, tinymce.isIE && !tinymce.isIE11 ? ' ' : '
'); + else + ed.dom.add(ed.getBody(), 'br', {'data-mce-bogus': '1'}); + } + }; + + // Fixes an bug where it's impossible to place the caret before a table in Gecko + // this fix solves it by detecting when the caret is at the beginning of such a table + // and then manually moves the caret infront of the table + if (tinymce.isGecko) { + ed.onKeyDown.add(function(ed, e) { + var rng, table, dom = ed.dom; + + // On gecko it's not possible to place the caret before a table + if (e.keyCode == 37 || e.keyCode == 38) { + rng = ed.selection.getRng(); + table = dom.getParent(rng.startContainer, 'table'); + + if (table && ed.getBody().firstChild == table) { + if (isAtStart(rng, table)) { + rng = dom.createRng(); + + rng.setStartBefore(table); + rng.setEndBefore(table); + + ed.selection.setRng(rng); + + e.preventDefault(); + } + } + } + }); + } + + ed.onKeyUp.add(fixTableCaretPos); + ed.onSetContent.add(fixTableCaretPos); + ed.onVisualAid.add(fixTableCaretPos); + + ed.onPreProcess.add(function(ed, o) { + var last = o.node.lastChild; + + if (last && (last.nodeName == "BR" || (last.childNodes.length == 1 && (last.firstChild.nodeName == 'BR' || last.firstChild.nodeValue == '\u00a0'))) && last.previousSibling && last.previousSibling.nodeName == "TABLE") { + ed.dom.remove(last); + } + }); + + + /** + * Fixes bug in Gecko where shift-enter in table cell does not place caret on new line + * + * Removed: Since the new enter logic seems to fix this one. + */ + /* + if (tinymce.isGecko) { + ed.onKeyDown.add(function(ed, e) { + if (e.keyCode === tinymce.VK.ENTER && e.shiftKey) { + var node = ed.selection.getRng().startContainer; + var tableCell = dom.getParent(node, 'td,th'); + if (tableCell) { + var zeroSizedNbsp = ed.getDoc().createTextNode("\uFEFF"); + dom.insertAfter(zeroSizedNbsp, node); + } + } + }); + } + */ + + fixTableCaretPos(); + ed.startContent = ed.getContent({format : 'raw'}); + }); + + // Register action commands + each({ + mceTableSplitCells : function(grid) { + grid.split(); + }, + + mceTableMergeCells : function(grid) { + var rowSpan, colSpan, cell; + + cell = ed.dom.getParent(ed.selection.getNode(), 'th,td'); + if (cell) { + rowSpan = cell.rowSpan; + colSpan = cell.colSpan; + } + + if (!ed.dom.select('td.mceSelected,th.mceSelected').length) { + winMan.open({ + url : url + '/merge_cells.htm', + width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)), + height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)), + inline : 1 + }, { + rows : rowSpan, + cols : colSpan, + onaction : function(data) { + grid.merge(cell, data.cols, data.rows); + }, + plugin_url : url + }); + } else + grid.merge(); + }, + + mceTableInsertRowBefore : function(grid) { + grid.insertRow(true); + }, + + mceTableInsertRowAfter : function(grid) { + grid.insertRow(); + }, + + mceTableInsertColBefore : function(grid) { + grid.insertCol(true); + }, + + mceTableInsertColAfter : function(grid) { + grid.insertCol(); + }, + + mceTableDeleteCol : function(grid) { + grid.deleteCols(); + }, + + mceTableDeleteRow : function(grid) { + grid.deleteRows(); + }, + + mceTableCutRow : function(grid) { + clipboardRows = grid.cutRows(); + }, + + mceTableCopyRow : function(grid) { + clipboardRows = grid.copyRows(); + }, + + mceTablePasteRowBefore : function(grid) { + grid.pasteRows(clipboardRows, true); + }, + + mceTablePasteRowAfter : function(grid) { + grid.pasteRows(clipboardRows); + }, + + mceTableDelete : function(grid) { + grid.deleteTable(); + } + }, function(func, name) { + ed.addCommand(name, function() { + var grid = createTableGrid(); + + if (grid) { + func(grid); + ed.execCommand('mceRepaint'); + cleanup(); + } + }); + }); + + // Register dialog commands + each({ + mceInsertTable : function(val) { + winMan.open({ + url : url + '/table.htm', + width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)), + height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + action : val ? val.action : 0 + }); + }, + + mceTableRowProps : function() { + winMan.open({ + url : url + '/row.htm', + width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)), + height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }, + + mceTableCellProps : function() { + winMan.open({ + url : url + '/cell.htm', + width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)), + height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + } + }, function(func, name) { + ed.addCommand(name, function(ui, val) { + func(val); + }); + }); + } + }); + + // Register plugin + tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin); +})(tinymce); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/js/cell.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/js/cell.js new file mode 100644 index 0000000..6f77e67 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/table/js/cell.js @@ -0,0 +1,319 @@ +tinyMCEPopup.requireLangPack(); + +var ed; + +function init() { + ed = tinyMCEPopup.editor; + tinyMCEPopup.resizeToInnerSize(); + + document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); + document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor'); + document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor') + + var inst = ed; + var tdElm = ed.dom.getParent(ed.selection.getStart(), "td,th"); + var formObj = document.forms[0]; + var st = ed.dom.parseStyle(ed.dom.getAttrib(tdElm, "style")); + + // Get table cell data + var celltype = tdElm.nodeName.toLowerCase(); + var align = ed.dom.getAttrib(tdElm, 'align'); + var valign = ed.dom.getAttrib(tdElm, 'valign'); + var width = trimSize(getStyle(tdElm, 'width', 'width')); + var height = trimSize(getStyle(tdElm, 'height', 'height')); + var bordercolor = convertRGBToHex(getStyle(tdElm, 'bordercolor', 'borderLeftColor')); + var bgcolor = convertRGBToHex(getStyle(tdElm, 'bgcolor', 'backgroundColor')); + var className = ed.dom.getAttrib(tdElm, 'class'); + var backgroundimage = getStyle(tdElm, 'background', 'backgroundImage').replace(new RegExp("url\\(['\"]?([^'\"]*)['\"]?\\)", 'gi'), "$1"); + var id = ed.dom.getAttrib(tdElm, 'id'); + var lang = ed.dom.getAttrib(tdElm, 'lang'); + var dir = ed.dom.getAttrib(tdElm, 'dir'); + var scope = ed.dom.getAttrib(tdElm, 'scope'); + + // Setup form + addClassesToList('class', 'table_cell_styles'); + TinyMCE_EditableSelects.init(); + + if (!ed.dom.hasClass(tdElm, 'mceSelected')) { + formObj.bordercolor.value = bordercolor; + formObj.bgcolor.value = bgcolor; + formObj.backgroundimage.value = backgroundimage; + formObj.width.value = width; + formObj.height.value = height; + formObj.id.value = id; + formObj.lang.value = lang; + formObj.style.value = ed.dom.serializeStyle(st); + selectByValue(formObj, 'align', align); + selectByValue(formObj, 'valign', valign); + selectByValue(formObj, 'class', className, true, true); + selectByValue(formObj, 'celltype', celltype); + selectByValue(formObj, 'dir', dir); + selectByValue(formObj, 'scope', scope); + + // Resize some elements + if (isVisible('backgroundimagebrowser')) + document.getElementById('backgroundimage').style.width = '180px'; + + updateColor('bordercolor_pick', 'bordercolor'); + updateColor('bgcolor_pick', 'bgcolor'); + } else + tinyMCEPopup.dom.hide('action'); +} + +function updateAction() { + var el, inst = ed, tdElm, trElm, tableElm, formObj = document.forms[0]; + + if (!AutoValidator.validate(formObj)) { + tinyMCEPopup.alert(AutoValidator.getErrorMessages(formObj).join('. ') + '.'); + return false; + } + + tinyMCEPopup.restoreSelection(); + el = ed.selection.getStart(); + tdElm = ed.dom.getParent(el, "td,th"); + trElm = ed.dom.getParent(el, "tr"); + tableElm = ed.dom.getParent(el, "table"); + + // Cell is selected + if (ed.dom.hasClass(tdElm, 'mceSelected')) { + // Update all selected sells + tinymce.each(ed.dom.select('td.mceSelected,th.mceSelected'), function(td) { + updateCell(td); + }); + + ed.addVisual(); + ed.nodeChanged(); + inst.execCommand('mceEndUndoLevel'); + tinyMCEPopup.close(); + return; + } + + switch (getSelectValue(formObj, 'action')) { + case "cell": + var celltype = getSelectValue(formObj, 'celltype'); + var scope = getSelectValue(formObj, 'scope'); + + function doUpdate(s) { + if (s) { + updateCell(tdElm); + + ed.addVisual(); + ed.nodeChanged(); + inst.execCommand('mceEndUndoLevel'); + tinyMCEPopup.close(); + } + }; + + if (ed.getParam("accessibility_warnings", 1)) { + if (celltype == "th" && scope == "") + tinyMCEPopup.confirm(ed.getLang('table_dlg.missing_scope', '', true), doUpdate); + else + doUpdate(1); + + return; + } + + updateCell(tdElm); + break; + + case "row": + var cell = trElm.firstChild; + + if (cell.nodeName != "TD" && cell.nodeName != "TH") + cell = nextCell(cell); + + do { + cell = updateCell(cell, true); + } while ((cell = nextCell(cell)) != null); + + break; + + case "col": + var curr, col = 0, cell = trElm.firstChild, rows = tableElm.getElementsByTagName("tr"); + + if (cell.nodeName != "TD" && cell.nodeName != "TH") + cell = nextCell(cell); + + do { + if (cell == tdElm) + break; + col += cell.getAttribute("colspan")?cell.getAttribute("colspan"):1; + } while ((cell = nextCell(cell)) != null); + + for (var i=0; i colLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.col_limit').replace(/\{\$cols\}/g, colLimit)); + return false; + } else if (rowLimit && rows > rowLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.row_limit').replace(/\{\$rows\}/g, rowLimit)); + return false; + } else if (cellLimit && cols * rows > cellLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.cell_limit').replace(/\{\$cells\}/g, cellLimit)); + return false; + } + + // Update table + if (action == "update") { + dom.setAttrib(elm, 'cellPadding', cellpadding, true); + dom.setAttrib(elm, 'cellSpacing', cellspacing, true); + + if (!isCssSize(border)) { + dom.setAttrib(elm, 'border', border); + } else { + dom.setAttrib(elm, 'border', ''); + } + + if (border == '') { + dom.setStyle(elm, 'border-width', ''); + dom.setStyle(elm, 'border', ''); + dom.setAttrib(elm, 'border', ''); + } + + dom.setAttrib(elm, 'align', align); + dom.setAttrib(elm, 'frame', frame); + dom.setAttrib(elm, 'rules', rules); + dom.setAttrib(elm, 'class', className); + dom.setAttrib(elm, 'style', style); + dom.setAttrib(elm, 'id', id); + dom.setAttrib(elm, 'summary', summary); + dom.setAttrib(elm, 'dir', dir); + dom.setAttrib(elm, 'lang', lang); + + capEl = inst.dom.select('caption', elm)[0]; + + if (capEl && !caption) + capEl.parentNode.removeChild(capEl); + + if (!capEl && caption) { + capEl = elm.ownerDocument.createElement('caption'); + + if (!tinymce.isIE || tinymce.isIE11) + capEl.innerHTML = '
'; + + elm.insertBefore(capEl, elm.firstChild); + } + + if (width && inst.settings.inline_styles) { + dom.setStyle(elm, 'width', width); + dom.setAttrib(elm, 'width', ''); + } else { + dom.setAttrib(elm, 'width', width, true); + dom.setStyle(elm, 'width', ''); + } + + // Remove these since they are not valid XHTML + dom.setAttrib(elm, 'borderColor', ''); + dom.setAttrib(elm, 'bgColor', ''); + dom.setAttrib(elm, 'background', ''); + + if (height && inst.settings.inline_styles) { + dom.setStyle(elm, 'height', height); + dom.setAttrib(elm, 'height', ''); + } else { + dom.setAttrib(elm, 'height', height, true); + dom.setStyle(elm, 'height', ''); + } + + if (background != '') + elm.style.backgroundImage = "url('" + background + "')"; + else + elm.style.backgroundImage = ''; + +/* if (tinyMCEPopup.getParam("inline_styles")) { + if (width != '') + elm.style.width = getCSSSize(width); + }*/ + + if (bordercolor != "") { + elm.style.borderColor = bordercolor; + elm.style.borderStyle = elm.style.borderStyle == "" ? "solid" : elm.style.borderStyle; + elm.style.borderWidth = cssSize(border); + } else + elm.style.borderColor = ''; + + elm.style.backgroundColor = bgcolor; + elm.style.height = getCSSSize(height); + + inst.addVisual(); + + // Fix for stange MSIE align bug + //elm.outerHTML = elm.outerHTML; + + inst.nodeChanged(); + inst.execCommand('mceEndUndoLevel', false, {}, {skip_undo: true}); + + // Repaint if dimensions changed + if (formObj.width.value != orgTableWidth || formObj.height.value != orgTableHeight) + inst.execCommand('mceRepaint'); + + tinyMCEPopup.close(); + return true; + } + + // Create new table + html += ''); + + tinymce.each('h1,h2,h3,h4,h5,h6,p'.split(','), function(n) { + if (patt) + patt += ','; + + patt += n + ' ._mce_marker'; + }); + + tinymce.each(inst.dom.select(patt), function(n) { + inst.dom.split(inst.dom.getParent(n, 'h1,h2,h3,h4,h5,h6,p'), n); + }); + + dom.setOuterHTML(dom.select('br._mce_marker')[0], html); + } else + inst.execCommand('mceInsertContent', false, html); + + tinymce.each(dom.select('table[data-mce-new]'), function(node) { + var tdorth = dom.select('td,th', node); + + // Fixes a bug in IE where the caret cannot be placed after the table if the table is at the end of the document + if (tinymce.isIE && !tinymce.isIE11 && node.nextSibling == null) { + if (inst.settings.forced_root_block) + dom.insertAfter(dom.create(inst.settings.forced_root_block), node); + else + dom.insertAfter(dom.create('br', {'data-mce-bogus': '1'}), node); + } + + try { + // IE9 might fail to do this selection + inst.selection.setCursorLocation(tdorth[0], 0); + } catch (ex) { + // Ignore + } + + dom.setAttrib(node, 'data-mce-new', ''); + }); + + inst.addVisual(); + inst.execCommand('mceEndUndoLevel', false, {}, {skip_undo: true}); + + tinyMCEPopup.close(); +} + +function makeAttrib(attrib, value) { + var formObj = document.forms[0]; + var valueElm = formObj.elements[attrib]; + + if (typeof(value) == "undefined" || value == null) { + value = ""; + + if (valueElm) + value = valueElm.value; + } + + if (value == "") + return ""; + + // XML encode it + value = value.replace(/&/g, '&'); + value = value.replace(/\"/g, '"'); + value = value.replace(//g, '>'); + + return ' ' + attrib + '="' + value + '"'; +} + +function init() { + tinyMCEPopup.resizeToInnerSize(); + + document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); + document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); + document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor'); + document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); + + var cols = 2, rows = 2, border = tinyMCEPopup.getParam('table_default_border', '0'), cellpadding = tinyMCEPopup.getParam('table_default_cellpadding', ''), cellspacing = tinyMCEPopup.getParam('table_default_cellspacing', ''); + var align = "", width = "", height = "", bordercolor = "", bgcolor = "", className = ""; + var id = "", summary = "", style = "", dir = "", lang = "", background = "", bgcolor = "", bordercolor = "", rules = "", frame = ""; + var inst = tinyMCEPopup.editor, dom = inst.dom; + var formObj = document.forms[0]; + var elm = dom.getParent(inst.selection.getNode(), "table"); + + // Hide advanced fields that isn't available in the schema + tinymce.each("summary id rules dir style frame".split(" "), function(name) { + var tr = tinyMCEPopup.dom.getParent(name, "tr") || tinyMCEPopup.dom.getParent("t" + name, "tr"); + + if (tr && !tinyMCEPopup.editor.schema.isValid("table", name)) { + tr.style.display = 'none'; + } + }); + + action = tinyMCEPopup.getWindowArg('action'); + + if (!action) + action = elm ? "update" : "insert"; + + if (elm && action != "insert") { + var rowsAr = elm.rows; + var cols = 0; + for (var i=0; i cols) + cols = rowsAr[i].cells.length; + + cols = cols; + rows = rowsAr.length; + + st = dom.parseStyle(dom.getAttrib(elm, "style")); + border = trimSize(getStyle(elm, 'border', 'borderWidth')); + cellpadding = dom.getAttrib(elm, 'cellpadding', ""); + cellspacing = dom.getAttrib(elm, 'cellspacing', ""); + width = trimSize(getStyle(elm, 'width', 'width')); + height = trimSize(getStyle(elm, 'height', 'height')); + bordercolor = convertRGBToHex(getStyle(elm, 'bordercolor', 'borderLeftColor')); + bgcolor = convertRGBToHex(getStyle(elm, 'bgcolor', 'backgroundColor')); + align = dom.getAttrib(elm, 'align', align); + frame = dom.getAttrib(elm, 'frame'); + rules = dom.getAttrib(elm, 'rules'); + className = tinymce.trim(dom.getAttrib(elm, 'class').replace(/mceItem.+/g, '')); + id = dom.getAttrib(elm, 'id'); + summary = dom.getAttrib(elm, 'summary'); + style = dom.serializeStyle(st); + dir = dom.getAttrib(elm, 'dir'); + lang = dom.getAttrib(elm, 'lang'); + background = getStyle(elm, 'background', 'backgroundImage').replace(new RegExp("url\\(['\"]?([^'\"]*)['\"]?\\)", 'gi'), "$1"); + formObj.caption.checked = elm.getElementsByTagName('caption').length > 0; + + orgTableWidth = width; + orgTableHeight = height; + + action = "update"; + formObj.insert.value = inst.getLang('update'); + } + + addClassesToList('class', "table_styles"); + TinyMCE_EditableSelects.init(); + + // Update form + selectByValue(formObj, 'align', align); + selectByValue(formObj, 'tframe', frame); + selectByValue(formObj, 'rules', rules); + selectByValue(formObj, 'class', className, true, true); + formObj.cols.value = cols; + formObj.rows.value = rows; + formObj.border.value = border; + formObj.cellpadding.value = cellpadding; + formObj.cellspacing.value = cellspacing; + formObj.width.value = width; + formObj.height.value = height; + formObj.bordercolor.value = bordercolor; + formObj.bgcolor.value = bgcolor; + formObj.id.value = id; + formObj.summary.value = summary; + formObj.style.value = style; + formObj.dir.value = dir; + formObj.lang.value = lang; + formObj.backgroundimage.value = background; + + updateColor('bordercolor_pick', 'bordercolor'); + updateColor('bgcolor_pick', 'bgcolor'); + + // Resize some elements + if (isVisible('backgroundimagebrowser')) + document.getElementById('backgroundimage').style.width = '180px'; + + // Disable some fields in update mode + if (action == "update") { + formObj.cols.disabled = true; + formObj.rows.disabled = true; + } +} + +function changedSize() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + +/* var width = formObj.width.value; + if (width != "") + st['width'] = tinyMCEPopup.getParam("inline_styles") ? getCSSSize(width) : ""; + else + st['width'] = "";*/ + + var height = formObj.height.value; + if (height != "") + st['height'] = getCSSSize(height); + else + st['height'] = ""; + + formObj.style.value = dom.serializeStyle(st); +} + +function isCssSize(value) { + return /^[0-9.]+(%|in|cm|mm|em|ex|pt|pc|px)$/.test(value); +} + +function cssSize(value, def) { + value = tinymce.trim(value || def); + + if (!isCssSize(value)) { + return parseInt(value, 10) + 'px'; + } + + return value; +} + +function changedBackgroundImage() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + st['background-image'] = "url('" + formObj.backgroundimage.value + "')"; + + formObj.style.value = dom.serializeStyle(st); +} + +function changedBorder() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + // Update border width if the element has a color + if (formObj.border.value != "" && (isCssSize(formObj.border.value) || formObj.bordercolor.value != "")) + st['border-width'] = cssSize(formObj.border.value); + else { + if (!formObj.border.value) { + st['border'] = ''; + st['border-width'] = ''; + } + } + + formObj.style.value = dom.serializeStyle(st); +} + +function changedColor() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + st['background-color'] = formObj.bgcolor.value; + + if (formObj.bordercolor.value != "") { + st['border-color'] = formObj.bordercolor.value; + + // Add border-width if it's missing + if (!st['border-width']) + st['border-width'] = cssSize(formObj.border.value, 1); + } + + formObj.style.value = dom.serializeStyle(st); +} + +function changedStyle() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + if (st['background-image']) + formObj.backgroundimage.value = st['background-image'].replace(new RegExp("url\\(['\"]?([^'\"]*)['\"]?\\)", 'gi'), "$1"); + else + formObj.backgroundimage.value = ''; + + if (st['width']) + formObj.width.value = trimSize(st['width']); + + if (st['height']) + formObj.height.value = trimSize(st['height']); + + if (st['background-color']) { + formObj.bgcolor.value = st['background-color']; + updateColor('bgcolor_pick','bgcolor'); + } + + if (st['border-color']) { + formObj.bordercolor.value = st['border-color']; + updateColor('bordercolor_pick','bordercolor'); + } +} + +tinyMCEPopup.onInit.add(init); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/template/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/template/editor_plugin_src.js new file mode 100644 index 0000000..28baed2 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/template/editor_plugin_src.js @@ -0,0 +1,159 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each; + + tinymce.create('tinymce.plugins.TemplatePlugin', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceTemplate', function(ui) { + ed.windowManager.open({ + file : url + '/template.htm', + width : ed.getParam('template_popup_width', 750), + height : ed.getParam('template_popup_height', 600), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceInsertTemplate', t._insertTemplate, t); + + // Register buttons + ed.addButton('template', {title : 'template.desc', cmd : 'mceTemplate'}); + + ed.onPreProcess.add(function(ed, o) { + var dom = ed.dom; + + each(dom.select('div', o.node), function(e) { + if (dom.hasClass(e, 'mceTmpl')) { + each(dom.select('*', e), function(e) { + if (dom.hasClass(e, ed.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|'))) + e.innerHTML = t._getDateTime(new Date(), ed.getParam("template_mdate_format", ed.getLang("template.mdate_format"))); + }); + + t._replaceVals(e); + } + }); + }); + }, + + getInfo : function() { + return { + longname : 'Template plugin', + author : 'Moxiecode Systems AB', + authorurl : 'http://www.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/template', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + _insertTemplate : function(ui, v) { + var t = this, ed = t.editor, h, el, dom = ed.dom, sel = ed.selection.getContent(); + + h = v.content; + + each(t.editor.getParam('template_replace_values'), function(v, k) { + if (typeof(v) != 'function') + h = h.replace(new RegExp('\\{\\$' + k + '\\}', 'g'), v); + }); + + el = dom.create('div', null, h); + + // Find template element within div + n = dom.select('.mceTmpl', el); + if (n && n.length > 0) { + el = dom.create('div', null); + el.appendChild(n[0].cloneNode(true)); + } + + function hasClass(n, c) { + return new RegExp('\\b' + c + '\\b', 'g').test(n.className); + }; + + each(dom.select('*', el), function(n) { + // Replace cdate + if (hasClass(n, ed.getParam('template_cdate_classes', 'cdate').replace(/\s+/g, '|'))) + n.innerHTML = t._getDateTime(new Date(), ed.getParam("template_cdate_format", ed.getLang("template.cdate_format"))); + + // Replace mdate + if (hasClass(n, ed.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|'))) + n.innerHTML = t._getDateTime(new Date(), ed.getParam("template_mdate_format", ed.getLang("template.mdate_format"))); + + // Replace selection + if (hasClass(n, ed.getParam('template_selected_content_classes', 'selcontent').replace(/\s+/g, '|'))) + n.innerHTML = sel; + }); + + t._replaceVals(el); + + ed.execCommand('mceInsertContent', false, el.innerHTML); + ed.addVisual(); + }, + + _replaceVals : function(e) { + var dom = this.editor.dom, vl = this.editor.getParam('template_replace_values'); + + each(dom.select('*', e), function(e) { + each(vl, function(v, k) { + if (dom.hasClass(e, k)) { + if (typeof(vl[k]) == 'function') + vl[k](e); + } + }); + }); + }, + + _getDateTime : function(d, fmt) { + if (!fmt) + return ""; + + function addZeros(value, len) { + var i; + + value = "" + value; + + if (value.length < len) { + for (i=0; i<(len-value.length); i++) + value = "0" + value; + } + + return value; + } + + fmt = fmt.replace("%D", "%m/%d/%y"); + fmt = fmt.replace("%r", "%I:%M:%S %p"); + fmt = fmt.replace("%Y", "" + d.getFullYear()); + fmt = fmt.replace("%y", "" + d.getYear()); + fmt = fmt.replace("%m", addZeros(d.getMonth()+1, 2)); + fmt = fmt.replace("%d", addZeros(d.getDate(), 2)); + fmt = fmt.replace("%H", "" + addZeros(d.getHours(), 2)); + fmt = fmt.replace("%M", "" + addZeros(d.getMinutes(), 2)); + fmt = fmt.replace("%S", "" + addZeros(d.getSeconds(), 2)); + fmt = fmt.replace("%I", "" + ((d.getHours() + 11) % 12 + 1)); + fmt = fmt.replace("%p", "" + (d.getHours() < 12 ? "AM" : "PM")); + fmt = fmt.replace("%B", "" + this.editor.getLang("template_months_long").split(',')[d.getMonth()]); + fmt = fmt.replace("%b", "" + this.editor.getLang("template_months_short").split(',')[d.getMonth()]); + fmt = fmt.replace("%A", "" + this.editor.getLang("template_day_long").split(',')[d.getDay()]); + fmt = fmt.replace("%a", "" + this.editor.getLang("template_day_short").split(',')[d.getDay()]); + fmt = fmt.replace("%%", "%"); + + return fmt; + } + }); + + // Register plugin + tinymce.PluginManager.add('template', tinymce.plugins.TemplatePlugin); +})(); \ No newline at end of file diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/template/js/template.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/template/js/template.js new file mode 100644 index 0000000..673395a --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/template/js/template.js @@ -0,0 +1,106 @@ +tinyMCEPopup.requireLangPack(); + +var TemplateDialog = { + preInit : function() { + var url = tinyMCEPopup.getParam("template_external_list_url"); + + if (url != null) + document.write(''); + }, + + init : function() { + var ed = tinyMCEPopup.editor, tsrc, sel, x, u; + + tsrc = ed.getParam("template_templates", false); + sel = document.getElementById('tpath'); + + // Setup external template list + if (!tsrc && typeof(tinyMCETemplateList) != 'undefined') { + for (x=0, tsrc = []; x'); + }); + }, + + selectTemplate : function(u, ti) { + var d = window.frames['templatesrc'].document, x, tsrc = this.tsrc; + + if (!u) + return; + + d.body.innerHTML = this.templateHTML = this.getFileContents(u); + + for (x=0; x$1'); + + div = ed.dom.create('div', null, nv); + while (node = div.lastChild) + ed.dom.insertAfter(node, nl[i]); + + ed.dom.remove(nl[i]); + } + } else { + nl = ed.dom.select('span.mceItemNbsp', b); + + for (i = nl.length - 1; i >= 0; i--) + ed.dom.remove(nl[i], 1); + } + + s.moveToBookmark(bm); + } + }); + + // Register plugin + tinymce.PluginManager.add('visualchars', tinymce.plugins.VisualChars); +})(); \ No newline at end of file diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/wordcount/editor_plugin_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/wordcount/editor_plugin_src.js new file mode 100644 index 0000000..32ac677 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/wordcount/editor_plugin_src.js @@ -0,0 +1,122 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.WordCount', { + block : 0, + id : null, + countre : null, + cleanre : null, + + init : function(ed, url) { + var t = this, last = 0, VK = tinymce.VK; + + t.countre = ed.getParam('wordcount_countregex', /[\w\u2019\u00co-\u00ff^\uc397^u00f7\'-]+/g); // u2019 == ’ u00c0-u00ff extended latin chars with diacritical marks. exclude uc397 multiplication & u00f7 division + t.cleanre = ed.getParam('wordcount_cleanregex', /[0-9.(),;:!?%#$?\'\"_+=\\\/-]*/g); + t.update_rate = ed.getParam('wordcount_update_rate', 2000); + t.update_on_delete = ed.getParam('wordcount_update_on_delete', false); + t.id = ed.id + '-word-count'; + + ed.onPostRender.add(function(ed, cm) { + var row, id; + + // Add it to the specified id or the theme advanced path + id = ed.getParam('wordcount_target_id'); + if (!id) { + row = tinymce.DOM.get(ed.id + '_path_row'); + + if (row) + tinymce.DOM.add(row.parentNode, 'div', {'style': 'float: right'}, ed.getLang('wordcount.words', 'Words: ') + '0'); + } else { + tinymce.DOM.add(id, 'span', {}, '0'); + } + }); + + ed.onInit.add(function(ed) { + ed.selection.onSetContent.add(function() { + t._count(ed); + }); + + t._count(ed); + }); + + ed.onSetContent.add(function(ed) { + t._count(ed); + }); + + function checkKeys(key) { + return key !== last && (key === VK.ENTER || last === VK.SPACEBAR || checkDelOrBksp(last)); + } + + function checkDelOrBksp(key) { + return key === VK.DELETE || key === VK.BACKSPACE; + } + + ed.onKeyUp.add(function(ed, e) { + if (checkKeys(e.keyCode) || t.update_on_delete && checkDelOrBksp(e.keyCode)) { + t._count(ed); + } + + last = e.keyCode; + }); + }, + + _getCount : function(ed) { + var tc = 0; + var tx = ed.getContent({ format: 'raw' }); + + if (tx) { + tx = tx.replace(/\.\.\./g, ' '); // convert ellipses to spaces + tx = tx.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' '); // remove html tags and space chars + + // deal with html entities + tx = tx.replace(/(\w+)(&.+?;)+(\w+)/, "$1$3").replace(/&.+?;/g, ' '); + tx = tx.replace(this.cleanre, ''); // remove numbers and punctuation + + var wordArray = tx.match(this.countre); + if (wordArray) { + tc = wordArray.length; + } + } + + return tc; + }, + + _count : function(ed) { + var t = this; + + // Keep multiple calls from happening at the same time + if (t.block) + return; + + t.block = 1; + + setTimeout(function() { + if (!ed.destroyed) { + var tc = t._getCount(ed); + tinymce.DOM.setHTML(t.id, tc.toString()); + setTimeout(function() {t.block = 0;}, t.update_rate); + } + }, 1); + }, + + getInfo: function() { + return { + longname : 'Word Count plugin', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + tinymce.PluginManager.add('wordcount', tinymce.plugins.WordCount); +})(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/xhtmlxtras/js/attributes.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/xhtmlxtras/js/attributes.js new file mode 100644 index 0000000..9e9b07e --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/xhtmlxtras/js/attributes.js @@ -0,0 +1,111 @@ +/** + * attributes.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +function init() { + tinyMCEPopup.resizeToInnerSize(); + var inst = tinyMCEPopup.editor; + var dom = inst.dom; + var elm = inst.selection.getNode(); + var f = document.forms[0]; + var onclick = dom.getAttrib(elm, 'onclick'); + + setFormValue('title', dom.getAttrib(elm, 'title')); + setFormValue('id', dom.getAttrib(elm, 'id')); + setFormValue('style', dom.getAttrib(elm, "style")); + setFormValue('dir', dom.getAttrib(elm, 'dir')); + setFormValue('lang', dom.getAttrib(elm, 'lang')); + setFormValue('tabindex', dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : "")); + setFormValue('accesskey', dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : "")); + setFormValue('onfocus', dom.getAttrib(elm, 'onfocus')); + setFormValue('onblur', dom.getAttrib(elm, 'onblur')); + setFormValue('onclick', onclick); + setFormValue('ondblclick', dom.getAttrib(elm, 'ondblclick')); + setFormValue('onmousedown', dom.getAttrib(elm, 'onmousedown')); + setFormValue('onmouseup', dom.getAttrib(elm, 'onmouseup')); + setFormValue('onmouseover', dom.getAttrib(elm, 'onmouseover')); + setFormValue('onmousemove', dom.getAttrib(elm, 'onmousemove')); + setFormValue('onmouseout', dom.getAttrib(elm, 'onmouseout')); + setFormValue('onkeypress', dom.getAttrib(elm, 'onkeypress')); + setFormValue('onkeydown', dom.getAttrib(elm, 'onkeydown')); + setFormValue('onkeyup', dom.getAttrib(elm, 'onkeyup')); + className = dom.getAttrib(elm, 'class'); + + addClassesToList('classlist', 'advlink_styles'); + selectByValue(f, 'classlist', className, true); + + TinyMCE_EditableSelects.init(); +} + +function setFormValue(name, value) { + if(value && document.forms[0].elements[name]){ + document.forms[0].elements[name].value = value; + } +} + +function insertAction() { + var inst = tinyMCEPopup.editor; + var elm = inst.selection.getNode(); + + setAllAttribs(elm); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); +} + +function setAttrib(elm, attrib, value) { + var formObj = document.forms[0]; + var valueElm = formObj.elements[attrib.toLowerCase()]; + var inst = tinyMCEPopup.editor; + var dom = inst.dom; + + if (typeof(value) == "undefined" || value == null) { + value = ""; + + if (valueElm) + value = valueElm.value; + } + + dom.setAttrib(elm, attrib.toLowerCase(), value); +} + +function setAllAttribs(elm) { + var f = document.forms[0]; + + setAttrib(elm, 'title'); + setAttrib(elm, 'id'); + setAttrib(elm, 'style'); + setAttrib(elm, 'class', getSelectValue(f, 'classlist')); + setAttrib(elm, 'dir'); + setAttrib(elm, 'lang'); + setAttrib(elm, 'tabindex'); + setAttrib(elm, 'accesskey'); + setAttrib(elm, 'onfocus'); + setAttrib(elm, 'onblur'); + setAttrib(elm, 'onclick'); + setAttrib(elm, 'ondblclick'); + setAttrib(elm, 'onmousedown'); + setAttrib(elm, 'onmouseup'); + setAttrib(elm, 'onmouseover'); + setAttrib(elm, 'onmousemove'); + setAttrib(elm, 'onmouseout'); + setAttrib(elm, 'onkeypress'); + setAttrib(elm, 'onkeydown'); + setAttrib(elm, 'onkeyup'); + + // Refresh in old MSIE +// if (tinyMCE.isMSIE5) +// elm.outerHTML = elm.outerHTML; +} + +function insertAttribute() { + tinyMCEPopup.close(); +} + +tinyMCEPopup.onInit.add(init); +tinyMCEPopup.requireLangPack(); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/xhtmlxtras/js/element_common.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/xhtmlxtras/js/element_common.js new file mode 100644 index 0000000..6df1b77 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/plugins/xhtmlxtras/js/element_common.js @@ -0,0 +1,229 @@ +/** + * element_common.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +tinyMCEPopup.requireLangPack(); + +function initCommonAttributes(elm) { + var formObj = document.forms[0], dom = tinyMCEPopup.editor.dom; + + // Setup form data for common element attributes + setFormValue('title', dom.getAttrib(elm, 'title')); + setFormValue('id', dom.getAttrib(elm, 'id')); + selectByValue(formObj, 'class', dom.getAttrib(elm, 'class'), true); + setFormValue('style', dom.getAttrib(elm, 'style')); + selectByValue(formObj, 'dir', dom.getAttrib(elm, 'dir')); + setFormValue('lang', dom.getAttrib(elm, 'lang')); + setFormValue('onfocus', dom.getAttrib(elm, 'onfocus')); + setFormValue('onblur', dom.getAttrib(elm, 'onblur')); + setFormValue('onclick', dom.getAttrib(elm, 'onclick')); + setFormValue('ondblclick', dom.getAttrib(elm, 'ondblclick')); + setFormValue('onmousedown', dom.getAttrib(elm, 'onmousedown')); + setFormValue('onmouseup', dom.getAttrib(elm, 'onmouseup')); + setFormValue('onmouseover', dom.getAttrib(elm, 'onmouseover')); + setFormValue('onmousemove', dom.getAttrib(elm, 'onmousemove')); + setFormValue('onmouseout', dom.getAttrib(elm, 'onmouseout')); + setFormValue('onkeypress', dom.getAttrib(elm, 'onkeypress')); + setFormValue('onkeydown', dom.getAttrib(elm, 'onkeydown')); + setFormValue('onkeyup', dom.getAttrib(elm, 'onkeyup')); +} + +function setFormValue(name, value) { + if(document.forms[0].elements[name]) document.forms[0].elements[name].value = value; +} + +function insertDateTime(id) { + document.getElementById(id).value = getDateTime(new Date(), "%Y-%m-%dT%H:%M:%S"); +} + +function getDateTime(d, fmt) { + fmt = fmt.replace("%D", "%m/%d/%y"); + fmt = fmt.replace("%r", "%I:%M:%S %p"); + fmt = fmt.replace("%Y", "" + d.getFullYear()); + fmt = fmt.replace("%y", "" + d.getYear()); + fmt = fmt.replace("%m", addZeros(d.getMonth()+1, 2)); + fmt = fmt.replace("%d", addZeros(d.getDate(), 2)); + fmt = fmt.replace("%H", "" + addZeros(d.getHours(), 2)); + fmt = fmt.replace("%M", "" + addZeros(d.getMinutes(), 2)); + fmt = fmt.replace("%S", "" + addZeros(d.getSeconds(), 2)); + fmt = fmt.replace("%I", "" + ((d.getHours() + 11) % 12 + 1)); + fmt = fmt.replace("%p", "" + (d.getHours() < 12 ? "AM" : "PM")); + fmt = fmt.replace("%%", "%"); + + return fmt; +} + +function addZeros(value, len) { + var i; + + value = "" + value; + + if (value.length < len) { + for (i=0; i<(len-value.length); i++) + value = "0" + value; + } + + return value; +} + +function selectByValue(form_obj, field_name, value, add_custom, ignore_case) { + if (!form_obj || !form_obj.elements[field_name]) + return; + + var sel = form_obj.elements[field_name]; + + var found = false; + for (var i=0; i 0) { + tagName = element_name; + + insertInlineElement(element_name); + var elementArray = tinymce.grep(SXE.inst.dom.select(element_name)); + for (var i=0; i -1) ? true : false; +} + +SXE.removeClass = function(elm,cl) { + if(elm.className == null || elm.className == "" || !SXE.containsClass(elm,cl)) { + return true; + } + var classNames = elm.className.split(" "); + var newClassNames = ""; + for (var x = 0, cnl = classNames.length; x < cnl; x++) { + if (classNames[x] != cl) { + newClassNames += (classNames[x] + " "); + } + } + elm.className = newClassNames.substring(0,newClassNames.length-1); //removes extra space at the end +} + +SXE.addClass = function(elm,cl) { + if(!SXE.containsClass(elm,cl)) elm.className ? elm.className += " " + cl : elm.className = cl; + return true; +} + +function insertInlineElement(en) { + var ed = tinyMCEPopup.editor, dom = ed.dom; + + ed.getDoc().execCommand('FontName', false, 'mceinline'); + tinymce.each(dom.select('span,font'), function(n) { + if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') + dom.replace(dom.create(en, {'data-mce-new' : 1}), n, 1); + }); +} diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/editor_template_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/editor_template_src.js new file mode 100644 index 0000000..84039ce --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/editor_template_src.js @@ -0,0 +1,1490 @@ +/** + * editor_template_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, each = tinymce.each, Cookie = tinymce.util.Cookie, lastExtID, explode = tinymce.explode; + + // Generates a preview for a format + function getPreviewCss(ed, fmt) { + var name, previewElm, dom = ed.dom, previewCss = '', parentFontSize, previewStylesName; + + previewStyles = ed.settings.preview_styles; + + // No preview forced + if (previewStyles === false) + return ''; + + // Default preview + if (!previewStyles) + previewStyles = 'font-family font-size font-weight text-decoration text-transform color background-color'; + + // Removes any variables since these can't be previewed + function removeVars(val) { + return val.replace(/%(\w+)/g, ''); + }; + + // Create block/inline element to use for preview + name = fmt.block || fmt.inline || 'span'; + previewElm = dom.create(name); + + // Add format styles to preview element + each(fmt.styles, function(value, name) { + value = removeVars(value); + + if (value) + dom.setStyle(previewElm, name, value); + }); + + // Add attributes to preview element + each(fmt.attributes, function(value, name) { + value = removeVars(value); + + if (value) + dom.setAttrib(previewElm, name, value); + }); + + // Add classes to preview element + each(fmt.classes, function(value) { + value = removeVars(value); + + if (!dom.hasClass(previewElm, value)) + dom.addClass(previewElm, value); + }); + + // Add the previewElm outside the visual area + dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF}); + ed.getBody().appendChild(previewElm); + + // Get parent container font size so we can compute px values out of em/% for older IE:s + parentFontSize = dom.getStyle(ed.getBody(), 'fontSize', true); + parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0; + + each(previewStyles.split(' '), function(name) { + var value = dom.getStyle(previewElm, name, true); + + // If background is transparent then check if the body has a background color we can use + if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) { + value = dom.getStyle(ed.getBody(), name, true); + + // Ignore white since it's the default color, not the nicest fix + if (dom.toHex(value).toLowerCase() == '#ffffff') { + return; + } + } + + // Old IE won't calculate the font size so we need to do that manually + if (name == 'font-size') { + if (/em|%$/.test(value)) { + if (parentFontSize === 0) { + return; + } + + // Convert font size from em/% to px + value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1); + value = (value * parentFontSize) + 'px'; + } + } + + previewCss += name + ':' + value + ';'; + }); + + dom.remove(previewElm); + + return previewCss; + }; + + // Tell it to load theme specific language pack(s) + tinymce.ThemeManager.requireLangPack('advanced'); + + tinymce.create('tinymce.themes.AdvancedTheme', { + sizes : [8, 10, 12, 14, 18, 24, 36], + + // Control name lookup, format: title, command + controls : { + bold : ['bold_desc', 'Bold'], + italic : ['italic_desc', 'Italic'], + underline : ['underline_desc', 'Underline'], + strikethrough : ['striketrough_desc', 'Strikethrough'], + justifyleft : ['justifyleft_desc', 'JustifyLeft'], + justifycenter : ['justifycenter_desc', 'JustifyCenter'], + justifyright : ['justifyright_desc', 'JustifyRight'], + justifyfull : ['justifyfull_desc', 'JustifyFull'], + bullist : ['bullist_desc', 'InsertUnorderedList'], + numlist : ['numlist_desc', 'InsertOrderedList'], + outdent : ['outdent_desc', 'Outdent'], + indent : ['indent_desc', 'Indent'], + cut : ['cut_desc', 'Cut'], + copy : ['copy_desc', 'Copy'], + paste : ['paste_desc', 'Paste'], + undo : ['undo_desc', 'Undo'], + redo : ['redo_desc', 'Redo'], + link : ['link_desc', 'mceLink'], + unlink : ['unlink_desc', 'unlink'], + image : ['image_desc', 'mceImage'], + cleanup : ['cleanup_desc', 'mceCleanup'], + help : ['help_desc', 'mceHelp'], + code : ['code_desc', 'mceCodeEditor'], + hr : ['hr_desc', 'InsertHorizontalRule'], + removeformat : ['removeformat_desc', 'RemoveFormat'], + sub : ['sub_desc', 'subscript'], + sup : ['sup_desc', 'superscript'], + forecolor : ['forecolor_desc', 'ForeColor'], + forecolorpicker : ['forecolor_desc', 'mceForeColor'], + backcolor : ['backcolor_desc', 'HiliteColor'], + backcolorpicker : ['backcolor_desc', 'mceBackColor'], + charmap : ['charmap_desc', 'mceCharMap'], + visualaid : ['visualaid_desc', 'mceToggleVisualAid'], + anchor : ['anchor_desc', 'mceInsertAnchor'], + newdocument : ['newdocument_desc', 'mceNewDocument'], + blockquote : ['blockquote_desc', 'mceBlockQuote'] + }, + + stateControls : ['bold', 'italic', 'underline', 'strikethrough', 'bullist', 'numlist', 'justifyleft', 'justifycenter', 'justifyright', 'justifyfull', 'sub', 'sup', 'blockquote'], + + init : function(ed, url) { + var t = this, s, v, o; + + t.editor = ed; + t.url = url; + t.onResolveName = new tinymce.util.Dispatcher(this); + s = ed.settings; + + ed.forcedHighContrastMode = ed.settings.detect_highcontrast && t._isHighContrast(); + ed.settings.skin = ed.forcedHighContrastMode ? 'highcontrast' : ed.settings.skin; + + // Setup default buttons + if (!s.theme_advanced_buttons1) { + s = extend({ + theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect", + theme_advanced_buttons2 : "bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code", + theme_advanced_buttons3 : "hr,removeformat,visualaid,|,sub,sup,|,charmap" + }, s); + } + + // Default settings + t.settings = s = extend({ + theme_advanced_path : true, + theme_advanced_toolbar_location : 'top', + theme_advanced_blockformats : "p,address,pre,h1,h2,h3,h4,h5,h6", + theme_advanced_toolbar_align : "left", + theme_advanced_statusbar_location : "bottom", + theme_advanced_fonts : "Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats", + theme_advanced_more_colors : 1, + theme_advanced_row_height : 23, + theme_advanced_resize_horizontal : 1, + theme_advanced_resizing_use_cookie : 1, + theme_advanced_font_sizes : "1,2,3,4,5,6,7", + theme_advanced_font_selector : "span", + theme_advanced_show_current_color: 0, + readonly : ed.settings.readonly + }, s); + + // Setup default font_size_style_values + if (!s.font_size_style_values) + s.font_size_style_values = "8pt,10pt,12pt,14pt,18pt,24pt,36pt"; + + if (tinymce.is(s.theme_advanced_font_sizes, 'string')) { + s.font_size_style_values = tinymce.explode(s.font_size_style_values); + s.font_size_classes = tinymce.explode(s.font_size_classes || ''); + + // Parse string value + o = {}; + ed.settings.theme_advanced_font_sizes = s.theme_advanced_font_sizes; + each(ed.getParam('theme_advanced_font_sizes', '', 'hash'), function(v, k) { + var cl; + + if (k == v && v >= 1 && v <= 7) { + k = v + ' (' + t.sizes[v - 1] + 'pt)'; + cl = s.font_size_classes[v - 1]; + v = s.font_size_style_values[v - 1] || (t.sizes[v - 1] + 'pt'); + } + + if (/^\s*\./.test(v)) + cl = v.replace(/\./g, ''); + + o[k] = cl ? {'class' : cl} : {fontSize : v}; + }); + + s.theme_advanced_font_sizes = o; + } + + if ((v = s.theme_advanced_path_location) && v != 'none') + s.theme_advanced_statusbar_location = s.theme_advanced_path_location; + + if (s.theme_advanced_statusbar_location == 'none') + s.theme_advanced_statusbar_location = 0; + + if (ed.settings.content_css !== false) + ed.contentCSS.push(ed.baseURI.toAbsolute(url + "/skins/" + ed.settings.skin + "/content.css")); + + // Init editor + ed.onInit.add(function() { + if (!ed.settings.readonly) { + ed.onNodeChange.add(t._nodeChanged, t); + ed.onKeyUp.add(t._updateUndoStatus, t); + ed.onMouseUp.add(t._updateUndoStatus, t); + ed.dom.bind(ed.dom.getRoot(), 'dragend', function() { + t._updateUndoStatus(ed); + }); + } + }); + + ed.onSetProgressState.add(function(ed, b, ti) { + var co, id = ed.id, tb; + + if (b) { + t.progressTimer = setTimeout(function() { + co = ed.getContainer(); + co = co.insertBefore(DOM.create('DIV', {style : 'position:relative'}), co.firstChild); + tb = DOM.get(ed.id + '_tbl'); + + DOM.add(co, 'div', {id : id + '_blocker', 'class' : 'mceBlocker', style : {width : tb.clientWidth + 2, height : tb.clientHeight + 2}}); + DOM.add(co, 'div', {id : id + '_progress', 'class' : 'mceProgress', style : {left : tb.clientWidth / 2, top : tb.clientHeight / 2}}); + }, ti || 0); + } else { + DOM.remove(id + '_blocker'); + DOM.remove(id + '_progress'); + clearTimeout(t.progressTimer); + } + }); + + DOM.loadCSS(s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : url + "/skins/" + ed.settings.skin + "/ui.css"); + + if (s.skin_variant) + DOM.loadCSS(url + "/skins/" + ed.settings.skin + "/ui_" + s.skin_variant + ".css"); + }, + + _isHighContrast : function() { + var actualColor, div = DOM.add(DOM.getRoot(), 'div', {'style': 'background-color: rgb(171,239,86);'}); + + actualColor = (DOM.getStyle(div, 'background-color', true) + '').toLowerCase().replace(/ /g, ''); + DOM.remove(div); + + return actualColor != 'rgb(171,239,86)' && actualColor != '#abef56'; + }, + + createControl : function(n, cf) { + var cd, c; + + if (c = cf.createControl(n)) + return c; + + switch (n) { + case "styleselect": + return this._createStyleSelect(); + + case "formatselect": + return this._createBlockFormats(); + + case "fontselect": + return this._createFontSelect(); + + case "fontsizeselect": + return this._createFontSizeSelect(); + + case "forecolor": + return this._createForeColorMenu(); + + case "backcolor": + return this._createBackColorMenu(); + } + + if ((cd = this.controls[n])) + return cf.createButton(n, {title : "advanced." + cd[0], cmd : cd[1], ui : cd[2], value : cd[3]}); + }, + + execCommand : function(cmd, ui, val) { + var f = this['_' + cmd]; + + if (f) { + f.call(this, ui, val); + return true; + } + + return false; + }, + + _importClasses : function(e) { + var ed = this.editor, ctrl = ed.controlManager.get('styleselect'); + + if (ctrl.getLength() == 0) { + each(ed.dom.getClasses(), function(o, idx) { + var name = 'style_' + idx, fmt; + + fmt = { + inline : 'span', + attributes : {'class' : o['class']}, + selector : '*' + }; + + ed.formatter.register(name, fmt); + + ctrl.add(o['class'], name, { + style: function() { + return getPreviewCss(ed, fmt); + } + }); + }); + } + }, + + _createStyleSelect : function(n) { + var t = this, ed = t.editor, ctrlMan = ed.controlManager, ctrl; + + // Setup style select box + ctrl = ctrlMan.createListBox('styleselect', { + title : 'advanced.style_select', + onselect : function(name) { + var matches, formatNames = [], removedFormat; + + each(ctrl.items, function(item) { + formatNames.push(item.value); + }); + + ed.focus(); + ed.undoManager.add(); + + // Toggle off the current format(s) + matches = ed.formatter.matchAll(formatNames); + tinymce.each(matches, function(match) { + if (!name || match == name) { + if (match) + ed.formatter.remove(match); + + removedFormat = true; + } + }); + + if (!removedFormat) + ed.formatter.apply(name); + + ed.undoManager.add(); + ed.nodeChanged(); + + return false; // No auto select + } + }); + + // Handle specified format + ed.onPreInit.add(function() { + var counter = 0, formats = ed.getParam('style_formats'); + + if (formats) { + each(formats, function(fmt) { + var name, keys = 0; + + each(fmt, function() {keys++;}); + + if (keys > 1) { + name = fmt.name = fmt.name || 'style_' + (counter++); + ed.formatter.register(name, fmt); + ctrl.add(fmt.title, name, { + style: function() { + return getPreviewCss(ed, fmt); + } + }); + } else + ctrl.add(fmt.title); + }); + } else { + each(ed.getParam('theme_advanced_styles', '', 'hash'), function(val, key) { + var name, fmt; + + if (val) { + name = 'style_' + (counter++); + fmt = { + inline : 'span', + classes : val, + selector : '*' + }; + + ed.formatter.register(name, fmt); + ctrl.add(t.editor.translate(key), name, { + style: function() { + return getPreviewCss(ed, fmt); + } + }); + } + }); + } + }); + + // Auto import classes if the ctrl box is empty + if (ctrl.getLength() == 0) { + ctrl.onPostRender.add(function(ed, n) { + if (!ctrl.NativeListBox) { + Event.add(n.id + '_text', 'focus', t._importClasses, t); + Event.add(n.id + '_text', 'mousedown', t._importClasses, t); + Event.add(n.id + '_open', 'focus', t._importClasses, t); + Event.add(n.id + '_open', 'mousedown', t._importClasses, t); + } else + Event.add(n.id, 'focus', t._importClasses, t); + }); + } + + return ctrl; + }, + + _createFontSelect : function() { + var c, t = this, ed = t.editor; + + c = ed.controlManager.createListBox('fontselect', { + title : 'advanced.fontdefault', + onselect : function(v) { + var cur = c.items[c.selectedIndex]; + + if (!v && cur) { + ed.execCommand('FontName', false, cur.value); + return; + } + + ed.execCommand('FontName', false, v); + + // Fake selection, execCommand will fire a nodeChange and update the selection + c.select(function(sv) { + return v == sv; + }); + + if (cur && cur.value == v) { + c.select(null); + } + + return false; // No auto select + } + }); + + if (c) { + each(ed.getParam('theme_advanced_fonts', t.settings.theme_advanced_fonts, 'hash'), function(v, k) { + c.add(ed.translate(k), v, {style : v.indexOf('dings') == -1 ? 'font-family:' + v : ''}); + }); + } + + return c; + }, + + _createFontSizeSelect : function() { + var t = this, ed = t.editor, c, i = 0, cl = []; + + c = ed.controlManager.createListBox('fontsizeselect', {title : 'advanced.font_size', onselect : function(v) { + var cur = c.items[c.selectedIndex]; + + if (!v && cur) { + cur = cur.value; + + if (cur['class']) { + ed.formatter.toggle('fontsize_class', {value : cur['class']}); + ed.undoManager.add(); + ed.nodeChanged(); + } else { + ed.execCommand('FontSize', false, cur.fontSize); + } + + return; + } + + if (v['class']) { + ed.focus(); + ed.undoManager.add(); + ed.formatter.toggle('fontsize_class', {value : v['class']}); + ed.undoManager.add(); + ed.nodeChanged(); + } else + ed.execCommand('FontSize', false, v.fontSize); + + // Fake selection, execCommand will fire a nodeChange and update the selection + c.select(function(sv) { + return v == sv; + }); + + if (cur && (cur.value.fontSize == v.fontSize || cur.value['class'] && cur.value['class'] == v['class'])) { + c.select(null); + } + + return false; // No auto select + }}); + + if (c) { + each(t.settings.theme_advanced_font_sizes, function(v, k) { + var fz = v.fontSize; + + if (fz >= 1 && fz <= 7) + fz = t.sizes[parseInt(fz) - 1] + 'pt'; + + c.add(k, v, {'style' : 'font-size:' + fz, 'class' : 'mceFontSize' + (i++) + (' ' + (v['class'] || ''))}); + }); + } + + return c; + }, + + _createBlockFormats : function() { + var c, fmts = { + p : 'advanced.paragraph', + address : 'advanced.address', + pre : 'advanced.pre', + h1 : 'advanced.h1', + h2 : 'advanced.h2', + h3 : 'advanced.h3', + h4 : 'advanced.h4', + h5 : 'advanced.h5', + h6 : 'advanced.h6', + div : 'advanced.div', + blockquote : 'advanced.blockquote', + code : 'advanced.code', + dt : 'advanced.dt', + dd : 'advanced.dd', + samp : 'advanced.samp' + }, t = this; + + c = t.editor.controlManager.createListBox('formatselect', {title : 'advanced.block', onselect : function(v) { + t.editor.execCommand('FormatBlock', false, v); + return false; + }}); + + if (c) { + each(t.editor.getParam('theme_advanced_blockformats', t.settings.theme_advanced_blockformats, 'hash'), function(v, k) { + c.add(t.editor.translate(k != v ? k : fmts[v]), v, {'class' : 'mce_formatPreview mce_' + v, style: function() { + return getPreviewCss(t.editor, {block: v}); + }}); + }); + } + + return c; + }, + + _createForeColorMenu : function() { + var c, t = this, s = t.settings, o = {}, v; + + if (s.theme_advanced_more_colors) { + o.more_colors_func = function() { + t._mceColorPicker(0, { + color : c.value, + func : function(co) { + c.setColor(co); + } + }); + }; + } + + if (v = s.theme_advanced_text_colors) + o.colors = v; + + if (s.theme_advanced_default_foreground_color) + o.default_color = s.theme_advanced_default_foreground_color; + + o.title = 'advanced.forecolor_desc'; + o.cmd = 'ForeColor'; + o.scope = this; + + c = t.editor.controlManager.createColorSplitButton('forecolor', o); + + return c; + }, + + _createBackColorMenu : function() { + var c, t = this, s = t.settings, o = {}, v; + + if (s.theme_advanced_more_colors) { + o.more_colors_func = function() { + t._mceColorPicker(0, { + color : c.value, + func : function(co) { + c.setColor(co); + } + }); + }; + } + + if (v = s.theme_advanced_background_colors) + o.colors = v; + + if (s.theme_advanced_default_background_color) + o.default_color = s.theme_advanced_default_background_color; + + o.title = 'advanced.backcolor_desc'; + o.cmd = 'HiliteColor'; + o.scope = this; + + c = t.editor.controlManager.createColorSplitButton('backcolor', o); + + return c; + }, + + renderUI : function(o) { + var n, ic, tb, t = this, ed = t.editor, s = t.settings, sc, p, nl; + + if (ed.settings) { + ed.settings.aria_label = s.aria_label + ed.getLang('advanced.help_shortcut'); + } + + // TODO: ACC Should have an aria-describedby attribute which is user-configurable to describe what this field is actually for. + // Maybe actually inherit it from the original textara? + n = p = DOM.create('span', {role : 'application', 'aria-labelledby' : ed.id + '_voice', id : ed.id + '_parent', 'class' : 'mceEditor ' + ed.settings.skin + 'Skin' + (s.skin_variant ? ' ' + ed.settings.skin + 'Skin' + t._ufirst(s.skin_variant) : '') + (ed.settings.directionality == "rtl" ? ' mceRtl' : '')}); + DOM.add(n, 'span', {'class': 'mceVoiceLabel', 'style': 'display:none;', id: ed.id + '_voice'}, s.aria_label); + + if (!DOM.boxModel) + n = DOM.add(n, 'div', {'class' : 'mceOldBoxModel'}); + + n = sc = DOM.add(n, 'table', {role : "presentation", id : ed.id + '_tbl', 'class' : 'mceLayout', cellSpacing : 0, cellPadding : 0}); + n = tb = DOM.add(n, 'tbody'); + + switch ((s.theme_advanced_layout_manager || '').toLowerCase()) { + case "rowlayout": + ic = t._rowLayout(s, tb, o); + break; + + case "customlayout": + ic = ed.execCallback("theme_advanced_custom_layout", s, tb, o, p); + break; + + default: + ic = t._simpleLayout(s, tb, o, p); + } + + n = o.targetNode; + + // Add classes to first and last TRs + nl = sc.rows; + DOM.addClass(nl[0], 'mceFirst'); + DOM.addClass(nl[nl.length - 1], 'mceLast'); + + // Add classes to first and last TDs + each(DOM.select('tr', tb), function(n) { + DOM.addClass(n.firstChild, 'mceFirst'); + DOM.addClass(n.childNodes[n.childNodes.length - 1], 'mceLast'); + }); + + if (DOM.get(s.theme_advanced_toolbar_container)) + DOM.get(s.theme_advanced_toolbar_container).appendChild(p); + else + DOM.insertAfter(p, n); + + Event.add(ed.id + '_path_row', 'click', function(e) { + e = e.target; + + if (e.nodeName == 'A') { + t._sel(e.className.replace(/^.*mcePath_([0-9]+).*$/, '$1')); + return false; + } + }); +/* + if (DOM.get(ed.id + '_path_row')) { + Event.add(ed.id + '_tbl', 'mouseover', function(e) { + var re; + + e = e.target; + + if (e.nodeName == 'SPAN' && DOM.hasClass(e.parentNode, 'mceButton')) { + re = DOM.get(ed.id + '_path_row'); + t.lastPath = re.innerHTML; + DOM.setHTML(re, e.parentNode.title); + } + }); + + Event.add(ed.id + '_tbl', 'mouseout', function(e) { + if (t.lastPath) { + DOM.setHTML(ed.id + '_path_row', t.lastPath); + t.lastPath = 0; + } + }); + } +*/ + + if (!ed.getParam('accessibility_focus')) + Event.add(DOM.add(p, 'a', {href : '#'}, ''), 'focus', function() {tinyMCE.get(ed.id).focus();}); + + if (s.theme_advanced_toolbar_location == 'external') + o.deltaHeight = 0; + + t.deltaHeight = o.deltaHeight; + o.targetNode = null; + + ed.onKeyDown.add(function(ed, evt) { + var DOM_VK_F10 = 121, DOM_VK_F11 = 122; + + if (evt.altKey) { + if (evt.keyCode === DOM_VK_F10) { + // Make sure focus is given to toolbar in Safari. + // We can't do this in IE as it prevents giving focus to toolbar when editor is in a frame + if (tinymce.isWebKit) { + window.focus(); + } + t.toolbarGroup.focus(); + return Event.cancel(evt); + } else if (evt.keyCode === DOM_VK_F11) { + DOM.get(ed.id + '_path_row').focus(); + return Event.cancel(evt); + } + } + }); + + // alt+0 is the UK recommended shortcut for accessing the list of access controls. + ed.addShortcut('alt+0', '', 'mceShortcuts', t); + + return { + iframeContainer : ic, + editorContainer : ed.id + '_parent', + sizeContainer : sc, + deltaHeight : o.deltaHeight + }; + }, + + getInfo : function() { + return { + longname : 'Advanced theme', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + version : tinymce.majorVersion + "." + tinymce.minorVersion + } + }, + + resizeBy : function(dw, dh) { + var e = DOM.get(this.editor.id + '_ifr'); + + this.resizeTo(e.clientWidth + dw, e.clientHeight + dh); + }, + + resizeTo : function(w, h, store) { + var ed = this.editor, s = this.settings, e = DOM.get(ed.id + '_tbl'), ifr = DOM.get(ed.id + '_ifr'); + + // Boundery fix box + w = Math.max(s.theme_advanced_resizing_min_width || 100, w); + h = Math.max(s.theme_advanced_resizing_min_height || 100, h); + w = Math.min(s.theme_advanced_resizing_max_width || 0xFFFF, w); + h = Math.min(s.theme_advanced_resizing_max_height || 0xFFFF, h); + + // Resize iframe and container + DOM.setStyle(e, 'height', ''); + DOM.setStyle(ifr, 'height', h); + + if (s.theme_advanced_resize_horizontal) { + DOM.setStyle(e, 'width', ''); + DOM.setStyle(ifr, 'width', w); + + // Make sure that the size is never smaller than the over all ui + if (w < e.clientWidth) { + w = e.clientWidth; + DOM.setStyle(ifr, 'width', e.clientWidth); + } + } + + // Store away the size + if (store && s.theme_advanced_resizing_use_cookie) { + Cookie.setHash("TinyMCE_" + ed.id + "_size", { + cw : w, + ch : h + }); + } + }, + + destroy : function() { + var id = this.editor.id; + + Event.clear(id + '_resize'); + Event.clear(id + '_path_row'); + Event.clear(id + '_external_close'); + }, + + // Internal functions + + _simpleLayout : function(s, tb, o, p) { + var t = this, ed = t.editor, lo = s.theme_advanced_toolbar_location, sl = s.theme_advanced_statusbar_location, n, ic, etb, c; + + if (s.readonly) { + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + return ic; + } + + // Create toolbar container at top + if (lo == 'top') + t._addToolbars(tb, o); + + // Create external toolbar + if (lo == 'external') { + n = c = DOM.create('div', {style : 'position:relative'}); + n = DOM.add(n, 'div', {id : ed.id + '_external', 'class' : 'mceExternalToolbar'}); + DOM.add(n, 'a', {id : ed.id + '_external_close', href : 'javascript:;', 'class' : 'mceExternalClose'}); + n = DOM.add(n, 'table', {id : ed.id + '_tblext', cellSpacing : 0, cellPadding : 0}); + etb = DOM.add(n, 'tbody'); + + if (p.firstChild.className == 'mceOldBoxModel') + p.firstChild.appendChild(c); + else + p.insertBefore(c, p.firstChild); + + t._addToolbars(etb, o); + + ed.onMouseUp.add(function() { + var e = DOM.get(ed.id + '_external'); + DOM.show(e); + + DOM.hide(lastExtID); + + var f = Event.add(ed.id + '_external_close', 'click', function() { + DOM.hide(ed.id + '_external'); + Event.remove(ed.id + '_external_close', 'click', f); + return false; + }); + + DOM.show(e); + DOM.setStyle(e, 'top', 0 - DOM.getRect(ed.id + '_tblext').h - 1); + + // Fixes IE rendering bug + DOM.hide(e); + DOM.show(e); + e.style.filter = ''; + + lastExtID = ed.id + '_external'; + + e = null; + }); + } + + if (sl == 'top') + t._addStatusBar(tb, o); + + // Create iframe container + if (!s.theme_advanced_toolbar_container) { + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + } + + // Create toolbar container at bottom + if (lo == 'bottom') + t._addToolbars(tb, o); + + if (sl == 'bottom') + t._addStatusBar(tb, o); + + return ic; + }, + + _rowLayout : function(s, tb, o) { + var t = this, ed = t.editor, dc, da, cf = ed.controlManager, n, ic, to, a; + + dc = s.theme_advanced_containers_default_class || ''; + da = s.theme_advanced_containers_default_align || 'center'; + + each(explode(s.theme_advanced_containers || ''), function(c, i) { + var v = s['theme_advanced_container_' + c] || ''; + + switch (c.toLowerCase()) { + case 'mceeditor': + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + break; + + case 'mceelementpath': + t._addStatusBar(tb, o); + break; + + default: + a = (s['theme_advanced_container_' + c + '_align'] || da).toLowerCase(); + a = 'mce' + t._ufirst(a); + + n = DOM.add(DOM.add(tb, 'tr'), 'td', { + 'class' : 'mceToolbar ' + (s['theme_advanced_container_' + c + '_class'] || dc) + ' ' + a || da + }); + + to = cf.createToolbar("toolbar" + i); + t._addControls(v, to); + DOM.setHTML(n, to.renderHTML()); + o.deltaHeight -= s.theme_advanced_row_height; + } + }); + + return ic; + }, + + _addControls : function(v, tb) { + var t = this, s = t.settings, di, cf = t.editor.controlManager; + + if (s.theme_advanced_disable && !t._disabled) { + di = {}; + + each(explode(s.theme_advanced_disable), function(v) { + di[v] = 1; + }); + + t._disabled = di; + } else + di = t._disabled; + + each(explode(v), function(n) { + var c; + + if (di && di[n]) + return; + + // Compatiblity with 2.x + if (n == 'tablecontrols') { + each(["table","|","row_props","cell_props","|","row_before","row_after","delete_row","|","col_before","col_after","delete_col","|","split_cells","merge_cells"], function(n) { + n = t.createControl(n, cf); + + if (n) + tb.add(n); + }); + + return; + } + + c = t.createControl(n, cf); + + if (c) + tb.add(c); + }); + }, + + _addToolbars : function(c, o) { + var t = this, i, tb, ed = t.editor, s = t.settings, v, cf = ed.controlManager, di, n, h = [], a, toolbarGroup, toolbarsExist = false; + + toolbarGroup = cf.createToolbarGroup('toolbargroup', { + 'name': ed.getLang('advanced.toolbar'), + 'tab_focus_toolbar':ed.getParam('theme_advanced_tab_focus_toolbar') + }); + + t.toolbarGroup = toolbarGroup; + + a = s.theme_advanced_toolbar_align.toLowerCase(); + a = 'mce' + t._ufirst(a); + + n = DOM.add(DOM.add(c, 'tr', {role: 'presentation'}), 'td', {'class' : 'mceToolbar ' + a, "role":"toolbar"}); + + // Create toolbar and add the controls + for (i=1; (v = s['theme_advanced_buttons' + i]); i++) { + toolbarsExist = true; + tb = cf.createToolbar("toolbar" + i, {'class' : 'mceToolbarRow' + i}); + + if (s['theme_advanced_buttons' + i + '_add']) + v += ',' + s['theme_advanced_buttons' + i + '_add']; + + if (s['theme_advanced_buttons' + i + '_add_before']) + v = s['theme_advanced_buttons' + i + '_add_before'] + ',' + v; + + t._addControls(v, tb); + toolbarGroup.add(tb); + + o.deltaHeight -= s.theme_advanced_row_height; + } + // Handle case when there are no toolbar buttons and ensure editor height is adjusted accordingly + if (!toolbarsExist) + o.deltaHeight -= s.theme_advanced_row_height; + h.push(toolbarGroup.renderHTML()); + h.push(DOM.createHTML('a', {href : '#', accesskey : 'z', title : ed.getLang("advanced.toolbar_focus"), onfocus : 'tinyMCE.getInstanceById(\'' + ed.id + '\').focus();'}, '')); + DOM.setHTML(n, h.join('')); + }, + + _addStatusBar : function(tb, o) { + var n, t = this, ed = t.editor, s = t.settings, r, mf, me, td; + + n = DOM.add(tb, 'tr'); + n = td = DOM.add(n, 'td', {'class' : 'mceStatusbar'}); + n = DOM.add(n, 'div', {id : ed.id + '_path_row', 'role': 'group', 'aria-labelledby': ed.id + '_path_voice'}); + if (s.theme_advanced_path) { + DOM.add(n, 'span', {id: ed.id + '_path_voice'}, ed.translate('advanced.path')); + DOM.add(n, 'span', {}, ': '); + } else { + DOM.add(n, 'span', {}, ' '); + } + + + if (s.theme_advanced_resizing) { + DOM.add(td, 'a', {id : ed.id + '_resize', href : 'javascript:;', onclick : "return false;", 'class' : 'mceResize', tabIndex:"-1"}); + + if (s.theme_advanced_resizing_use_cookie) { + ed.onPostRender.add(function() { + var o = Cookie.getHash("TinyMCE_" + ed.id + "_size"), c = DOM.get(ed.id + '_tbl'); + + if (!o) + return; + + t.resizeTo(o.cw, o.ch); + }); + } + + ed.onPostRender.add(function() { + Event.add(ed.id + '_resize', 'click', function(e) { + e.preventDefault(); + }); + + Event.add(ed.id + '_resize', 'mousedown', function(e) { + var mouseMoveHandler1, mouseMoveHandler2, + mouseUpHandler1, mouseUpHandler2, + startX, startY, startWidth, startHeight, width, height, ifrElm; + + function resizeOnMove(e) { + e.preventDefault(); + + width = startWidth + (e.screenX - startX); + height = startHeight + (e.screenY - startY); + + t.resizeTo(width, height); + }; + + function endResize(e) { + // Stop listening + Event.remove(DOM.doc, 'mousemove', mouseMoveHandler1); + Event.remove(ed.getDoc(), 'mousemove', mouseMoveHandler2); + Event.remove(DOM.doc, 'mouseup', mouseUpHandler1); + Event.remove(ed.getDoc(), 'mouseup', mouseUpHandler2); + + width = startWidth + (e.screenX - startX); + height = startHeight + (e.screenY - startY); + t.resizeTo(width, height, true); + + ed.nodeChanged(); + }; + + e.preventDefault(); + + // Get the current rect size + startX = e.screenX; + startY = e.screenY; + ifrElm = DOM.get(t.editor.id + '_ifr'); + startWidth = width = ifrElm.clientWidth; + startHeight = height = ifrElm.clientHeight; + + // Register envent handlers + mouseMoveHandler1 = Event.add(DOM.doc, 'mousemove', resizeOnMove); + mouseMoveHandler2 = Event.add(ed.getDoc(), 'mousemove', resizeOnMove); + mouseUpHandler1 = Event.add(DOM.doc, 'mouseup', endResize); + mouseUpHandler2 = Event.add(ed.getDoc(), 'mouseup', endResize); + }); + }); + } + + o.deltaHeight -= 21; + n = tb = null; + }, + + _updateUndoStatus : function(ed) { + var cm = ed.controlManager, um = ed.undoManager; + + cm.setDisabled('undo', !um.hasUndo() && !um.typing); + cm.setDisabled('redo', !um.hasRedo()); + }, + + _nodeChanged : function(ed, cm, n, co, ob) { + var t = this, p, de = 0, v, c, s = t.settings, cl, fz, fn, fc, bc, formatNames, matches; + + tinymce.each(t.stateControls, function(c) { + cm.setActive(c, ed.queryCommandState(t.controls[c][1])); + }); + + function getParent(name) { + var i, parents = ob.parents, func = name; + + if (typeof(name) == 'string') { + func = function(node) { + return node.nodeName == name; + }; + } + + for (i = 0; i < parents.length; i++) { + if (func(parents[i])) + return parents[i]; + } + }; + + cm.setActive('visualaid', ed.hasVisual); + t._updateUndoStatus(ed); + cm.setDisabled('outdent', !ed.queryCommandState('Outdent')); + + p = getParent('A'); + if (c = cm.get('link')) { + c.setDisabled((!p && co) || (p && !p.href)); + c.setActive(!!p && (!p.name && !p.id)); + } + + if (c = cm.get('unlink')) { + c.setDisabled(!p && co); + c.setActive(!!p && !p.name && !p.id); + } + + if (c = cm.get('anchor')) { + c.setActive(!co && !!p && (p.name || (p.id && !p.href))); + } + + p = getParent('IMG'); + if (c = cm.get('image')) + c.setActive(!co && !!p && n.className.indexOf('mceItem') == -1); + + if (c = cm.get('styleselect')) { + t._importClasses(); + + formatNames = []; + each(c.items, function(item) { + formatNames.push(item.value); + }); + + matches = ed.formatter.matchAll(formatNames); + c.select(matches[0]); + tinymce.each(matches, function(match, index) { + if (index > 0) { + c.mark(match); + } + }); + } + + if (c = cm.get('formatselect')) { + p = getParent(ed.dom.isBlock); + + if (p) + c.select(p.nodeName.toLowerCase()); + } + + // Find out current fontSize, fontFamily and fontClass + getParent(function(n) { + if (n.nodeName === 'SPAN') { + if (!cl && n.className) + cl = n.className; + } + + if (ed.dom.is(n, s.theme_advanced_font_selector)) { + if (!fz && n.style.fontSize) + fz = n.style.fontSize; + + if (!fn && n.style.fontFamily) + fn = n.style.fontFamily.replace(/[\"\']+/g, '').replace(/^([^,]+).*/, '$1').toLowerCase(); + + if (!fc && n.style.color) + fc = n.style.color; + + if (!bc && n.style.backgroundColor) + bc = n.style.backgroundColor; + } + + return false; + }); + + if (c = cm.get('fontselect')) { + c.select(function(v) { + return v.replace(/^([^,]+).*/, '$1').toLowerCase() == fn; + }); + } + + // Select font size + if (c = cm.get('fontsizeselect')) { + // Use computed style + if (s.theme_advanced_runtime_fontsize && !fz && !cl) + fz = ed.dom.getStyle(n, 'fontSize', true); + + c.select(function(v) { + if (v.fontSize && v.fontSize === fz) + return true; + + if (v['class'] && v['class'] === cl) + return true; + }); + } + + if (s.theme_advanced_show_current_color) { + function updateColor(controlId, color) { + if (c = cm.get(controlId)) { + if (!color) + color = c.settings.default_color; + if (color !== c.value) { + c.displayColor(color); + } + } + } + updateColor('forecolor', fc); + updateColor('backcolor', bc); + } + + if (s.theme_advanced_show_current_color) { + function updateColor(controlId, color) { + if (c = cm.get(controlId)) { + if (!color) + color = c.settings.default_color; + if (color !== c.value) { + c.displayColor(color); + } + } + }; + + updateColor('forecolor', fc); + updateColor('backcolor', bc); + } + + if (s.theme_advanced_path && s.theme_advanced_statusbar_location) { + p = DOM.get(ed.id + '_path') || DOM.add(ed.id + '_path_row', 'span', {id : ed.id + '_path'}); + + if (t.statusKeyboardNavigation) { + t.statusKeyboardNavigation.destroy(); + t.statusKeyboardNavigation = null; + } + + DOM.setHTML(p, ''); + + getParent(function(n) { + var na = n.nodeName.toLowerCase(), u, pi, ti = ''; + + // Ignore non element and bogus/hidden elements + if (n.nodeType != 1 || na === 'br' || n.getAttribute('data-mce-bogus') || DOM.hasClass(n, 'mceItemHidden') || DOM.hasClass(n, 'mceItemRemoved')) + return; + + // Handle prefix + if (tinymce.isIE && n.scopeName !== 'HTML' && n.scopeName) + na = n.scopeName + ':' + na; + + // Remove internal prefix + na = na.replace(/mce\:/g, ''); + + // Handle node name + switch (na) { + case 'b': + na = 'strong'; + break; + + case 'i': + na = 'em'; + break; + + case 'img': + if (v = DOM.getAttrib(n, 'src')) + ti += 'src: ' + v + ' '; + + break; + + case 'a': + if (v = DOM.getAttrib(n, 'name')) { + ti += 'name: ' + v + ' '; + na += '#' + v; + } + + if (v = DOM.getAttrib(n, 'href')) + ti += 'href: ' + v + ' '; + + break; + + case 'font': + if (v = DOM.getAttrib(n, 'face')) + ti += 'font: ' + v + ' '; + + if (v = DOM.getAttrib(n, 'size')) + ti += 'size: ' + v + ' '; + + if (v = DOM.getAttrib(n, 'color')) + ti += 'color: ' + v + ' '; + + break; + + case 'span': + if (v = DOM.getAttrib(n, 'style')) + ti += 'style: ' + v + ' '; + + break; + } + + if (v = DOM.getAttrib(n, 'id')) + ti += 'id: ' + v + ' '; + + if (v = n.className) { + v = v.replace(/\b\s*(webkit|mce|Apple-)\w+\s*\b/g, ''); + + if (v) { + ti += 'class: ' + v + ' '; + + if (ed.dom.isBlock(n) || na == 'img' || na == 'span') + na += '.' + v; + } + } + + na = na.replace(/(html:)/g, ''); + na = {name : na, node : n, title : ti}; + t.onResolveName.dispatch(t, na); + ti = na.title; + na = na.name; + + //u = "javascript:tinymce.EditorManager.get('" + ed.id + "').theme._sel('" + (de++) + "');"; + pi = DOM.create('a', {'href' : "javascript:;", role: 'button', onmousedown : "return false;", title : ti, 'class' : 'mcePath_' + (de++)}, na); + + if (p.hasChildNodes()) { + p.insertBefore(DOM.create('span', {'aria-hidden': 'true'}, '\u00a0\u00bb '), p.firstChild); + p.insertBefore(pi, p.firstChild); + } else + p.appendChild(pi); + }, ed.getBody()); + + if (DOM.select('a', p).length > 0) { + t.statusKeyboardNavigation = new tinymce.ui.KeyboardNavigation({ + root: ed.id + "_path_row", + items: DOM.select('a', p), + excludeFromTabOrder: true, + onCancel: function() { + ed.focus(); + } + }, DOM); + } + } + }, + + // Commands gets called by execCommand + + _sel : function(v) { + this.editor.execCommand('mceSelectNodeDepth', false, v); + }, + + _mceInsertAnchor : function(ui, v) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/anchor.htm', + width : 320 + parseInt(ed.getLang('advanced.anchor_delta_width', 0)), + height : 90 + parseInt(ed.getLang('advanced.anchor_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceCharMap : function() { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/charmap.htm', + width : 550 + parseInt(ed.getLang('advanced.charmap_delta_width', 0)), + height : 265 + parseInt(ed.getLang('advanced.charmap_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceHelp : function() { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/about.htm', + width : 480, + height : 380, + inline : true + }, { + theme_url : this.url + }); + }, + + _mceShortcuts : function() { + var ed = this.editor; + ed.windowManager.open({ + url: this.url + '/shortcuts.htm', + width: 480, + height: 380, + inline: true + }, { + theme_url: this.url + }); + }, + + _mceColorPicker : function(u, v) { + var ed = this.editor; + + v = v || {}; + + ed.windowManager.open({ + url : this.url + '/color_picker.htm', + width : 375 + parseInt(ed.getLang('advanced.colorpicker_delta_width', 0)), + height : 250 + parseInt(ed.getLang('advanced.colorpicker_delta_height', 0)), + close_previous : false, + inline : true + }, { + input_color : v.color, + func : v.func, + theme_url : this.url + }); + }, + + _mceCodeEditor : function(ui, val) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/source_editor.htm', + width : parseInt(ed.getParam("theme_advanced_source_editor_width", 720)), + height : parseInt(ed.getParam("theme_advanced_source_editor_height", 580)), + inline : true, + resizable : true, + maximizable : true + }, { + theme_url : this.url + }); + }, + + _mceImage : function(ui, val) { + var ed = this.editor; + + // Internal image object like a flash placeholder + if (ed.dom.getAttrib(ed.selection.getNode(), 'class', '').indexOf('mceItem') != -1) + return; + + ed.windowManager.open({ + url : this.url + '/image.htm', + width : 355 + parseInt(ed.getLang('advanced.image_delta_width', 0)), + height : 275 + parseInt(ed.getLang('advanced.image_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceLink : function(ui, val) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/link.htm', + width : 310 + parseInt(ed.getLang('advanced.link_delta_width', 0)), + height : 200 + parseInt(ed.getLang('advanced.link_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceNewDocument : function() { + var ed = this.editor; + + ed.windowManager.confirm('advanced.newdocument', function(s) { + if (s) + ed.execCommand('mceSetContent', false, ''); + }); + }, + + _mceForeColor : function() { + var t = this; + + this._mceColorPicker(0, { + color: t.fgColor, + func : function(co) { + t.fgColor = co; + t.editor.execCommand('ForeColor', false, co); + } + }); + }, + + _mceBackColor : function() { + var t = this; + + this._mceColorPicker(0, { + color: t.bgColor, + func : function(co) { + t.bgColor = co; + t.editor.execCommand('HiliteColor', false, co); + } + }); + }, + + _ufirst : function(s) { + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + }); + + tinymce.ThemeManager.add('advanced', tinymce.themes.AdvancedTheme); +}(tinymce)); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/anchor.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/anchor.js new file mode 100644 index 0000000..a3a0186 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/anchor.js @@ -0,0 +1,56 @@ +tinyMCEPopup.requireLangPack(); + +var AnchorDialog = { + init : function(ed) { + var action, elm, f = document.forms[0]; + + this.editor = ed; + elm = ed.dom.getParent(ed.selection.getNode(), 'A'); + v = ed.dom.getAttrib(elm, 'name') || ed.dom.getAttrib(elm, 'id'); + + if (v) { + this.action = 'update'; + f.anchorName.value = v; + } + + f.insert.value = ed.getLang(elm ? 'update' : 'insert'); + }, + + update : function() { + var ed = this.editor, elm, name = document.forms[0].anchorName.value, attribName; + + if (!name || !/^[a-z][a-z0-9\-\_:\.]*$/i.test(name)) { + tinyMCEPopup.alert('advanced_dlg.anchor_invalid'); + return; + } + + tinyMCEPopup.restoreSelection(); + + if (this.action != 'update') + ed.selection.collapse(1); + + var aRule = ed.schema.getElementRule('a'); + if (!aRule || aRule.attributes.name) { + attribName = 'name'; + } else { + attribName = 'id'; + } + + elm = ed.dom.getParent(ed.selection.getNode(), 'A'); + if (elm) { + elm.setAttribute(attribName, name); + elm[attribName] = name; + ed.undoManager.add(); + } else { + // create with zero-sized nbsp so that in Webkit where anchor is on last line by itself caret cannot be placed after it + var attrs = {'class' : 'mceItemAnchor'}; + attrs[attribName] = name; + ed.execCommand('mceInsertContent', 0, ed.dom.createHTML('a', attrs, '\uFEFF')); + ed.nodeChanged(); + } + + tinyMCEPopup.close(); + } +}; + +tinyMCEPopup.onInit.add(AnchorDialog.init, AnchorDialog); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/charmap.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/charmap.js new file mode 100644 index 0000000..cbb4172 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/charmap.js @@ -0,0 +1,363 @@ +/** + * charmap.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +tinyMCEPopup.requireLangPack(); + +var charmap = [ + [' ', ' ', true, 'no-break space'], + ['&', '&', true, 'ampersand'], + ['"', '"', true, 'quotation mark'], +// finance + ['¢', '¢', true, 'cent sign'], + ['€', '€', true, 'euro sign'], + ['£', '£', true, 'pound sign'], + ['¥', '¥', true, 'yen sign'], +// signs + ['©', '©', true, 'copyright sign'], + ['®', '®', true, 'registered sign'], + ['™', '™', true, 'trade mark sign'], + ['‰', '‰', true, 'per mille sign'], + ['µ', 'µ', true, 'micro sign'], + ['·', '·', true, 'middle dot'], + ['•', '•', true, 'bullet'], + ['…', '…', true, 'three dot leader'], + ['′', '′', true, 'minutes / feet'], + ['″', '″', true, 'seconds / inches'], + ['§', '§', true, 'section sign'], + ['¶', '¶', true, 'paragraph sign'], + ['ß', 'ß', true, 'sharp s / ess-zed'], +// quotations + ['‹', '‹', true, 'single left-pointing angle quotation mark'], + ['›', '›', true, 'single right-pointing angle quotation mark'], + ['«', '«', true, 'left pointing guillemet'], + ['»', '»', true, 'right pointing guillemet'], + ['‘', '‘', true, 'left single quotation mark'], + ['’', '’', true, 'right single quotation mark'], + ['“', '“', true, 'left double quotation mark'], + ['”', '”', true, 'right double quotation mark'], + ['‚', '‚', true, 'single low-9 quotation mark'], + ['„', '„', true, 'double low-9 quotation mark'], + ['<', '<', true, 'less-than sign'], + ['>', '>', true, 'greater-than sign'], + ['≤', '≤', true, 'less-than or equal to'], + ['≥', '≥', true, 'greater-than or equal to'], + ['–', '–', true, 'en dash'], + ['—', '—', true, 'em dash'], + ['¯', '¯', true, 'macron'], + ['‾', '‾', true, 'overline'], + ['¤', '¤', true, 'currency sign'], + ['¦', '¦', true, 'broken bar'], + ['¨', '¨', true, 'diaeresis'], + ['¡', '¡', true, 'inverted exclamation mark'], + ['¿', '¿', true, 'turned question mark'], + ['ˆ', 'ˆ', true, 'circumflex accent'], + ['˜', '˜', true, 'small tilde'], + ['°', '°', true, 'degree sign'], + ['−', '−', true, 'minus sign'], + ['±', '±', true, 'plus-minus sign'], + ['÷', '÷', true, 'division sign'], + ['⁄', '⁄', true, 'fraction slash'], + ['×', '×', true, 'multiplication sign'], + ['¹', '¹', true, 'superscript one'], + ['²', '²', true, 'superscript two'], + ['³', '³', true, 'superscript three'], + ['¼', '¼', true, 'fraction one quarter'], + ['½', '½', true, 'fraction one half'], + ['¾', '¾', true, 'fraction three quarters'], +// math / logical + ['ƒ', 'ƒ', true, 'function / florin'], + ['∫', '∫', true, 'integral'], + ['∑', '∑', true, 'n-ary sumation'], + ['∞', '∞', true, 'infinity'], + ['√', '√', true, 'square root'], + ['∼', '∼', false,'similar to'], + ['≅', '≅', false,'approximately equal to'], + ['≈', '≈', true, 'almost equal to'], + ['≠', '≠', true, 'not equal to'], + ['≡', '≡', true, 'identical to'], + ['∈', '∈', false,'element of'], + ['∉', '∉', false,'not an element of'], + ['∋', '∋', false,'contains as member'], + ['∏', '∏', true, 'n-ary product'], + ['∧', '∧', false,'logical and'], + ['∨', '∨', false,'logical or'], + ['¬', '¬', true, 'not sign'], + ['∩', '∩', true, 'intersection'], + ['∪', '∪', false,'union'], + ['∂', '∂', true, 'partial differential'], + ['∀', '∀', false,'for all'], + ['∃', '∃', false,'there exists'], + ['∅', '∅', false,'diameter'], + ['∇', '∇', false,'backward difference'], + ['∗', '∗', false,'asterisk operator'], + ['∝', '∝', false,'proportional to'], + ['∠', '∠', false,'angle'], +// undefined + ['´', '´', true, 'acute accent'], + ['¸', '¸', true, 'cedilla'], + ['ª', 'ª', true, 'feminine ordinal indicator'], + ['º', 'º', true, 'masculine ordinal indicator'], + ['†', '†', true, 'dagger'], + ['‡', '‡', true, 'double dagger'], +// alphabetical special chars + ['À', 'À', true, 'A - grave'], + ['Á', 'Á', true, 'A - acute'], + ['Â', 'Â', true, 'A - circumflex'], + ['Ã', 'Ã', true, 'A - tilde'], + ['Ä', 'Ä', true, 'A - diaeresis'], + ['Å', 'Å', true, 'A - ring above'], + ['Æ', 'Æ', true, 'ligature AE'], + ['Ç', 'Ç', true, 'C - cedilla'], + ['È', 'È', true, 'E - grave'], + ['É', 'É', true, 'E - acute'], + ['Ê', 'Ê', true, 'E - circumflex'], + ['Ë', 'Ë', true, 'E - diaeresis'], + ['Ì', 'Ì', true, 'I - grave'], + ['Í', 'Í', true, 'I - acute'], + ['Î', 'Î', true, 'I - circumflex'], + ['Ï', 'Ï', true, 'I - diaeresis'], + ['Ð', 'Ð', true, 'ETH'], + ['Ñ', 'Ñ', true, 'N - tilde'], + ['Ò', 'Ò', true, 'O - grave'], + ['Ó', 'Ó', true, 'O - acute'], + ['Ô', 'Ô', true, 'O - circumflex'], + ['Õ', 'Õ', true, 'O - tilde'], + ['Ö', 'Ö', true, 'O - diaeresis'], + ['Ø', 'Ø', true, 'O - slash'], + ['Œ', 'Œ', true, 'ligature OE'], + ['Š', 'Š', true, 'S - caron'], + ['Ù', 'Ù', true, 'U - grave'], + ['Ú', 'Ú', true, 'U - acute'], + ['Û', 'Û', true, 'U - circumflex'], + ['Ü', 'Ü', true, 'U - diaeresis'], + ['Ý', 'Ý', true, 'Y - acute'], + ['Ÿ', 'Ÿ', true, 'Y - diaeresis'], + ['Þ', 'Þ', true, 'THORN'], + ['à', 'à', true, 'a - grave'], + ['á', 'á', true, 'a - acute'], + ['â', 'â', true, 'a - circumflex'], + ['ã', 'ã', true, 'a - tilde'], + ['ä', 'ä', true, 'a - diaeresis'], + ['å', 'å', true, 'a - ring above'], + ['æ', 'æ', true, 'ligature ae'], + ['ç', 'ç', true, 'c - cedilla'], + ['è', 'è', true, 'e - grave'], + ['é', 'é', true, 'e - acute'], + ['ê', 'ê', true, 'e - circumflex'], + ['ë', 'ë', true, 'e - diaeresis'], + ['ì', 'ì', true, 'i - grave'], + ['í', 'í', true, 'i - acute'], + ['î', 'î', true, 'i - circumflex'], + ['ï', 'ï', true, 'i - diaeresis'], + ['ð', 'ð', true, 'eth'], + ['ñ', 'ñ', true, 'n - tilde'], + ['ò', 'ò', true, 'o - grave'], + ['ó', 'ó', true, 'o - acute'], + ['ô', 'ô', true, 'o - circumflex'], + ['õ', 'õ', true, 'o - tilde'], + ['ö', 'ö', true, 'o - diaeresis'], + ['ø', 'ø', true, 'o slash'], + ['œ', 'œ', true, 'ligature oe'], + ['š', 'š', true, 's - caron'], + ['ù', 'ù', true, 'u - grave'], + ['ú', 'ú', true, 'u - acute'], + ['û', 'û', true, 'u - circumflex'], + ['ü', 'ü', true, 'u - diaeresis'], + ['ý', 'ý', true, 'y - acute'], + ['þ', 'þ', true, 'thorn'], + ['ÿ', 'ÿ', true, 'y - diaeresis'], + ['Α', 'Α', true, 'Alpha'], + ['Β', 'Β', true, 'Beta'], + ['Γ', 'Γ', true, 'Gamma'], + ['Δ', 'Δ', true, 'Delta'], + ['Ε', 'Ε', true, 'Epsilon'], + ['Ζ', 'Ζ', true, 'Zeta'], + ['Η', 'Η', true, 'Eta'], + ['Θ', 'Θ', true, 'Theta'], + ['Ι', 'Ι', true, 'Iota'], + ['Κ', 'Κ', true, 'Kappa'], + ['Λ', 'Λ', true, 'Lambda'], + ['Μ', 'Μ', true, 'Mu'], + ['Ν', 'Ν', true, 'Nu'], + ['Ξ', 'Ξ', true, 'Xi'], + ['Ο', 'Ο', true, 'Omicron'], + ['Π', 'Π', true, 'Pi'], + ['Ρ', 'Ρ', true, 'Rho'], + ['Σ', 'Σ', true, 'Sigma'], + ['Τ', 'Τ', true, 'Tau'], + ['Υ', 'Υ', true, 'Upsilon'], + ['Φ', 'Φ', true, 'Phi'], + ['Χ', 'Χ', true, 'Chi'], + ['Ψ', 'Ψ', true, 'Psi'], + ['Ω', 'Ω', true, 'Omega'], + ['α', 'α', true, 'alpha'], + ['β', 'β', true, 'beta'], + ['γ', 'γ', true, 'gamma'], + ['δ', 'δ', true, 'delta'], + ['ε', 'ε', true, 'epsilon'], + ['ζ', 'ζ', true, 'zeta'], + ['η', 'η', true, 'eta'], + ['θ', 'θ', true, 'theta'], + ['ι', 'ι', true, 'iota'], + ['κ', 'κ', true, 'kappa'], + ['λ', 'λ', true, 'lambda'], + ['μ', 'μ', true, 'mu'], + ['ν', 'ν', true, 'nu'], + ['ξ', 'ξ', true, 'xi'], + ['ο', 'ο', true, 'omicron'], + ['π', 'π', true, 'pi'], + ['ρ', 'ρ', true, 'rho'], + ['ς', 'ς', true, 'final sigma'], + ['σ', 'σ', true, 'sigma'], + ['τ', 'τ', true, 'tau'], + ['υ', 'υ', true, 'upsilon'], + ['φ', 'φ', true, 'phi'], + ['χ', 'χ', true, 'chi'], + ['ψ', 'ψ', true, 'psi'], + ['ω', 'ω', true, 'omega'], +// symbols + ['ℵ', 'ℵ', false,'alef symbol'], + ['ϖ', 'ϖ', false,'pi symbol'], + ['ℜ', 'ℜ', false,'real part symbol'], + ['ϑ','ϑ', false,'theta symbol'], + ['ϒ', 'ϒ', false,'upsilon - hook symbol'], + ['℘', '℘', false,'Weierstrass p'], + ['ℑ', 'ℑ', false,'imaginary part'], +// arrows + ['←', '←', true, 'leftwards arrow'], + ['↑', '↑', true, 'upwards arrow'], + ['→', '→', true, 'rightwards arrow'], + ['↓', '↓', true, 'downwards arrow'], + ['↔', '↔', true, 'left right arrow'], + ['↵', '↵', false,'carriage return'], + ['⇐', '⇐', false,'leftwards double arrow'], + ['⇑', '⇑', false,'upwards double arrow'], + ['⇒', '⇒', false,'rightwards double arrow'], + ['⇓', '⇓', false,'downwards double arrow'], + ['⇔', '⇔', false,'left right double arrow'], + ['∴', '∴', false,'therefore'], + ['⊂', '⊂', false,'subset of'], + ['⊃', '⊃', false,'superset of'], + ['⊄', '⊄', false,'not a subset of'], + ['⊆', '⊆', false,'subset of or equal to'], + ['⊇', '⊇', false,'superset of or equal to'], + ['⊕', '⊕', false,'circled plus'], + ['⊗', '⊗', false,'circled times'], + ['⊥', '⊥', false,'perpendicular'], + ['⋅', '⋅', false,'dot operator'], + ['⌈', '⌈', false,'left ceiling'], + ['⌉', '⌉', false,'right ceiling'], + ['⌊', '⌊', false,'left floor'], + ['⌋', '⌋', false,'right floor'], + ['⟨', '〈', false,'left-pointing angle bracket'], + ['⟩', '〉', false,'right-pointing angle bracket'], + ['◊', '◊', true, 'lozenge'], + ['♠', '♠', true, 'black spade suit'], + ['♣', '♣', true, 'black club suit'], + ['♥', '♥', true, 'black heart suit'], + ['♦', '♦', true, 'black diamond suit'], + [' ', ' ', false,'en space'], + [' ', ' ', false,'em space'], + [' ', ' ', false,'thin space'], + ['‌', '‌', false,'zero width non-joiner'], + ['‍', '‍', false,'zero width joiner'], + ['‎', '‎', false,'left-to-right mark'], + ['‏', '‏', false,'right-to-left mark'], + ['­', '­', false,'soft hyphen'] +]; + +tinyMCEPopup.onInit.add(function() { + tinyMCEPopup.dom.setHTML('charmapView', renderCharMapHTML()); + addKeyboardNavigation(); +}); + +function addKeyboardNavigation(){ + var tableElm, cells, settings; + + cells = tinyMCEPopup.dom.select("a.charmaplink", "charmapgroup"); + + settings ={ + root: "charmapgroup", + items: cells + }; + cells[0].tabindex=0; + tinyMCEPopup.dom.addClass(cells[0], "mceFocus"); + if (tinymce.isGecko) { + cells[0].focus(); + } else { + setTimeout(function(){ + cells[0].focus(); + }, 100); + } + tinyMCEPopup.editor.windowManager.createInstance('tinymce.ui.KeyboardNavigation', settings, tinyMCEPopup.dom); +} + +function renderCharMapHTML() { + var charsPerRow = 20, tdWidth=20, tdHeight=20, i; + var html = '
'+ + ''; + var cols=-1; + + for (i=0; i' + + '' + + charmap[i][1] + + ''; + if ((cols+1) % charsPerRow == 0) + html += ''; + } + } + + if (cols % charsPerRow > 0) { + var padd = charsPerRow - (cols % charsPerRow); + for (var i=0; i '; + } + + html += '
'; + html = html.replace(/<\/tr>/g, ''); + + return html; +} + +function insertChar(chr) { + tinyMCEPopup.execCommand('mceInsertContent', false, '&#' + chr + ';'); + + // Refocus in window + if (tinyMCEPopup.isWindow) + window.focus(); + + tinyMCEPopup.editor.focus(); + tinyMCEPopup.close(); +} + +function previewChar(codeA, codeB, codeN) { + var elmA = document.getElementById('codeA'); + var elmB = document.getElementById('codeB'); + var elmV = document.getElementById('codeV'); + var elmN = document.getElementById('codeN'); + + if (codeA=='#160;') { + elmV.innerHTML = '__'; + } else { + elmV.innerHTML = '&' + codeA; + } + + elmB.innerHTML = '&' + codeA; + elmA.innerHTML = '&' + codeB; + elmN.innerHTML = codeN; +} diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/color_picker.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/color_picker.js new file mode 100644 index 0000000..cc891c1 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/color_picker.js @@ -0,0 +1,345 @@ +tinyMCEPopup.requireLangPack(); + +var detail = 50, strhex = "0123456789abcdef", i, isMouseDown = false, isMouseOver = false; + +var colors = [ + "#000000","#000033","#000066","#000099","#0000cc","#0000ff","#330000","#330033", + "#330066","#330099","#3300cc","#3300ff","#660000","#660033","#660066","#660099", + "#6600cc","#6600ff","#990000","#990033","#990066","#990099","#9900cc","#9900ff", + "#cc0000","#cc0033","#cc0066","#cc0099","#cc00cc","#cc00ff","#ff0000","#ff0033", + "#ff0066","#ff0099","#ff00cc","#ff00ff","#003300","#003333","#003366","#003399", + "#0033cc","#0033ff","#333300","#333333","#333366","#333399","#3333cc","#3333ff", + "#663300","#663333","#663366","#663399","#6633cc","#6633ff","#993300","#993333", + "#993366","#993399","#9933cc","#9933ff","#cc3300","#cc3333","#cc3366","#cc3399", + "#cc33cc","#cc33ff","#ff3300","#ff3333","#ff3366","#ff3399","#ff33cc","#ff33ff", + "#006600","#006633","#006666","#006699","#0066cc","#0066ff","#336600","#336633", + "#336666","#336699","#3366cc","#3366ff","#666600","#666633","#666666","#666699", + "#6666cc","#6666ff","#996600","#996633","#996666","#996699","#9966cc","#9966ff", + "#cc6600","#cc6633","#cc6666","#cc6699","#cc66cc","#cc66ff","#ff6600","#ff6633", + "#ff6666","#ff6699","#ff66cc","#ff66ff","#009900","#009933","#009966","#009999", + "#0099cc","#0099ff","#339900","#339933","#339966","#339999","#3399cc","#3399ff", + "#669900","#669933","#669966","#669999","#6699cc","#6699ff","#999900","#999933", + "#999966","#999999","#9999cc","#9999ff","#cc9900","#cc9933","#cc9966","#cc9999", + "#cc99cc","#cc99ff","#ff9900","#ff9933","#ff9966","#ff9999","#ff99cc","#ff99ff", + "#00cc00","#00cc33","#00cc66","#00cc99","#00cccc","#00ccff","#33cc00","#33cc33", + "#33cc66","#33cc99","#33cccc","#33ccff","#66cc00","#66cc33","#66cc66","#66cc99", + "#66cccc","#66ccff","#99cc00","#99cc33","#99cc66","#99cc99","#99cccc","#99ccff", + "#cccc00","#cccc33","#cccc66","#cccc99","#cccccc","#ccccff","#ffcc00","#ffcc33", + "#ffcc66","#ffcc99","#ffcccc","#ffccff","#00ff00","#00ff33","#00ff66","#00ff99", + "#00ffcc","#00ffff","#33ff00","#33ff33","#33ff66","#33ff99","#33ffcc","#33ffff", + "#66ff00","#66ff33","#66ff66","#66ff99","#66ffcc","#66ffff","#99ff00","#99ff33", + "#99ff66","#99ff99","#99ffcc","#99ffff","#ccff00","#ccff33","#ccff66","#ccff99", + "#ccffcc","#ccffff","#ffff00","#ffff33","#ffff66","#ffff99","#ffffcc","#ffffff" +]; + +var named = { + '#F0F8FF':'Alice Blue','#FAEBD7':'Antique White','#00FFFF':'Aqua','#7FFFD4':'Aquamarine','#F0FFFF':'Azure','#F5F5DC':'Beige', + '#FFE4C4':'Bisque','#000000':'Black','#FFEBCD':'Blanched Almond','#0000FF':'Blue','#8A2BE2':'Blue Violet','#A52A2A':'Brown', + '#DEB887':'Burly Wood','#5F9EA0':'Cadet Blue','#7FFF00':'Chartreuse','#D2691E':'Chocolate','#FF7F50':'Coral','#6495ED':'Cornflower Blue', + '#FFF8DC':'Cornsilk','#DC143C':'Crimson','#00FFFF':'Cyan','#00008B':'Dark Blue','#008B8B':'Dark Cyan','#B8860B':'Dark Golden Rod', + '#A9A9A9':'Dark Gray','#A9A9A9':'Dark Grey','#006400':'Dark Green','#BDB76B':'Dark Khaki','#8B008B':'Dark Magenta','#556B2F':'Dark Olive Green', + '#FF8C00':'Darkorange','#9932CC':'Dark Orchid','#8B0000':'Dark Red','#E9967A':'Dark Salmon','#8FBC8F':'Dark Sea Green','#483D8B':'Dark Slate Blue', + '#2F4F4F':'Dark Slate Gray','#2F4F4F':'Dark Slate Grey','#00CED1':'Dark Turquoise','#9400D3':'Dark Violet','#FF1493':'Deep Pink','#00BFFF':'Deep Sky Blue', + '#696969':'Dim Gray','#696969':'Dim Grey','#1E90FF':'Dodger Blue','#B22222':'Fire Brick','#FFFAF0':'Floral White','#228B22':'Forest Green', + '#FF00FF':'Fuchsia','#DCDCDC':'Gainsboro','#F8F8FF':'Ghost White','#FFD700':'Gold','#DAA520':'Golden Rod','#808080':'Gray','#808080':'Grey', + '#008000':'Green','#ADFF2F':'Green Yellow','#F0FFF0':'Honey Dew','#FF69B4':'Hot Pink','#CD5C5C':'Indian Red','#4B0082':'Indigo','#FFFFF0':'Ivory', + '#F0E68C':'Khaki','#E6E6FA':'Lavender','#FFF0F5':'Lavender Blush','#7CFC00':'Lawn Green','#FFFACD':'Lemon Chiffon','#ADD8E6':'Light Blue', + '#F08080':'Light Coral','#E0FFFF':'Light Cyan','#FAFAD2':'Light Golden Rod Yellow','#D3D3D3':'Light Gray','#D3D3D3':'Light Grey','#90EE90':'Light Green', + '#FFB6C1':'Light Pink','#FFA07A':'Light Salmon','#20B2AA':'Light Sea Green','#87CEFA':'Light Sky Blue','#778899':'Light Slate Gray','#778899':'Light Slate Grey', + '#B0C4DE':'Light Steel Blue','#FFFFE0':'Light Yellow','#00FF00':'Lime','#32CD32':'Lime Green','#FAF0E6':'Linen','#FF00FF':'Magenta','#800000':'Maroon', + '#66CDAA':'Medium Aqua Marine','#0000CD':'Medium Blue','#BA55D3':'Medium Orchid','#9370D8':'Medium Purple','#3CB371':'Medium Sea Green','#7B68EE':'Medium Slate Blue', + '#00FA9A':'Medium Spring Green','#48D1CC':'Medium Turquoise','#C71585':'Medium Violet Red','#191970':'Midnight Blue','#F5FFFA':'Mint Cream','#FFE4E1':'Misty Rose','#FFE4B5':'Moccasin', + '#FFDEAD':'Navajo White','#000080':'Navy','#FDF5E6':'Old Lace','#808000':'Olive','#6B8E23':'Olive Drab','#FFA500':'Orange','#FF4500':'Orange Red','#DA70D6':'Orchid', + '#EEE8AA':'Pale Golden Rod','#98FB98':'Pale Green','#AFEEEE':'Pale Turquoise','#D87093':'Pale Violet Red','#FFEFD5':'Papaya Whip','#FFDAB9':'Peach Puff', + '#CD853F':'Peru','#FFC0CB':'Pink','#DDA0DD':'Plum','#B0E0E6':'Powder Blue','#800080':'Purple','#FF0000':'Red','#BC8F8F':'Rosy Brown','#4169E1':'Royal Blue', + '#8B4513':'Saddle Brown','#FA8072':'Salmon','#F4A460':'Sandy Brown','#2E8B57':'Sea Green','#FFF5EE':'Sea Shell','#A0522D':'Sienna','#C0C0C0':'Silver', + '#87CEEB':'Sky Blue','#6A5ACD':'Slate Blue','#708090':'Slate Gray','#708090':'Slate Grey','#FFFAFA':'Snow','#00FF7F':'Spring Green', + '#4682B4':'Steel Blue','#D2B48C':'Tan','#008080':'Teal','#D8BFD8':'Thistle','#FF6347':'Tomato','#40E0D0':'Turquoise','#EE82EE':'Violet', + '#F5DEB3':'Wheat','#FFFFFF':'White','#F5F5F5':'White Smoke','#FFFF00':'Yellow','#9ACD32':'Yellow Green' +}; + +var namedLookup = {}; + +function init() { + var inputColor = convertRGBToHex(tinyMCEPopup.getWindowArg('input_color')), key, value; + + tinyMCEPopup.resizeToInnerSize(); + + generatePicker(); + generateWebColors(); + generateNamedColors(); + + if (inputColor) { + changeFinalColor(inputColor); + + col = convertHexToRGB(inputColor); + + if (col) + updateLight(col.r, col.g, col.b); + } + + for (key in named) { + value = named[key]; + namedLookup[value.replace(/\s+/, '').toLowerCase()] = key.replace(/#/, '').toLowerCase(); + } +} + +function toHexColor(color) { + var matches, red, green, blue, toInt = parseInt; + + function hex(value) { + value = parseInt(value).toString(16); + + return value.length > 1 ? value : '0' + value; // Padd with leading zero + }; + + color = tinymce.trim(color); + color = color.replace(/^[#]/, '').toLowerCase(); // remove leading '#' + color = namedLookup[color] || color; + + matches = /^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)$/.exec(color); + + if (matches) { + red = toInt(matches[1]); + green = toInt(matches[2]); + blue = toInt(matches[3]); + } else { + matches = /^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/.exec(color); + + if (matches) { + red = toInt(matches[1], 16); + green = toInt(matches[2], 16); + blue = toInt(matches[3], 16); + } else { + matches = /^([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(color); + + if (matches) { + red = toInt(matches[1] + matches[1], 16); + green = toInt(matches[2] + matches[2], 16); + blue = toInt(matches[3] + matches[3], 16); + } else { + return ''; + } + } + } + + return '#' + hex(red) + hex(green) + hex(blue); +} + +function insertAction() { + var color = document.getElementById("color").value, f = tinyMCEPopup.getWindowArg('func'); + + var hexColor = toHexColor(color); + + if (hexColor === '') { + var text = tinyMCEPopup.editor.getLang('advanced_dlg.invalid_color_value'); + tinyMCEPopup.alert(text + ': ' + color); + } + else { + tinyMCEPopup.restoreSelection(); + + if (f) + f(hexColor); + + tinyMCEPopup.close(); + } +} + +function showColor(color, name) { + if (name) + document.getElementById("colorname").innerHTML = name; + + document.getElementById("preview").style.backgroundColor = color; + document.getElementById("color").value = color.toUpperCase(); +} + +function convertRGBToHex(col) { + var re = new RegExp("rgb\\s*\\(\\s*([0-9]+).*,\\s*([0-9]+).*,\\s*([0-9]+).*\\)", "gi"); + + if (!col) + return col; + + var rgb = col.replace(re, "$1,$2,$3").split(','); + if (rgb.length == 3) { + r = parseInt(rgb[0]).toString(16); + g = parseInt(rgb[1]).toString(16); + b = parseInt(rgb[2]).toString(16); + + r = r.length == 1 ? '0' + r : r; + g = g.length == 1 ? '0' + g : g; + b = b.length == 1 ? '0' + b : b; + + return "#" + r + g + b; + } + + return col; +} + +function convertHexToRGB(col) { + if (col.indexOf('#') != -1) { + col = col.replace(new RegExp('[^0-9A-F]', 'gi'), ''); + + r = parseInt(col.substring(0, 2), 16); + g = parseInt(col.substring(2, 4), 16); + b = parseInt(col.substring(4, 6), 16); + + return {r : r, g : g, b : b}; + } + + return null; +} + +function generatePicker() { + var el = document.getElementById('light'), h = '', i; + + for (i = 0; i < detail; i++){ + h += '
'; + } + + el.innerHTML = h; +} + +function generateWebColors() { + var el = document.getElementById('webcolors'), h = '', i; + + if (el.className == 'generated') + return; + + // TODO: VoiceOver doesn't seem to support legend as a label referenced by labelledby. + h += '
' + + ''; + + for (i=0; i' + + ''; + if (tinyMCEPopup.editor.forcedHighContrastMode) { + h += ''; + } + h += ''; + h += ''; + if ((i+1) % 18 == 0) + h += ''; + } + + h += '
'; + + el.innerHTML = h; + el.className = 'generated'; + + paintCanvas(el); + enableKeyboardNavigation(el.firstChild); +} + +function paintCanvas(el) { + tinyMCEPopup.getWin().tinymce.each(tinyMCEPopup.dom.select('canvas.mceColorSwatch', el), function(canvas) { + var context; + if (canvas.getContext && (context = canvas.getContext("2d"))) { + context.fillStyle = canvas.getAttribute('data-color'); + context.fillRect(0, 0, 10, 10); + } + }); +} +function generateNamedColors() { + var el = document.getElementById('namedcolors'), h = '', n, v, i = 0; + + if (el.className == 'generated') + return; + + for (n in named) { + v = named[n]; + h += ''; + if (tinyMCEPopup.editor.forcedHighContrastMode) { + h += ''; + } + h += ''; + h += ''; + i++; + } + + el.innerHTML = h; + el.className = 'generated'; + + paintCanvas(el); + enableKeyboardNavigation(el); +} + +function enableKeyboardNavigation(el) { + tinyMCEPopup.editor.windowManager.createInstance('tinymce.ui.KeyboardNavigation', { + root: el, + items: tinyMCEPopup.dom.select('a', el) + }, tinyMCEPopup.dom); +} + +function dechex(n) { + return strhex.charAt(Math.floor(n / 16)) + strhex.charAt(n % 16); +} + +function computeColor(e) { + var x, y, partWidth, partDetail, imHeight, r, g, b, coef, i, finalCoef, finalR, finalG, finalB, pos = tinyMCEPopup.dom.getPos(e.target); + + x = e.offsetX ? e.offsetX : (e.target ? e.clientX - pos.x : 0); + y = e.offsetY ? e.offsetY : (e.target ? e.clientY - pos.y : 0); + + partWidth = document.getElementById('colors').width / 6; + partDetail = detail / 2; + imHeight = document.getElementById('colors').height; + + r = (x >= 0)*(x < partWidth)*255 + (x >= partWidth)*(x < 2*partWidth)*(2*255 - x * 255 / partWidth) + (x >= 4*partWidth)*(x < 5*partWidth)*(-4*255 + x * 255 / partWidth) + (x >= 5*partWidth)*(x < 6*partWidth)*255; + g = (x >= 0)*(x < partWidth)*(x * 255 / partWidth) + (x >= partWidth)*(x < 3*partWidth)*255 + (x >= 3*partWidth)*(x < 4*partWidth)*(4*255 - x * 255 / partWidth); + b = (x >= 2*partWidth)*(x < 3*partWidth)*(-2*255 + x * 255 / partWidth) + (x >= 3*partWidth)*(x < 5*partWidth)*255 + (x >= 5*partWidth)*(x < 6*partWidth)*(6*255 - x * 255 / partWidth); + + coef = (imHeight - y) / imHeight; + r = 128 + (r - 128) * coef; + g = 128 + (g - 128) * coef; + b = 128 + (b - 128) * coef; + + changeFinalColor('#' + dechex(r) + dechex(g) + dechex(b)); + updateLight(r, g, b); +} + +function updateLight(r, g, b) { + var i, partDetail = detail / 2, finalCoef, finalR, finalG, finalB, color; + + for (i=0; i=0) && (i'); + }, + + init : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor; + + // Setup browse button + document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image'); + if (isVisible('srcbrowser')) + document.getElementById('src').style.width = '180px'; + + e = ed.selection.getNode(); + + this.fillFileList('image_list', tinyMCEPopup.getParam('external_image_list', 'tinyMCEImageList')); + + if (e.nodeName == 'IMG') { + f.src.value = ed.dom.getAttrib(e, 'src'); + f.alt.value = ed.dom.getAttrib(e, 'alt'); + f.border.value = this.getAttrib(e, 'border'); + f.vspace.value = this.getAttrib(e, 'vspace'); + f.hspace.value = this.getAttrib(e, 'hspace'); + f.width.value = ed.dom.getAttrib(e, 'width'); + f.height.value = ed.dom.getAttrib(e, 'height'); + f.insert.value = ed.getLang('update'); + this.styleVal = ed.dom.getAttrib(e, 'style'); + selectByValue(f, 'image_list', f.src.value); + selectByValue(f, 'align', this.getAttrib(e, 'align')); + this.updateStyle(); + } + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = typeof(l) === 'function' ? l() : window[l]; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + update : function() { + var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, args = {}, el; + + tinyMCEPopup.restoreSelection(); + + if (f.src.value === '') { + if (ed.selection.getNode().nodeName == 'IMG') { + ed.dom.remove(ed.selection.getNode()); + ed.execCommand('mceRepaint'); + } + + tinyMCEPopup.close(); + return; + } + + if (!ed.settings.inline_styles) { + args = tinymce.extend(args, { + vspace : nl.vspace.value, + hspace : nl.hspace.value, + border : nl.border.value, + align : getSelectValue(f, 'align') + }); + } else + args.style = this.styleVal; + + tinymce.extend(args, { + src : f.src.value.replace(/ /g, '%20'), + alt : f.alt.value, + width : f.width.value, + height : f.height.value + }); + + el = ed.selection.getNode(); + + if (el && el.nodeName == 'IMG') { + ed.dom.setAttribs(el, args); + tinyMCEPopup.editor.execCommand('mceRepaint'); + tinyMCEPopup.editor.focus(); + } else { + tinymce.each(args, function(value, name) { + if (value === "") { + delete args[name]; + } + }); + + ed.execCommand('mceInsertContent', false, tinyMCEPopup.editor.dom.createHTML('img', args), {skip_undo : 1}); + ed.undoManager.add(); + } + + tinyMCEPopup.close(); + }, + + updateStyle : function() { + var dom = tinyMCEPopup.dom, st = {}, v, f = document.forms[0]; + + if (tinyMCEPopup.editor.settings.inline_styles) { + tinymce.each(tinyMCEPopup.dom.parseStyle(this.styleVal), function(value, key) { + st[key] = value; + }); + + // Handle align + v = getSelectValue(f, 'align'); + if (v) { + if (v == 'left' || v == 'right') { + st['float'] = v; + delete st['vertical-align']; + } else { + st['vertical-align'] = v; + delete st['float']; + } + } else { + delete st['float']; + delete st['vertical-align']; + } + + // Handle border + v = f.border.value; + if (v || v == '0') { + if (v == '0') + st['border'] = '0'; + else + st['border'] = v + 'px solid black'; + } else + delete st['border']; + + // Handle hspace + v = f.hspace.value; + if (v) { + delete st['margin']; + st['margin-left'] = v + 'px'; + st['margin-right'] = v + 'px'; + } else { + delete st['margin-left']; + delete st['margin-right']; + } + + // Handle vspace + v = f.vspace.value; + if (v) { + delete st['margin']; + st['margin-top'] = v + 'px'; + st['margin-bottom'] = v + 'px'; + } else { + delete st['margin-top']; + delete st['margin-bottom']; + } + + // Merge + st = tinyMCEPopup.dom.parseStyle(dom.serializeStyle(st), 'img'); + this.styleVal = dom.serializeStyle(st, 'img'); + } + }, + + getAttrib : function(e, at) { + var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2; + + if (ed.settings.inline_styles) { + switch (at) { + case 'align': + if (v = dom.getStyle(e, 'float')) + return v; + + if (v = dom.getStyle(e, 'vertical-align')) + return v; + + break; + + case 'hspace': + v = dom.getStyle(e, 'margin-left') + v2 = dom.getStyle(e, 'margin-right'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'vspace': + v = dom.getStyle(e, 'margin-top') + v2 = dom.getStyle(e, 'margin-bottom'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'border': + v = 0; + + tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) { + sv = dom.getStyle(e, 'border-' + sv + '-width'); + + // False or not the same as prev + if (!sv || (sv != v && v !== 0)) { + v = 0; + return false; + } + + if (sv) + v = sv; + }); + + if (v) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + } + } + + if (v = dom.getAttrib(e, at)) + return v; + + return ''; + }, + + resetImageData : function() { + var f = document.forms[0]; + + f.width.value = f.height.value = ""; + }, + + updateImageData : function() { + var f = document.forms[0], t = ImageDialog; + + if (f.width.value == "") + f.width.value = t.preloadImg.width; + + if (f.height.value == "") + f.height.value = t.preloadImg.height; + }, + + getImageData : function() { + var f = document.forms[0]; + + this.preloadImg = new Image(); + this.preloadImg.onload = this.updateImageData; + this.preloadImg.onerror = this.resetImageData; + this.preloadImg.src = tinyMCEPopup.editor.documentBaseURI.toAbsolute(f.src.value); + } +}; + +ImageDialog.preInit(); +tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/link.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/link.js new file mode 100644 index 0000000..b08b2ba --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/advanced/js/link.js @@ -0,0 +1,159 @@ +tinyMCEPopup.requireLangPack(); + +var LinkDialog = { + preInit : function() { + var url; + + if (url = tinyMCEPopup.getParam("external_link_list_url")) + document.write(''); + }, + + init : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor; + + // Setup browse button + document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser', 'href', 'file', 'theme_advanced_link'); + if (isVisible('hrefbrowser')) + document.getElementById('href').style.width = '180px'; + + this.fillClassList('class_list'); + this.fillFileList('link_list', 'tinyMCELinkList'); + this.fillTargetList('target_list'); + + if (e = ed.dom.getParent(ed.selection.getNode(), 'A')) { + f.href.value = ed.dom.getAttrib(e, 'href'); + f.linktitle.value = ed.dom.getAttrib(e, 'title'); + f.insert.value = ed.getLang('update'); + selectByValue(f, 'link_list', f.href.value); + selectByValue(f, 'target_list', ed.dom.getAttrib(e, 'target')); + selectByValue(f, 'class_list', ed.dom.getAttrib(e, 'class')); + } + }, + + update : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor, e, b, href = f.href.value.replace(/ /g, '%20'); + + tinyMCEPopup.restoreSelection(); + e = ed.dom.getParent(ed.selection.getNode(), 'A'); + + // Remove element if there is no href + if (!f.href.value) { + if (e) { + b = ed.selection.getBookmark(); + ed.dom.remove(e, 1); + ed.selection.moveToBookmark(b); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + return; + } + } + + // Create new anchor elements + if (e == null) { + ed.getDoc().execCommand("unlink", false, null); + tinyMCEPopup.execCommand("mceInsertLink", false, "#mce_temp_url#", {skip_undo : 1}); + + tinymce.each(ed.dom.select("a"), function(n) { + if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') { + e = n; + + ed.dom.setAttribs(e, { + href : href, + title : f.linktitle.value, + target : f.target_list ? getSelectValue(f, "target_list") : null, + 'class' : f.class_list ? getSelectValue(f, "class_list") : null + }); + } + }); + } else { + ed.dom.setAttribs(e, { + href : href, + title : f.linktitle.value + }); + + if (f.target_list) { + ed.dom.setAttrib(e, 'target', getSelectValue(f, "target_list")); + } + + if (f.class_list) { + ed.dom.setAttrib(e, 'class', getSelectValue(f, "class_list")); + } + } + + // Don't move caret if selection was image + if (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') { + ed.focus(); + ed.selection.select(e); + ed.selection.collapse(0); + tinyMCEPopup.storeSelection(); + } + + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + }, + + checkPrefix : function(n) { + if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_email'))) + n.value = 'mailto:' + n.value; + + if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_external'))) + n.value = 'http://' + n.value; + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = window[l]; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillClassList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + if (v = tinyMCEPopup.getParam('theme_advanced_styles')) { + cl = []; + + tinymce.each(v.split(';'), function(v) { + var p = v.split('='); + + cl.push({'title' : p[0], 'class' : p[1]}); + }); + } else + cl = tinyMCEPopup.editor.dom.getClasses(); + + if (cl.length > 0) { + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + + tinymce.each(cl, function(o) { + lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillTargetList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v; + + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_same'), '_self'); + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_blank'), '_blank'); + + if (v = tinyMCEPopup.getParam('theme_advanced_link_targets')) { + tinymce.each(v.split(','), function(v) { + v = v.split('='); + lst.options[lst.options.length] = new Option(v[0], v[1]); + }); + } + } +}; + +LinkDialog.preInit(); +tinyMCEPopup.onInit.add(LinkDialog.init, LinkDialog); diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/simple/editor_template_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/simple/editor_template_src.js new file mode 100644 index 0000000..35c19a6 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/themes/simple/editor_template_src.js @@ -0,0 +1,84 @@ +/** + * editor_template_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM; + + // Tell it to load theme specific language pack(s) + tinymce.ThemeManager.requireLangPack('simple'); + + tinymce.create('tinymce.themes.SimpleTheme', { + init : function(ed, url) { + var t = this, states = ['Bold', 'Italic', 'Underline', 'Strikethrough', 'InsertUnorderedList', 'InsertOrderedList'], s = ed.settings; + + t.editor = ed; + ed.contentCSS.push(url + "/skins/" + s.skin + "/content.css"); + + ed.onInit.add(function() { + ed.onNodeChange.add(function(ed, cm) { + tinymce.each(states, function(c) { + cm.get(c.toLowerCase()).setActive(ed.queryCommandState(c)); + }); + }); + }); + + DOM.loadCSS((s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : '') || url + "/skins/" + s.skin + "/ui.css"); + }, + + renderUI : function(o) { + var t = this, n = o.targetNode, ic, tb, ed = t.editor, cf = ed.controlManager, sc; + + n = DOM.insertAfter(DOM.create('span', {id : ed.id + '_container', 'class' : 'mceEditor ' + ed.settings.skin + 'SimpleSkin'}), n); + n = sc = DOM.add(n, 'table', {cellPadding : 0, cellSpacing : 0, 'class' : 'mceLayout'}); + n = tb = DOM.add(n, 'tbody'); + + // Create iframe container + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(DOM.add(n, 'td'), 'div', {'class' : 'mceIframeContainer'}); + + // Create toolbar container + n = DOM.add(DOM.add(tb, 'tr', {'class' : 'last'}), 'td', {'class' : 'mceToolbar mceLast', align : 'center'}); + + // Create toolbar + tb = t.toolbar = cf.createToolbar("tools1"); + tb.add(cf.createButton('bold', {title : 'simple.bold_desc', cmd : 'Bold'})); + tb.add(cf.createButton('italic', {title : 'simple.italic_desc', cmd : 'Italic'})); + tb.add(cf.createButton('underline', {title : 'simple.underline_desc', cmd : 'Underline'})); + tb.add(cf.createButton('strikethrough', {title : 'simple.striketrough_desc', cmd : 'Strikethrough'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('undo', {title : 'simple.undo_desc', cmd : 'Undo'})); + tb.add(cf.createButton('redo', {title : 'simple.redo_desc', cmd : 'Redo'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('cleanup', {title : 'simple.cleanup_desc', cmd : 'mceCleanup'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('insertunorderedlist', {title : 'simple.bullist_desc', cmd : 'InsertUnorderedList'})); + tb.add(cf.createButton('insertorderedlist', {title : 'simple.numlist_desc', cmd : 'InsertOrderedList'})); + tb.renderTo(n); + + return { + iframeContainer : ic, + editorContainer : ed.id + '_container', + sizeContainer : sc, + deltaHeight : -20 + }; + }, + + getInfo : function() { + return { + longname : 'Simple theme', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + version : tinymce.majorVersion + "." + tinymce.minorVersion + } + } + }); + + tinymce.ThemeManager.add('simple', tinymce.themes.SimpleTheme); +})(); \ No newline at end of file diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/tiny_mce_src.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/tiny_mce_src.js new file mode 100644 index 0000000..3635061 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/tiny_mce_src.js @@ -0,0 +1,17942 @@ +// FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY +(function(win) { + var whiteSpaceRe = /^\s*|\s*$/g, + undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; + + var tinymce = { + majorVersion : '3', + + minorVersion : '5.11', + + releaseDate : '2014-05-08', + + _init : function() { + var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; + + t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1); + + t.isOpera = win.opera && opera.buildNumber; + + t.isWebKit = /WebKit/.test(ua); + + t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11; + + t.isIE6 = t.isIE && /MSIE [56]/.test(ua); + + t.isIE7 = t.isIE && /MSIE [7]/.test(ua); + + t.isIE8 = t.isIE && /MSIE [8]/.test(ua); + + t.isIE9 = t.isIE && /MSIE [9]/.test(ua); + + t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua); + + t.isMac = ua.indexOf('Mac') != -1; + + t.isAir = /adobeair/i.test(ua); + + t.isIDevice = /(iPad|iPhone)/.test(ua); + + t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; + + // TinyMCE .NET webcontrol might be setting the values for TinyMCE + if (win.tinyMCEPreInit) { + t.suffix = tinyMCEPreInit.suffix; + t.baseURL = tinyMCEPreInit.base; + t.query = tinyMCEPreInit.query; + return; + } + + // Get suffix and base + t.suffix = ''; + + // If base element found, add that infront of baseURL + nl = d.getElementsByTagName('base'); + for (i=0; i : + s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); + cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name + + // Create namespace for new class + ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); + + // Class already exists + if (ns[cn]) + return; + + // Make pure static class + if (s[2] == 'static') { + ns[cn] = p; + + if (this.onCreate) + this.onCreate(s[2], s[3], ns[cn]); + + return; + } + + // Create default constructor + if (!p[cn]) { + p[cn] = function() {}; + de = 1; + } + + // Add constructor and methods + ns[cn] = p[cn]; + t.extend(ns[cn].prototype, p); + + // Extend + if (s[5]) { + sp = t.resolve(s[5]).prototype; + scn = s[5].match(/\.(\w+)$/i)[1]; // Class name + + // Extend constructor + c = ns[cn]; + if (de) { + // Add passthrough constructor + ns[cn] = function() { + return sp[scn].apply(this, arguments); + }; + } else { + // Add inherit constructor + ns[cn] = function() { + this.parent = sp[scn]; + return c.apply(this, arguments); + }; + } + ns[cn].prototype[cn] = ns[cn]; + + // Add super methods + t.each(sp, function(f, n) { + ns[cn].prototype[n] = sp[n]; + }); + + // Add overridden methods + t.each(p, function(f, n) { + // Extend methods if needed + if (sp[n]) { + ns[cn].prototype[n] = function() { + this.parent = sp[n]; + return f.apply(this, arguments); + }; + } else { + if (n != cn) + ns[cn].prototype[n] = f; + } + }); + } + + // Add static methods + t.each(p['static'], function(f, n) { + ns[cn][n] = f; + }); + + if (this.onCreate) + this.onCreate(s[2], s[3], ns[cn].prototype); + }, + + walk : function(o, f, n, s) { + s = s || this; + + if (o) { + if (n) + o = o[n]; + + tinymce.each(o, function(o, i) { + if (f.call(s, o, i, n) === false) + return false; + + tinymce.walk(o, f, n, s); + }); + } + }, + + createNS : function(n, o) { + var i, v; + + o = o || win; + + n = n.split('.'); + for (i=0; i 0 ? args : [listener.scope]); + + if (returnValue === false) + break; + } + + self.inDispatch = false; + + return returnValue; + } + + }); + +(function() { + var each = tinymce.each; + + tinymce.create('tinymce.util.URI', { + URI : function(u, s) { + var t = this, o, a, b, base_url; + + // Trim whitespace + u = tinymce.trim(u); + + // Default settings + s = t.settings = s || {}; + + // Strange app protocol that isn't http/https or local anchor + // For example: mailto,skype,tel etc. + if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { + t.source = u; + return; + } + + // Absolute path with no host, fake host and protocol + if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) + u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; + + // Relative path http:// or protocol relative //path + if (!/^[\w\-]*:?\/\//.test(u)) { + base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; + u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); + } + + // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) + u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something + u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); + each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { + var s = u[i]; + + // Zope 3 workaround, they use @@something + if (s) + s = s.replace(/\(mce_at\)/g, '@@'); + + t[v] = s; + }); + + b = s.base_uri; + if (b) { + if (!t.protocol) + t.protocol = b.protocol; + + if (!t.userInfo) + t.userInfo = b.userInfo; + + if (!t.port && t.host === 'mce_host') + t.port = b.port; + + if (!t.host || t.host === 'mce_host') + t.host = b.host; + + t.source = ''; + } + + //t.path = t.path || '/'; + }, + + setPath : function(p) { + var t = this; + + p = /^(.*?)\/?(\w+)?$/.exec(p); + + // Update path parts + t.path = p[0]; + t.directory = p[1]; + t.file = p[2]; + + // Rebuild source + t.source = ''; + t.getURI(); + }, + + toRelative : function(u) { + var t = this, o; + + if (u === "./") + return u; + + u = new tinymce.util.URI(u, {base_uri : t}); + + // Not on same domain/port or protocol + if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) + return u.getURI(); + + var tu = t.getURI(), uu = u.getURI(); + + // Allow usage of the base_uri when relative_urls = true + if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) + return tu; + + o = t.toRelPath(t.path, u.path); + + // Add query + if (u.query) + o += '?' + u.query; + + // Add anchor + if (u.anchor) + o += '#' + u.anchor; + + return o; + }, + + toAbsolute : function(u, nh) { + u = new tinymce.util.URI(u, {base_uri : this}); + + return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); + }, + + toRelPath : function(base, path) { + var items, bp = 0, out = '', i, l; + + // Split the paths + base = base.substring(0, base.lastIndexOf('/')); + base = base.split('/'); + items = path.split('/'); + + if (base.length >= items.length) { + for (i = 0, l = base.length; i < l; i++) { + if (i >= items.length || base[i] != items[i]) { + bp = i + 1; + break; + } + } + } + + if (base.length < items.length) { + for (i = 0, l = items.length; i < l; i++) { + if (i >= base.length || base[i] != items[i]) { + bp = i + 1; + break; + } + } + } + + if (bp === 1) + return path; + + for (i = 0, l = base.length - (bp - 1); i < l; i++) + out += "../"; + + for (i = bp - 1, l = items.length; i < l; i++) { + if (i != bp - 1) + out += "/" + items[i]; + else + out += items[i]; + } + + return out; + }, + + toAbsPath : function(base, path) { + var i, nb = 0, o = [], tr, outPath; + + // Split paths + tr = /\/$/.test(path) ? '/' : ''; + base = base.split('/'); + path = path.split('/'); + + // Remove empty chunks + each(base, function(k) { + if (k) + o.push(k); + }); + + base = o; + + // Merge relURLParts chunks + for (i = path.length - 1, o = []; i >= 0; i--) { + // Ignore empty or . + if (path[i].length === 0 || path[i] === ".") + continue; + + // Is parent + if (path[i] === '..') { + nb++; + continue; + } + + // Move up + if (nb > 0) { + nb--; + continue; + } + + o.push(path[i]); + } + + i = base.length - nb; + + // If /a/b/c or / + if (i <= 0) + outPath = o.reverse().join('/'); + else + outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); + + // Add front / if it's needed + if (outPath.indexOf('/') !== 0) + outPath = '/' + outPath; + + // Add traling / if it's needed + if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) + outPath += tr; + + return outPath; + }, + + getURI : function(nh) { + var s, t = this; + + // Rebuild source + if (!t.source || nh) { + s = ''; + + if (!nh) { + if (t.protocol) + s += t.protocol + '://'; + + if (t.userInfo) + s += t.userInfo + '@'; + + if (t.host) + s += t.host; + + if (t.port) + s += ':' + t.port; + } + + if (t.path) + s += t.path; + + if (t.query) + s += '?' + t.query; + + if (t.anchor) + s += '#' + t.anchor; + + t.source = s; + } + + return t.source; + } + }); +})(); + +(function() { + var each = tinymce.each; + + tinymce.create('static tinymce.util.Cookie', { + getHash : function(n) { + var v = this.get(n), h; + + if (v) { + each(v.split('&'), function(v) { + v = v.split('='); + h = h || {}; + h[unescape(v[0])] = unescape(v[1]); + }); + } + + return h; + }, + + setHash : function(n, v, e, p, d, s) { + var o = ''; + + each(v, function(v, k) { + o += (!o ? '' : '&') + escape(k) + '=' + escape(v); + }); + + this.set(n, o, e, p, d, s); + }, + + get : function(n) { + var c = document.cookie, e, p = n + "=", b; + + // Strict mode + if (!c) + return; + + b = c.indexOf("; " + p); + + if (b == -1) { + b = c.indexOf(p); + + if (b !== 0) + return null; + } else + b += 2; + + e = c.indexOf(";", b); + + if (e == -1) + e = c.length; + + return unescape(c.substring(b + p.length, e)); + }, + + set : function(n, v, e, p, d, s) { + document.cookie = n + "=" + escape(v) + + ((e) ? "; expires=" + e.toGMTString() : "") + + ((p) ? "; path=" + escape(p) : "") + + ((d) ? "; domain=" + d : "") + + ((s) ? "; secure" : ""); + }, + + remove : function(name, path, domain) { + var date = new Date(); + + date.setTime(date.getTime() - 1000); + + this.set(name, '', date, path, domain); + } + }); +})(); + +(function() { + function serialize(o, quote) { + var i, v, t, name; + + quote = quote || '"'; + + if (o == null) + return 'null'; + + t = typeof o; + + if (t == 'string') { + v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; + + return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { + // Make sure single quotes never get encoded inside double quotes for JSON compatibility + if (quote === '"' && a === "'") + return a; + + i = v.indexOf(b); + + if (i + 1) + return '\\' + v.charAt(i + 1); + + a = b.charCodeAt().toString(16); + + return '\\u' + '0000'.substring(a.length) + a; + }) + quote; + } + + if (t == 'object') { + if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') { + for (i=0, v = '['; i 0 ? ',' : '') + serialize(o[i], quote); + + return v + ']'; + } + + v = '{'; + + for (name in o) { + if (o.hasOwnProperty(name)) { + v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; + } + } + + return v + '}'; + } + + return '' + o; + }; + + tinymce.util.JSON = { + serialize: serialize, + + parse: function(s) { + try { + return eval('(' + s + ')'); + } catch (ex) { + // Ignore + } + } + + }; +})(); + +tinymce.create('static tinymce.util.XHR', { + send : function(o) { + var x, t, w = window, c = 0; + + function ready() { + if (!o.async || x.readyState == 4 || c++ > 10000) { + if (o.success && c < 10000 && x.status == 200) + o.success.call(o.success_scope, '' + x.responseText, x, o); + else if (o.error) + o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); + + x = null; + } else + w.setTimeout(ready, 10); + }; + + // Default settings + o.scope = o.scope || this; + o.success_scope = o.success_scope || o.scope; + o.error_scope = o.error_scope || o.scope; + o.async = o.async === false ? false : true; + o.data = o.data || ''; + + function get(s) { + x = 0; + + try { + x = new ActiveXObject(s); + } catch (ex) { + } + + return x; + }; + + x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); + + if (x) { + if (x.overrideMimeType) + x.overrideMimeType(o.content_type); + + x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); + + if (o.content_type) + x.setRequestHeader('Content-Type', o.content_type); + + x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + x.send(o.data); + + // Syncronous request + if (!o.async) + return ready(); + + // Wait for response, onReadyStateChange can not be used since it leaks memory in IE + t = w.setTimeout(ready, 10); + } + } +}); + +(function() { + var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; + + tinymce.create('tinymce.util.JSONRequest', { + JSONRequest : function(s) { + this.settings = extend({ + }, s); + this.count = 0; + }, + + send : function(o) { + var ecb = o.error, scb = o.success; + + o = extend(this.settings, o); + + o.success = function(c, x) { + c = JSON.parse(c); + + if (typeof(c) == 'undefined') { + c = { + error : 'JSON Parse error.' + }; + } + + if (c.error) + ecb.call(o.error_scope || o.scope, c.error, x); + else + scb.call(o.success_scope || o.scope, c.result); + }; + + o.error = function(ty, x) { + if (ecb) + ecb.call(o.error_scope || o.scope, ty, x); + }; + + o.data = JSON.serialize({ + id : o.id || 'c' + (this.count++), + method : o.method, + params : o.params + }); + + // JSON content type for Ruby on rails. Bug: #1883287 + o.content_type = 'application/json'; + + XHR.send(o); + }, + + 'static' : { + sendRPC : function(o) { + return new tinymce.util.JSONRequest().send(o); + } + } + }); +}()); +(function(tinymce){ + tinymce.VK = { + BACKSPACE: 8, + DELETE: 46, + DOWN: 40, + ENTER: 13, + LEFT: 37, + RIGHT: 39, + SPACEBAR: 32, + TAB: 9, + UP: 38, + + modifierPressed: function (e) { + return e.shiftKey || e.ctrlKey || e.altKey; + }, + + metaKeyPressed: function(e) { + // Check if ctrl or meta key is pressed also check if alt is false for Polish users + return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey; + } + }; +})(tinymce); + +tinymce.util.Quirks = function(editor) { + var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, + settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each; + + function setEditorCommandState(cmd, state) { + try { + editor.getDoc().execCommand(cmd, false, state); + } catch (ex) { + // Ignore + } + } + + function getDocumentMode() { + var documentMode = editor.getDoc().documentMode; + + return documentMode ? documentMode : 6; + }; + + function isDefaultPrevented(e) { + return e.isDefaultPrevented(); + }; + + function cleanupStylesWhenDeleting() { + function removeMergedFormatSpans(isDelete) { + var rng, blockElm, wrapperElm, bookmark, container, offset, elm; + + function isAtStartOrEndOfElm() { + if (container.nodeType == 3) { + if (isDelete && offset == container.length) { + return true; + } + + if (!isDelete && offset === 0) { + return true; + } + } + } + + rng = selection.getRng(); + var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset]; + + if (!rng.collapsed) { + isDelete = true; + } + + container = rng[(isDelete ? 'start' : 'end') + 'Container']; + offset = rng[(isDelete ? 'start' : 'end') + 'Offset']; + + if (container.nodeType == 3) { + blockElm = dom.getParent(rng.startContainer, dom.isBlock); + + // On delete clone the root span of the next block element + if (isDelete) { + blockElm = dom.getNext(blockElm, dom.isBlock); + } + + if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) { + // Wrap children of block in a EM and let WebKit stick is + // runtime styles junk into that EM + wrapperElm = dom.create('em', {'id': '__mceDel'}); + + each(tinymce.grep(blockElm.childNodes), function(node) { + wrapperElm.appendChild(node); + }); + + blockElm.appendChild(wrapperElm); + } + } + + // Do the backspace/delete action + rng = dom.createRng(); + rng.setStart(tmpRng[0], tmpRng[1]); + rng.setEnd(tmpRng[2], tmpRng[3]); + selection.setRng(rng); + editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); + + // Remove temp wrapper element + if (wrapperElm) { + bookmark = selection.getBookmark(); + + while (elm = dom.get('__mceDel')) { + dom.remove(elm, true); + } + + selection.moveToBookmark(bookmark); + } + } + + editor.onKeyDown.add(function(editor, e) { + var isDelete; + + isDelete = e.keyCode == DELETE; + if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { + e.preventDefault(); + removeMergedFormatSpans(isDelete); + } + }); + + editor.addCommand('Delete', function() {removeMergedFormatSpans();}); + }; + + function emptyEditorWhenDeleting() { + function serializeRng(rng) { + var body = dom.create("body"); + var contents = rng.cloneContents(); + body.appendChild(contents); + return selection.serializer.serialize(body, {format: 'html'}); + } + + function allContentsSelected(rng) { + var selection = serializeRng(rng); + + var allRng = dom.createRng(); + allRng.selectNode(editor.getBody()); + + var allSelection = serializeRng(allRng); + return selection === allSelection; + } + + editor.onKeyDown.add(function(editor, e) { + var keyCode = e.keyCode, isCollapsed; + + // Empty the editor if it's needed for example backspace at

|

+ if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { + isCollapsed = editor.selection.isCollapsed(); + + // Selection is collapsed but the editor isn't empty + if (isCollapsed && !dom.isEmpty(editor.getBody())) { + return; + } + + // IE deletes all contents correctly when everything is selected + if (tinymce.isIE && !isCollapsed) { + return; + } + + // Selection isn't collapsed but not all the contents is selected + if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { + return; + } + + // Manually empty the editor + editor.setContent(''); + editor.selection.setCursorLocation(editor.getBody(), 0); + editor.nodeChanged(); + } + }); + }; + + function selectAll() { + editor.onKeyDown.add(function(editor, e) { + if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) { + e.preventDefault(); + editor.execCommand('SelectAll'); + } + }); + }; + + function inputMethodFocus() { + if (!editor.settings.content_editable) { + // Case 1 IME doesn't initialize if you focus the document + dom.bind(editor.getDoc(), 'focusin', function(e) { + selection.setRng(selection.getRng()); + }); + + // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event + dom.bind(editor.getDoc(), 'mousedown', function(e) { + if (e.target == editor.getDoc().documentElement) { + editor.getWin().focus(); + selection.setRng(selection.getRng()); + } + }); + } + }; + + function removeHrOnBackspace() { + editor.onKeyDown.add(function(editor, e) { + if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { + if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { + var node = selection.getNode(); + var previousSibling = node.previousSibling; + + if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { + dom.remove(previousSibling); + tinymce.dom.Event.cancel(e); + } + } + } + }) + } + + function focusBody() { + // Fix for a focus bug in FF 3.x where the body element + // wouldn't get proper focus if the user clicked on the HTML element + if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 + editor.onMouseDown.add(function(editor, e) { + if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { + var body = editor.getBody(); + + // Blur the body it's focused but not correctly focused + body.blur(); + + // Refocus the body after a little while + setTimeout(function() { + body.focus(); + }, 0); + } + }); + } + }; + + function selectControlElements() { + editor.onClick.add(function(editor, e) { + e = e.target; + + // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 + // WebKit can't even do simple things like selecting an image + // Needs tobe the setBaseAndExtend or it will fail to select floated images + if (/^(IMG|HR)$/.test(e.nodeName)) { + selection.getSel().setBaseAndExtent(e, 0, e, 1); + } + + if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { + selection.select(e); + } + + editor.nodeChanged(); + }); + }; + + function removeStylesWhenDeletingAccrossBlockElements() { + function getAttributeApplyFunction() { + var template = dom.getAttribs(selection.getStart().cloneNode(false)); + + return function() { + var target = selection.getStart(); + + if (target !== editor.getBody()) { + dom.setAttrib(target, "style", null); + + each(template, function(attr) { + target.setAttributeNode(attr.cloneNode(true)); + }); + } + }; + } + + function isSelectionAcrossElements() { + return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); + } + + function blockEvent(editor, e) { + e.preventDefault(); + return false; + } + + editor.onKeyPress.add(function(editor, e) { + var applyAttributes; + + if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + editor.getDoc().execCommand('delete', false, null); + applyAttributes(); + e.preventDefault(); + return false; + } + }); + + dom.bind(editor.getDoc(), 'cut', function(e) { + var applyAttributes; + + if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + editor.onKeyUp.addToTop(blockEvent); + + setTimeout(function() { + applyAttributes(); + editor.onKeyUp.remove(blockEvent); + }, 0); + } + }); + } + + function selectionChangeNodeChanged() { + var lastRng, selectionTimer; + + dom.bind(editor.getDoc(), 'selectionchange', function() { + if (selectionTimer) { + clearTimeout(selectionTimer); + selectionTimer = 0; + } + + selectionTimer = window.setTimeout(function() { + var rng = selection.getRng(); + + // Compare the ranges to see if it was a real change or not + if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { + editor.nodeChanged(); + lastRng = rng; + } + }, 50); + }); + } + + function ensureBodyHasRoleApplication() { + document.body.setAttribute("role", "application"); + } + + function disableBackspaceIntoATable() { + editor.onKeyDown.add(function(editor, e) { + if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { + if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { + var previousSibling = selection.getNode().previousSibling; + if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { + return tinymce.dom.Event.cancel(e); + } + } + } + }) + } + + function addNewLinesBeforeBrInPre() { + // IE8+ rendering mode does the right thing with BR in PRE + if (getDocumentMode() > 7) { + return; + } + + // Enable display: none in area and add a specific class that hides all BR elements in PRE to + // avoid the caret from getting stuck at the BR elements while pressing the right arrow key + setEditorCommandState('RespectVisibilityInDesign', true); + editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); + dom.addClass(editor.getBody(), 'mceHideBrInPre'); + + // Adds a \n before all BR elements in PRE to get them visual + parser.addNodeFilter('pre', function(nodes, name) { + var i = nodes.length, brNodes, j, brElm, sibling; + + while (i--) { + brNodes = nodes[i].getAll('br'); + j = brNodes.length; + while (j--) { + brElm = brNodes[j]; + + // Add \n before BR in PRE elements on older IE:s so the new lines get rendered + sibling = brElm.prev; + if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { + sibling.value += '\n'; + } else { + brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; + } + } + } + }); + + // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible + serializer.addNodeFilter('pre', function(nodes, name) { + var i = nodes.length, brNodes, j, brElm, sibling; + + while (i--) { + brNodes = nodes[i].getAll('br'); + j = brNodes.length; + while (j--) { + brElm = brNodes[j]; + sibling = brElm.prev; + if (sibling && sibling.type == 3) { + sibling.value = sibling.value.replace(/\r?\n$/, ''); + } + } + } + }); + } + + function removePreSerializedStylesWhenSelectingControls() { + dom.bind(editor.getBody(), 'mouseup', function(e) { + var value, node = selection.getNode(); + + // Moved styles to attributes on IMG eements + if (node.nodeName == 'IMG') { + // Convert style width to width attribute + if (value = dom.getStyle(node, 'width')) { + dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); + dom.setStyle(node, 'width', ''); + } + + // Convert style height to height attribute + if (value = dom.getStyle(node, 'height')) { + dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); + dom.setStyle(node, 'height', ''); + } + } + }); + } + + function keepInlineElementOnDeleteBackspace() { + editor.onKeyDown.add(function(editor, e) { + var isDelete, rng, container, offset, brElm, sibling, collapsed; + + isDelete = e.keyCode == DELETE; + if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { + rng = selection.getRng(); + container = rng.startContainer; + offset = rng.startOffset; + collapsed = rng.collapsed; + + // Override delete if the start container is a text node and is at the beginning of text or + // just before/after the last character to be deleted in collapsed mode + if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { + // Edge case when deleting

|x

+ sibling = container.previousSibling; + if (sibling && sibling.nodeName == "IMG") { + return; + } + + nonEmptyElements = editor.schema.getNonEmptyElements(); + + // Prevent default logic since it's broken + e.preventDefault(); + + // Insert a BR before the text node this will prevent the containing element from being deleted/converted + brElm = dom.create('br', {id: '__tmp'}); + container.parentNode.insertBefore(brElm, container); + + // Do the browser delete + editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); + + // Check if the previous sibling is empty after deleting for example:

|

+ container = selection.getRng().startContainer; + sibling = container.previousSibling; + if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { + dom.remove(sibling); + } + + // Remove the temp element we inserted + dom.remove('__tmp'); + } + } + }); + } + + function removeBlockQuoteOnBackSpace() { + // Add block quote deletion handler + editor.onKeyDown.add(function(editor, e) { + var rng, container, offset, root, parent; + + if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { + return; + } + + rng = selection.getRng(); + container = rng.startContainer; + offset = rng.startOffset; + root = dom.getRoot(); + parent = container; + + if (!rng.collapsed || offset !== 0) { + return; + } + + while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { + parent = parent.parentNode; + } + + // Is the cursor at the beginning of a blockquote? + if (parent.tagName === 'BLOCKQUOTE') { + // Remove the blockquote + editor.formatter.toggle('blockquote', null, parent); + + // Move the caret to the beginning of container + rng = dom.createRng(); + rng.setStart(container, 0); + rng.setEnd(container, 0); + selection.setRng(rng); + } + }); + }; + + function setGeckoEditingOptions() { + function setOpts() { + editor._refreshContentEditable(); + + setEditorCommandState("StyleWithCSS", false); + setEditorCommandState("enableInlineTableEditing", false); + + if (!settings.object_resizing) { + setEditorCommandState("enableObjectResizing", false); + } + }; + + if (!settings.readonly) { + editor.onBeforeExecCommand.add(setOpts); + editor.onMouseDown.add(setOpts); + } + }; + + function addBrAfterLastLinks() { + function fixLinks(editor, o) { + each(dom.select('a'), function(node) { + var parentNode = node.parentNode, root = dom.getRoot(); + + if (parentNode.lastChild === node) { + while (parentNode && !dom.isBlock(parentNode)) { + if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { + return; + } + + parentNode = parentNode.parentNode; + } + + dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); + } + }); + }; + + editor.onExecCommand.add(function(editor, cmd) { + if (cmd === 'CreateLink') { + fixLinks(editor); + } + }); + + editor.onSetContent.add(selection.onSetContent.add(fixLinks)); + }; + + function setDefaultBlockType() { + if (settings.forced_root_block) { + editor.onInit.add(function() { + setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); + }); + } + } + + function removeGhostSelection() { + function repaint(sender, args) { + if (!sender || !args.initial) { + editor.execCommand('mceRepaint'); + } + }; + + editor.onUndo.add(repaint); + editor.onRedo.add(repaint); + editor.onSetContent.add(repaint); + }; + + function deleteControlItemOnBackSpace() { + editor.onKeyDown.add(function(editor, e) { + var rng; + + if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { + rng = editor.getDoc().selection.createRange(); + if (rng && rng.item) { + e.preventDefault(); + editor.undoManager.beforeChange(); + dom.remove(rng.item(0)); + editor.undoManager.add(); + } + } + }); + }; + + function renderEmptyBlocksFix() { + var emptyBlocksCSS; + + // IE10+ + if (getDocumentMode() >= 10) { + emptyBlocksCSS = ''; + each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { + emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; + }); + + editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); + } + }; + + function fakeImageResize() { + var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio, + resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc(); + + if (!settings.object_resizing || settings.webkit_fake_resize === false) { + return; + } + + // Try disabling object resizing if WebKit implements resizing in the future + setEditorCommandState("enableObjectResizing", false); + + // Details about each resize handle how to scale etc + resizeHandles = { + // Name: x multiplier, y multiplier, delta size x, delta size y + n: [.5, 0, 0, -1], + e: [1, .5, 1, 0], + s: [.5, 1, 0, 1], + w: [0, .5, -1, 0], + nw: [0, 0, -1, -1], + ne: [1, 0, 1, -1], + se: [1, 1, 1, 1], + sw : [0, 1, -1, 1] + }; + + function resizeElement(e) { + var deltaX, deltaY; + + // Calc new width/height + deltaX = e.screenX - startX; + deltaY = e.screenY - startY; + + // Calc new size + width = deltaX * selectedHandle[2] + startW; + height = deltaY * selectedHandle[3] + startH; + + // Never scale down lower than 5 pixels + width = width < 5 ? 5 : width; + height = height < 5 ? 5 : height; + + // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image + if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) { + width = Math.round(height / ratio); + height = Math.round(width * ratio); + } + + // Update ghost size + dom.setStyles(selectedElmGhost, { + width: width, + height: height + }); + + // Update ghost X position if needed + if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) { + dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width)); + } + + // Update ghost Y position if needed + if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) { + dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height)); + } + } + + function endResize() { + function setSizeProp(name, value) { + if (value) { + // Resize by using style or attribute + if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) { + dom.setStyle(selectedElm, name, value); + } else { + dom.setAttrib(selectedElm, name, value); + } + } + } + + // Set width/height properties + setSizeProp('width', width); + setSizeProp('height', height); + + dom.unbind(editableDoc, 'mousemove', resizeElement); + dom.unbind(editableDoc, 'mouseup', endResize); + + if (rootDocument != editableDoc) { + dom.unbind(rootDocument, 'mousemove', resizeElement); + dom.unbind(rootDocument, 'mouseup', endResize); + } + + // Remove ghost and update resize handle positions + dom.remove(selectedElmGhost); + showResizeRect(selectedElm); + } + + function showResizeRect(targetElm) { + var position, targetWidth, targetHeight; + + hideResizeRect(); + + // Get position and size of target + position = dom.getPos(targetElm); + selectedElmX = position.x; + selectedElmY = position.y; + targetWidth = targetElm.offsetWidth; + targetHeight = targetElm.offsetHeight; + + // Reset width/height if user selects a new image/table + if (selectedElm != targetElm) { + selectedElm = targetElm; + width = height = 0; + } + + each(resizeHandles, function(handle, name) { + var handleElm; + + // Get existing or render resize handle + handleElm = dom.get('mceResizeHandle' + name); + if (!handleElm) { + handleElm = dom.add(editableDoc.documentElement, 'div', { + id: 'mceResizeHandle' + name, + 'class': 'mceResizeHandle', + style: 'cursor:' + name + '-resize; margin:0; padding:0' + }); + + dom.bind(handleElm, 'mousedown', function(e) { + e.preventDefault(); + + endResize(); + + startX = e.screenX; + startY = e.screenY; + startW = selectedElm.clientWidth; + startH = selectedElm.clientHeight; + ratio = startH / startW; + selectedHandle = handle; + + selectedElmGhost = selectedElm.cloneNode(true); + dom.addClass(selectedElmGhost, 'mceClonedResizable'); + dom.setStyles(selectedElmGhost, { + left: selectedElmX, + top: selectedElmY, + margin: 0 + }); + + editableDoc.documentElement.appendChild(selectedElmGhost); + + dom.bind(editableDoc, 'mousemove', resizeElement); + dom.bind(editableDoc, 'mouseup', endResize); + + if (rootDocument != editableDoc) { + dom.bind(rootDocument, 'mousemove', resizeElement); + dom.bind(rootDocument, 'mouseup', endResize); + } + }); + } else { + dom.show(handleElm); + } + + // Position element + dom.setStyles(handleElm, { + left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2), + top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2) + }); + }); + + // Only add resize rectangle on WebKit and only on images + if (!tinymce.isOpera && selectedElm.nodeName == "IMG") { + selectedElm.setAttribute('data-mce-selected', '1'); + } + } + + function hideResizeRect() { + if (selectedElm) { + selectedElm.removeAttribute('data-mce-selected'); + } + + for (var name in resizeHandles) { + dom.hide('mceResizeHandle' + name); + } + } + + // Add CSS for resize handles, cloned element and selected + editor.contentStyles.push( + '.mceResizeHandle {' + + 'position: absolute;' + + 'border: 1px solid black;' + + 'background: #FFF;' + + 'width: 5px;' + + 'height: 5px;' + + 'z-index: 10000' + + '}' + + '.mceResizeHandle:hover {' + + 'background: #000' + + '}' + + 'img[data-mce-selected] {' + + 'outline: 1px solid black' + + '}' + + 'img.mceClonedResizable, table.mceClonedResizable {' + + 'position: absolute;' + + 'outline: 1px dashed black;' + + 'opacity: .5;' + + 'z-index: 10000' + + '}' + ); + + function updateResizeRect() { + var controlElm = dom.getParent(selection.getNode(), 'table,img'); + + // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v + each(dom.select('img[data-mce-selected]'), function(img) { + img.removeAttribute('data-mce-selected'); + }); + + if (controlElm) { + showResizeRect(controlElm); + } else { + hideResizeRect(); + } + } + + // Show/hide resize rect when image is selected + editor.onNodeChange.add(updateResizeRect); + + // Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container + dom.bind(editableDoc, 'selectionchange', updateResizeRect); + + // Remove the internal attribute when serializing the DOM + editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) { + var i = nodes.length; + + while (i--) { + nodes[i].attr(name, null); + } + }); + } + + function keepNoScriptContents() { + if (getDocumentMode() < 9) { + parser.addNodeFilter('noscript', function(nodes) { + var i = nodes.length, node, textNode; + + while (i--) { + node = nodes[i]; + textNode = node.firstChild; + + if (textNode) { + node.attr('data-mce-innertext', textNode.value); + } + } + }); + + serializer.addNodeFilter('noscript', function(nodes) { + var i = nodes.length, node, textNode, value; + + while (i--) { + node = nodes[i]; + textNode = nodes[i].firstChild; + + if (textNode) { + textNode.value = tinymce.html.Entities.decode(textNode.value); + } else { + // Old IE can't retain noscript value so an attribute is used to store it + value = node.attributes.map['data-mce-innertext']; + if (value) { + node.attr('data-mce-innertext', null); + textNode = new tinymce.html.Node('#text', 3); + textNode.value = value; + textNode.raw = true; + node.append(textNode); + } + } + } + }); + } + } + + function bodyHeight() { + editor.contentStyles.push('body {min-height: 100px}'); + editor.onClick.add(function(ed, e) { + if (e.target.nodeName == 'HTML') { + editor.execCommand('SelectAll'); + editor.selection.collapse(true); + editor.nodeChanged(); + } + }); + } + + function fixControlSelection() { + editor.onInit.add(function() { + var selectedRng; + + editor.getBody().addEventListener('mscontrolselect', function(e) { + setTimeout(function() { + if (editor.selection.getNode() != e.target) { + selectedRng = editor.selection.getRng(); + selection.fakeRng = editor.dom.createRng(); + selection.fakeRng.setStartBefore(e.target); + selection.fakeRng.setEndAfter(e.target); + } + }, 0); + }, false); + + editor.getDoc().addEventListener('selectionchange', function(e) { + if (selectedRng && !tinymce.dom.RangeUtils.compareRanges(editor.selection.getRng(), selectedRng)) { + selection.fakeRng = selectedRng = null; + } + }, false); + }); + } + + // All browsers + disableBackspaceIntoATable(); + removeBlockQuoteOnBackSpace(); + emptyEditorWhenDeleting(); + + // WebKit + if (tinymce.isWebKit) { + keepInlineElementOnDeleteBackspace(); + cleanupStylesWhenDeleting(); + inputMethodFocus(); + selectControlElements(); + setDefaultBlockType(); + + // iOS + if (tinymce.isIDevice) { + selectionChangeNodeChanged(); + } else { + fakeImageResize(); + selectAll(); + } + } + + // IE + if (tinymce.isIE && !tinymce.isIE11) { + removeHrOnBackspace(); + ensureBodyHasRoleApplication(); + addNewLinesBeforeBrInPre(); + removePreSerializedStylesWhenSelectingControls(); + deleteControlItemOnBackSpace(); + renderEmptyBlocksFix(); + keepNoScriptContents(); + } + + // IE 11+ + if (tinymce.isIE11) { + bodyHeight(); + fixControlSelection(); + } + + // Gecko + if (tinymce.isGecko && !tinymce.isIE11) { + removeHrOnBackspace(); + focusBody(); + removeStylesWhenDeletingAccrossBlockElements(); + setGeckoEditingOptions(); + addBrAfterLastLinks(); + removeGhostSelection(); + } + + // Opera + if (tinymce.isOpera) { + fakeImageResize(); + } +}; +(function(tinymce) { + var namedEntities, baseEntities, reverseEntities, + attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + rawCharsRegExp = /[<>&\"\']/g, + entityRegExp = /&(#x|#)?([\w]+);/g, + asciiMap = { + 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", + 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", + 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", + 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", + 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" + }; + + // Raw entities + baseEntities = { + '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code + "'" : ''', + '<' : '<', + '>' : '>', + '&' : '&' + }; + + // Reverse lookup table for raw entities + reverseEntities = { + '<' : '<', + '>' : '>', + '&' : '&', + '"' : '"', + ''' : "'" + }; + + // Decodes text by using the browser + function nativeDecode(text) { + var elm; + + elm = document.createElement("div"); + elm.innerHTML = text; + + return elm.textContent || elm.innerText || text; + }; + + // Build a two way lookup table for the entities + function buildEntitiesLookup(items, radix) { + var i, chr, entity, lookup = {}; + + if (items) { + items = items.split(','); + radix = radix || 10; + + // Build entities lookup table + for (i = 0; i < items.length; i += 2) { + chr = String.fromCharCode(parseInt(items[i], radix)); + + // Only add non base entities + if (!baseEntities[chr]) { + entity = '&' + items[i + 1] + ';'; + lookup[chr] = entity; + lookup[entity] = chr; + } + } + + return lookup; + } + }; + + // Unpack entities lookup where the numbers are in radix 32 to reduce the size + namedEntities = buildEntitiesLookup( + '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + + '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + + '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + + '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + + '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + + '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + + '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + + '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + + '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + + '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + + 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + + 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + + 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + + 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + + 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + + '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + + '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + + '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + + '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + + '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + + 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + + 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + + 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + + '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + + '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); + + tinymce.html = tinymce.html || {}; + + tinymce.html.Entities = { + encodeRaw : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); + }, + + encodeAllRaw : function(text) { + return ('' + text).replace(rawCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); + }, + + encodeNumeric : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + // Multi byte sequence convert it to a single entity + if (chr.length > 1) + return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; + + return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; + }); + }, + + encodeNamed : function(text, attr, entities) { + entities = entities || namedEntities; + + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || chr; + }); + }, + + getEncodeFunc : function(name, entities) { + var Entities = tinymce.html.Entities; + + entities = buildEntitiesLookup(entities) || namedEntities; + + function encodeNamedAndNumeric(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; + }); + }; + + function encodeCustomNamed(text, attr) { + return Entities.encodeNamed(text, attr, entities); + }; + + // Replace + with , to be compatible with previous TinyMCE versions + name = tinymce.makeMap(name.replace(/\+/g, ',')); + + // Named and numeric encoder + if (name.named && name.numeric) + return encodeNamedAndNumeric; + + // Named encoder + if (name.named) { + // Custom names + if (entities) + return encodeCustomNamed; + + return Entities.encodeNamed; + } + + // Numeric + if (name.numeric) + return Entities.encodeNumeric; + + // Raw encoder + return Entities.encodeRaw; + }, + + decode : function(text) { + return text.replace(entityRegExp, function(all, numeric, value) { + if (numeric) { + value = parseInt(value, numeric.length === 2 ? 16 : 10); + + // Support upper UTF + if (value > 0xFFFF) { + value -= 0x10000; + + return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); + } else + return asciiMap[value] || String.fromCharCode(value); + } + + return reverseEntities[all] || namedEntities[all] || nativeDecode(all); + }); + } + }; +})(tinymce); + +tinymce.html.Styles = function(settings, schema) { + var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, + urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, + styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, + trimRightRegExp = /\s+$/, + urlColorRegExp = /rgb/, + undef, i, encodingLookup = {}, encodingItems; + + settings = settings || {}; + + encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); + for (i = 0; i < encodingItems.length; i++) { + encodingLookup[encodingItems[i]] = '\uFEFF' + i; + encodingLookup['\uFEFF' + i] = encodingItems[i]; + } + + function toHex(match, r, g, b) { + function hex(val) { + val = parseInt(val).toString(16); + + return val.length > 1 ? val : '0' + val; // 0 -> 00 + }; + + return '#' + hex(r) + hex(g) + hex(b); + }; + + return { + toHex : function(color) { + return color.replace(rgbRegExp, toHex); + }, + + parse : function(css) { + var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; + + function compress(prefix, suffix) { + var top, right, bottom, left; + + // IE 11 will produce a border-image: none when getting the style attribute from

+ // So lets asume it shouldn't be there + if (styles['border-image'] === 'none') { + delete styles['border-image']; + } + + // Get values and check it it needs compressing + top = styles[prefix + '-top' + suffix]; + if (!top) + return; + + right = styles[prefix + '-right' + suffix]; + if (top != right) + return; + + bottom = styles[prefix + '-bottom' + suffix]; + if (right != bottom) + return; + + left = styles[prefix + '-left' + suffix]; + if (bottom != left) + return; + + // Compress + styles[prefix + suffix] = left; + delete styles[prefix + '-top' + suffix]; + delete styles[prefix + '-right' + suffix]; + delete styles[prefix + '-bottom' + suffix]; + delete styles[prefix + '-left' + suffix]; + }; + + function canCompress(key) { + var value = styles[key], i; + + if (!value || value.indexOf(' ') < 0) + return; + + value = value.split(' '); + i = value.length; + while (i--) { + if (value[i] !== value[0]) + return false; + } + + styles[key] = value[0]; + + return true; + }; + + function compress2(target, a, b, c) { + if (!canCompress(a)) + return; + + if (!canCompress(b)) + return; + + if (!canCompress(c)) + return; + + // Compress + styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; + delete styles[a]; + delete styles[b]; + delete styles[c]; + }; + + // Encodes the specified string by replacing all \" \' ; : with _ + function encode(str) { + isEncoded = true; + + return encodingLookup[str]; + }; + + // Decodes the specified string by replacing all _ with it's original value \" \' etc + // It will also decode the \" \' if keep_slashes is set to fale or omitted + function decode(str, keep_slashes) { + if (isEncoded) { + str = str.replace(/\uFEFF[0-9]/g, function(str) { + return encodingLookup[str]; + }); + } + + if (!keep_slashes) + str = str.replace(/\\([\'\";:])/g, "$1"); + + return str; + }; + + function processUrl(match, url, url2, url3, str, str2) { + str = str || str2; + + if (str) { + str = decode(str); + + // Force strings into single quote format + return "'" + str.replace(/\'/g, "\\'") + "'"; + } + + url = decode(url || url2 || url3); + + // Convert the URL to relative/absolute depending on config + if (urlConverter) + url = urlConverter.call(urlConverterScope, url, 'style'); + + // Output new URL format + return "url('" + url.replace(/\'/g, "\\'") + "')"; + }; + + if (css) { + // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing + css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { + return str.replace(/[;:]/g, encode); + }); + + // Parse styles + while (matches = styleRegExp.exec(css)) { + name = matches[1].replace(trimRightRegExp, '').toLowerCase(); + value = matches[2].replace(trimRightRegExp, ''); + + if (name && value.length > 0) { + // Opera will produce 700 instead of bold in their style values + if (name === 'font-weight' && value === '700') + value = 'bold'; + else if (name === 'color' || name === 'background-color') // Lowercase colors like RED + value = value.toLowerCase(); + + // Convert RGB colors to HEX + value = value.replace(rgbRegExp, toHex); + + // Convert URLs and force them into url('value') format + value = value.replace(urlOrStrRegExp, processUrl); + styles[name] = isEncoded ? decode(value, true) : value; + } + + styleRegExp.lastIndex = matches.index + matches[0].length; + } + + // Compress the styles to reduce it's size for example IE will expand styles + compress("border", ""); + compress("border", "-width"); + compress("border", "-color"); + compress("border", "-style"); + compress("padding", ""); + compress("margin", ""); + compress2('border', 'border-width', 'border-style', 'border-color'); + + // Remove pointless border, IE produces these + if (styles.border === 'medium none') + delete styles.border; + } + + return styles; + }, + + serialize : function(styles, element_name) { + var css = '', name, value; + + function serializeStyles(name) { + var styleList, i, l, value; + + styleList = schema.styles[name]; + if (styleList) { + for (i = 0, l = styleList.length; i < l; i++) { + name = styleList[i]; + value = styles[name]; + + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + }; + + // Serialize styles according to schema + if (element_name && schema && schema.styles) { + // Serialize global styles and element specific styles + serializeStyles('*'); + serializeStyles(element_name); + } else { + // Output the styles in the order they are inside the object + for (name in styles) { + value = styles[name]; + + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + + return css; + } + }; +}; + +(function(tinymce) { + var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; + + function split(str, delim) { + return str.split(delim || ','); + }; + + function unpack(lookup, data) { + var key, elements = {}; + + function replace(value) { + return value.replace(/[A-Z]+/g, function(key) { + return replace(lookup[key]); + }); + }; + + // Unpack lookup + for (key in lookup) { + if (lookup.hasOwnProperty(key)) + lookup[key] = replace(lookup[key]); + } + + // Unpack and parse data into object map + replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { + attributes = split(attributes, '|'); + + elements[name] = { + attributes : makeMap(attributes), + attributesOrder : attributes, + children : makeMap(children, '|', {'#comment' : {}}) + } + }); + + return elements; + }; + + function getHTML5() { + var html5 = mapCache.html5; + + if (!html5) { + html5 = mapCache.html5 = unpack({ + A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', + B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' + + 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', + C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' + + 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' + + 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' + }, 'html[A|manifest][body|head]' + + 'head[A][base|command|link|meta|noscript|script|style|title]' + + 'title[A][#]' + + 'base[A|href|target][]' + + 'link[A|href|rel|media|type|sizes][]' + + 'meta[A|http-equiv|name|content|charset][]' + + 'style[A|type|media|scoped][#]' + + 'script[A|charset|type|src|defer|async][#]' + + 'noscript[A][C]' + + 'body[A][C]' + + 'section[A][C]' + + 'nav[A][C]' + + 'article[A][C]' + + 'aside[A][C]' + + 'h1[A][B]' + + 'h2[A][B]' + + 'h3[A][B]' + + 'h4[A][B]' + + 'h5[A][B]' + + 'h6[A][B]' + + 'hgroup[A][h1|h2|h3|h4|h5|h6]' + + 'header[A][C]' + + 'footer[A][C]' + + 'address[A][C]' + + 'p[A][B]' + + 'br[A][]' + + 'pre[A][B]' + + 'dialog[A][dd|dt]' + + 'blockquote[A|cite][C]' + + 'ol[A|start|reversed][li]' + + 'ul[A][li]' + + 'li[A|value][C]' + + 'dl[A][dd|dt]' + + 'dt[A][B]' + + 'dd[A][C]' + + 'a[A|href|target|ping|rel|media|type][B]' + + 'em[A][B]' + + 'strong[A][B]' + + 'small[A][B]' + + 'cite[A][B]' + + 'q[A|cite][B]' + + 'dfn[A][B]' + + 'abbr[A][B]' + + 'code[A][B]' + + 'var[A][B]' + + 'samp[A][B]' + + 'kbd[A][B]' + + 'sub[A][B]' + + 'sup[A][B]' + + 'i[A][B]' + + 'b[A][B]' + + 'mark[A][B]' + + 'progress[A|value|max][B]' + + 'meter[A|value|min|max|low|high|optimum][B]' + + 'time[A|datetime][B]' + + 'ruby[A][B|rt|rp]' + + 'rt[A][B]' + + 'rp[A][B]' + + 'bdo[A][B]' + + 'span[A][B]' + + 'ins[A|cite|datetime][B]' + + 'del[A|cite|datetime][B]' + + 'figure[A][C|legend|figcaption]' + + 'figcaption[A][C]' + + 'img[A|alt|src|height|width|usemap|ismap][]' + + 'iframe[A|name|src|height|width|sandbox|seamless][]' + + 'embed[A|src|height|width|type][]' + + 'object[A|data|type|height|width|usemap|name|form|classid][param]' + + 'param[A|name|value][]' + + 'details[A|open][C|legend]' + + 'command[A|type|label|icon|disabled|checked|radiogroup][]' + + 'menu[A|type|label][C|li]' + + 'legend[A][C|B]' + + 'div[A][C]' + + 'source[A|src|type|media][]' + + 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + + 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + + 'hr[A][]' + + 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + + 'fieldset[A|disabled|form|name][C|legend]' + + 'label[A|form|for][B]' + + 'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + + 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + + 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + + 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + + 'datalist[A][B|option]' + + 'optgroup[A|disabled|label][option]' + + 'option[A|disabled|selected|label|value][]' + + 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + + 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + + 'output[A|for|form|name][B]' + + 'canvas[A|width|height][]' + + 'map[A|name][B|C]' + + 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + + 'mathml[A][]' + + 'svg[A][]' + + 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + + 'caption[A][C]' + + 'colgroup[A|span][col]' + + 'col[A|span][]' + + 'thead[A][tr]' + + 'tfoot[A][tr]' + + 'tbody[A][tr]' + + 'tr[A][th|td]' + + 'th[A|headers|rowspan|colspan|scope][B]' + + 'td[A|headers|rowspan|colspan][C]' + + 'wbr[A][]' + ); + } + + return html5; + }; + + function getHTML4() { + var html4 = mapCache.html4; + + if (!html4) { + // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size + html4 = mapCache.html4 = unpack({ + Z : 'H|K|N|O|P', + Y : 'X|form|R|Q', + ZG : 'E|span|width|align|char|charoff|valign', + X : 'p|T|div|U|W|isindex|fieldset|table', + ZF : 'E|align|char|charoff|valign', + W : 'pre|hr|blockquote|address|center|noframes', + ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', + ZD : '[E][S]', + U : 'ul|ol|dl|menu|dir', + ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', + T : 'h1|h2|h3|h4|h5|h6', + ZB : 'X|S|Q', + S : 'R|P', + ZA : 'a|G|J|M|O|P', + R : 'a|H|K|N|O', + Q : 'noscript|P', + P : 'ins|del|script', + O : 'input|select|textarea|label|button', + N : 'M|L', + M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', + L : 'sub|sup', + K : 'J|I', + J : 'tt|i|b|u|s|strike', + I : 'big|small|font|basefont', + H : 'G|F', + G : 'br|span|bdo', + F : 'object|applet|img|map|iframe', + E : 'A|B|C', + D : 'accesskey|tabindex|onfocus|onblur', + C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', + B : 'lang|xml:lang|dir', + A : 'id|class|style|title' + }, 'script[id|charset|type|language|src|defer|xml:space][]' + + 'style[B|id|type|media|title|xml:space][]' + + 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + + 'param[id|name|value|valuetype|type][]' + + 'p[E|align][#|S]' + + 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + + 'br[A|clear][]' + + 'span[E][#|S]' + + 'bdo[A|C|B][#|S]' + + 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + + 'h1[E|align][#|S]' + + 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + + 'map[B|C|A|name][X|form|Q|area]' + + 'h2[E|align][#|S]' + + 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + + 'h3[E|align][#|S]' + + 'tt[E][#|S]' + + 'i[E][#|S]' + + 'b[E][#|S]' + + 'u[E][#|S]' + + 's[E][#|S]' + + 'strike[E][#|S]' + + 'big[E][#|S]' + + 'small[E][#|S]' + + 'font[A|B|size|color|face][#|S]' + + 'basefont[id|size|color|face][]' + + 'em[E][#|S]' + + 'strong[E][#|S]' + + 'dfn[E][#|S]' + + 'code[E][#|S]' + + 'q[E|cite][#|S]' + + 'samp[E][#|S]' + + 'kbd[E][#|S]' + + 'var[E][#|S]' + + 'cite[E][#|S]' + + 'abbr[E][#|S]' + + 'acronym[E][#|S]' + + 'sub[E][#|S]' + + 'sup[E][#|S]' + + 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + + 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + + 'optgroup[E|disabled|label][option]' + + 'option[E|selected|disabled|label|value][]' + + 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + + 'label[E|for|accesskey|onfocus|onblur][#|S]' + + 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + + 'h4[E|align][#|S]' + + 'ins[E|cite|datetime][#|Y]' + + 'h5[E|align][#|S]' + + 'del[E|cite|datetime][#|Y]' + + 'h6[E|align][#|S]' + + 'div[E|align][#|Y]' + + 'ul[E|type|compact][li]' + + 'li[E|type|value][#|Y]' + + 'ol[E|type|compact|start][li]' + + 'dl[E|compact][dt|dd]' + + 'dt[E][#|S]' + + 'dd[E][#|Y]' + + 'menu[E|compact][li]' + + 'dir[E|compact][li]' + + 'pre[E|width|xml:space][#|ZA]' + + 'hr[E|align|noshade|size|width][]' + + 'blockquote[E|cite][#|Y]' + + 'address[E][#|S|p]' + + 'center[E][#|Y]' + + 'noframes[E][#|Y]' + + 'isindex[A|B|prompt][]' + + 'fieldset[E][#|legend|Y]' + + 'legend[E|accesskey|align][#|S]' + + 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + + 'caption[E|align][#|S]' + + 'col[ZG][]' + + 'colgroup[ZG][col]' + + 'thead[ZF][tr]' + + 'tr[ZF|bgcolor][th|td]' + + 'th[E|ZE][#|Y]' + + 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + + 'noscript[E][#|Y]' + + 'td[E|ZE][#|Y]' + + 'tfoot[ZF][tr]' + + 'tbody[ZF][tr]' + + 'area[E|D|shape|coords|href|nohref|alt|target][]' + + 'base[id|href|target][]' + + 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' + ); + } + + return html4; + }; + + tinymce.html.Schema = function(settings) { + var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; + var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; + + // Creates an lookup table map object for the specified option or the default value + function createLookupTable(option, default_value, extend) { + var value = settings[option]; + + if (!value) { + // Get cached default map or make it if needed + value = mapCache[option]; + + if (!value) { + value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); + value = tinymce.extend(value, extend); + + mapCache[option] = value; + } + } else { + // Create custom map + value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); + } + + return value; + }; + + settings = settings || {}; + schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); + + // Allow all elements and attributes if verify_html is set to false + if (settings.verify_html === false) + settings.valid_elements = '*[*]'; + + // Build styles list + if (settings.valid_styles) { + validStyles = {}; + + // Convert styles into a rule list + each(settings.valid_styles, function(value, key) { + validStyles[key] = tinymce.explode(value); + }); + } + + // Setup map objects + whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea'); + selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); + shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); + boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); + nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap); + textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + + 'blockquote center dir fieldset header footer article section hgroup aside nav figure'); + blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + + 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap); + + // Converts a wildcard expression string to a regexp for example *a will become /.*a/. + function patternToRegExp(str) { + return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); + }; + + // Parses the specified valid_elements string and adds to the current rules + // This function is a bit hard to read since it's heavily optimized for speed + function addValidElements(valid_elements) { + var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, + prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, + elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, + attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, + hasPatternsRegExp = /[*?+]/; + + if (valid_elements) { + // Split valid elements into an array with rules + valid_elements = split(valid_elements); + + if (elements['@']) { + globalAttributes = elements['@'].attributes; + globalAttributesOrder = elements['@'].attributesOrder; + } + + // Loop all rules + for (ei = 0, el = valid_elements.length; ei < el; ei++) { + // Parse element rule + matches = elementRuleRegExp.exec(valid_elements[ei]); + if (matches) { + // Setup local names for matches + prefix = matches[1]; + elementName = matches[2]; + outputName = matches[3]; + attrData = matches[4]; + + // Create new attributes and attributesOrder + attributes = {}; + attributesOrder = []; + + // Create the new element + element = { + attributes : attributes, + attributesOrder : attributesOrder + }; + + // Padd empty elements prefix + if (prefix === '#') + element.paddEmpty = true; + + // Remove empty elements prefix + if (prefix === '-') + element.removeEmpty = true; + + // Copy attributes from global rule into current rule + if (globalAttributes) { + for (key in globalAttributes) + attributes[key] = globalAttributes[key]; + + attributesOrder.push.apply(attributesOrder, globalAttributesOrder); + } + + // Attributes defined + if (attrData) { + attrData = split(attrData, '|'); + for (ai = 0, al = attrData.length; ai < al; ai++) { + matches = attrRuleRegExp.exec(attrData[ai]); + if (matches) { + attr = {}; + attrType = matches[1]; + attrName = matches[2].replace(/::/g, ':'); + prefix = matches[3]; + value = matches[4]; + + // Required + if (attrType === '!') { + element.attributesRequired = element.attributesRequired || []; + element.attributesRequired.push(attrName); + attr.required = true; + } + + // Denied from global + if (attrType === '-') { + delete attributes[attrName]; + attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); + continue; + } + + // Default value + if (prefix) { + // Default value + if (prefix === '=') { + element.attributesDefault = element.attributesDefault || []; + element.attributesDefault.push({name: attrName, value: value}); + attr.defaultValue = value; + } + + // Forced value + if (prefix === ':') { + element.attributesForced = element.attributesForced || []; + element.attributesForced.push({name: attrName, value: value}); + attr.forcedValue = value; + } + + // Required values + if (prefix === '<') + attr.validValues = makeMap(value, '?'); + } + + // Check for attribute patterns + if (hasPatternsRegExp.test(attrName)) { + element.attributePatterns = element.attributePatterns || []; + attr.pattern = patternToRegExp(attrName); + element.attributePatterns.push(attr); + } else { + // Add attribute to order list if it doesn't already exist + if (!attributes[attrName]) + attributesOrder.push(attrName); + + attributes[attrName] = attr; + } + } + } + } + + // Global rule, store away these for later usage + if (!globalAttributes && elementName == '@') { + globalAttributes = attributes; + globalAttributesOrder = attributesOrder; + } + + // Handle substitute elements such as b/strong + if (outputName) { + element.outputName = elementName; + elements[outputName] = element; + } + + // Add pattern or exact element + if (hasPatternsRegExp.test(elementName)) { + element.pattern = patternToRegExp(elementName); + patternElements.push(element); + } else + elements[elementName] = element; + } + } + } + }; + + function setValidElements(valid_elements) { + elements = {}; + patternElements = []; + + addValidElements(valid_elements); + + each(schemaItems, function(element, name) { + children[name] = element.children; + }); + }; + + // Adds custom non HTML elements to the schema + function addCustomElements(custom_elements) { + var customElementRegExp = /^(~)?(.+)$/; + + if (custom_elements) { + each(split(custom_elements), function(rule) { + var matches = customElementRegExp.exec(rule), + inline = matches[1] === '~', + cloneName = inline ? 'span' : 'div', + name = matches[2]; + + children[name] = children[cloneName]; + customElementsMap[name] = cloneName; + + // If it's not marked as inline then add it to valid block elements + if (!inline) { + blockElementsMap[name.toUpperCase()] = {}; + blockElementsMap[name] = {}; + } + + // Add elements clone if needed + if (!elements[name]) { + elements[name] = elements[cloneName]; + } + + // Add custom elements at span/div positions + each(children, function(element, child) { + if (element[cloneName]) + element[name] = element[cloneName]; + }); + }); + } + }; + + // Adds valid children to the schema object + function addValidChildren(valid_children) { + var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; + + if (valid_children) { + each(split(valid_children), function(rule) { + var matches = childRuleRegExp.exec(rule), parent, prefix; + + if (matches) { + prefix = matches[1]; + + // Add/remove items from default + if (prefix) + parent = children[matches[2]]; + else + parent = children[matches[2]] = {'#comment' : {}}; + + parent = children[matches[2]]; + + each(split(matches[3], '|'), function(child) { + if (prefix === '-') + delete parent[child]; + else + parent[child] = {}; + }); + } + }); + } + }; + + function getElementRule(name) { + var element = elements[name], i; + + // Exact match found + if (element) + return element; + + // No exact match then try the patterns + i = patternElements.length; + while (i--) { + element = patternElements[i]; + + if (element.pattern.test(name)) + return element; + } + }; + + if (!settings.valid_elements) { + // No valid elements defined then clone the elements from the schema spec + each(schemaItems, function(element, name) { + elements[name] = { + attributes : element.attributes, + attributesOrder : element.attributesOrder + }; + + children[name] = element.children; + }); + + // Switch these on HTML4 + if (settings.schema != "html5") { + each(split('strong/b,em/i'), function(item) { + item = split(item, '/'); + elements[item[1]].outputName = item[0]; + }); + } + + // Add default alt attribute for images + elements.img.attributesDefault = [{name: 'alt', value: ''}]; + + // Remove these if they are empty by default + each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { + if (elements[name]) { + elements[name].removeEmpty = true; + } + }); + + // Padd these by default + each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { + elements[name].paddEmpty = true; + }); + } else + setValidElements(settings.valid_elements); + + addCustomElements(settings.custom_elements); + addValidChildren(settings.valid_children); + addValidElements(settings.extended_valid_elements); + + // Todo: Remove this when we fix list handling to be valid + addValidChildren('+ol[ul|ol],+ul[ul|ol]'); + + // Delete invalid elements + if (settings.invalid_elements) { + tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { + if (elements[item]) + delete elements[item]; + }); + } + + // If the user didn't allow span only allow internal spans + if (!getElementRule('span')) + addValidElements('span[!data-mce-type|*]'); + + self.children = children; + + self.styles = validStyles; + + self.getBoolAttrs = function() { + return boolAttrMap; + }; + + self.getBlockElements = function() { + return blockElementsMap; + }; + + self.getTextBlockElements = function() { + return textBlockElementsMap; + }; + + self.getShortEndedElements = function() { + return shortEndedElementsMap; + }; + + self.getSelfClosingElements = function() { + return selfClosingElementsMap; + }; + + self.getNonEmptyElements = function() { + return nonEmptyElementsMap; + }; + + self.getWhiteSpaceElements = function() { + return whiteSpaceElementsMap; + }; + + self.isValidChild = function(name, child) { + var parent = children[name]; + + return !!(parent && parent[child]); + }; + + self.isValid = function(name, attr) { + var attrPatterns, i, rule = getElementRule(name); + + // Check if it's a valid element + if (rule) { + if (attr) { + // Check if attribute name exists + if (rule.attributes[attr]) { + return true; + } + + // Check if attribute matches a regexp pattern + attrPatterns = rule.attributePatterns; + if (attrPatterns) { + i = attrPatterns.length; + while (i--) { + if (attrPatterns[i].pattern.test(name)) { + return true; + } + } + } + } else { + return true; + } + } + + // No match + return false; + }; + + self.getElementRule = getElementRule; + + self.getCustomElements = function() { + return customElementsMap; + }; + + self.addValidElements = addValidElements; + + self.setValidElements = setValidElements; + + self.addCustomElements = addCustomElements; + + self.addValidChildren = addValidChildren; + + self.elements = elements; + }; +})(tinymce); + +(function(tinymce) { + tinymce.html.SaxParser = function(settings, schema) { + var self = this, noop = function() {}; + + settings = settings || {}; + self.schema = schema = schema || new tinymce.html.Schema(); + + if (settings.fix_self_closing !== false) + settings.fix_self_closing = true; + + // Add handler functions from settings and setup default handlers + tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { + if (name) + self[name] = settings[name] || noop; + }); + + self.parse = function(html) { + var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, + shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, + validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, + tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; + + function processEndTag(name) { + var pos, i; + + // Find position of parent of the same type + pos = stack.length; + while (pos--) { + if (stack[pos].name === name) + break; + } + + // Found parent + if (pos >= 0) { + // Close all the open elements + for (i = stack.length - 1; i >= pos; i--) { + name = stack[i]; + + if (name.valid) + self.end(name.name); + } + + // Remove the open elements from the stack + stack.length = pos; + } + }; + + function parseAttribute(match, name, value, val2, val3) { + var attrRule, i; + + name = name.toLowerCase(); + value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute + + // Validate name and value + if (validate && !isInternalElement && name.indexOf('data-') !== 0) { + attrRule = validAttributesMap[name]; + + // Find rule by pattern matching + if (!attrRule && validAttributePatterns) { + i = validAttributePatterns.length; + while (i--) { + attrRule = validAttributePatterns[i]; + if (attrRule.pattern.test(name)) + break; + } + + // No rule matched + if (i === -1) + attrRule = null; + } + + // No attribute rule found + if (!attrRule) + return; + + // Validate value + if (attrRule.validValues && !(value in attrRule.validValues)) + return; + } + + // Add attribute to list and map + attrList.map[name] = value; + attrList.push({ + name: name, + value: value + }); + }; + + // Precompile RegExps and map objects + tokenRegExp = new RegExp('<(?:' + + '(?:!--([\\w\\W]*?)-->)|' + // Comment + '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA + '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE + '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI + '(?:\\/([^>]+)>)|' + // End element + '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element + ')', 'g'); + + attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g; + specialElements = { + 'script' : /<\/script[^>]*>/gi, + 'style' : /<\/style[^>]*>/gi, + 'noscript' : /<\/noscript[^>]*>/gi + }; + + // Setup lookup tables for empty elements and boolean attributes + shortEndedElements = schema.getShortEndedElements(); + selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); + fillAttrsMap = schema.getBoolAttrs(); + validate = settings.validate; + removeInternalElements = settings.remove_internals; + fixSelfClosing = settings.fix_self_closing; + isIE = tinymce.isIE; + invalidPrefixRegExp = /^:/; + + while (matches = tokenRegExp.exec(html)) { + // Text + if (index < matches.index) + self.text(decode(html.substr(index, matches.index - index))); + + if (value = matches[6]) { // End element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + processEndTag(value); + } else if (value = matches[7]) { // Start element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + isShortEnded = value in shortEndedElements; + + // Is self closing tag for example an
  • after an open
  • + if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) + processEndTag(value); + + // Validate element + if (!validate || (elementRule = schema.getElementRule(value))) { + isValidElement = true; + + // Grab attributes map and patters when validation is enabled + if (validate) { + validAttributesMap = elementRule.attributes; + validAttributePatterns = elementRule.attributePatterns; + } + + // Parse attributes + if (attribsValue = matches[8]) { + isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element + + // If the element has internal attributes then remove it if we are told to do so + if (isInternalElement && removeInternalElements) + isValidElement = false; + + attrList = []; + attrList.map = {}; + + attribsValue.replace(attrRegExp, parseAttribute); + } else { + attrList = []; + attrList.map = {}; + } + + // Process attributes if validation is enabled + if (validate && !isInternalElement) { + attributesRequired = elementRule.attributesRequired; + attributesDefault = elementRule.attributesDefault; + attributesForced = elementRule.attributesForced; + + // Handle forced attributes + if (attributesForced) { + i = attributesForced.length; + while (i--) { + attr = attributesForced[i]; + name = attr.name; + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + + // Handle default attributes + if (attributesDefault) { + i = attributesDefault.length; + while (i--) { + attr = attributesDefault[i]; + name = attr.name; + + if (!(name in attrList.map)) { + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + } + + // Handle required attributes + if (attributesRequired) { + i = attributesRequired.length; + while (i--) { + if (attributesRequired[i] in attrList.map) + break; + } + + // None of the required attributes where found + if (i === -1) + isValidElement = false; + } + + // Invalidate element if it's marked as bogus + if (attrList.map['data-mce-bogus']) + isValidElement = false; + } + + if (isValidElement) + self.start(value, attrList, isShortEnded); + } else + isValidElement = false; + + // Treat script, noscript and style a bit different since they may include code that looks like elements + if (endRegExp = specialElements[value]) { + endRegExp.lastIndex = index = matches.index + matches[0].length; + + if (matches = endRegExp.exec(html)) { + if (isValidElement) + text = html.substr(index, matches.index - index); + + index = matches.index + matches[0].length; + } else { + text = html.substr(index); + index = html.length; + } + + if (isValidElement && text.length > 0) + self.text(text, true); + + if (isValidElement) + self.end(value); + + tokenRegExp.lastIndex = index; + continue; + } + + // Push value on to stack + if (!isShortEnded) { + if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) + stack.push({name: value, valid: isValidElement}); + else if (isValidElement) + self.end(value); + } + } else if (value = matches[1]) { // Comment + self.comment(value); + } else if (value = matches[2]) { // CDATA + self.cdata(value); + } else if (value = matches[3]) { // DOCTYPE + self.doctype(value); + } else if (value = matches[4]) { // PI + self.pi(value, matches[5]); + } + + index = matches.index + matches[0].length; + } + + // Text + if (index < html.length) + self.text(decode(html.substr(index))); + + // Close any open elements + for (i = stack.length - 1; i >= 0; i--) { + value = stack[i]; + + if (value.valid) + self.end(value.name); + } + }; + } +})(tinymce); + +(function(tinymce) { + var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { + '#text' : 3, + '#comment' : 8, + '#cdata' : 4, + '#pi' : 7, + '#doctype' : 10, + '#document-fragment' : 11 + }; + + // Walks the tree left/right + function walk(node, root_node, prev) { + var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; + + // Walk into nodes if it has a start + if (node[startName]) + return node[startName]; + + // Return the sibling if it has one + if (node !== root_node) { + sibling = node[siblingName]; + + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { + sibling = parent[siblingName]; + + if (sibling) + return sibling; + } + } + }; + + function Node(name, type) { + this.name = name; + this.type = type; + + if (type === 1) { + this.attributes = []; + this.attributes.map = {}; + } + } + + tinymce.extend(Node.prototype, { + replace : function(node) { + var self = this; + + if (node.parent) + node.remove(); + + self.insert(node, self); + self.remove(); + + return self; + }, + + attr : function(name, value) { + var self = this, attrs, i, undef; + + if (typeof name !== "string") { + for (i in name) + self.attr(i, name[i]); + + return self; + } + + if (attrs = self.attributes) { + if (value !== undef) { + // Remove attribute + if (value === null) { + if (name in attrs.map) { + delete attrs.map[name]; + + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs = attrs.splice(i, 1); + return self; + } + } + } + + return self; + } + + // Set attribute + if (name in attrs.map) { + // Set attribute + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs[i].value = value; + break; + } + } + } else + attrs.push({name: name, value: value}); + + attrs.map[name] = value; + + return self; + } else { + return attrs.map[name]; + } + } + }, + + clone : function() { + var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; + + // Clone element attributes + if (selfAttrs = self.attributes) { + cloneAttrs = []; + cloneAttrs.map = {}; + + for (i = 0, l = selfAttrs.length; i < l; i++) { + selfAttr = selfAttrs[i]; + + // Clone everything except id + if (selfAttr.name !== 'id') { + cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; + cloneAttrs.map[selfAttr.name] = selfAttr.value; + } + } + + clone.attributes = cloneAttrs; + } + + clone.value = self.value; + clone.shortEnded = self.shortEnded; + + return clone; + }, + + wrap : function(wrapper) { + var self = this; + + self.parent.insert(wrapper, self); + wrapper.append(self); + + return self; + }, + + unwrap : function() { + var self = this, node, next; + + for (node = self.firstChild; node; ) { + next = node.next; + self.insert(node, self, true); + node = next; + } + + self.remove(); + }, + + remove : function() { + var self = this, parent = self.parent, next = self.next, prev = self.prev; + + if (parent) { + if (parent.firstChild === self) { + parent.firstChild = next; + + if (next) + next.prev = null; + } else { + prev.next = next; + } + + if (parent.lastChild === self) { + parent.lastChild = prev; + + if (prev) + prev.next = null; + } else { + next.prev = prev; + } + + self.parent = self.next = self.prev = null; + } + + return self; + }, + + append : function(node) { + var self = this, last; + + if (node.parent) + node.remove(); + + last = self.lastChild; + if (last) { + last.next = node; + node.prev = last; + self.lastChild = node; + } else + self.lastChild = self.firstChild = node; + + node.parent = self; + + return node; + }, + + insert : function(node, ref_node, before) { + var parent; + + if (node.parent) + node.remove(); + + parent = ref_node.parent || this; + + if (before) { + if (ref_node === parent.firstChild) + parent.firstChild = node; + else + ref_node.prev.next = node; + + node.prev = ref_node.prev; + node.next = ref_node; + ref_node.prev = node; + } else { + if (ref_node === parent.lastChild) + parent.lastChild = node; + else + ref_node.next.prev = node; + + node.next = ref_node.next; + node.prev = ref_node; + ref_node.next = node; + } + + node.parent = parent; + + return node; + }, + + getAll : function(name) { + var self = this, node, collection = []; + + for (node = self.firstChild; node; node = walk(node, self)) { + if (node.name === name) + collection.push(node); + } + + return collection; + }, + + empty : function() { + var self = this, nodes, i, node; + + // Remove all children + if (self.firstChild) { + nodes = []; + + // Collect the children + for (node = self.firstChild; node; node = walk(node, self)) + nodes.push(node); + + // Remove the children + i = nodes.length; + while (i--) { + node = nodes[i]; + node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; + } + } + + self.firstChild = self.lastChild = null; + + return self; + }, + + isEmpty : function(elements) { + var self = this, node = self.firstChild, i, name; + + if (node) { + do { + if (node.type === 1) { + // Ignore bogus elements + if (node.attributes.map['data-mce-bogus']) + continue; + + // Keep empty elements like + if (elements[node.name]) + return false; + + // Keep elements with data attributes or name attribute like + i = node.attributes.length; + while (i--) { + name = node.attributes[i].name; + if (name === "name" || name.indexOf('data-mce-') === 0) + return false; + } + } + + // Keep comments + if (node.type === 8) + return false; + + // Keep non whitespace text nodes + if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) + return false; + } while (node = walk(node, self)); + } + + return true; + }, + + walk : function(prev) { + return walk(this, null, prev); + } + }); + + tinymce.extend(Node, { + create : function(name, attrs) { + var node, attrName; + + // Create node + node = new Node(name, typeLookup[name] || 1); + + // Add attributes if needed + if (attrs) { + for (attrName in attrs) + node.attr(attrName, attrs[attrName]); + } + + return node; + } + }); + + tinymce.html.Node = Node; +})(tinymce); + +(function(tinymce) { + var Node = tinymce.html.Node; + + tinymce.html.DomParser = function(settings, schema) { + var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + settings.root_name = settings.root_name || 'body'; + self.schema = schema = schema || new tinymce.html.Schema(); + + function fixInvalidChildren(nodes) { + var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, + childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode; + + nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); + nonEmptyElements = schema.getNonEmptyElements(); + textBlockElements = schema.getTextBlockElements(); + + for (ni = 0; ni < nodes.length; ni++) { + node = nodes[ni]; + + // Already removed or fixed + if (!node.parent || node.fixed) + continue; + + // If the invalid element is a text block and the text block is within a parent LI element + // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office + if (textBlockElements[node.name] && node.parent.name == 'li') { + // Move sibling text blocks after LI element + sibling = node.next; + while (sibling) { + if (textBlockElements[sibling.name]) { + sibling.name = 'li'; + sibling.fixed = true; + node.parent.insert(sibling, node.parent); + } else { + break; + } + + sibling = sibling.next; + } + + // Unwrap current text block + node.unwrap(node); + continue; + } + + // Get list of all parent nodes until we find a valid parent to stick the child into + parents = [node]; + for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) + parents.push(parent); + + // Found a suitable parent + if (parent && parents.length > 1) { + // Reverse the array since it makes looping easier + parents.reverse(); + + // Clone the related parent and insert that after the moved node + newParent = currentNode = self.filterNode(parents[0].clone()); + + // Start cloning and moving children on the left side of the target node + for (i = 0; i < parents.length - 1; i++) { + if (schema.isValidChild(currentNode.name, parents[i].name)) { + tempNode = self.filterNode(parents[i].clone()); + currentNode.append(tempNode); + } else + tempNode = currentNode; + + for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { + nextNode = childNode.next; + tempNode.append(childNode); + childNode = nextNode; + } + + currentNode = tempNode; + } + + if (!newParent.isEmpty(nonEmptyElements)) { + parent.insert(newParent, parents[0], true); + parent.insert(node, newParent); + } else { + parent.insert(node, parents[0], true); + } + + // Check if the element is empty by looking through it's contents and special treatment for


    + parent = parents[0]; + if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { + parent.empty().remove(); + } + } else if (node.parent) { + // If it's an LI try to find a UL/OL for it or wrap it + if (node.name === 'li') { + sibling = node.prev; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.append(node); + continue; + } + + sibling = node.next; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.insert(node, sibling.firstChild, true); + continue; + } + + node.wrap(self.filterNode(new Node('ul', 1))); + continue; + } + + // Try wrapping the element in a DIV + if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { + node.wrap(self.filterNode(new Node('div', 1))); + } else { + // We failed wrapping it, then remove or unwrap it + if (node.name === 'style' || node.name === 'script') + node.empty().remove(); + else + node.unwrap(); + } + } + } + }; + + self.filterNode = function(node) { + var i, name, list; + + // Run element filters + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + // Run attribute filters + i = attributeFilters.length; + while (i--) { + name = attributeFilters[i].name; + + if (name in node.attributes.map) { + list = matchedAttributes[name]; + + if (list) + list.push(node); + else + matchedAttributes[name] = [node]; + } + } + + return node; + }; + + self.addNodeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var list = nodeFilters[name]; + + if (!list) + nodeFilters[name] = list = []; + + list.push(callback); + }); + }; + + self.addAttributeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var i; + + for (i = 0; i < attributeFilters.length; i++) { + if (attributeFilters[i].name === name) { + attributeFilters[i].callbacks.push(callback); + return; + } + } + + attributeFilters.push({name: name, callbacks: [callback]}); + }); + }; + + self.parse = function(html, args) { + var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, + blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, + endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; + + args = args || {}; + matchedNodes = {}; + matchedAttributes = {}; + blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); + nonEmptyElements = schema.getNonEmptyElements(); + children = schema.children; + validate = settings.validate; + rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; + + whiteSpaceElements = schema.getWhiteSpaceElements(); + startWhiteSpaceRegExp = /^[ \t\r\n]+/; + endWhiteSpaceRegExp = /[ \t\r\n]+$/; + allWhiteSpaceRegExp = /[ \t\r\n]+/g; + isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; + + function addRootBlocks() { + var node = rootNode.firstChild, next, rootBlockNode; + + while (node) { + next = node.next; + + if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { + if (!rootBlockNode) { + // Create a new root block element + rootBlockNode = createNode(rootBlockName, 1); + rootNode.insert(rootBlockNode, node); + rootBlockNode.append(node); + } else + rootBlockNode.append(node); + } else { + rootBlockNode = null; + } + + node = next; + }; + }; + + function createNode(name, type) { + var node = new Node(name, type), list; + + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + return node; + }; + + function removeWhitespaceBefore(node) { + var textNode, textVal, sibling; + + for (textNode = node.prev; textNode && textNode.type === 3; ) { + textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (textVal.length > 0) { + textNode.value = textVal; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + }; + + function cloneAndExcludeBlocks(input) { + var name, output = {}; + + for (name in input) { + if (name !== 'li' && name != 'p') { + output[name] = input[name]; + } + } + + return output; + }; + + parser = new tinymce.html.SaxParser({ + validate : validate, + + // Exclude P and LI from DOM parsing since it's treated better by the DOM parser + self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), + + cdata: function(text) { + node.append(createNode('#cdata', 4)).value = text; + }, + + text: function(text, raw) { + var textNode; + + // Trim all redundant whitespace on non white space elements + if (!isInWhiteSpacePreservedElement) { + text = text.replace(allWhiteSpaceRegExp, ' '); + + if (node.lastChild && blockElements[node.lastChild.name]) + text = text.replace(startWhiteSpaceRegExp, ''); + } + + // Do we need to create the node + if (text.length !== 0) { + textNode = createNode('#text', 3); + textNode.raw = !!raw; + node.append(textNode).value = text; + } + }, + + comment: function(text) { + node.append(createNode('#comment', 8)).value = text; + }, + + pi: function(name, text) { + node.append(createNode(name, 7)).value = text; + removeWhitespaceBefore(node); + }, + + doctype: function(text) { + var newNode; + + newNode = node.append(createNode('#doctype', 10)); + newNode.value = text; + removeWhitespaceBefore(node); + }, + + start: function(name, attrs, empty) { + var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + newNode = createNode(elementRule.outputName || name, 1); + newNode.attributes = attrs; + newNode.shortEnded = empty; + + node.append(newNode); + + // Check if node is valid child of the parent node is the child is + // unknown we don't collect it since it's probably a custom element + parent = children[node.name]; + if (parent && children[newNode.name] && !parent[newNode.name]) + invalidChildren.push(newNode); + + attrFiltersLen = attributeFilters.length; + while (attrFiltersLen--) { + attrName = attributeFilters[attrFiltersLen].name; + + if (attrName in attrs.map) { + list = matchedAttributes[attrName]; + + if (list) + list.push(newNode); + else + matchedAttributes[attrName] = [newNode]; + } + } + + // Trim whitespace before block + if (blockElements[name]) + removeWhitespaceBefore(newNode); + + // Change current node if the element wasn't empty i.e not
    or + if (!empty) + node = newNode; + + // Check if we are inside a whitespace preserved element + if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { + isInWhiteSpacePreservedElement = true; + } + } + }, + + end: function(name) { + var textNode, elementRule, text, sibling, tempNode; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + if (blockElements[name]) { + if (!isInWhiteSpacePreservedElement) { + // Trim whitespace of the first node in a block + textNode = node.firstChild; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + // Any characters left after trim or should we remove it + if (text.length > 0) { + textNode.value = text; + textNode = textNode.next; + } else { + sibling = textNode.next; + textNode.remove(); + textNode = sibling; + } + + // Remove any pure whitespace siblings + while (textNode && textNode.type === 3) { + text = textNode.value; + sibling = textNode.next; + + if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { + textNode.remove(); + textNode = sibling; + } + + textNode = sibling; + } + } + + // Trim whitespace of the last node in a block + textNode = node.lastChild; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(endWhiteSpaceRegExp, ''); + + // Any characters left after trim or should we remove it + if (text.length > 0) { + textNode.value = text; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + + // Remove any pure whitespace siblings + while (textNode && textNode.type === 3) { + text = textNode.value; + sibling = textNode.prev; + + if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { + textNode.remove(); + textNode = sibling; + } + + textNode = sibling; + } + } + } + + // Trim start white space + // Removed due to: #5424 + /*textNode = node.prev; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) + textNode.value = text; + else + textNode.remove(); + }*/ + } + + // Check if we exited a whitespace preserved element + if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { + isInWhiteSpacePreservedElement = false; + } + + // Handle empty nodes + if (elementRule.removeEmpty || elementRule.paddEmpty) { + if (node.isEmpty(nonEmptyElements)) { + if (elementRule.paddEmpty) + node.empty().append(new Node('#text', '3')).value = '\u00a0'; + else { + // Leave nodes that have a name like + if (!node.attributes.map.name && !node.attributes.map.id) { + tempNode = node.parent; + node.empty().remove(); + node = tempNode; + return; + } + } + } + } + + node = node.parent; + } + } + }, schema); + + rootNode = node = new Node(args.context || settings.root_name, 11); + + parser.parse(html); + + // Fix invalid children or report invalid children in a contextual parsing + if (validate && invalidChildren.length) { + if (!args.context) + fixInvalidChildren(invalidChildren); + else + args.invalid = true; + } + + // Wrap nodes in the root into block elements if the root is body + if (rootBlockName && rootNode.name == 'body') + addRootBlocks(); + + // Run filters only when the contents is valid + if (!args.invalid) { + // Run node filters + for (name in matchedNodes) { + list = nodeFilters[name]; + nodes = matchedNodes[name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (i = 0, l = list.length; i < l; i++) + list[i](nodes, name, args); + } + + // Run attribute filters + for (i = 0, l = attributeFilters.length; i < l; i++) { + list = attributeFilters[i]; + + if (list.name in matchedAttributes) { + nodes = matchedAttributes[list.name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) + list.callbacks[fi](nodes, list.name, args); + } + } + } + + return rootNode; + }; + + // Remove
    at end of block elements Gecko and WebKit injects BR elements to + // make it possible to place the caret inside empty blocks. This logic tries to remove + // these elements and keep br elements that where intended to be there intact + if (settings.remove_trailing_brs) { + self.addNodeFilter('br', function(nodes, name) { + var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), + nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; + + // Remove brs from body element as well + blockElements.body = 1; + + // Must loop forwards since it will otherwise remove all brs in

    a


    + for (i = 0; i < l; i++) { + node = nodes[i]; + parent = node.parent; + + if (blockElements[node.parent.name] && node === parent.lastChild) { + // Loop all nodes to the left of the current node and check for other BR elements + // excluding bookmarks since they are invisible + prev = node.prev; + while (prev) { + prevName = prev.name; + + // Ignore bookmarks + if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { + // Found a non BR element + if (prevName !== "br") + break; + + // Found another br it's a

    structure then don't remove anything + if (prevName === 'br') { + node = null; + break; + } + } + + prev = prev.prev; + } + + if (node) { + node.remove(); + + // Is the parent to be considered empty after we removed the BR + if (parent.isEmpty(nonEmptyElements)) { + elementRule = schema.getElementRule(parent.name); + + // Remove or padd the element depending on schema rule + if (elementRule) { + if (elementRule.removeEmpty) + parent.remove(); + else if (elementRule.paddEmpty) + parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; + } + } + } + } else { + // Replaces BR elements inside inline elements like


    so they become

     

    + lastParent = node; + while (parent.firstChild === lastParent && parent.lastChild === lastParent) { + lastParent = parent; + + if (blockElements[parent.name]) { + break; + } + + parent = parent.parent; + } + + if (lastParent === parent) { + textNode = new tinymce.html.Node('#text', 3); + textNode.value = '\u00a0'; + node.replace(textNode); + } + } + } + }); + } + + // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. + if (!settings.allow_html_in_named_anchor) { + self.addAttributeFilter('id,name', function(nodes, name) { + var i = nodes.length, sibling, prevSibling, parent, node; + + while (i--) { + node = nodes[i]; + if (node.name === 'a' && node.firstChild && !node.attr('href')) { + parent = node.parent; + + // Move children after current node + sibling = node.lastChild; + do { + prevSibling = sibling.prev; + parent.insert(sibling, node); + sibling = prevSibling; + } while (sibling); + } + } + }); + } + } +})(tinymce); + +tinymce.html.Writer = function(settings) { + var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; + + settings = settings || {}; + indent = settings.indent; + indentBefore = tinymce.makeMap(settings.indent_before || ''); + indentAfter = tinymce.makeMap(settings.indent_after || ''); + encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); + htmlOutput = settings.element_format == "html"; + + return { + start: function(name, attrs, empty) { + var i, l, attr, value; + + if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + + html.push('<', name); + + if (attrs) { + for (i = 0, l = attrs.length; i < l; i++) { + attr = attrs[i]; + html.push(' ', attr.name, '="', encode(attr.value, true), '"'); + } + } + + if (!empty || htmlOutput) + html[html.length] = '>'; + else + html[html.length] = ' />'; + + if (empty && indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + end: function(name) { + var value; + + /*if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + }*/ + + html.push(''); + + if (indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + text: function(text, raw) { + if (text.length > 0) + html[html.length] = raw ? text : encode(text); + }, + + cdata: function(text) { + html.push(''); + }, + + comment: function(text) { + html.push(''); + }, + + pi: function(name, text) { + if (text) + html.push(''); + else + html.push(''); + + if (indent) + html.push('\n'); + }, + + doctype: function(text) { + html.push('', indent ? '\n' : ''); + }, + + reset: function() { + html.length = 0; + }, + + getContent: function() { + return html.join('').replace(/\n$/, ''); + } + }; +}; + +(function(tinymce) { + tinymce.html.Serializer = function(settings, schema) { + var self = this, writer = new tinymce.html.Writer(settings); + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + + self.schema = schema = schema || new tinymce.html.Schema(); + self.writer = writer; + + self.serialize = function(node) { + var handlers, validate; + + validate = settings.validate; + + handlers = { + // #text + 3: function(node, raw) { + writer.text(node.value, node.raw); + }, + + // #comment + 8: function(node) { + writer.comment(node.value); + }, + + // Processing instruction + 7: function(node) { + writer.pi(node.name, node.value); + }, + + // Doctype + 10: function(node) { + writer.doctype(node.value); + }, + + // CDATA + 4: function(node) { + writer.cdata(node.value); + }, + + // Document fragment + 11: function(node) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + } + }; + + writer.reset(); + + function walk(node) { + var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; + + if (!handler) { + name = node.name; + isEmpty = node.shortEnded; + attrs = node.attributes; + + // Sort attributes + if (validate && attrs && attrs.length > 1) { + sortedAttrs = []; + sortedAttrs.map = {}; + + elementRule = schema.getElementRule(node.name); + for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { + attrName = elementRule.attributesOrder[i]; + + if (attrName in attrs.map) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + for (i = 0, l = attrs.length; i < l; i++) { + attrName = attrs[i].name; + + if (!(attrName in sortedAttrs.map)) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + attrs = sortedAttrs; + } + + writer.start(node.name, attrs, isEmpty); + + if (!isEmpty) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + + writer.end(name); + } + } else + handler(node); + } + + // Serialize element and treat all non elements as fragments + if (node.type == 1 && !settings.inner) + walk(node); + else + handlers[11](node); + + return writer.getContent(); + }; + } +})(tinymce); + +// JSLint defined globals +/*global tinymce:false, window:false */ + +tinymce.dom = {}; + +(function(namespace, expando) { + var w3cEventModel = !!document.addEventListener; + + function addEvent(target, name, callback, capture) { + if (target.addEventListener) { + target.addEventListener(name, callback, capture || false); + } else if (target.attachEvent) { + target.attachEvent('on' + name, callback); + } + } + + function removeEvent(target, name, callback, capture) { + if (target.removeEventListener) { + target.removeEventListener(name, callback, capture || false); + } else if (target.detachEvent) { + target.detachEvent('on' + name, callback); + } + } + + function fix(original_event, data) { + var name, event = data || {}; + + // Dummy function that gets replaced on the delegation state functions + function returnFalse() { + return false; + } + + // Dummy function that gets replaced on the delegation state functions + function returnTrue() { + return true; + } + + // Copy all properties from the original event + for (name in original_event) { + // layerX/layerY is deprecated in Chrome and produces a warning + if (name !== "layerX" && name !== "layerY") { + event[name] = original_event[name]; + } + } + + // Normalize target IE uses srcElement + if (!event.target) { + event.target = event.srcElement || document; + } + + // Add preventDefault method + event.preventDefault = function() { + event.isDefaultPrevented = returnTrue; + + // Execute preventDefault on the original event object + if (original_event) { + if (original_event.preventDefault) { + original_event.preventDefault(); + } else { + original_event.returnValue = false; // IE + } + } + }; + + // Add stopPropagation + event.stopPropagation = function() { + event.isPropagationStopped = returnTrue; + + // Execute stopPropagation on the original event object + if (original_event) { + if (original_event.stopPropagation) { + original_event.stopPropagation(); + } else { + original_event.cancelBubble = true; // IE + } + } + }; + + // Add stopImmediatePropagation + event.stopImmediatePropagation = function() { + event.isImmediatePropagationStopped = returnTrue; + event.stopPropagation(); + }; + + // Add event delegation states + if (!event.isDefaultPrevented) { + event.isDefaultPrevented = returnFalse; + event.isPropagationStopped = returnFalse; + event.isImmediatePropagationStopped = returnFalse; + } + + return event; + } + + function bindOnReady(win, callback, event_utils) { + var doc = win.document, event = {type: 'ready'}; + + // Gets called when the DOM is ready + function readyHandler() { + if (!event_utils.domLoaded) { + event_utils.domLoaded = true; + callback(event); + } + } + + // Page already loaded then fire it directly + if (doc.readyState == "complete") { + readyHandler(); + return; + } + + // Use W3C method + if (w3cEventModel) { + addEvent(win, 'DOMContentLoaded', readyHandler); + } else { + // Use IE method + addEvent(doc, "readystatechange", function() { + if (doc.readyState === "complete") { + removeEvent(doc, "readystatechange", arguments.callee); + readyHandler(); + } + }); + + // Wait until we can scroll, when we can the DOM is initialized + if (doc.documentElement.doScroll && win === win.top) { + (function() { + try { + // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. + // http://javascript.nwbox.com/IEContentLoaded/ + doc.documentElement.doScroll("left"); + } catch (ex) { + setTimeout(arguments.callee, 0); + return; + } + + readyHandler(); + })(); + } + } + + // Fallback if any of the above methods should fail for some odd reason + addEvent(win, 'load', readyHandler); + } + + function EventUtils(proxy) { + var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; + + hasMouseEnterLeave = "onmouseenter" in document.documentElement; + hasFocusIn = "onfocusin" in document.documentElement; + mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; + count = 1; + + // State if the DOMContentLoaded was executed or not + self.domLoaded = false; + self.events = events; + + function executeHandlers(evt, id) { + var callbackList, i, l, callback; + + callbackList = events[id][evt.type]; + if (callbackList) { + for (i = 0, l = callbackList.length; i < l; i++) { + callback = callbackList[i]; + + // Check if callback exists might be removed if a unbind is called inside the callback + if (callback && callback.func.call(callback.scope, evt) === false) { + evt.preventDefault(); + } + + // Should we stop propagation to immediate listeners + if (evt.isImmediatePropagationStopped()) { + return; + } + } + } + } + + self.bind = function(target, names, callback, scope) { + var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; + + // Native event handler function patches the event and executes the callbacks for the expando + function defaultNativeHandler(evt) { + executeHandlers(fix(evt || win.event), id); + } + + // Don't bind to text nodes or comments + if (!target || target.nodeType === 3 || target.nodeType === 8) { + return; + } + + // Create or get events id for the target + if (!target[expando]) { + id = count++; + target[expando] = id; + events[id] = {}; + } else { + id = target[expando]; + + if (!events[id]) { + events[id] = {}; + } + } + + // Setup the specified scope or use the target as a default + scope = scope || target; + + // Split names and bind each event, enables you to bind multiple events with one call + names = names.split(' '); + i = names.length; + while (i--) { + name = names[i]; + nativeHandler = defaultNativeHandler; + fakeName = capture = false; + + // Use ready instead of DOMContentLoaded + if (name === "DOMContentLoaded") { + name = "ready"; + } + + // DOM is already ready + if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { + self.domLoaded = true; + callback.call(scope, fix({type: name})); + continue; + } + + // Handle mouseenter/mouseleaver + if (!hasMouseEnterLeave) { + fakeName = mouseEnterLeave[name]; + + if (fakeName) { + nativeHandler = function(evt) { + var current, related; + + current = evt.currentTarget; + related = evt.relatedTarget; + + // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element + if (related && current.contains) { + // Use contains for performance + related = current.contains(related); + } else { + while (related && related !== current) { + related = related.parentNode; + } + } + + // Fire fake event + if (!related) { + evt = fix(evt || win.event); + evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; + evt.target = current; + executeHandlers(evt, id); + } + }; + } + } + + // Fake bubbeling of focusin/focusout + if (!hasFocusIn && (name === "focusin" || name === "focusout")) { + capture = true; + fakeName = name === "focusin" ? "focus" : "blur"; + nativeHandler = function(evt) { + evt = fix(evt || win.event); + evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; + executeHandlers(evt, id); + }; + } + + // Setup callback list and bind native event + callbackList = events[id][name]; + if (!callbackList) { + events[id][name] = callbackList = [{func: callback, scope: scope}]; + callbackList.fakeName = fakeName; + callbackList.capture = capture; + + // Add the nativeHandler to the callback list so that we can later unbind it + callbackList.nativeHandler = nativeHandler; + if (!w3cEventModel) { + callbackList.proxyHandler = proxy(id); + } + + // Check if the target has native events support + if (name === "ready") { + bindOnReady(target, nativeHandler, self); + } else { + addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); + } + } else { + // If it already has an native handler then just push the callback + callbackList.push({func: callback, scope: scope}); + } + } + + target = callbackList = 0; // Clean memory for IE + + return callback; + }; + + self.unbind = function(target, names, callback) { + var id, callbackList, i, ci, name, eventMap; + + // Don't bind to text nodes or comments + if (!target || target.nodeType === 3 || target.nodeType === 8) { + return self; + } + + // Unbind event or events if the target has the expando + id = target[expando]; + if (id) { + eventMap = events[id]; + + // Specific callback + if (names) { + names = names.split(' '); + i = names.length; + while (i--) { + name = names[i]; + callbackList = eventMap[name]; + + // Unbind the event if it exists in the map + if (callbackList) { + // Remove specified callback + if (callback) { + ci = callbackList.length; + while (ci--) { + if (callbackList[ci].func === callback) { + callbackList.splice(ci, 1); + } + } + } + + // Remove all callbacks if there isn't a specified callback or there is no callbacks left + if (!callback || callbackList.length === 0) { + delete eventMap[name]; + removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); + } + } + } + } else { + // All events for a specific element + for (name in eventMap) { + callbackList = eventMap[name]; + removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); + } + + eventMap = {}; + } + + // Check if object is empty, if it isn't then we won't remove the expando map + for (name in eventMap) { + return self; + } + + // Delete event object + delete events[id]; + + // Remove expando from target + try { + // IE will fail here since it can't delete properties from window + delete target[expando]; + } catch (ex) { + // IE will set it to null + target[expando] = null; + } + } + + return self; + }; + + self.fire = function(target, name, args) { + var id, event; + + // Don't bind to text nodes or comments + if (!target || target.nodeType === 3 || target.nodeType === 8) { + return self; + } + + // Build event object by patching the args + event = fix(null, args); + event.type = name; + + do { + // Found an expando that means there is listeners to execute + id = target[expando]; + if (id) { + executeHandlers(event, id); + } + + // Walk up the DOM + target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; + } while (target && !event.isPropagationStopped()); + + return self; + }; + + self.clean = function(target) { + var i, children, unbind = self.unbind; + + // Don't bind to text nodes or comments + if (!target || target.nodeType === 3 || target.nodeType === 8) { + return self; + } + + // Unbind any element on the specificed target + if (target[expando]) { + unbind(target); + } + + // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children + if (!target.getElementsByTagName) { + target = target.document; + } + + // Remove events from each child element + if (target && target.getElementsByTagName) { + unbind(target); + + children = target.getElementsByTagName('*'); + i = children.length; + while (i--) { + target = children[i]; + + if (target[expando]) { + unbind(target); + } + } + } + + return self; + }; + + self.callNativeHandler = function(id, evt) { + if (events) { + events[id][evt.type].nativeHandler(evt); + } + }; + + self.destory = function() { + events = {}; + }; + + // Legacy function calls + + self.add = function(target, events, func, scope) { + // Old API supported direct ID assignment + if (typeof(target) === "string") { + target = document.getElementById(target); + } + + // Old API supported multiple targets + if (target && target instanceof Array) { + var i = target.length; + + while (i--) { + self.add(target[i], events, func, scope); + } + + return; + } + + // Old API called ready init + if (events === "init") { + events = "ready"; + } + + return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); + }; + + self.remove = function(target, events, func, scope) { + if (!target) { + return self; + } + + // Old API supported direct ID assignment + if (typeof(target) === "string") { + target = document.getElementById(target); + } + + // Old API supported multiple targets + if (target instanceof Array) { + var i = target.length; + + while (i--) { + self.remove(target[i], events, func, scope); + } + + return self; + } + + return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); + }; + + self.clear = function(target) { + // Old API supported direct ID assignment + if (typeof(target) === "string") { + target = document.getElementById(target); + } + + return self.clean(target); + }; + + self.cancel = function(e) { + if (e) { + self.prevent(e); + self.stop(e); + } + + return false; + }; + + self.prevent = function(e) { + if (!e.preventDefault) { + e = fix(e); + } + + e.preventDefault(); + + return false; + }; + + self.stop = function(e) { + if (!e.stopPropagation) { + e = fix(e); + } + + e.stopPropagation(); + + return false; + }; + } + + namespace.EventUtils = EventUtils; + + namespace.Event = new EventUtils(function(id) { + return function(evt) { + tinymce.dom.Event.callNativeHandler(id, evt); + }; + }); + + // Bind ready event when tinymce script is loaded + namespace.Event.bind(window, 'ready', function() {}); + + namespace = 0; +})(tinymce.dom, 'data-mce-expando'); // Namespace and expando + +tinymce.dom.TreeWalker = function(start_node, root_node) { + var node = start_node; + + function findSibling(node, start_name, sibling_name, shallow) { + var sibling, parent; + + if (node) { + // Walk into nodes if it has a start + if (!shallow && node[start_name]) + return node[start_name]; + + // Return the sibling if it has one + if (node != root_node) { + sibling = node[sibling_name]; + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { + sibling = parent[sibling_name]; + if (sibling) + return sibling; + } + } + } + }; + + this.current = function() { + return node; + }; + + this.next = function(shallow) { + return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); + }; + + this.prev = function(shallow) { + return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); + }; +}; + +(function(tinymce) { + // Shorten names + var each = tinymce.each, + is = tinymce.is, + isWebKit = tinymce.isWebKit, + isIE = tinymce.isIE, + Entities = tinymce.html.Entities, + simpleSelectorRe = /^([a-z0-9],?)+$/i, + whiteSpaceRegExp = /^[ \t\r\n]*$/; + + tinymce.create('tinymce.dom.DOMUtils', { + doc : null, + root : null, + files : null, + pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, + props : { + "for" : "htmlFor", + "class" : "className", + className : "className", + checked : "checked", + disabled : "disabled", + maxlength : "maxLength", + readonly : "readOnly", + selected : "selected", + value : "value", + id : "id", + name : "name", + type : "type" + }, + + DOMUtils : function(d, s) { + var t = this, globalStyle, name, blockElementsMap; + + t.doc = d; + t.win = window; + t.files = {}; + t.cssFlicker = false; + t.counter = 0; + t.stdMode = !tinymce.isIE || d.documentMode >= 8; + t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; + t.hasOuterHTML = "outerHTML" in d.createElement("a"); + + t.settings = s = tinymce.extend({ + keep_values : false, + hex_colors : 1 + }, s); + + t.schema = s.schema; + t.styles = new tinymce.html.Styles({ + url_converter : s.url_converter, + url_converter_scope : s.url_converter_scope + }, s.schema); + + // Fix IE6SP2 flicker and check it failed for pre SP2 + if (tinymce.isIE6) { + try { + d.execCommand('BackgroundImageCache', false, true); + } catch (e) { + t.cssFlicker = true; + } + } + + t.fixDoc(d); + t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; + tinymce.addUnload(t.destroy, t); + blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; + + t.isBlock = function(node) { + // Fix for #5446 + if (!node) { + return false; + } + + // This function is called in module pattern style since it might be executed with the wrong this scope + var type = node.nodeType; + + // If it's a node then check the type and use the nodeName + if (type) + return !!(type === 1 && blockElementsMap[node.nodeName]); + + return !!blockElementsMap[node]; + }; + }, + + fixDoc: function(doc) { + var settings = this.settings, name; + + if (isIE && !tinymce.isIE11 && settings.schema) { + // Add missing HTML 4/5 elements to IE + ('abbr article aside audio canvas ' + + 'details figcaption figure footer ' + + 'header hgroup mark menu meter nav ' + + 'output progress section summary ' + + 'time video').replace(/\w+/g, function(name) { + doc.createElement(name); + }); + + // Create all custom elements + for (name in settings.schema.getCustomElements()) { + doc.createElement(name); + } + } + }, + + clone: function(node, deep) { + var self = this, clone, doc; + + // TODO: Add feature detection here in the future + if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) { + return node.cloneNode(deep); + } + + doc = self.doc; + + // Make a HTML5 safe shallow copy + if (!deep) { + clone = doc.createElement(node.nodeName); + + // Copy attribs + each(self.getAttribs(node), function(attr) { + self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); + }); + + return clone; + } +/* + // Setup HTML5 patched document fragment + if (!self.frag) { + self.frag = doc.createDocumentFragment(); + self.fixDoc(self.frag); + } + + // Make a deep copy by adding it to the document fragment then removing it this removed the :section + clone = doc.createElement('div'); + self.frag.appendChild(clone); + clone.innerHTML = node.outerHTML; + self.frag.removeChild(clone); +*/ + return clone.firstChild; + }, + + getRoot : function() { + var t = this, s = t.settings; + + return (s && t.get(s.root_element)) || t.doc.body; + }, + + getViewPort : function(w) { + var d, b; + + w = !w ? this.win : w; + d = w.document; + b = this.boxModel ? d.documentElement : d.body; + + // Returns viewport size excluding scrollbars + return { + x : w.pageXOffset || b.scrollLeft, + y : w.pageYOffset || b.scrollTop, + w : w.innerWidth || b.clientWidth, + h : w.innerHeight || b.clientHeight + }; + }, + + getRect : function(e) { + var p, t = this, sr; + + e = t.get(e); + p = t.getPos(e); + sr = t.getSize(e); + + return { + x : p.x, + y : p.y, + w : sr.w, + h : sr.h + }; + }, + + getSize : function(e) { + var t = this, w, h; + + e = t.get(e); + w = t.getStyle(e, 'width'); + h = t.getStyle(e, 'height'); + + // Non pixel value, then force offset/clientWidth + if (w.indexOf('px') === -1) + w = 0; + + // Non pixel value, then force offset/clientWidth + if (h.indexOf('px') === -1) + h = 0; + + return { + w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, + h : parseInt(h, 10) || e.offsetHeight || e.clientHeight + }; + }, + + getParent : function(n, f, r) { + return this.getParents(n, f, r, false); + }, + + getParents : function(n, f, r, c) { + var t = this, na, se = t.settings, o = []; + + n = t.get(n); + c = c === undefined; + + if (se.strict_root) + r = r || t.getRoot(); + + // Wrap node name as func + if (is(f, 'string')) { + na = f; + + if (f === '*') { + f = function(n) {return n.nodeType == 1;}; + } else { + f = function(n) { + return t.is(n, na); + }; + } + } + + while (n) { + if (n == r || !n.nodeType || n.nodeType === 9) + break; + + if (!f || f(n)) { + if (c) + o.push(n); + else + return n; + } + + n = n.parentNode; + } + + return c ? o : null; + }, + + get : function(e) { + var n; + + if (e && this.doc && typeof(e) == 'string') { + n = e; + e = this.doc.getElementById(e); + + // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick + if (e && e.id !== n) + return this.doc.getElementsByName(n)[1]; + } + + return e; + }, + + getNext : function(node, selector) { + return this._findSib(node, selector, 'nextSibling'); + }, + + getPrev : function(node, selector) { + return this._findSib(node, selector, 'previousSibling'); + }, + + + add : function(p, n, a, h, c) { + var t = this; + + return this.run(p, function(p) { + var e, k; + + e = is(n, 'string') ? t.doc.createElement(n) : n; + t.setAttribs(e, a); + + if (h) { + if (h.nodeType) + e.appendChild(h); + else + t.setHTML(e, h); + } + + return !c ? p.appendChild(e) : e; + }); + }, + + create : function(n, a, h) { + return this.add(this.doc.createElement(n), n, a, h, 1); + }, + + createHTML : function(n, a, h) { + var o = '', t = this, k; + + o += '<' + n; + + for (k in a) { + if (a.hasOwnProperty(k)) + o += ' ' + k + '="' + t.encode(a[k]) + '"'; + } + + // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime + if (typeof(h) != "undefined") + return o + '>' + h + ''; + + return o + ' />'; + }, + + remove : function(node, keep_children) { + return this.run(node, function(node) { + var child, parent = node.parentNode; + + if (!parent) + return null; + + if (keep_children) { + while (child = node.firstChild) { + // IE 8 will crash if you don't remove completely empty text nodes + if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) + parent.insertBefore(child, node); + else + node.removeChild(child); + } + } + + return parent.removeChild(node); + }); + }, + + setStyle : function(n, na, v) { + var t = this; + + return t.run(n, function(e) { + var s, i; + + s = e.style; + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + // Default px suffix on these + if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) + v += 'px'; + + switch (na) { + case 'opacity': + // IE specific opacity + if (isIE && ! tinymce.isIE11) { + s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; + + if (!n.currentStyle || !n.currentStyle.hasLayout) + s.display = 'inline-block'; + } + + // Fix for older browsers + s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; + break; + + case 'float': + (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v; + break; + + default: + s[na] = v || ''; + } + + // Force update of the style data + if (t.settings.update_styles) + t.setAttrib(e, 'data-mce-style'); + }); + }, + + getStyle : function(n, na, c) { + n = this.get(n); + + if (!n) + return; + + // Gecko + if (this.doc.defaultView && c) { + // Remove camelcase + na = na.replace(/[A-Z]/g, function(a){ + return '-' + a; + }); + + try { + return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); + } catch (ex) { + // Old safari might fail + return null; + } + } + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + if (na == 'float') + na = isIE ? 'styleFloat' : 'cssFloat'; + + // IE & Opera + if (n.currentStyle && c) + return n.currentStyle[na]; + + return n.style ? n.style[na] : undefined; + }, + + setStyles : function(e, o) { + var t = this, s = t.settings, ol; + + ol = s.update_styles; + s.update_styles = 0; + + each(o, function(v, n) { + t.setStyle(e, n, v); + }); + + // Update style info + s.update_styles = ol; + if (s.update_styles) + t.setAttrib(e, s.cssText); + }, + + removeAllAttribs: function(e) { + return this.run(e, function(e) { + var i, attrs = e.attributes; + for (i = attrs.length - 1; i >= 0; i--) { + e.removeAttributeNode(attrs.item(i)); + } + }); + }, + + setAttrib : function(e, n, v) { + var t = this; + + // Whats the point + if (!e || !n) + return; + + // Strict XML mode + if (t.settings.strict) + n = n.toLowerCase(); + + return this.run(e, function(e) { + var s = t.settings; + var originalValue = e.getAttribute(n); + if (v !== null) { + switch (n) { + case "style": + if (!is(v, 'string')) { + each(v, function(v, n) { + t.setStyle(e, n, v); + }); + + return; + } + + // No mce_style for elements with these since they might get resized by the user + if (s.keep_values) { + if (v && !t._isRes(v)) + e.setAttribute('data-mce-style', v, 2); + else + e.removeAttribute('data-mce-style', 2); + } + + e.style.cssText = v; + break; + + case "class": + e.className = v || ''; // Fix IE null bug + break; + + case "src": + case "href": + if (s.keep_values) { + if (s.url_converter) + v = s.url_converter.call(s.url_converter_scope || t, v, n, e); + + t.setAttrib(e, 'data-mce-' + n, v, 2); + } + + break; + + case "shape": + e.setAttribute('data-mce-style', v); + break; + } + } + if (is(v) && v !== null && v.length !== 0) + e.setAttribute(n, '' + v, 2); + else + e.removeAttribute(n, 2); + + // fire onChangeAttrib event for attributes that have changed + if (tinyMCE.activeEditor && originalValue != v) { + var ed = tinyMCE.activeEditor; + ed.onSetAttrib.dispatch(ed, e, n, v); + } + }); + }, + + setAttribs : function(e, o) { + var t = this; + + return this.run(e, function(e) { + each(o, function(v, n) { + t.setAttrib(e, n, v); + }); + }); + }, + + getAttrib : function(e, n, dv) { + var v, t = this, undef; + + e = t.get(e); + + if (!e || e.nodeType !== 1) + return dv === undef ? false : dv; + + if (!is(dv)) + dv = ''; + + // Try the mce variant for these + if (/^(src|href|style|coords|shape)$/.test(n)) { + v = e.getAttribute("data-mce-" + n); + + if (v) + return v; + } + + if (isIE && t.props[n]) { + v = e[t.props[n]]; + v = v && v.nodeValue ? v.nodeValue : v; + } + + if (!v) + v = e.getAttribute(n, 2); + + // Check boolean attribs + if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { + if (e[t.props[n]] === true && v === '') + return n; + + return v ? n : ''; + } + + // Inner input elements will override attributes on form elements + if (e.nodeName === "FORM" && e.getAttributeNode(n)) + return e.getAttributeNode(n).nodeValue; + + if (n === 'style') { + v = v || e.style.cssText; + + if (v) { + v = t.serializeStyle(t.parseStyle(v), e.nodeName); + + if (t.settings.keep_values && !t._isRes(v)) + e.setAttribute('data-mce-style', v); + } + } + + // Remove Apple and WebKit stuff + if (isWebKit && n === "class" && v) + v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); + + // Handle IE issues + if (isIE) { + switch (n) { + case 'rowspan': + case 'colspan': + // IE returns 1 as default value + if (v === 1) + v = ''; + + break; + + case 'size': + // IE returns +0 as default value for size + if (v === '+0' || v === 20 || v === 0) + v = ''; + + break; + + case 'width': + case 'height': + case 'vspace': + case 'checked': + case 'disabled': + case 'readonly': + if (v === 0) + v = ''; + + break; + + case 'hspace': + // IE returns -1 as default value + if (v === -1) + v = ''; + + break; + + case 'maxlength': + case 'tabindex': + // IE returns default value + if (v === 32768 || v === 2147483647 || v === '32768') + v = ''; + + break; + + case 'multiple': + case 'compact': + case 'noshade': + case 'nowrap': + if (v === 65535) + return n; + + return dv; + + case 'shape': + v = v.toLowerCase(); + break; + + default: + // IE has odd anonymous function for event attributes + if (n.indexOf('on') === 0 && v) + v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); + } + } + + return (v !== undef && v !== null && v !== '') ? '' + v : dv; + }, + + getPos : function(n, ro) { + var t = this, x = 0, y = 0, e, d = t.doc, r; + + n = t.get(n); + ro = ro || d.body; + + if (n) { + // Use getBoundingClientRect if it exists since it's faster than looping offset nodes + if (n.getBoundingClientRect) { + n = n.getBoundingClientRect(); + e = t.boxModel ? d.documentElement : d.body; + + // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit + // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position + x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; + y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; + + return {x : x, y : y}; + } + + r = n; + while (r && r != ro && r.nodeType) { + x += r.offsetLeft || 0; + y += r.offsetTop || 0; + r = r.offsetParent; + } + + r = n.parentNode; + while (r && r != ro && r.nodeType) { + x -= r.scrollLeft || 0; + y -= r.scrollTop || 0; + r = r.parentNode; + } + } + + return {x : x, y : y}; + }, + + parseStyle : function(st) { + return this.styles.parse(st); + }, + + serializeStyle : function(o, name) { + return this.styles.serialize(o, name); + }, + + addStyle: function(cssText) { + var doc = this.doc, head; + + // Create style element if needed + styleElm = doc.getElementById('mceDefaultStyles'); + if (!styleElm) { + styleElm = doc.createElement('style'), + styleElm.id = 'mceDefaultStyles'; + styleElm.type = 'text/css'; + + head = doc.getElementsByTagName('head')[0]; + if (head.firstChild) { + head.insertBefore(styleElm, head.firstChild); + } else { + head.appendChild(styleElm); + } + } + + // Append style data to old or new style element + if (styleElm.styleSheet) { + styleElm.styleSheet.cssText += cssText; + } else { + styleElm.appendChild(doc.createTextNode(cssText)); + } + }, + + loadCSS : function(u) { + var t = this, d = t.doc, head; + + if (!u) + u = ''; + + head = d.getElementsByTagName('head')[0]; + + each(u.split(','), function(u) { + var link; + + if (t.files[u]) + return; + + t.files[u] = true; + link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); + + // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug + // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading + // It's ugly but it seems to work fine. + if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) { + link.onload = function() { + if (d.recalc) + d.recalc(); + + link.onload = null; + }; + } + + head.appendChild(link); + }); + }, + + addClass : function(e, c) { + return this.run(e, function(e) { + var o; + + if (!c) + return 0; + + if (this.hasClass(e, c)) + return e.className; + + o = this.removeClass(e, c); + + return e.className = (o != '' ? (o + ' ') : '') + c; + }); + }, + + removeClass : function(e, c) { + var t = this, re; + + return t.run(e, function(e) { + var v; + + if (t.hasClass(e, c)) { + if (!re) + re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); + + v = e.className.replace(re, ' '); + v = tinymce.trim(v != ' ' ? v : ''); + + e.className = v; + + // Empty class attr + if (!v) { + e.removeAttribute('class'); + e.removeAttribute('className'); + } + + return v; + } + + return e.className; + }); + }, + + hasClass : function(n, c) { + n = this.get(n); + + if (!n || !c) + return false; + + return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; + }, + + show : function(e) { + return this.setStyle(e, 'display', 'block'); + }, + + hide : function(e) { + return this.setStyle(e, 'display', 'none'); + }, + + isHidden : function(e) { + e = this.get(e); + + return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; + }, + + uniqueId : function(p) { + return (!p ? 'mce_' : p) + (this.counter++); + }, + + setHTML : function(element, html) { + var self = this; + + return self.run(element, function(element) { + if (isIE) { + // Remove all child nodes, IE keeps empty text nodes in DOM + while (element.firstChild) + element.removeChild(element.firstChild); + + try { + // IE will remove comments from the beginning + // unless you padd the contents with something + element.innerHTML = '
    ' + html; + element.removeChild(element.firstChild); + } catch (ex) { + // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p + // This seems to fix this problem + + // Create new div with HTML contents and a BR infront to keep comments + var newElement = self.create('div'); + newElement.innerHTML = '
    ' + html; + + // Add all children from div to target + each (tinymce.grep(newElement.childNodes), function(node, i) { + // Skip br element + if (i && element.canHaveHTML) + element.appendChild(node); + }); + } + } else + element.innerHTML = html; + + return html; + }); + }, + + getOuterHTML : function(elm) { + var doc, self = this; + + elm = self.get(elm); + + if (!elm) + return null; + + if (elm.nodeType === 1 && self.hasOuterHTML) + return elm.outerHTML; + + doc = (elm.ownerDocument || self.doc).createElement("body"); + doc.appendChild(elm.cloneNode(true)); + + return doc.innerHTML; + }, + + setOuterHTML : function(e, h, d) { + var t = this; + + function setHTML(e, h, d) { + var n, tp; + + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + }; + + return this.run(e, function(e) { + e = t.get(e); + + // Only set HTML on elements + if (e.nodeType == 1) { + d = d || e.ownerDocument || t.doc; + + if (isIE) { + try { + // Try outerHTML for IE it sometimes produces an unknown runtime error + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else + setHTML(e, h, d); + } catch (ex) { + // Fix for unknown runtime error + setHTML(e, h, d); + } + } else + setHTML(e, h, d); + } + }); + }, + + decode : Entities.decode, + + encode : Entities.encodeAllRaw, + + insertAfter : function(node, reference_node) { + reference_node = this.get(reference_node); + + return this.run(node, function(node) { + var parent, nextSibling; + + parent = reference_node.parentNode; + nextSibling = reference_node.nextSibling; + + if (nextSibling) + parent.insertBefore(node, nextSibling); + else + parent.appendChild(node); + + return node; + }); + }, + + replace : function(n, o, k) { + var t = this; + + if (is(o, 'array')) + n = n.cloneNode(true); + + return t.run(o, function(o) { + if (k) { + each(tinymce.grep(o.childNodes), function(c) { + n.appendChild(c); + }); + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + rename : function(elm, name) { + var t = this, newElm; + + if (elm.nodeName != name.toUpperCase()) { + // Rename block element + newElm = t.create(name); + + // Copy attribs to new block + each(t.getAttribs(elm), function(attr_node) { + t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); + }); + + // Replace block + t.replace(newElm, elm, 1); + } + + return newElm || elm; + }, + + findCommonAncestor : function(a, b) { + var ps = a, pe; + + while (ps) { + pe = b; + + while (pe && ps != pe) + pe = pe.parentNode; + + if (ps == pe) + break; + + ps = ps.parentNode; + } + + if (!ps && a.ownerDocument) + return a.ownerDocument.documentElement; + + return ps; + }, + + toHex : function(s) { + var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); + + function hex(s) { + s = parseInt(s, 10).toString(16); + + return s.length > 1 ? s : '0' + s; // 0 -> 00 + }; + + if (c) { + s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); + + return s; + } + + return s; + }, + + getClasses : function() { + var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; + + if (t.classes) + return t.classes; + + function addClasses(s) { + // IE style imports + each(s.imports, function(r) { + addClasses(r); + }); + + each(s.cssRules || s.rules, function(r) { + // Real type or fake it on IE + switch (r.type || 1) { + // Rule + case 1: + if (r.selectorText) { + each(r.selectorText.split(','), function(v) { + v = v.replace(/^\s*|\s*$|^\s\./g, ""); + + // Is internal or it doesn't contain a class + if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) + return; + + // Remove everything but class name + ov = v; + v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); + + // Filter classes + if (f && !(v = f(v, ov))) + return; + + if (!lo[v]) { + cl.push({'class' : v}); + lo[v] = 1; + } + }); + } + break; + + // Import + case 3: + try { + addClasses(r.styleSheet); + } catch (ex) { + // Ignore + } + + break; + } + }); + }; + + try { + each(t.doc.styleSheets, addClasses); + } catch (ex) { + // Ignore + } + + if (cl.length > 0) + t.classes = cl; + + return cl; + }, + + run : function(e, f, s) { + var t = this, o; + + if (t.doc && typeof(e) === 'string') + e = t.get(e); + + if (!e) + return false; + + s = s || this; + if (!e.nodeType && (e.length || e.length === 0)) { + o = []; + + each(e, function(e, i) { + if (e) { + if (typeof(e) == 'string') + e = t.doc.getElementById(e); + + o.push(f.call(s, e, i)); + } + }); + + return o; + } + + return f.call(s, e); + }, + + getAttribs : function(n) { + var o; + + n = this.get(n); + + if (!n) + return []; + + if (isIE) { + o = []; + + // Object will throw exception in IE + if (n.nodeName == 'OBJECT') + return n.attributes; + + // IE doesn't keep the selected attribute if you clone option elements + if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) + o.push({specified : 1, nodeName : 'selected'}); + + // It's crazy that this is faster in IE but it's because it returns all attributes all the time + n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { + o.push({specified : 1, nodeName : a}); + }); + + return o; + } + + return n.attributes; + }, + + isEmpty : function(node, elements) { + var self = this, i, attributes, type, walker, name, brCount = 0; + + node = node.firstChild; + if (node) { + walker = new tinymce.dom.TreeWalker(node, node.parentNode); + elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; + + do { + type = node.nodeType; + + if (type === 1) { + // Ignore bogus elements + if (node.getAttribute('data-mce-bogus')) + continue; + + // Keep empty elements like + name = node.nodeName.toLowerCase(); + if (elements && elements[name]) { + // Ignore single BR elements in blocks like


    or


    + if (name === 'br') { + brCount++; + continue; + } + + return false; + } + + // Keep elements with data-bookmark attributes or name attribute like
    + attributes = self.getAttribs(node); + i = node.attributes.length; + while (i--) { + name = node.attributes[i].nodeName; + if (name === "name" || name === 'data-mce-bookmark') + return false; + } + } + + // Keep comment nodes + if (type == 8) + return false; + + // Keep non whitespace text nodes + if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) + return false; + } while (node = walker.next()); + } + + return brCount <= 1; + }, + + destroy : function(s) { + var t = this; + + t.win = t.doc = t.root = t.events = t.frag = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + createRng : function() { + var d = this.doc; + + return d.createRange ? d.createRange() : new tinymce.dom.Range(this); + }, + + nodeIndex : function(node, normalized) { + var idx = 0, lastNodeType, lastNode, nodeType; + + if (node) { + for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { + nodeType = node.nodeType; + + // Normalize text nodes + if (normalized && nodeType == 3) { + if (nodeType == lastNodeType || !node.nodeValue.length) + continue; + } + idx++; + lastNodeType = nodeType; + } + } + + return idx; + }, + + split : function(pe, e, re) { + var t = this, r = t.createRng(), bef, aft, pa; + + // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense + // but we don't want that in our code since it serves no purpose for the end user + // For example if this is chopped: + //

    text 1CHOPtext 2

    + // would produce: + //

    text 1

    CHOP

    text 2

    + // this function will then trim of empty edges and produce: + //

    text 1

    CHOP

    text 2

    + function trim(node) { + var i, children = node.childNodes, type = node.nodeType; + + function surroundedBySpans(node) { + var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; + var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; + return previousIsSpan && nextIsSpan; + } + + if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') + return; + + for (i = children.length - 1; i >= 0; i--) + trim(children[i]); + + if (type != 9) { + // Keep non whitespace text nodes + if (type == 3 && node.nodeValue.length > 0) { + // If parent element isn't a block or there isn't any useful contents for example "

    " + // Also keep text nodes with only spaces if surrounded by spans. + // eg. "

    a b

    " should keep space between a and b + var trimmedLength = tinymce.trim(node.nodeValue).length; + if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) + return; + } else if (type == 1) { + // If the only child is a bookmark then move it up + children = node.childNodes; + if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') + node.parentNode.insertBefore(children[0], node); + + // Keep non empty elements or img, hr etc + if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) + return; + } + + t.remove(node); + } + + return node; + }; + + if (pe && e) { + // Get before chunk + r.setStart(pe.parentNode, t.nodeIndex(pe)); + r.setEnd(e.parentNode, t.nodeIndex(e)); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStart(e.parentNode, t.nodeIndex(e) + 1); + r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); + aft = r.extractContents(); + + // Insert before chunk + pa = pe.parentNode; + pa.insertBefore(trim(bef), pe); + + // Insert middle chunk + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Insert after chunk + pa.insertBefore(trim(aft), pe); + t.remove(pe); + + return re || e; + } + }, + + bind : function(target, name, func, scope) { + return this.events.add(target, name, func, scope || this); + }, + + unbind : function(target, name, func) { + return this.events.remove(target, name, func); + }, + + fire : function(target, name, evt) { + return this.events.fire(target, name, evt); + }, + + // Returns the content editable state of a node + getContentEditable: function(node) { + var contentEditable; + + // Check type + if (node.nodeType != 1) { + return null; + } + + // Check for fake content editable + contentEditable = node.getAttribute("data-mce-contenteditable"); + if (contentEditable && contentEditable !== "inherit") { + return contentEditable; + } + + // Check for real content editable + return node.contentEditable !== "inherit" ? node.contentEditable : null; + }, + + + _findSib : function(node, selector, name) { + var t = this, f = selector; + + if (node) { + // If expression make a function of it using is + if (is(f, 'string')) { + f = function(node) { + return t.is(node, selector); + }; + } + + // Loop all siblings + for (node = node[name]; node; node = node[name]) { + if (f(node)) + return node; + } + } + + return null; + }, + + _isRes : function(c) { + // Is live resizble element + return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); + } + + /* + walk : function(n, f, s) { + var d = this.doc, w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(s || this, n); + } else + tinymce.walk(n, f, 'childNodes', s); + } + */ + + /* + toRGB : function(s) { + var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); + + if (c) { + // #FFF -> #FFFFFF + if (!is(c[3])) + c[3] = c[2] = c[1]; + + return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; + } + + return s; + } + */ + }); + + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); + +(function(ns) { + // Range constructor + function Range(dom) { + var t = this, + doc = dom.doc, + EXTRACT = 0, + CLONE = 1, + DELETE = 2, + TRUE = true, + FALSE = false, + START_OFFSET = 'startOffset', + START_CONTAINER = 'startContainer', + END_CONTAINER = 'endContainer', + END_OFFSET = 'endOffset', + extend = tinymce.extend, + nodeIndex = dom.nodeIndex; + + extend(t, { + // Inital states + startContainer : doc, + startOffset : 0, + endContainer : doc, + endOffset : 0, + collapsed : TRUE, + commonAncestorContainer : doc, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3, + + // Public methods + setStart : setStart, + setEnd : setEnd, + setStartBefore : setStartBefore, + setStartAfter : setStartAfter, + setEndBefore : setEndBefore, + setEndAfter : setEndAfter, + collapse : collapse, + selectNode : selectNode, + selectNodeContents : selectNodeContents, + compareBoundaryPoints : compareBoundaryPoints, + deleteContents : deleteContents, + extractContents : extractContents, + cloneContents : cloneContents, + insertNode : insertNode, + surroundContents : surroundContents, + cloneRange : cloneRange, + toStringIE : toStringIE + }); + + function createDocumentFragment() { + return doc.createDocumentFragment(); + }; + + function setStart(n, o) { + _setEndPoint(TRUE, n, o); + }; + + function setEnd(n, o) { + _setEndPoint(FALSE, n, o); + }; + + function setStartBefore(n) { + setStart(n.parentNode, nodeIndex(n)); + }; + + function setStartAfter(n) { + setStart(n.parentNode, nodeIndex(n) + 1); + }; + + function setEndBefore(n) { + setEnd(n.parentNode, nodeIndex(n)); + }; + + function setEndAfter(n) { + setEnd(n.parentNode, nodeIndex(n) + 1); + }; + + function collapse(ts) { + if (ts) { + t[END_CONTAINER] = t[START_CONTAINER]; + t[END_OFFSET] = t[START_OFFSET]; + } else { + t[START_CONTAINER] = t[END_CONTAINER]; + t[START_OFFSET] = t[END_OFFSET]; + } + + t.collapsed = TRUE; + }; + + function selectNode(n) { + setStartBefore(n); + setEndAfter(n); + }; + + function selectNodeContents(n) { + setStart(n, 0); + setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }; + + function compareBoundaryPoints(h, r) { + var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], + rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; + + // Check START_TO_START + if (h === 0) + return _compareBoundaryPoints(sc, so, rsc, rso); + + // Check START_TO_END + if (h === 1) + return _compareBoundaryPoints(ec, eo, rsc, rso); + + // Check END_TO_END + if (h === 2) + return _compareBoundaryPoints(ec, eo, rec, reo); + + // Check END_TO_START + if (h === 3) + return _compareBoundaryPoints(sc, so, rec, reo); + }; + + function deleteContents() { + _traverse(DELETE); + }; + + function extractContents() { + return _traverse(EXTRACT); + }; + + function cloneContents() { + return _traverse(CLONE); + }; + + function insertNode(n) { + var startContainer = this[START_CONTAINER], + startOffset = this[START_OFFSET], nn, o; + + // Node is TEXT_NODE or CDATA + if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { + if (!startOffset) { + // At the start of text + startContainer.parentNode.insertBefore(n, startContainer); + } else if (startOffset >= startContainer.nodeValue.length) { + // At the end of text + dom.insertAfter(n, startContainer); + } else { + // Middle, need to split + nn = startContainer.splitText(startOffset); + startContainer.parentNode.insertBefore(n, nn); + } + } else { + // Insert element node + if (startContainer.childNodes.length > 0) + o = startContainer.childNodes[startOffset]; + + if (o) + startContainer.insertBefore(n, o); + else + startContainer.appendChild(n); + } + }; + + function surroundContents(n) { + var f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }; + + function cloneRange() { + return extend(new Range(dom), { + startContainer : t[START_CONTAINER], + startOffset : t[START_OFFSET], + endContainer : t[END_CONTAINER], + endOffset : t[END_OFFSET], + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }; + + // Private methods + + function _getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child) + return child; + + return container; + }; + + function _isCollapsed() { + return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); + }; + + function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { + var c, offsetC, n, cmnRoot, childA, childB; + + // In the first case the boundary-points have the same container. A is before B + // if its offset is less than the offset of B, A is equal to B if its offset is + // equal to the offset of B, and A is after B if its offset is greater than the + // offset of B. + if (containerA == containerB) { + if (offsetA == offsetB) + return 0; // equal + + if (offsetA < offsetB) + return -1; // before + + return 1; // after + } + + // In the second case a child node C of the container of A is an ancestor + // container of B. In this case, A is before B if the offset of A is less than or + // equal to the index of the child node C and A is after B otherwise. + c = containerB; + while (c && c.parentNode != containerA) + c = c.parentNode; + + if (c) { + offsetC = 0; + n = containerA.firstChild; + + while (n != c && offsetC < offsetA) { + offsetC++; + n = n.nextSibling; + } + + if (offsetA <= offsetC) + return -1; // before + + return 1; // after + } + + // In the third case a child node C of the container of B is an ancestor container + // of A. In this case, A is before B if the index of the child node C is less than + // the offset of B and A is after B otherwise. + c = containerA; + while (c && c.parentNode != containerB) { + c = c.parentNode; + } + + if (c) { + offsetC = 0; + n = containerB.firstChild; + + while (n != c && offsetC < offsetB) { + offsetC++; + n = n.nextSibling; + } + + if (offsetC < offsetB) + return -1; // before + + return 1; // after + } + + // In the fourth case, none of three other cases hold: the containers of A and B + // are siblings or descendants of sibling nodes. In this case, A is before B if + // the container of A is before the container of B in a pre-order traversal of the + // Ranges' context tree and A is after B otherwise. + cmnRoot = dom.findCommonAncestor(containerA, containerB); + childA = containerA; + + while (childA && childA.parentNode != cmnRoot) + childA = childA.parentNode; + + if (!childA) + childA = cmnRoot; + + childB = containerB; + while (childB && childB.parentNode != cmnRoot) + childB = childB.parentNode; + + if (!childB) + childB = cmnRoot; + + if (childA == childB) + return 0; // equal + + n = cmnRoot.firstChild; + while (n) { + if (n == childA) + return -1; // before + + if (n == childB) + return 1; // after + + n = n.nextSibling; + } + }; + + function _setEndPoint(st, n, o) { + var ec, sc; + + if (st) { + t[START_CONTAINER] = n; + t[START_OFFSET] = o; + } else { + t[END_CONTAINER] = n; + t[END_OFFSET] = o; + } + + // If one boundary-point of a Range is set to have a root container + // other than the current one for the Range, the Range is collapsed to + // the new position. This enforces the restriction that both boundary- + // points of a Range must have the same root container. + ec = t[END_CONTAINER]; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t[START_CONTAINER]; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc == ec) { + // The start position of a Range is guaranteed to never be after the + // end position. To enforce this restriction, if the start is set to + // be at a position after the end, the Range is collapsed to that + // position. + if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) + t.collapse(st); + } else + t.collapse(st); + + t.collapsed = _isCollapsed(); + t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); + }; + + function _traverse(how) { + var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t[START_CONTAINER] == t[END_CONTAINER]) + return _traverseSameContainer(how); + + for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[START_CONTAINER]) + return _traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[END_CONTAINER]) + return _traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t[START_CONTAINER]; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t[END_CONTAINER]; + while (depthDiff < 0) { + endNode = endNode.parentNode; + depthDiff++; + } + + // ascend the ancestor hierarchy until we have a common parent. + for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { + startNode = sp; + endNode = ep; + } + + return _traverseCommonAncestors(startNode, endNode, how); + }; + + function _traverseSameContainer(how) { + var frag, s, sub, n, cnt, sibling, xferNode, start, len; + + if (how != DELETE) + frag = createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t[START_OFFSET] == t[END_OFFSET]) + return frag; + + // Text node needs special case handling + if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t[START_CONTAINER].nodeValue; + sub = s.substring(t[START_OFFSET], t[END_OFFSET]); + + // set the original text node to its new value + if (how != CLONE) { + n = t[START_CONTAINER]; + start = t[START_OFFSET]; + len = t[END_OFFSET] - t[START_OFFSET]; + + if (start === 0 && len >= n.nodeValue.length - 1) { + n.parentNode.removeChild(n); + } else { + n.deleteData(start, len); + } + + // Nothing is partially selected, so collapse to start point + t.collapse(TRUE); + } + + if (how == DELETE) + return; + + if (sub.length > 0) { + frag.appendChild(doc.createTextNode(sub)); + } + + return frag; + } + + // Copy nodes between the start/end offsets. + n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); + cnt = t[END_OFFSET] - t[START_OFFSET]; + + while (n && cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild( xferNode ); + + --cnt; + n = sibling; + } + + // Nothing is partially selected, so collapse to start point + if (how != CLONE) + t.collapse(TRUE); + + return frag; + }; + + function _traverseCommonStartContainer(endAncestor, how) { + var frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = createDocumentFragment(); + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = nodeIndex(endAncestor); + cnt = endIdx - t[START_OFFSET]; + + if (cnt <= 0) { + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + } + + n = endAncestor.previousSibling; + while (cnt > 0) { + sibling = n.previousSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.insertBefore(xferNode, frag.firstChild); + + --cnt; + n = sibling; + } + + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + }; + + function _traverseCommonEndContainer(startAncestor, how) { + var frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = nodeIndex(startAncestor); + ++startIdx; // Because we already traversed it + + cnt = t[END_OFFSET] - startIdx; + n = startAncestor.nextSibling; + while (n && cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseCommonAncestors(startAncestor, endAncestor, how) { + var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = nodeIndex(startAncestor); + endOffset = nodeIndex(endAncestor); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = _traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseRightBoundary(root, how) { + var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; + + if (next == root) + return _traverseNode(next, isFullySelected, FALSE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, FALSE, how); + + while (parent) { + while (next) { + prevSibling = next.previousSibling; + clonedChild = _traverseNode(next, isFullySelected, FALSE, how); + + if (how != DELETE) + clonedParent.insertBefore(clonedChild, clonedParent.firstChild); + + isFullySelected = TRUE; + next = prevSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.previousSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseLeftBoundary(root, how) { + var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return _traverseNode(next, isFullySelected, TRUE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, TRUE, how); + + while (parent) { + while (next) { + nextSibling = next.nextSibling; + clonedChild = _traverseNode(next, isFullySelected, TRUE, how); + + if (how != DELETE) + clonedParent.appendChild(clonedChild); + + isFullySelected = TRUE; + next = nextSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.nextSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseNode(n, isFullySelected, isLeft, how) { + var txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return _traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t[START_OFFSET]; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t[END_OFFSET]; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return; + + newNode = dom.clone(n, FALSE); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return; + + return dom.clone(n, FALSE); + }; + + function _traverseFullySelected(n, how) { + if (how != DELETE) + return how == CLONE ? dom.clone(n, TRUE) : n; + + n.parentNode.removeChild(n); + }; + + function toStringIE() { + return dom.create('body', null, cloneContents()).outerText; + } + + return t; + }; + + ns.Range = Range; + + // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype + Range.prototype.toString = function() { + return this.toStringIE(); + }; +})(tinymce.dom); + +(function() { + function Selection(selection) { + var self = this, dom = selection.dom, TRUE = true, FALSE = false; + + function getPosition(rng, start) { + var checkRng, startIndex = 0, endIndex, inside, + children, child, offset, index, position = -1, parent; + + // Setup test range, collapse it and get the parent + checkRng = rng.duplicate(); + checkRng.collapse(start); + parent = checkRng.parentElement(); + + // Check if the selection is within the right document + if (parent.ownerDocument !== selection.dom.doc) + return; + + // IE will report non editable elements as it's parent so look for an editable one + while (parent.contentEditable === "false") { + parent = parent.parentNode; + } + + // If parent doesn't have any children then return that we are inside the element + if (!parent.hasChildNodes()) { + return {node : parent, inside : 1}; + } + + // Setup node list and endIndex + children = parent.children; + endIndex = children.length - 1; + + // Perform a binary search for the position + while (startIndex <= endIndex) { + index = Math.floor((startIndex + endIndex) / 2); + + // Move selection to node and compare the ranges + child = children[index]; + checkRng.moveToElementText(child); + position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); + + // Before/after or an exact match + if (position > 0) { + endIndex = index - 1; + } else if (position < 0) { + startIndex = index + 1; + } else { + return {node : child}; + } + } + + // Check if child position is before or we didn't find a position + if (position < 0) { + // No element child was found use the parent element and the offset inside that + if (!child) { + checkRng.moveToElementText(parent); + checkRng.collapse(true); + child = parent; + inside = true; + } else + checkRng.collapse(false); + + // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one + // We need to walk char by char since rng.text or rng.htmlText will trim line endings + offset = 0; + while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { + if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { + break; + } + + offset++; + } + } else { + // Child position is after the selection endpoint + checkRng.collapse(true); + + // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one + offset = 0; + while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { + if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { + break; + } + + offset++; + } + } + + return {node : child, position : position, offset : offset, inside : inside}; + }; + + // Returns a W3C DOM compatible range object by using the IE Range API + function getRange() { + var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; + + // If selection is outside the current document just return an empty range + element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); + if (element.ownerDocument != dom.doc) + return domRange; + + collapsed = selection.isCollapsed(); + + // Handle control selection + if (ieRange.item) { + domRange.setStart(element.parentNode, dom.nodeIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + function findEndPoint(start) { + var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; + + container = endPoint.node; + offset = endPoint.offset; + + if (endPoint.inside && !container.hasChildNodes()) { + domRange[start ? 'setStart' : 'setEnd'](container, 0); + return; + } + + if (offset === undef) { + domRange[start ? 'setStartBefore' : 'setEndAfter'](container); + return; + } + + if (endPoint.position < 0) { + sibling = endPoint.inside ? container.firstChild : container.nextSibling; + + if (!sibling) { + domRange[start ? 'setStartAfter' : 'setEndAfter'](container); + return; + } + + if (!offset) { + if (sibling.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, 0); + else + domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); + + return; + } + + // Find the text node and offset + while (sibling) { + nodeValue = sibling.nodeValue; + textNodeOffset += nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + textNodeOffset = nodeValue.length - textNodeOffset; + break; + } + + sibling = sibling.nextSibling; + } + } else { + // Find the text node and offset + sibling = container.previousSibling; + + if (!sibling) + return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); + + // If there isn't any text to loop then use the first position + if (!offset) { + if (container.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); + else + domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); + + return; + } + + while (sibling) { + textNodeOffset += sibling.nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + break; + } + + sibling = sibling.previousSibling; + } + } + + domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); + }; + + try { + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } catch (ex) { + // IE has a nasty bug where text nodes might throw "invalid argument" when you + // access the nodeValue or other properties of text nodes. This seems to happend when + // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. + if (ex.number == -2147024809) { + // Get the current selection + bookmark = self.getBookmark(2); + + // Get start element + tmpRange = ieRange.duplicate(); + tmpRange.collapse(true); + element = tmpRange.parentElement(); + + // Get end element + if (!collapsed) { + tmpRange = ieRange.duplicate(); + tmpRange.collapse(false); + element2 = tmpRange.parentElement(); + element2.innerHTML = element2.innerHTML; + } + + // Remove the broken elements + element.innerHTML = element.innerHTML; + + // Restore the selection + self.moveToBookmark(bookmark); + + // Since the range has moved we need to re-get it + ieRange = selection.getRng(); + + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } else + throw ex; // Throw other errors + } + + return domRange; + }; + + this.getBookmark = function(type) { + var rng = selection.getRng(), start, end, bookmark = {}; + + function getIndexes(node) { + var parent, root, children, i, indexes = []; + + parent = node.parentNode; + root = dom.getRoot().parentNode; + + while (parent != root && parent.nodeType !== 9) { + children = parent.children; + + i = children.length; + while (i--) { + if (node === children[i]) { + indexes.push(i); + break; + } + } + + node = parent; + parent = parent.parentNode; + } + + return indexes; + }; + + function getBookmarkEndPoint(start) { + var position; + + position = getPosition(rng, start); + if (position) { + return { + position : position.position, + offset : position.offset, + indexes : getIndexes(position.node), + inside : position.inside + }; + } + }; + + // Non ubstructive bookmark + if (type === 2) { + // Handle text selection + if (!rng.item) { + bookmark.start = getBookmarkEndPoint(true); + + if (!selection.isCollapsed()) + bookmark.end = getBookmarkEndPoint(); + } else + bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; + } + + return bookmark; + }; + + this.moveToBookmark = function(bookmark) { + var rng, body = dom.doc.body; + + function resolveIndexes(indexes) { + var node, i, idx, children; + + node = dom.getRoot(); + for (i = indexes.length - 1; i >= 0; i--) { + children = node.children; + idx = indexes[i]; + + if (idx <= children.length - 1) { + node = children[idx]; + } + } + + return node; + }; + + function setBookmarkEndPoint(start) { + var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; + + if (endPoint) { + moveLeft = endPoint.position > 0; + + moveRng = body.createTextRange(); + moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); + + offset = endPoint.offset; + if (offset !== undef) { + moveRng.collapse(endPoint.inside || moveLeft); + moveRng.moveStart('character', moveLeft ? -offset : offset); + } else + moveRng.collapse(start); + + rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); + + if (start) + rng.collapse(true); + } + }; + + if (bookmark.start) { + if (bookmark.start.ctrl) { + rng = body.createControlRange(); + rng.addElement(resolveIndexes(bookmark.start.indexes)); + rng.select(); + } else { + rng = body.createTextRange(); + setBookmarkEndPoint(true); + setBookmarkEndPoint(); + rng.select(); + } + } + }; + + this.addRange = function(rng) { + var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, + doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; + + function setEndPoint(start) { + var container, offset, marker, tmpRng, nodes; + + marker = dom.create('a'); + container = start ? startContainer : endContainer; + offset = start ? startOffset : endOffset; + tmpRng = ieRng.duplicate(); + + if (container == doc || container == doc.documentElement) { + container = body; + offset = 0; + } + + if (container.nodeType == 3) { + container.parentNode.insertBefore(marker, container); + tmpRng.moveToElementText(marker); + tmpRng.moveStart('character', offset); + dom.remove(marker); + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + } else { + nodes = container.childNodes; + + if (nodes.length) { + if (offset >= nodes.length) { + dom.insertAfter(marker, nodes[nodes.length - 1]); + } else { + container.insertBefore(marker, nodes[offset]); + } + + tmpRng.moveToElementText(marker); + } else if (container.canHaveHTML) { + // Empty node selection for example
    |
    + // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open + container.innerHTML = '\uFEFF'; + marker = container.firstChild; + tmpRng.moveToElementText(marker); + tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason + } + + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + dom.remove(marker); + } + } + + // Setup some shorter versions + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + ieRng = body.createTextRange(); + + // If single element selection then try making a control selection out of it + if (startContainer == endContainer && startContainer.nodeType == 1) { + // Trick to place the caret inside an empty block element like

    + if (startOffset == endOffset && !startContainer.hasChildNodes()) { + if (startContainer.canHaveHTML) { + // Check if previous sibling is an empty block if it is then we need to render it + // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 + // Example this:

    |

    would become this:

    |

    + sibling = startContainer.previousSibling; + if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { + sibling.innerHTML = '\uFEFF'; + } else { + sibling = null; + } + + startContainer.innerHTML = '\uFEFF\uFEFF'; + ieRng.moveToElementText(startContainer.lastChild); + ieRng.select(); + dom.doc.selection.clear(); + startContainer.innerHTML = ''; + + if (sibling) { + sibling.innerHTML = ''; + } + return; + } else { + startOffset = dom.nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + } + + if (startOffset == endOffset - 1) { + try { + ctrlElm = startContainer.childNodes[startOffset]; + ctrlRng = body.createControlRange(); + ctrlRng.addElement(ctrlElm); + ctrlRng.select(); + + // Check if the range produced is on the correct element and is a control range + // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 + nativeRng = selection.getRng(); + if (nativeRng.item && ctrlElm === nativeRng.item(0)) { + return; + } + } catch (ex) { + // Ignore + } + } + } + + // Set start/end point of selection + setEndPoint(true); + setEndPoint(); + + // Select the new range and scroll it into view + ieRng.select(); + }; + + // Expose range method + this.getRangeAt = getRange; + }; + + // Expose the selection object + tinymce.dom.TridentSelection = Selection; +})(); + + +(function(tinymce) { + tinymce.dom.Element = function(id, settings) { + var t = this, dom, el; + + t.settings = settings = settings || {}; + t.id = id; + t.dom = dom = settings.dom || tinymce.DOM; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = dom.get(t.id); + + tinymce.each( + ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + + 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + + 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + + 'isHidden,setHTML,get').split(/,/), function(k) { + t[k] = function() { + var a = [id], i; + + for (i = 0; i < arguments.length; i++) + a.push(arguments[i]); + + a = dom[k].apply(dom, a); + t.update(k); + + return a; + }; + } + ); + + tinymce.extend(t, { + on : function(n, f, s) { + return tinymce.dom.Event.add(t.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(t.getStyle('left')), + y : parseInt(t.getStyle('top')) + }; + }, + + getSize : function() { + var n = dom.get(t.id); + + return { + w : parseInt(t.getStyle('width') || n.clientWidth), + h : parseInt(t.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + t.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = t.getXY(); + + t.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + t.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = t.getSize(); + + t.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var b; + + if (tinymce.isIE6 && settings.blocker) { + k = k || ''; + + // Ignore getters + if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) + return; + + // Remove blocker on remove + if (k == 'remove') { + dom.remove(t.blocker); + return; + } + + if (!t.blocker) { + t.blocker = dom.uniqueId(); + b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); + dom.setStyle(b, 'opacity', 0); + } else + b = dom.get(t.blocker); + + dom.setStyles(b, { + left : t.getStyle('left', 1), + top : t.getStyle('top', 1), + width : t.getStyle('width', 1), + height : t.getStyle('height', 1), + display : t.getStyle('display', 1), + zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 + }); + } + } + }); + }; +})(tinymce); + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; + + tinymce.create('tinymce.dom.Selection', { + Selection : function(dom, win, serializer, editor) { + var t = this; + + t.dom = dom; + t.win = win; + t.serializer = serializer; + t.editor = editor; + + // Add events + each([ + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent' + ], function(e) { + t[e] = new tinymce.util.Dispatcher(t); + }); + + // No W3C Range support + if (!t.win.getSelection) + t.tridentSel = new tinymce.dom.TridentSelection(t); + + if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel) + this._fixIESelection(); + + // Prevent leaks + tinymce.addUnload(t.destroy, t); + }, + + setCursorLocation: function(node, offset) { + var t = this; var r = t.dom.createRng(); + r.setStart(node, offset); + r.setEnd(node, offset); + t.setRng(r); + t.collapse(false); + }, + getContent : function(s) { + var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; + + s = s || {}; + wb = wa = ''; + s.get = true; + s.format = s.format || 'html'; + s.forced_root_block = ''; + t.onBeforeGetContent.dispatch(t, s); + + if (s.format == 'text') + return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); + + if (r.cloneContents) { + n = r.cloneContents(); + + if (n) + e.appendChild(n); + } else if (is(r.item) || is(r.htmlText)) { + // IE will produce invalid markup if elements are present that + // it doesn't understand like custom elements or HTML5 elements. + // Adding a BR in front of the contents and then remoiving it seems to fix it though. + e.innerHTML = '
    ' + (r.item ? r.item(0).outerHTML : r.htmlText); + e.removeChild(e.firstChild); + } else + e.innerHTML = r.toString(); + + // Keep whitespace before and after + if (/^\s/.test(e.innerHTML)) + wb = ' '; + + if (/\s+$/.test(e.innerHTML)) + wa = ' '; + + s.getInner = true; + + s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; + t.onGetContent.dispatch(t, s); + + return s.content; + }, + + setContent : function(content, args) { + var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; + + args = args || {format : 'html'}; + args.set = true; + content = args.content = content; + + // Dispatch before set content event + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + if (rng.insertNode) { + // Make caret marker since insertNode places the caret in the beginning of text after insert + content += '_'; + + // Delete and insert new node + if (rng.startContainer == doc && rng.endContainer == doc) { + // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents + doc.body.innerHTML = content; + } else { + rng.deleteContents(); + + if (doc.body.childNodes.length === 0) { + doc.body.innerHTML = content; + } else { + // createContextualFragment doesn't exists in IE 9 DOMRanges + if (rng.createContextualFragment) { + rng.insertNode(rng.createContextualFragment(content)); + } else { + // Fake createContextualFragment call in IE 9 + frag = doc.createDocumentFragment(); + temp = doc.createElement('div'); + + frag.appendChild(temp); + temp.outerHTML = content; + + rng.insertNode(frag); + } + } + } + + // Move to caret marker + caretNode = self.dom.get('__caret'); + + // Make sure we wrap it compleatly, Opera fails with a simple select call + rng = doc.createRange(); + rng.setStartBefore(caretNode); + rng.setEndBefore(caretNode); + self.setRng(rng); + + // Remove the caret position + self.dom.remove('__caret'); + + try { + self.setRng(rng); + } catch (ex) { + // Might fail on Opera for some odd reason + } + } else { + if (rng.item) { + // Delete content and get caret text selection + doc.execCommand('Delete', false, null); + rng = self.getRng(); + } + + // Explorer removes spaces from the beginning of pasted contents + if (/^\s+/.test(content)) { + rng.pasteHTML('_' + content); + self.dom.remove('__mce_tmp'); + } else + rng.pasteHTML(content); + } + + // Dispatch set content event + if (!args.no_events) + self.onSetContent.dispatch(self, args); + }, + + getStart : function() { + var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; + + if (rng.duplicate || rng.item) { + // Control selection, return first item + if (rng.item) + return rng.item(0); + + // Get start element + checkRng = rng.duplicate(); + checkRng.collapse(1); + startElement = checkRng.parentElement(); + if (startElement.ownerDocument !== self.dom.doc) { + startElement = self.dom.getRoot(); + } + + // Check if range parent is inside the start element, then return the inner parent element + // This will fix issues when a single element is selected, IE would otherwise return the wrong start element + parentElement = node = rng.parentElement(); + while (node = node.parentNode) { + if (node == startElement) { + startElement = parentElement; + break; + } + } + + return startElement; + } else { + startElement = rng.startContainer; + + if (startElement.nodeType == 1 && startElement.hasChildNodes()) + startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + + if (startElement && startElement.nodeType == 3) + return startElement.parentNode; + + return startElement; + } + }, + + getEnd : function() { + var self = this, rng = self.getRng(), endElement, endOffset; + + if (rng.duplicate || rng.item) { + if (rng.item) + return rng.item(0); + + rng = rng.duplicate(); + rng.collapse(0); + endElement = rng.parentElement(); + if (endElement.ownerDocument !== self.dom.doc) { + endElement = self.dom.getRoot(); + } + + if (endElement && endElement.nodeName == 'BODY') + return endElement.lastChild || endElement; + + return endElement; + } else { + endElement = rng.endContainer; + endOffset = rng.endOffset; + + if (endElement.nodeType == 1 && endElement.hasChildNodes()) + endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; + + if (endElement && endElement.nodeType == 3) + return endElement.parentNode; + + return endElement; + } + }, + + getBookmark : function(type, normalized) { + var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; + + function findIndex(name, element) { + var index = 0; + + each(dom.select(name), function(node, i) { + if (node == element) + index = i; + }); + + return index; + }; + + function normalizeTableCellSelection(rng) { + function moveEndPoint(start) { + var container, offset, childNodes, prefix = start ? 'start' : 'end'; + + container = rng[prefix + 'Container']; + offset = rng[prefix + 'Offset']; + + if (container.nodeType == 1 && container.nodeName == "TR") { + childNodes = container.childNodes; + container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; + if (container) { + offset = start ? 0 : container.childNodes.length; + rng['set' + (start ? 'Start' : 'End')](container, offset); + } + } + }; + + moveEndPoint(true); + moveEndPoint(); + + return rng; + }; + + function getLocation() { + var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; + + function getPoint(rng, start) { + var container = rng[start ? 'startContainer' : 'endContainer'], + offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; + + if (container.nodeType == 3) { + if (normalized) { + for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) + offset += node.nodeValue.length; + } + + point.push(offset); + } else { + childNodes = container.childNodes; + + if (offset >= childNodes.length && childNodes.length) { + after = 1; + offset = Math.max(0, childNodes.length - 1); + } + + point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + } + + for (; container && container != root; container = container.parentNode) + point.push(t.dom.nodeIndex(container, normalized)); + + return point; + }; + + bookmark.start = getPoint(rng, true); + + if (!t.isCollapsed()) + bookmark.end = getPoint(rng); + + return bookmark; + }; + + if (type == 2) { + if (t.tridentSel) + return t.tridentSel.getBookmark(type); + + return getLocation(); + } + + // Handle simple range + if (type) { + rng = t.getRng(); + + if (rng.setStart) { + rng = { + startContainer: rng.startContainer, + startOffset: rng.startOffset, + endContainer: rng.endContainer, + endOffset: rng.endOffset + }; + } + + return {rng : rng}; + } + + rng = t.getRng(); + id = dom.uniqueId(); + collapsed = tinyMCE.activeEditor.selection.isCollapsed(); + styles = 'overflow:hidden;line-height:0px'; + + // Explorer method + if (rng.duplicate || rng.item) { + // Text selection + if (!rng.item) { + rng2 = rng.duplicate(); + + try { + // Insert start marker + rng.collapse(); + rng.pasteHTML('' + chr + ''); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + + // Detect the empty space after block elements in IE and move the end back one character

    ] becomes

    ]

    + rng.moveToElementText(rng2.parentElement()); + if (rng.compareEndPoints('StartToEnd', rng2) === 0) + rng2.move('character', -1); + + rng2.pasteHTML('' + chr + ''); + } + } catch (ex) { + // IE might throw unspecified error so lets ignore it + return null; + } + } else { + // Control selection + element = rng.item(0); + name = element.nodeName; + + return {name : name, index : findIndex(name, element)}; + } + } else { + element = t.getNode(); + name = element.nodeName; + if (name == 'IMG') + return {name : name, index : findIndex(name, element)}; + + // W3C method + rng2 = normalizeTableCellSelection(rng.cloneRange()); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); + } + + rng = normalizeTableCellSelection(rng); + rng.collapse(true); + rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); + } + + t.moveToBookmark({id : id, keep : 1}); + + return {id : id}; + }, + + moveToBookmark : function(bookmark) { + var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset; + + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; + + if (point) { + offset = point[0]; + + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; + + if (point[i] > children.length - 1) + return; + + node = children[point[i]]; + } + + // Move text offset to best suitable location + if (node.nodeType === 3) + offset = Math.min(point[0], node.nodeValue.length); + + // Move element offset to best suitable location + if (node.nodeType === 1) + offset = Math.min(point[0], node.childNodes.length); + + // Set offset within container node + if (start) + rng.setStart(node, offset); + else + rng.setEnd(node, offset); + } + + return true; + }; + + function restoreEndPoint(suffix) { + var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + + if (marker) { + node = marker.parentNode; + + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + endContainer = node; + endOffset = idx; + } + + if (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; + + // Remove all marker text nodes + each(tinymce.grep(marker.childNodes), function(node) { + if (node.nodeType == 3) + node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); + }); + + // Remove marker but keep children if for example contents where inserted into the marker + // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature + while (marker = dom.get(bookmark.id + '_' + suffix)) + dom.remove(marker, 1); + + // If siblings are text nodes then merge them unless it's Opera since it some how removes the node + // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact + if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { + idx = prev.nodeValue.length; + prev.appendData(next.nodeValue); + dom.remove(next); + + if (suffix == 'start') { + startContainer = endContainer = prev; + startOffset = endOffset = idx; + } else { + endContainer = prev; + endOffset = idx; + } + } + } + } + }; + + function addBogus(node) { + // Adds a bogus BR element for empty block elements + if (dom.isBlock(node) && !node.innerHTML && !isIE) + node.innerHTML = '
    '; + + return node; + }; + + if (bookmark) { + if (bookmark.start) { + rng = dom.createRng(); + root = dom.getRoot(); + + if (t.tridentSel) + return t.tridentSel.moveToBookmark(bookmark); + + if (setEndPoint(true) && setEndPoint()) { + t.setRng(rng); + } + } else if (bookmark.id) { + // Restore start/end points + restoreEndPoint('start'); + restoreEndPoint('end'); + + if (startContainer) { + rng = dom.createRng(); + rng.setStart(addBogus(startContainer), startOffset); + rng.setEnd(addBogus(endContainer), endOffset); + t.setRng(rng); + } + } else if (bookmark.name) { + t.select(dom.select(bookmark.name)[bookmark.index]); + } else if (bookmark.rng) { + rng = bookmark.rng; + + if (rng.startContainer) { + rng2 = t.dom.createRng(); + + try { + rng2.setStart(rng.startContainer, rng.startOffset); + rng2.setEnd(rng.endContainer, rng.endOffset); + } catch (e) { + // Might fail with index error + } + + rng = rng2; + } + + t.setRng(rng); + } + } + }, + + select : function(node, content) { + var t = this, dom = t.dom, rng = dom.createRng(), idx; + + function setPoint(node, start) { + var walker = new TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); + + return; + } + } while (node = (start ? walker.next() : walker.prev())); + }; + + if (node) { + idx = dom.nodeIndex(node); + rng.setStart(node.parentNode, idx); + rng.setEnd(node.parentNode, idx + 1); + + // Find first/last text node or BR element + if (content) { + setPoint(node, 1); + setPoint(node); + } + + t.setRng(rng); + } + + return node; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + if (r.compareEndPoints) + return r.compareEndPoints('StartToEnd', r) === 0; + + return !s || r.collapsed; + }, + + collapse : function(to_start) { + var self = this, rng = self.getRng(), node; + + // Control range on IE + if (rng.item) { + node = rng.item(0); + rng = self.win.document.body.createTextRange(); + rng.moveToElementText(node); + } + + rng.collapse(!!to_start); + self.setRng(rng); + }, + + getSel : function() { + var t = this, w = this.win; + + return w.getSelection ? w.getSelection() : w.document.selection; + }, + + getRng : function(w3c) { + var self = this, selection, rng, elm, doc = self.win.document; + + // Workaround for IE 11 not being able to select images properly see #6613 see quirk fix + if (self.fakeRng) { + return self.fakeRng; + } + + // Found tridentSel object then we need to use that one + if (w3c && self.tridentSel) { + return self.tridentSel.getRangeAt(0); + } + + try { + if (selection = self.getSel()) { + rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); + } + } catch (ex) { + // IE throws unspecified error here if TinyMCE is placed in a frame/iframe + } + + // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet + if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) { + elm = doc.selection.createRange().item(0); + rng = doc.createRange(); + rng.setStartBefore(elm); + rng.setEndAfter(elm); + } + + // No range found then create an empty one + // This can occur when the editor is placed in a hidden container element on Gecko + // Or on IE when there was an exception + if (!rng) { + rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); + } + + // If range is at start of document then move it to start of body + if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { + elm = self.dom.getRoot(); + rng.setStart(elm, 0); + rng.setEnd(elm, 0); + } + + if (self.selectedRange && self.explicitRange) { + if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { + // Safari, Opera and Chrome only ever select text which causes the range to change. + // This lets us use the originally set range if the selection hasn't been changed by the user. + rng = self.explicitRange; + } else { + self.selectedRange = null; + self.explicitRange = null; + } + } + + return rng; + }, + + setRng : function(r, forward) { + var s, t = this; + + if (!t.tridentSel) { + s = t.getSel(); + + if (s) { + t.explicitRange = r; + + try { + s.removeAllRanges(); + } catch (ex) { + // IE9 might throw errors here don't know why + } + + s.addRange(r); + + // Forward is set to false and we have an extend function + if (forward === false && s.extend) { + s.collapse(r.endContainer, r.endOffset); + s.extend(r.startContainer, r.startOffset); + } + + // adding range isn't always successful so we need to check range count otherwise an exception can occur + t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; + } + } else { + // Is W3C Range + if (r.cloneRange) { + try { + t.tridentSel.addRange(r); + return; + } catch (ex) { + //IE9 throws an error here if called before selection is placed in the editor + } + } + + // Is IE specific range + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + } + }, + + setNode : function(n) { + var t = this; + + t.setContent(t.dom.getOuterHTML(n)); + + return n; + }, + + getNode : function() { + var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; + + function skipEmptyTextNodes(n, forwards) { + var orig = n; + while (n && n.nodeType === 3 && n.length === 0) { + n = forwards ? n.nextSibling : n.previousSibling; + } + return n || orig; + }; + + // Range maybe lost after the editor is made visible again + if (!rng) + return t.dom.getRoot(); + + if (rng.setStart) { + elm = rng.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!rng.collapsed) { + if (rng.startContainer == rng.endContainer) { + if (rng.endOffset - rng.startOffset < 2) { + if (rng.startContainer.hasChildNodes()) + elm = rng.startContainer.childNodes[rng.startOffset]; + } + } + + // If the anchor node is a element instead of a text node then return this element + //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) + // return sel.anchorNode.childNodes[sel.anchorOffset]; + + // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. + // This happens when you double click an underlined word in FireFox. + if (start.nodeType === 3 && end.nodeType === 3) { + if (start.length === rng.startOffset) { + start = skipEmptyTextNodes(start.nextSibling, true); + } else { + start = start.parentNode; + } + if (rng.endOffset === 0) { + end = skipEmptyTextNodes(end.previousSibling, false); + } else { + end = end.parentNode; + } + + if (start && start === end) + return start; + } + } + + if (elm && elm.nodeType == 3) + return elm.parentNode; + + return elm; + } + + return rng.item ? rng.item(0) : rng.parentElement(); + }, + + getSelectedBlocks : function(st, en) { + var t = this, dom = t.dom, sb, eb, n, bl = []; + + sb = dom.getParent(st || t.getStart(), dom.isBlock); + eb = dom.getParent(en || t.getEnd(), dom.isBlock); + + if (sb) + bl.push(sb); + + if (sb && eb && sb != eb) { + n = sb; + + var walker = new TreeWalker(sb, dom.getRoot()); + while ((n = walker.next()) && n != eb) { + if (dom.isBlock(n)) + bl.push(n); + } + } + + if (eb && sb != eb) + bl.push(eb); + + return bl; + }, + + isForward: function(){ + var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; + + // No support for selection direction then always return true + if (!sel || sel.anchorNode == null || sel.focusNode == null) { + return true; + } + + anchorRange = dom.createRng(); + anchorRange.setStart(sel.anchorNode, sel.anchorOffset); + anchorRange.collapse(true); + + focusRange = dom.createRng(); + focusRange.setStart(sel.focusNode, sel.focusOffset); + focusRange.collapse(true); + + return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; + }, + + normalize : function() { + var self = this, rng, normalized, collapsed, node, sibling; + + function normalizeEndPoint(start) { + var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; + + function hasBrBeforeAfter(node, left) { + var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); + + while (node = walker[left ? 'prev' : 'next']()) { + if (node.nodeName === "BR") { + return true; + } + } + }; + + // Walks the dom left/right to find a suitable text node to move the endpoint into + // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG + function findTextNodeRelative(left, startNode) { + var walker, lastInlineElement; + + startNode = startNode || container; + walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); + + // Walk left until we hit a text node we can move to or a block/br/img + while (node = walker[left ? 'prev' : 'next']()) { + // Found text node that has a length + if (node.nodeType === 3 && node.nodeValue.length > 0) { + container = node; + offset = left ? node.nodeValue.length : 0; + normalized = true; + return; + } + + // Break if we find a block or a BR/IMG/INPUT etc + if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { + return; + } + + lastInlineElement = node; + } + + // Only fetch the last inline element when in caret mode for now + if (collapsed && lastInlineElement) { + container = lastInlineElement; + normalized = true; + offset = 0; + } + }; + + container = rng[(start ? 'start' : 'end') + 'Container']; + offset = rng[(start ? 'start' : 'end') + 'Offset']; + nonEmptyElementsMap = dom.schema.getNonEmptyElements(); + + // If the container is a document move it to the body element + if (container.nodeType === 9) { + container = dom.getRoot(); + offset = 0; + } + + // If the container is body try move it into the closest text node or position + if (container === body) { + // If start is before/after a image, table etc + if (start) { + node = container.childNodes[offset > 0 ? offset - 1 : 0]; + if (node) { + nodeName = node.nodeName.toLowerCase(); + if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { + return; + } + } + } + + // Resolve the index + if (container.hasChildNodes()) { + container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; + offset = 0; + + // Don't walk into elements that doesn't have any child nodes like a IMG + if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { + // Walk the DOM to find a text node to place the caret at or a BR + node = container; + walker = new TreeWalker(container, body); + + do { + // Found a text node use that position + if (node.nodeType === 3 && node.nodeValue.length > 0) { + offset = start ? 0 : node.nodeValue.length; + container = node; + normalized = true; + break; + } + + // Found a BR/IMG element that we can place the caret before + if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { + offset = dom.nodeIndex(node); + container = node.parentNode; + + // Put caret after image when moving the end point + if (node.nodeName == "IMG" && !start) { + offset++; + } + + normalized = true; + break; + } + } while (node = (start ? walker.next() : walker.prev())); + } + } + } + + // Lean the caret to the left if possible + if (collapsed) { + // So this: x|x + // Becomes: x|x + // Seems that only gecko has issues with this + if (container.nodeType === 3 && offset === 0) { + findTextNodeRelative(true); + } + + // Lean left into empty inline elements when the caret is before a BR + // So this: |
    + // Becomes: |
    + // Seems that only gecko has issues with this + if (container.nodeType === 1) { + node = container.childNodes[offset]; + if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { + findTextNodeRelative(true, container.childNodes[offset]); + } + } + } + + // Lean the start of the selection right if possible + // So this: x[x] + // Becomes: x[x] + if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { + findTextNodeRelative(false); + } + + // Set endpoint if it was normalized + if (normalized) + rng['set' + (start ? 'Start' : 'End')](container, offset); + }; + + // Normalize only on non IE browsers for now + if (tinymce.isIE) + return; + + rng = self.getRng(); + collapsed = rng.collapsed; + + // Normalize the end points + normalizeEndPoint(true); + + if (!collapsed) + normalizeEndPoint(); + + // Set the selection if it was normalized + if (normalized) { + // If it was collapsed then make sure it still is + if (collapsed) { + rng.collapse(true); + } + + //console.log(self.dom.dumpRng(rng)); + self.setRng(rng, self.isForward()); + } + }, + + selectorChanged: function(selector, callback) { + var self = this, currentSelectors; + + if (!self.selectorChangedData) { + self.selectorChangedData = {}; + currentSelectors = {}; + + self.editor.onNodeChange.addToTop(function(ed, cm, node) { + var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; + + // Check for new matching selectors + each(self.selectorChangedData, function(callbacks, selector) { + each(parents, function(node) { + if (dom.is(node, selector)) { + if (!currentSelectors[selector]) { + // Execute callbacks + each(callbacks, function(callback) { + callback(true, {node: node, selector: selector, parents: parents}); + }); + + currentSelectors[selector] = callbacks; + } + + matchedSelectors[selector] = callbacks; + return false; + } + }); + }); + + // Check if current selectors still match + each(currentSelectors, function(callbacks, selector) { + if (!matchedSelectors[selector]) { + delete currentSelectors[selector]; + + each(callbacks, function(callback) { + callback(false, {node: node, selector: selector, parents: parents}); + }); + } + }); + }); + } + + // Add selector listeners + if (!self.selectorChangedData[selector]) { + self.selectorChangedData[selector] = []; + } + + self.selectorChangedData[selector].push(callback); + + return self; + }, + + scrollIntoView: function(elm) { + var y, viewPort, self = this, dom = self.dom; + + viewPort = dom.getViewPort(self.editor.getWin()); + y = dom.getPos(elm).y; + if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { + self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); + } + }, + + destroy : function(manual) { + var self = this; + + self.win = null; + + // Manual destroy then remove unload handler + if (!manual) + tinymce.removeUnload(self.destroy); + }, + + // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode + _fixIESelection : function() { + var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; + + // Return range from point or null if it failed + function rngFromPoint(x, y) { + var rng = body.createTextRange(); + + try { + rng.moveToPoint(x, y); + } catch (ex) { + // IE sometimes throws and exception, so lets just ignore it + rng = null; + } + + return rng; + }; + + // Fires while the selection is changing + function selectionChange(e) { + var pointRng; + + // Check if the button is down or not + if (e.button) { + // Create range from mouse position + pointRng = rngFromPoint(e.x, e.y); + + if (pointRng) { + // Check if pointRange is before/after selection then change the endPoint + if (pointRng.compareEndPoints('StartToStart', startRng) > 0) + pointRng.setEndPoint('StartToStart', startRng); + else + pointRng.setEndPoint('EndToEnd', startRng); + + pointRng.select(); + } + } else + endSelection(); + } + + // Removes listeners + function endSelection() { + var rng = doc.selection.createRange(); + + // If the range is collapsed then use the last start range + if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) + startRng.select(); + + dom.unbind(doc, 'mouseup', endSelection); + dom.unbind(doc, 'mousemove', selectionChange); + startRng = started = 0; + }; + + // Make HTML element unselectable since we are going to handle selection by hand + doc.documentElement.unselectable = true; + + // Detect when user selects outside BODY + dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { + if (e.target.nodeName === 'HTML') { + if (started) + endSelection(); + + // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML + htmlElm = doc.documentElement; + if (htmlElm.scrollHeight > htmlElm.clientHeight) + return; + + started = 1; + // Setup start position + startRng = rngFromPoint(e.x, e.y); + if (startRng) { + // Listen for selection change events + dom.bind(doc, 'mouseup', endSelection); + dom.bind(doc, 'mousemove', selectionChange); + + dom.win.focus(); + startRng.select(); + } + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + tinymce.dom.Serializer = function(settings, dom, schema) { + var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; + + // Support the old apply_source_formatting option + if (!settings.apply_source_formatting) + settings.indent = false; + + // Default DOM and Schema if they are undefined + dom = dom || tinymce.DOM; + schema = schema || new tinymce.html.Schema(settings); + settings.entity_encoding = settings.entity_encoding || 'named'; + settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; + + onPreProcess = new tinymce.util.Dispatcher(self); + + onPostProcess = new tinymce.util.Dispatcher(self); + + htmlParser = new tinymce.html.DomParser(settings, schema); + + // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed + htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; + + while (i--) { + node = nodes[i]; + + value = node.attributes.map[internalName]; + if (value !== undef) { + // Set external name to internal value and remove internal + node.attr(name, value.length > 0 ? value : null); + node.attr(internalName, null); + } else { + // No internal attribute found then convert the value we have in the DOM + value = node.attributes.map[name]; + + if (name === "style") + value = dom.serializeStyle(dom.parseStyle(value), node.name); + else if (urlConverter) + value = urlConverter.call(urlConverterScope, value, name, node.name); + + node.attr(name, value.length > 0 ? value : null); + } + } + }); + + // Remove internal classes mceItem<..> or mceSelected + htmlParser.addAttributeFilter('class', function(nodes, name) { + var i = nodes.length, node, value; + + while (i--) { + node = nodes[i]; + value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); + node.attr('class', value.length > 0 ? value : null); + } + }); + + // Remove bookmark elements + htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) + node.remove(); + } + }); + + // Remove expando attributes + htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { + var i = nodes.length; + + while (i--) { + nodes[i].attr(name, null); + } + }); + + htmlParser.addNodeFilter('noscript', function(nodes) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i].firstChild; + + if (node) { + node.value = tinymce.html.Entities.decode(node.value); + } + } + }); + + // Force script into CDATA sections and remove the mce- prefix also add comments around styles + htmlParser.addNodeFilter('script,style', function(nodes, name) { + var i = nodes.length, node, value; + + function trim(value) { + return value.replace(/()/g, '\n') + .replace(/^[\r\n]*|[\r\n]*$/g, '') + .replace(/^\s*(()?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); + }; + + while (i--) { + node = nodes[i]; + value = node.firstChild ? node.firstChild.value : ''; + + if (name === "script") { + // Remove mce- prefix from script elements + node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); + + if (value.length > 0) + node.firstChild.value = '// '; + } else { + if (value.length > 0) + node.firstChild.value = ''; + } + } + }); + + // Convert comments to cdata and handle protected comments + htmlParser.addNodeFilter('#comment', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.value.indexOf('[CDATA[') === 0) { + node.name = '#cdata'; + node.type = 4; + node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); + } else if (node.value.indexOf('mce:protected ') === 0) { + node.name = "#text"; + node.type = 3; + node.raw = true; + node.value = unescape(node.value).substr(14); + } + } + }); + + htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + if (node.type === 7) + node.remove(); + else if (node.type === 1) { + if (name === "input" && !("type" in node.attributes.map)) + node.attr('type', 'text'); + } + } + }); + + // Fix list elements, TODO: Replace this later + if (settings.fix_list_elements) { + htmlParser.addNodeFilter('ul,ol', function(nodes, name) { + var i = nodes.length, node, parentNode; + + while (i--) { + node = nodes[i]; + parentNode = node.parent; + + if (parentNode.name === 'ul' || parentNode.name === 'ol') { + if (node.prev && node.prev.name === 'li') { + node.prev.append(node); + } + } + } + }); + } + + // Remove internal data attributes + htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { + var i = nodes.length; + + while (i--) { + nodes[i].attr(name, null); + } + }); + + // Return public methods + return { + schema : schema, + + addNodeFilter : htmlParser.addNodeFilter, + + addAttributeFilter : htmlParser.addAttributeFilter, + + onPreProcess : onPreProcess, + + onPostProcess : onPostProcess, + + serialize : function(node, args) { + var impl, doc, oldDoc, htmlSerializer, content; + + // Explorer won't clone contents of script and style and the + // selected index of select elements are cleared on a clone operation. + if (isIE && dom.select('script,style,select,map').length > 0) { + content = node.innerHTML; + node = node.cloneNode(false); + dom.setHTML(node, content); + } else + node = node.cloneNode(true); + + // Nodes needs to be attached to something in WebKit/Opera + // Older builds of Opera crashes if you attach the node to an document created dynamically + // and since we can't feature detect a crash we need to sniff the acutal build number + // This fix will make DOM ranges and make Sizzle happy! + impl = node.ownerDocument.implementation; + if (impl.createHTMLDocument) { + // Create an empty HTML document + doc = impl.createHTMLDocument(""); + + // Add the element or it's children if it's a body element to the new document + each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { + doc.body.appendChild(doc.importNode(node, true)); + }); + + // Grab first child or body element for serialization + if (node.nodeName != 'BODY') + node = doc.body.firstChild; + else + node = doc.body; + + // set the new document in DOMUtils so createElement etc works + oldDoc = dom.doc; + dom.doc = doc; + } + + args = args || {}; + args.format = args.format || 'html'; + + // Pre process + if (!args.no_events) { + args.node = node; + onPreProcess.dispatch(self, args); + } + + // Setup serializer + htmlSerializer = new tinymce.html.Serializer(settings, schema); + + // Parse and serialize HTML + args.content = htmlSerializer.serialize( + htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) + ); + + // Replace all BOM characters for now until we can find a better solution + if (!args.cleanup) + args.content = args.content.replace(/\uFEFF/g, ''); + + // Post process + if (!args.no_events) + onPostProcess.dispatch(self, args); + + // Restore the old document if it was changed + if (oldDoc) + dom.doc = oldDoc; + + args.node = null; + + return args.content; + }, + + addRules : function(rules) { + schema.addValidElements(rules); + }, + + setRules : function(rules) { + schema.setValidElements(rules); + } + }; + }; +})(tinymce); +(function(tinymce) { + tinymce.dom.ScriptLoader = function(settings) { + var QUEUED = 0, + LOADING = 1, + LOADED = 2, + states = {}, + queue = [], + scriptLoadedCallbacks = {}, + queueLoadedCallbacks = [], + loading = 0, + undef; + + function loadScript(url, callback) { + var t = this, dom = tinymce.DOM, elm, uri, loc, id; + + // Execute callback when script is loaded + function done() { + dom.remove(id); + + if (elm) + elm.onreadystatechange = elm.onload = elm = null; + + callback(); + }; + + function error() { + // Report the error so it's easier for people to spot loading errors + if (typeof(console) !== "undefined" && console.log) + console.log("Failed to load: " + url); + + // We can't mark it as done if there is a load error since + // A) We don't want to produce 404 errors on the server and + // B) the onerror event won't fire on all browsers. + // done(); + }; + + id = dom.uniqueId(); + + if (tinymce.isIE6) { + uri = new tinymce.util.URI(url); + loc = location; + + // If script is from same domain and we + // use IE 6 then use XHR since it's more reliable + if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { + tinymce.util.XHR.send({ + url : tinymce._addVer(uri.getURI()), + success : function(content) { + // Create new temp script element + var script = dom.create('script', { + type : 'text/javascript' + }); + + // Evaluate script in global scope + script.text = content; + document.getElementsByTagName('head')[0].appendChild(script); + dom.remove(script); + + done(); + }, + + error : error + }); + + return; + } + } + + // Create new script element + elm = document.createElement('script'); + elm.id = id; + elm.type = 'text/javascript'; + elm.src = tinymce._addVer(url); + + // Add onload listener for non IE browsers since IE9 + // fires onload event before the script is parsed and executed + if (!tinymce.isIE || tinymce.isIE11) + elm.onload = done; + + // Add onerror event will get fired on some browsers but not all of them + elm.onerror = error; + + // Opera 9.60 doesn't seem to fire the onreadystate event at correctly + if (!tinymce.isOpera) { + elm.onreadystatechange = function() { + var state = elm.readyState; + + // Loaded state is passed on IE 6 however there + // are known issues with this method but we can't use + // XHR in a cross domain loading + if (state == 'complete' || state == 'loaded') + done(); + }; + } + + // Most browsers support this feature so we report errors + // for those at least to help users track their missing plugins etc + // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option + /*elm.onerror = function() { + alert('Failed to load: ' + url); + };*/ + + // Add script to document + (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); + }; + + this.isDone = function(url) { + return states[url] == LOADED; + }; + + this.markDone = function(url) { + states[url] = LOADED; + }; + + this.add = this.load = function(url, callback, scope) { + var item, state = states[url]; + + // Add url to load queue + if (state == undef) { + queue.push(url); + states[url] = QUEUED; + } + + if (callback) { + // Store away callback for later execution + if (!scriptLoadedCallbacks[url]) + scriptLoadedCallbacks[url] = []; + + scriptLoadedCallbacks[url].push({ + func : callback, + scope : scope || this + }); + } + }; + + this.loadQueue = function(callback, scope) { + this.loadScripts(queue, callback, scope); + }; + + this.loadScripts = function(scripts, callback, scope) { + var loadScripts; + + function execScriptLoadedCallbacks(url) { + // Execute URL callback functions + tinymce.each(scriptLoadedCallbacks[url], function(callback) { + callback.func.call(callback.scope); + }); + + scriptLoadedCallbacks[url] = undef; + }; + + queueLoadedCallbacks.push({ + func : callback, + scope : scope || this + }); + + loadScripts = function() { + var loadingScripts = tinymce.grep(scripts); + + // Current scripts has been handled + scripts.length = 0; + + // Load scripts that needs to be loaded + tinymce.each(loadingScripts, function(url) { + // Script is already loaded then execute script callbacks directly + if (states[url] == LOADED) { + execScriptLoadedCallbacks(url); + return; + } + + // Is script not loading then start loading it + if (states[url] != LOADING) { + states[url] = LOADING; + loading++; + + loadScript(url, function() { + states[url] = LOADED; + loading--; + + execScriptLoadedCallbacks(url); + + // Load more scripts if they where added by the recently loaded script + loadScripts(); + }); + } + }); + + // No scripts are currently loading then execute all pending queue loaded callbacks + if (!loading) { + tinymce.each(queueLoadedCallbacks, function(callback) { + callback.func.call(callback.scope); + }); + + queueLoadedCallbacks.length = 0; + } + }; + + loadScripts(); + }; + }; + + // Global script loader + tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); +})(tinymce); + +(function(tinymce) { + tinymce.dom.RangeUtils = function(dom) { + var INVISIBLE_CHAR = '\uFEFF'; + + this.walk = function(rng, callback) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, + ancestor, startPoint, + endPoint, node, parent, siblings, nodes; + + // Handle table cell selection the table plugin enables + // you to fake select table cells and perform formatting actions on them + nodes = dom.select('td.mceSelected,th.mceSelected'); + if (nodes.length > 0) { + tinymce.each(nodes, function(node) { + callback([node]); + }); + + return; + } + + function exclude(nodes) { + var node; + + // First node is excluded + node = nodes[0]; + if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { + nodes.splice(0, 1); + } + + // Last node is excluded + node = nodes[nodes.length - 1]; + if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { + nodes.splice(nodes.length - 1, 1); + } + + return nodes; + }; + + function collectSiblings(node, name, end_node) { + var siblings = []; + + for (; node && node != end_node; node = node[name]) + siblings.push(node); + + return siblings; + }; + + function findEndPoint(node, root) { + do { + if (node.parentNode == root) + return node; + + node = node.parentNode; + } while(node); + }; + + function walkBoundary(start_node, end_node, next) { + var siblingName = next ? 'nextSibling' : 'previousSibling'; + + for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { + parent = node.parentNode; + siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); + + if (siblings.length) { + if (!next) + siblings.reverse(); + + callback(exclude(siblings)); + } + } + }; + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) + startContainer = startContainer.childNodes[startOffset]; + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) + endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; + + // Same container + if (startContainer == endContainer) + return callback(exclude([startContainer])); + + // Find common ancestor and end points + ancestor = dom.findCommonAncestor(startContainer, endContainer); + + // Process left side + for (node = startContainer; node; node = node.parentNode) { + if (node === endContainer) + return walkBoundary(startContainer, ancestor, true); + + if (node === ancestor) + break; + } + + // Process right side + for (node = endContainer; node; node = node.parentNode) { + if (node === startContainer) + return walkBoundary(endContainer, ancestor); + + if (node === ancestor) + break; + } + + // Find start/end point + startPoint = findEndPoint(startContainer, ancestor) || startContainer; + endPoint = findEndPoint(endContainer, ancestor) || endContainer; + + // Walk left leaf + walkBoundary(startContainer, startPoint, true); + + // Walk the middle from start to end point + siblings = collectSiblings( + startPoint == startContainer ? startPoint : startPoint.nextSibling, + 'nextSibling', + endPoint == endContainer ? endPoint.nextSibling : endPoint + ); + + if (siblings.length) + callback(exclude(siblings)); + + // Walk right leaf + walkBoundary(endContainer, endPoint); + }; + + this.split = function(rng) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset; + + function splitText(node, offset) { + return node.splitText(offset); + }; + + // Handle single text node + if (startContainer == endContainer && startContainer.nodeType == 3) { + if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { + endContainer = splitText(startContainer, startOffset); + startContainer = endContainer.previousSibling; + + if (endOffset > startOffset) { + endOffset = endOffset - startOffset; + startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + startOffset = 0; + } else { + endOffset = 0; + } + } + } else { + // Split startContainer text node if needed + if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { + startContainer = splitText(startContainer, startOffset); + startOffset = 0; + } + + // Split endContainer text node if needed + if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { + endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + } + } + + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + }; + + }; + + tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { + if (rng1 && rng2) { + // Compare native IE ranges + if (rng1.item || rng1.duplicate) { + // Both are control ranges and the selected element matches + if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) + return true; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return true; + } else { + // Compare w3c ranges + return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; + } + } + + return false; + }; +})(tinymce); + +(function(tinymce) { + var Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.KeyboardNavigation', { + KeyboardNavigation: function(settings, dom) { + var t = this, root = settings.root, items = settings.items, + enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, + excludeFromTabOrder = settings.excludeFromTabOrder, + itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; + + dom = dom || tinymce.DOM; + + itemFocussed = function(evt) { + focussedId = evt.target.id; + }; + + itemBlurred = function(evt) { + dom.setAttrib(evt.target.id, 'tabindex', '-1'); + }; + + rootFocussed = function(evt) { + var item = dom.get(focussedId); + dom.setAttrib(item, 'tabindex', '0'); + item.focus(); + }; + + t.focus = function() { + dom.get(focussedId).focus(); + }; + + t.destroy = function() { + each(items, function(item) { + var elm = dom.get(item.id); + + dom.unbind(elm, 'focus', itemFocussed); + dom.unbind(elm, 'blur', itemBlurred); + }); + + var rootElm = dom.get(root); + dom.unbind(rootElm, 'focus', rootFocussed); + dom.unbind(rootElm, 'keydown', rootKeydown); + + items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; + t.destroy = function() {}; + }; + + t.moveFocus = function(dir, evt) { + var idx = -1, controls = t.controls, newFocus; + + if (!focussedId) + return; + + each(items, function(item, index) { + if (item.id === focussedId) { + idx = index; + return false; + } + }); + + idx += dir; + if (idx < 0) { + idx = items.length - 1; + } else if (idx >= items.length) { + idx = 0; + } + + newFocus = items[idx]; + dom.setAttrib(focussedId, 'tabindex', '-1'); + dom.setAttrib(newFocus.id, 'tabindex', '0'); + dom.get(newFocus.id).focus(); + + if (settings.actOnFocus) { + settings.onAction(newFocus.id); + } + + if (evt) + Event.cancel(evt); + }; + + rootKeydown = function(evt) { + var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; + + switch (evt.keyCode) { + case DOM_VK_LEFT: + if (enableLeftRight) t.moveFocus(-1); + Event.cancel(evt); + break; + + case DOM_VK_RIGHT: + if (enableLeftRight) t.moveFocus(1); + Event.cancel(evt); + break; + + case DOM_VK_UP: + if (enableUpDown) t.moveFocus(-1); + Event.cancel(evt); + break; + + case DOM_VK_DOWN: + if (enableUpDown) t.moveFocus(1); + Event.cancel(evt); + break; + + case DOM_VK_ESCAPE: + if (settings.onCancel) { + settings.onCancel(); + Event.cancel(evt); + } + break; + + case DOM_VK_ENTER: + case DOM_VK_RETURN: + case DOM_VK_SPACE: + if (settings.onAction) { + settings.onAction(focussedId); + Event.cancel(evt); + } + break; + } + }; + + // Set up state and listeners for each item. + each(items, function(item, idx) { + var tabindex, elm; + + if (!item.id) { + item.id = dom.uniqueId('_mce_item_'); + } + + elm = dom.get(item.id); + + if (excludeFromTabOrder) { + dom.bind(elm, 'blur', itemBlurred); + tabindex = '-1'; + } else { + tabindex = (idx === 0 ? '0' : '-1'); + } + + elm.setAttribute('tabindex', tabindex); + dom.bind(elm, 'focus', itemFocussed); + }); + + // Setup initial state for root element. + if (items[0]){ + focussedId = items[0].id; + } + + dom.setAttrib(root, 'tabindex', '-1'); + + // Setup listeners for root element. + var rootElm = dom.get(root); + dom.bind(rootElm, 'focus', rootFocussed); + dom.bind(rootElm, 'keydown', rootKeydown); + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten class names + var DOM = tinymce.DOM, is = tinymce.is; + + tinymce.create('tinymce.ui.Control', { + Control : function(id, s, editor) { + this.id = id; + this.settings = s = s || {}; + this.rendered = false; + this.onRender = new tinymce.util.Dispatcher(this); + this.classPrefix = ''; + this.scope = s.scope || this; + this.disabled = 0; + this.active = 0; + this.editor = editor; + }, + + setAriaProperty : function(property, value) { + var element = DOM.get(this.id + '_aria') || DOM.get(this.id); + if (element) { + DOM.setAttrib(element, 'aria-' + property, !!value); + } + }, + + focus : function() { + DOM.get(this.id).focus(); + }, + + setDisabled : function(s) { + if (s != this.disabled) { + this.setAriaProperty('disabled', s); + + this.setState('Disabled', s); + this.setState('Enabled', !s); + this.disabled = s; + } + }, + + isDisabled : function() { + return this.disabled; + }, + + setActive : function(s) { + if (s != this.active) { + this.setState('Active', s); + this.active = s; + this.setAriaProperty('pressed', s); + } + }, + + isActive : function() { + return this.active; + }, + + setState : function(c, s) { + var n = DOM.get(this.id); + + c = this.classPrefix + c; + + if (s) + DOM.addClass(n, c); + else + DOM.removeClass(n, c); + }, + + isRendered : function() { + return this.rendered; + }, + + renderHTML : function() { + }, + + renderTo : function(n) { + DOM.setHTML(n, this.renderHTML()); + }, + + postRender : function() { + var t = this, b; + + // Set pending states + if (is(t.disabled)) { + b = t.disabled; + t.disabled = -1; + t.setDisabled(b); + } + + if (is(t.active)) { + b = t.active; + t.active = -1; + t.setActive(b); + } + }, + + remove : function() { + DOM.remove(this.id); + this.destroy(); + }, + + destroy : function() { + tinymce.dom.Event.clear(this.id); + } + }); +})(tinymce); +tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { + Container : function(id, s, editor) { + this.parent(id, s, editor); + + this.controls = []; + + this.lookup = {}; + }, + + add : function(c) { + this.lookup[c.id] = c; + this.controls.push(c); + + return c; + }, + + get : function(n) { + return this.lookup[n]; + } +}); + + +tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { + Separator : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceSeparator'; + this.setDisabled(true); + }, + + renderHTML : function() { + return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); + } +}); + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { + MenuItem : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceMenuItem'; + }, + + setSelected : function(s) { + this.setState('Selected', s); + this.setAriaProperty('checked', !!s); + this.selected = s; + }, + + isSelected : function() { + return this.selected; + }, + + postRender : function() { + var t = this; + + t.parent(); + + // Set pending state + if (is(t.selected)) + t.setSelected(t.selected); + } + }); +})(tinymce); + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { + Menu : function(id, s) { + var t = this; + + t.parent(id, s); + t.items = {}; + t.collapsed = false; + t.menuCount = 0; + t.onAddItem = new tinymce.util.Dispatcher(this); + }, + + expand : function(d) { + var t = this; + + if (d) { + walk(t, function(o) { + if (o.expand) + o.expand(); + }, 'items', t); + } + + t.collapsed = false; + }, + + collapse : function(d) { + var t = this; + + if (d) { + walk(t, function(o) { + if (o.collapse) + o.collapse(); + }, 'items', t); + } + + t.collapsed = true; + }, + + isCollapsed : function() { + return this.collapsed; + }, + + add : function(o) { + if (!o.settings) + o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); + + this.onAddItem.dispatch(this, o); + + return this.items[o.id] = o; + }, + + addSeparator : function() { + return this.add({separator : true}); + }, + + addMenu : function(o) { + if (!o.collapse) + o = this.createMenu(o); + + this.menuCount++; + + return this.add(o); + }, + + hasMenus : function() { + return this.menuCount !== 0; + }, + + remove : function(o) { + delete this.items[o.id]; + }, + + removeAll : function() { + var t = this; + + walk(t, function(o) { + if (o.removeAll) + o.removeAll(); + else + o.remove(); + + o.destroy(); + }, 'items', t); + + t.items = {}; + }, + + createMenu : function(o) { + var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); + + m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); + + return m; + } + }); +})(tinymce); +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; + + tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { + DropMenu : function(id, s) { + s = s || {}; + s.container = s.container || DOM.doc.body; + s.offset_x = s.offset_x || 0; + s.offset_y = s.offset_y || 0; + s.vp_offset_x = s.vp_offset_x || 0; + s.vp_offset_y = s.vp_offset_y || 0; + + if (is(s.icons) && !s.icons) + s['class'] += ' mceNoIcons'; + + this.parent(id, s); + this.onShowMenu = new tinymce.util.Dispatcher(this); + this.onHideMenu = new tinymce.util.Dispatcher(this); + this.classPrefix = 'mceMenu'; + }, + + createMenu : function(s) { + var t = this, cs = t.settings, m; + + s.container = s.container || cs.container; + s.parent = t; + s.constrain = s.constrain || cs.constrain; + s['class'] = s['class'] || cs['class']; + s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; + s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; + s.keyboard_focus = cs.keyboard_focus; + m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); + + m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); + + return m; + }, + + focus : function() { + var t = this; + if (t.keyboardNav) { + t.keyboardNav.focus(); + } + }, + + update : function() { + var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; + + tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; + th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; + + if (!DOM.boxModel) + t.element.setStyles({width : tw + 2, height : th + 2}); + else + t.element.setStyles({width : tw, height : th}); + + if (s.max_width) + DOM.setStyle(co, 'width', tw); + + if (s.max_height) { + DOM.setStyle(co, 'height', th); + + if (tb.clientHeight < s.max_height) + DOM.setStyle(co, 'overflow', 'hidden'); + } + }, + + showMenu : function(x, y, px) { + var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; + + t.collapse(1); + + if (t.isMenuVisible) + return; + + if (!t.rendered) { + co = DOM.add(t.settings.container, t.renderNode()); + + each(t.items, function(o) { + o.postRender(); + }); + + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + } else + co = DOM.get('menu_' + t.id); + + // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug + if (!tinymce.isOpera) + DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); + + DOM.show(co); + t.update(); + + x += s.offset_x || 0; + y += s.offset_y || 0; + vp.w -= 4; + vp.h -= 4; + + // Move inside viewport if not submenu + if (s.constrain) { + w = co.clientWidth - ot; + h = co.clientHeight - ot; + mx = vp.x + vp.w; + my = vp.y + vp.h; + + if ((x + s.vp_offset_x + w) > mx) + x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); + + if ((y + s.vp_offset_y + h) > my) + y = Math.max(0, (my - s.vp_offset_y) - h); + } + + DOM.setStyles(co, {left : x , top : y}); + t.element.update(); + + t.isMenuVisible = 1; + t.mouseClickFunc = Event.add(co, 'click', function(e) { + var m; + + e = e.target; + + if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { + m = t.items[e.id]; + + if (m.isDisabled()) + return; + + dm = t; + + while (dm) { + if (dm.hideMenu) + dm.hideMenu(); + + dm = dm.settings.parent; + } + + if (m.settings.onclick) + m.settings.onclick(e); + + return false; // Cancel to fix onbeforeunload problem + } + }); + + if (t.hasMenus()) { + t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { + var m, r, mi; + + e = e.target; + if (e && (e = DOM.getParent(e, 'tr'))) { + m = t.items[e.id]; + + if (t.lastMenu) + t.lastMenu.collapse(1); + + if (m.isDisabled()) + return; + + if (e && DOM.hasClass(e, cp + 'ItemSub')) { + //p = DOM.getPos(s.container); + r = DOM.getRect(e); + m.showMenu((r.x + r.w - ot), r.y - ot, r.x); + t.lastMenu = m; + DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); + } + } + }); + } + + Event.add(co, 'keydown', t._keyHandler, t); + + t.onShowMenu.dispatch(t); + + if (s.keyboard_focus) { + t._setupKeyboardNav(); + } + }, + + hideMenu : function(c) { + var t = this, co = DOM.get('menu_' + t.id), e; + + if (!t.isMenuVisible) + return; + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + DOM.hide(co); + t.isMenuVisible = 0; + + if (!c) + t.collapse(1); + + if (t.element) + t.element.hide(); + + if (e = DOM.get(t.id)) + DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); + + t.onHideMenu.dispatch(t); + }, + + add : function(o) { + var t = this, co; + + o = t.parent(o); + + if (t.isRendered && (co = DOM.get('menu_' + t.id))) + t._add(DOM.select('tbody', co)[0], o); + + return o; + }, + + collapse : function(d) { + this.parent(d); + this.hideMenu(1); + }, + + remove : function(o) { + DOM.remove(o.id); + this.destroy(); + + return this.parent(o); + }, + + destroy : function() { + var t = this, co = DOM.get('menu_' + t.id); + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + + if (t.element) + t.element.remove(); + + DOM.remove(co); + }, + + renderNode : function() { + var t = this, s = t.settings, n, tb, co, w; + + w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); + if (t.settings.parent) { + DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); + } + co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + + if (s.menu_line) + DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); + +// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); + n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); + tb = DOM.add(n, 'tbody'); + + each(t.items, function(o) { + t._add(tb, o); + }); + + t.rendered = true; + + return w; + }, + + // Internal functions + _setupKeyboardNav : function(){ + var contextMenu, menuItems, t=this; + contextMenu = DOM.get('menu_' + t.id); + menuItems = DOM.select('a[role=option]', 'menu_' + t.id); + menuItems.splice(0,0,contextMenu); + t.keyboardNav = new tinymce.ui.KeyboardNavigation({ + root: 'menu_' + t.id, + items: menuItems, + onCancel: function() { + t.hideMenu(); + }, + enableUpDown: true + }); + contextMenu.focus(); + }, + + _keyHandler : function(evt) { + var t = this, e; + switch (evt.keyCode) { + case 37: // Left + if (t.settings.parent) { + t.hideMenu(); + t.settings.parent.focus(); + Event.cancel(evt); + } + break; + case 39: // Right + if (t.mouseOverFunc) + t.mouseOverFunc(evt); + break; + } + }, + + _add : function(tb, o) { + var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; + + if (s.separator) { + ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); + DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); + + if (n = ro.previousSibling) + DOM.addClass(n, 'mceLast'); + + return; + } + + n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); + n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); + n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); + + if (s.parent) { + DOM.setAttrib(a, 'aria-haspopup', 'true'); + DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); + } + + DOM.addClass(it, s['class']); +// n = DOM.add(n, 'span', {'class' : 'item'}); + + ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); + + if (s.icon_src) + DOM.add(ic, 'img', {src : s.icon_src}); + + n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); + + if (o.settings.style) { + if (typeof o.settings.style == "function") + o.settings.style = o.settings.style(); + + DOM.setAttrib(n, 'style', o.settings.style); + } + + if (tb.childNodes.length == 1) + DOM.addClass(ro, 'mceFirst'); + + if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) + DOM.addClass(ro, 'mceFirst'); + + if (o.collapse) + DOM.addClass(ro, cp + 'ItemSub'); + + if (n = ro.previousSibling) + DOM.removeClass(n, 'mceLast'); + + DOM.addClass(ro, 'mceLast'); + } + }); +})(tinymce); +(function(tinymce) { + var DOM = tinymce.DOM; + + tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { + Button : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceButton'; + }, + + renderHTML : function() { + var cp = this.classPrefix, s = this.settings, h, l; + + l = DOM.encode(s.label || ''); + h = ''; + if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) + h += '' + DOM.encode(s.title) + '' + (l ? '' + l + '' : ''); + else + h += '' + (l ? '' + l + '' : ''); + + h += ''; + h += ''; + return h; + }, + + postRender : function() { + var t = this, s = t.settings, imgBookmark; + + // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so + // need to keep the selection in case the selection is lost + if (tinymce.isIE && t.editor) { + tinymce.dom.Event.add(t.id, 'mousedown', function(e) { + var nodeName = t.editor.selection.getNode().nodeName; + imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; + }); + } + tinymce.dom.Event.add(t.id, 'click', function(e) { + if (!t.isDisabled()) { + // restore the selection in case the selection is lost in IE + if (tinymce.isIE && t.editor && imgBookmark !== null) { + t.editor.selection.moveToBookmark(imgBookmark); + } + return s.onclick.call(s.scope, e); + } + }); + tinymce.dom.Event.add(t.id, 'keydown', function(e) { + if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) { + tinymce.dom.Event.cancel(e); + return s.onclick.call(s.scope, e); + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; + + tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { + ListBox : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + t.items = []; + + t.onChange = new Dispatcher(t); + + t.onPostRender = new Dispatcher(t); + + t.onAdd = new Dispatcher(t); + + t.onRenderMenu = new tinymce.util.Dispatcher(this); + + t.classPrefix = 'mceListBox'; + t.marked = {}; + }, + + select : function(va) { + var t = this, fv, f; + + t.marked = {}; + + if (va == undef) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && typeof(va)=="function") + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + selectByIndex : function(idx) { + var t = this, e, o, label; + + t.marked = {}; + + if (idx != t.selectedIndex) { + e = DOM.get(t.id + '_text'); + label = DOM.get(t.id + '_voiceDesc'); + o = t.items[idx]; + + if (o) { + t.selectedValue = o.value; + t.selectedIndex = idx; + DOM.setHTML(e, DOM.encode(o.title)); + DOM.setHTML(label, t.settings.title + " - " + o.title); + DOM.removeClass(e, 'mceTitle'); + DOM.setAttrib(t.id, 'aria-valuenow', o.title); + } else { + DOM.setHTML(e, DOM.encode(t.settings.title)); + DOM.setHTML(label, DOM.encode(t.settings.title)); + DOM.addClass(e, 'mceTitle'); + t.selectedValue = t.selectedIndex = null; + DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); + } + e = 0; + } + }, + + mark : function(value) { + this.marked[value] = true; + }, + + add : function(n, v, o) { + var t = this; + + o = o || {}; + o = tinymce.extend(o, { + title : n, + value : v + }); + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + getLength : function() { + return this.items.length; + }, + + renderHTML : function() { + var h = '', t = this, s = t.settings, cp = t.classPrefix; + + h = ''; + h += ''; + h += ''; + h += ''; + + return h; + }, + + showMenu : function() { + var t = this, p2, e = DOM.get(this.id), m; + + if (t.isDisabled() || t.items.length === 0) + return; + + if (t.menu && t.menu.isMenuVisible) + return t.hideMenu(); + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus + + // Select in menu + each(t.items, function(o) { + if (m.items[o.id]) { + m.items[o.id].setSelected(0); + } + }); + + each(t.items, function(o) { + if (m.items[o.id] && t.marked[o.value]) { + m.items[o.id].setSelected(1); + } + + if (o.value === t.selectedValue) { + m.items[o.id].setSelected(1); + } + }); + + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + DOM.addClass(t.id, t.classPrefix + 'Selected'); + + //DOM.get(t.id + '_text').focus(); + }, + + hideMenu : function(e) { + var t = this; + + if (t.menu && t.menu.isMenuVisible) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + t.menu.hideMenu(); + } + } + }, + + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : t.classPrefix + 'Menu mceNoIcons', + max_width : 250, + max_height : 150 + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + m.add({ + title : t.settings.title, + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + + each(t.items, function(o) { + // No value then treat it as a title + if (o.value === undef) { + m.add({ + title : o.title, + role : "option", + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + } else { + o.id = DOM.uniqueId(); + o.role= "option"; + o.onclick = function() { + if (t.settings.onselect(o.value) !== false) + t.select(o.value); // Must be runned after + }; + + m.add(o); + } + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + postRender : function() { + var t = this, cp = t.classPrefix; + + Event.add(t.id, 'click', t.showMenu, t); + Event.add(t.id, 'keydown', function(evt) { + if (evt.keyCode == 32) { // Space + t.showMenu(evt); + Event.cancel(evt); + } + }); + Event.add(t.id, 'focus', function() { + if (!t._focused) { + t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { + if (e.keyCode == 40) { + t.showMenu(); + Event.cancel(e); + } + }); + t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { + var v; + if (e.keyCode == 13) { + // Fake select on enter + v = t.selectedValue; + t.selectedValue = null; // Needs to be null to fake change + Event.cancel(e); + t.settings.onselect(v); + } + }); + } + + t._focused = 1; + }); + Event.add(t.id, 'blur', function() { + Event.remove(t.id, 'keydown', t.keyDownHandler); + Event.remove(t.id, 'keypress', t.keyPressHandler); + t._focused = 0; + }); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.addClass(t.id, cp + 'Hover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.removeClass(t.id, cp + 'Hover'); + }); + } + + t.onPostRender.dispatch(t, DOM.get(t.id)); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_text'); + Event.clear(this.id + '_open'); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; + + tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { + NativeListBox : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceNativeListBox'; + }, + + setDisabled : function(s) { + DOM.get(this.id).disabled = s; + this.setAriaProperty('disabled', s); + }, + + isDisabled : function() { + return DOM.get(this.id).disabled; + }, + + select : function(va) { + var t = this, fv, f; + + if (va == undef) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && typeof(va)=="function") + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + selectByIndex : function(idx) { + DOM.get(this.id).selectedIndex = idx + 1; + this.selectedValue = this.items[idx] ? this.items[idx].value : null; + }, + + add : function(n, v, a) { + var o, t = this; + + a = a || {}; + a.value = v; + + if (t.isRendered()) + DOM.add(DOM.get(this.id), 'option', a, n); + + o = { + title : n, + value : v, + attribs : a + }; + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + getLength : function() { + return this.items.length; + }, + + renderHTML : function() { + var h, t = this; + + h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); + + each(t.items, function(it) { + h += DOM.createHTML('option', {value : it.value}, it.title); + }); + + h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); + h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); + return h; + }, + + postRender : function() { + var t = this, ch, changeListenerAdded = true; + + t.rendered = true; + + function onChange(e) { + var v = t.items[e.target.selectedIndex - 1]; + + if (v && (v = v.value)) { + t.onChange.dispatch(t, v); + + if (t.settings.onselect) + t.settings.onselect(v); + } + }; + + Event.add(t.id, 'change', onChange); + + // Accessibility keyhandler + Event.add(t.id, 'keydown', function(e) { + var bf, DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; + + Event.remove(t.id, 'change', ch); + changeListenerAdded = false; + + bf = Event.add(t.id, 'blur', function() { + if (changeListenerAdded) return; + changeListenerAdded = true; + Event.add(t.id, 'change', onChange); + Event.remove(t.id, 'blur', bf); + }); + + if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) { + onChange(e); + return Event.cancel(e); + } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) { + // allow native implementation (navigate select element options) + e.stopImmediatePropagation(); + } + }); + + t.onPostRender.dispatch(t, DOM.get(t.id)); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { + MenuButton : function(id, s, ed) { + this.parent(id, s, ed); + + this.onRenderMenu = new tinymce.util.Dispatcher(this); + + s.menu_container = s.menu_container || DOM.doc.body; + }, + + showMenu : function() { + var t = this, p1, p2, e = DOM.get(t.id), m; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + p1 = DOM.getPos(t.settings.menu_container); + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.vp_offset_x = p2.x; + m.settings.vp_offset_y = p2.y; + m.settings.keyboard_focus = t._focused; + m.showMenu(0, e.firstChild.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.setState('Selected', 1); + + t.isMenuVisible = 1; + }, + + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : this.classPrefix + 'Menu', + icons : t.settings.icons + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + hideMenu : function(e) { + var t = this; + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + t.setState('Selected', 0); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + if (t.menu) + t.menu.hideMenu(); + } + + t.isMenuVisible = 0; + }, + + postRender : function() { + var t = this, s = t.settings; + + Event.add(t.id, 'click', function() { + if (!t.isDisabled()) { + if (s.onclick) + s.onclick(t.value); + + t.showMenu(); + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { + SplitButton : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceSplitButton'; + }, + + renderHTML : function() { + var h, t = this, s = t.settings, h1; + + h = ''; + + if (s.image) + h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); + else + h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); + + h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, ''); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h += ''; + h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); + return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); + }, + + postRender : function() { + var t = this, s = t.settings, activate; + + if (s.onclick) { + activate = function(evt) { + if (!t.isDisabled()) { + s.onclick(t.value); + Event.cancel(evt); + } + }; + Event.add(t.id + '_action', 'click', activate); + Event.add(t.id, ['click', 'keydown'], function(evt) { + var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; + if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { + activate(); + Event.cancel(evt); + } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { + t.showMenu(); + Event.cancel(evt); + } + }); + } + + Event.add(t.id + '_open', 'click', function (evt) { + t.showMenu(); + Event.cancel(evt); + }); + Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); + Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.addClass(t.id, 'mceSplitButtonHover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.removeClass(t.id, 'mceSplitButtonHover'); + }); + } + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_action'); + Event.clear(this.id + '_open'); + Event.clear(this.id); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; + + tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { + ColorSplitButton : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + t.settings = s = tinymce.extend({ + colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', + grid_width : 8, + default_color : '#888888' + }, t.settings); + + t.onShowMenu = new tinymce.util.Dispatcher(t); + + t.onHideMenu = new tinymce.util.Dispatcher(t); + + t.value = s.default_color; + }, + + showMenu : function() { + var t = this, r, p, e, p2; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + e = DOM.get(t.id); + DOM.show(t.id + '_menu'); + DOM.addClass(e, 'mceSplitButtonSelected'); + p2 = DOM.getPos(e); + DOM.setStyles(t.id + '_menu', { + left : p2.x, + top : p2.y + e.firstChild.clientHeight, + zIndex : 200000 + }); + e = 0; + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.onShowMenu.dispatch(t); + + if (t._focused) { + t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { + if (e.keyCode == 27) + t.hideMenu(); + }); + + DOM.select('a', t.id + '_menu')[0].focus(); // Select first link + } + + t.keyboardNav = new tinymce.ui.KeyboardNavigation({ + root: t.id + '_menu', + items: DOM.select('a', t.id + '_menu'), + onCancel: function() { + t.hideMenu(); + t.focus(); + } + }); + + t.keyboardNav.focus(); + t.isMenuVisible = 1; + }, + + hideMenu : function(e) { + var t = this; + + if (t.isMenuVisible) { + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { + DOM.removeClass(t.id, 'mceSplitButtonSelected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + Event.remove(t.id + '_menu', 'keydown', t._keyHandler); + DOM.hide(t.id + '_menu'); + } + + t.isMenuVisible = 0; + t.onHideMenu.dispatch(); + t.keyboardNav.destroy(); + } + }, + + renderMenu : function() { + var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; + + w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); + m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); + DOM.add(m, 'span', {'class' : 'mceMenuLine'}); + + n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); + tb = DOM.add(n, 'tbody'); + + // Generate color grid + i = 0; + each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { + c = c.replace(/^#/, ''); + + if (!i--) { + tr = DOM.add(tb, 'tr'); + i = s.grid_width - 1; + } + + n = DOM.add(tr, 'td'); + var settings = { + href : 'javascript:;', + style : { + backgroundColor : '#' + c + }, + 'title': t.editor.getLang('colors.' + c, c), + 'data-mce-color' : '#' + c + }; + + // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. + if (!tinymce.isIE ) { + settings.role = 'option'; + } + + n = DOM.add(n, 'a', settings); + + if (t.editor.forcedHighContrastMode) { + n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); + if (n.getContext && (context = n.getContext("2d"))) { + context.fillStyle = '#' + c; + context.fillRect(0, 0, 16, 16); + } else { + // No point leaving a canvas element around if it's not supported for drawing on anyway. + DOM.remove(n); + } + } + }); + + if (s.more_colors_func) { + n = DOM.add(tb, 'tr'); + n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); + n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); + + Event.add(n, 'click', function(e) { + s.more_colors_func.call(s.more_colors_scope || this); + return Event.cancel(e); // Cancel to fix onbeforeunload problem + }); + } + + DOM.addClass(m, 'mceColorSplitMenu'); + + // Prevent IE from scrolling and hindering click to occur #4019 + Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); + + Event.add(t.id + '_menu', 'click', function(e) { + var c; + + e = DOM.getParent(e.target, 'a', tb); + + if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) + t.setColor(c); + + return false; // Prevent IE auto save warning + }); + + return w; + }, + + setColor : function(c) { + this.displayColor(c); + this.hideMenu(); + this.settings.onselect(c); + }, + + displayColor : function(c) { + var t = this; + + DOM.setStyle(t.id + '_preview', 'backgroundColor', c); + + t.value = c; + }, + + postRender : function() { + var t = this, id = t.id; + + t.parent(); + DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); + DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); + }, + + destroy : function() { + var self = this; + + self.parent(); + + Event.clear(self.id + '_menu'); + Event.clear(self.id + '_more'); + DOM.remove(self.id + '_menu'); + + if (self.keyboardNav) { + self.keyboardNav.destroy(); + } + } + }); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; +tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; + + h.push('
    '); + //TODO: ACC test this out - adding a role = application for getting the landmarks working well. + h.push(""); + h.push(''); + each(controls, function(toolbar) { + h.push(toolbar.renderHTML()); + }); + h.push(""); + h.push('
    '); + + return h.join(''); + }, + + focus : function() { + var t = this; + dom.get(t.id).focus(); + }, + + postRender : function() { + var t = this, items = []; + + each(t.controls, function(toolbar) { + each (toolbar.controls, function(control) { + if (control.id) { + items.push(control); + } + }); + }); + + t.keyNav = new tinymce.ui.KeyboardNavigation({ + root: t.id, + items: items, + onCancel: function() { + //Move focus if webkit so that navigation back will read the item. + if (tinymce.isWebKit) { + dom.get(t.editor.id+"_ifr").focus(); + } + t.editor.focus(); + }, + excludeFromTabOrder: !t.settings.tab_focus_toolbar + }); + }, + + destroy : function() { + var self = this; + + self.parent(); + self.keyNav.destroy(); + Event.clear(self.id); + } +}); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each; +tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; + + cl = t.controls; + for (i=0; i')); + } + + // Add toolbar end before list box and after the previous button + // This is to fix the o2k7 editor skins + if (pr && co.ListBox) { + if (pr.Button || pr.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '')); + } + + // Render control HTML + + // IE 8 quick fix, needed to propertly generate a hit area for anchors + if (dom.stdMode) + h += '' + co.renderHTML() + ''; + else + h += '' + co.renderHTML() + ''; + + // Add toolbar start after list box and before the next button + // This is to fix the o2k7 editor skins + if (nx && co.ListBox) { + if (nx.Button || nx.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); + } + } + + c = 'mceToolbarEnd'; + + if (co.Button) + c += ' mceToolbarEndButton'; + else if (co.SplitButton) + c += ' mceToolbarEndSplitButton'; + else if (co.ListBox) + c += ' mceToolbarEndListBox'; + + h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '')); + + return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '' + h + ''); + } +}); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; + + tinymce.create('tinymce.AddOnManager', { + AddOnManager : function() { + var self = this; + + self.items = []; + self.urls = {}; + self.lookup = {}; + self.onAdd = new Dispatcher(self); + }, + + get : function(n) { + if (this.lookup[n]) { + return this.lookup[n].instance; + } else { + return undefined; + } + }, + + dependencies : function(n) { + var result; + if (this.lookup[n]) { + result = this.lookup[n].dependencies; + } + return result || []; + }, + + requireLangPack : function(n) { + var s = tinymce.settings; + + if (s && s.language && s.language_load !== false) + tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); + }, + + add : function(id, o, dependencies) { + this.items.push(o); + this.lookup[id] = {instance:o, dependencies:dependencies}; + this.onAdd.dispatch(this, id, o); + + return o; + }, + createUrl: function(baseUrl, dep) { + if (typeof dep === "object") { + return dep + } else { + return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; + } + }, + + addComponents: function(pluginName, scripts) { + var pluginUrl = this.urls[pluginName]; + tinymce.each(scripts, function(script){ + tinymce.ScriptLoader.add(pluginUrl+"/"+script); + }); + }, + + load : function(n, u, cb, s) { + var t = this, url = u; + + function loadDependencies() { + var dependencies = t.dependencies(n); + tinymce.each(dependencies, function(dep) { + var newUrl = t.createUrl(u, dep); + t.load(newUrl.resource, newUrl, undefined, undefined); + }); + if (cb) { + if (s) { + cb.call(s); + } else { + cb.call(tinymce.ScriptLoader); + } + } + } + + if (t.urls[n]) + return; + if (typeof u === "object") + url = u.prefix + u.resource + u.suffix; + + if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) + url = tinymce.baseURL + '/' + url; + + t.urls[n] = url.substring(0, url.lastIndexOf('/')); + + if (t.lookup[n]) { + loadDependencies(); + } else { + tinymce.ScriptLoader.add(url, loadDependencies, s); + } + } + }); + + // Create plugin and theme managers + tinymce.PluginManager = new tinymce.AddOnManager(); + tinymce.ThemeManager = new tinymce.AddOnManager(); +}(tinymce)); + +(function(tinymce) { + // Shorten names + var each = tinymce.each, extend = tinymce.extend, + DOM = tinymce.DOM, Event = tinymce.dom.Event, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + explode = tinymce.explode, + Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + + tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); + + // Add before unload listener + // This was required since IE was leaking memory if you added and removed beforeunload listeners + // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event + tinymce.onBeforeUnload = new Dispatcher(tinymce); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + tinymce.onBeforeUnload.dispatch(tinymce, e); + }); + + tinymce.onAddEditor = new Dispatcher(tinymce); + + tinymce.onRemoveEditor = new Dispatcher(tinymce); + + tinymce.EditorManager = extend(tinymce, { + editors : [], + + i18n : {}, + + activeEditor : null, + + init : function(s) { + var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; + + function createId(elm) { + var id = elm.id; + + // Use element id, or unique name or generate a unique id + if (!id) { + id = elm.name; + + if (id && !DOM.get(id)) { + id = elm.name; + } else { + // Generate unique name + id = DOM.uniqueId(); + } + + elm.setAttribute('id', id); + } + + return id; + }; + + function execCallback(se, n, s) { + var f = se[n]; + + if (!f) + return; + + if (tinymce.is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + } + + return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); + }; + + function hasClass(n, c) { + return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); + }; + + t.settings = s; + + // Legacy call + Event.bind(window, 'ready', function() { + var l, co; + + execCallback(s, 'onpageload'); + + switch (s.mode) { + case "exact": + l = s.elements || ''; + + if(l.length > 0) { + each(explode(l), function(v) { + if (DOM.get(v)) { + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } else { + each(document.forms, function(f) { + each(f.elements, function(e) { + if (e.name === v) { + v = 'mce_editor_' + instanceCounter++; + DOM.setAttrib(e, 'id', v); + + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } + }); + }); + } + }); + } + break; + + case "textareas": + case "specific_textareas": + each(DOM.select('textarea'), function(elm) { + if (s.editor_deselector && hasClass(elm, s.editor_deselector)) + return; + + if (!s.editor_selector || hasClass(elm, s.editor_selector)) { + ed = new tinymce.Editor(createId(elm), s); + el.push(ed); + ed.render(1); + } + }); + break; + + default: + if (s.types) { + // Process type specific selector + each(s.types, function(type) { + each(DOM.select(type.selector), function(elm) { + var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); + el.push(editor); + editor.render(1); + }); + }); + } else if (s.selector) { + // Process global selector + each(DOM.select(s.selector), function(elm) { + var editor = new tinymce.Editor(createId(elm), s); + el.push(editor); + editor.render(1); + }); + } + } + + // Call onInit when all editors are initialized + if (s.oninit) { + l = co = 0; + + each(el, function(ed) { + co++; + + if (!ed.initialized) { + // Wait for it + ed.onInit.add(function() { + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } else + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } + }); + }, + + get : function(id) { + if (id === undef) + return this.editors; + + if (!this.editors.hasOwnProperty(id)) + return undef; + + return this.editors[id]; + }, + + getInstanceById : function(id) { + return this.get(id); + }, + + add : function(editor) { + var self = this, editors = self.editors; + + // Add named and index editor instance + editors[editor.id] = editor; + editors.push(editor); + + self._setActive(editor); + self.onAddEditor.dispatch(self, editor); + + + // Patch the tinymce.Editor instance with jQuery adapter logic + if (tinymce.adapter) + tinymce.adapter.patchEditor(editor); + + + return editor; + }, + + remove : function(editor) { + var t = this, i, editors = t.editors; + + // Not in the collection + if (!editors[editor.id]) + return null; + + delete editors[editor.id]; + + for (i = 0; i < editors.length; i++) { + if (editors[i] == editor) { + editors.splice(i, 1); + break; + } + } + + // Select another editor since the active one was removed + if (t.activeEditor == editor) + t._setActive(editors[0]); + + editor.destroy(); + t.onRemoveEditor.dispatch(t, editor); + + return editor; + }, + + execCommand : function(c, u, v) { + var t = this, ed = t.get(v), w; + + function clr() { + ed.destroy(); + w.detachEvent('onunload', clr); + w = w.tinyMCE = w.tinymce = null; // IE leak + }; + + // Manager commands + switch (c) { + case "mceFocus": + ed.focus(); + return true; + + case "mceAddEditor": + case "mceAddControl": + if (!t.get(v)) + new tinymce.Editor(v, t.settings).render(); + + return true; + + case "mceAddFrameControl": + w = v.window; + + // Add tinyMCE global instance and tinymce namespace to specified window + w.tinyMCE = tinyMCE; + w.tinymce = tinymce; + + tinymce.DOM.doc = w.document; + tinymce.DOM.win = w; + + ed = new tinymce.Editor(v.element_id, v); + ed.render(); + + // Fix IE memory leaks + if (tinymce.isIE && ! tinymce.isIE11) { + w.attachEvent('onunload', clr); + } + + v.page_window = null; + + return true; + + case "mceRemoveEditor": + case "mceRemoveControl": + if (ed) + ed.remove(); + + return true; + + case 'mceToggleEditor': + if (!ed) { + t.execCommand('mceAddControl', 0, v); + return true; + } + + if (ed.isHidden()) + ed.show(); + else + ed.hide(); + + return true; + } + + // Run command on active editor + if (t.activeEditor) + return t.activeEditor.execCommand(c, u, v); + + return false; + }, + + execInstanceCommand : function(id, c, u, v) { + var ed = this.get(id); + + if (ed) + return ed.execCommand(c, u, v); + + return false; + }, + + triggerSave : function() { + each(this.editors, function(e) { + e.save(); + }); + }, + + addI18n : function(p, o) { + var lo, i18n = this.i18n; + + if (!tinymce.is(p, 'string')) { + each(p, function(o, lc) { + each(o, function(o, g) { + each(o, function(o, k) { + if (g === 'common') + i18n[lc + '.' + k] = o; + else + i18n[lc + '.' + g + '.' + k] = o; + }); + }); + }); + } else { + each(o, function(o, k) { + i18n[p + '.' + k] = o; + }); + } + }, + + // Private methods + + _setActive : function(editor) { + this.selectedInstance = this.activeEditor = editor; + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten these names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, + each = tinymce.each, isGecko = tinymce.isGecko, + isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + explode = tinymce.explode; + + tinymce.create('tinymce.Editor', { + Editor : function(id, settings) { + var self = this, TRUE = true; + + self.settings = settings = extend({ + id : id, + language : 'en', + theme : 'advanced', + skin : 'default', + delta_width : 0, + delta_height : 0, + popup_css : '', + plugins : '', + document_base_url : tinymce.documentBaseURL, + add_form_submit_trigger : TRUE, + submit_patch : TRUE, + add_unload_trigger : TRUE, + convert_urls : TRUE, + relative_urls : TRUE, + remove_script_host : TRUE, + table_inline_editing : false, + object_resizing : TRUE, + accessibility_focus : TRUE, + doctype : tinymce.isIE6 ? '' : '', // Use old doctype on IE 6 to avoid horizontal scroll + visual : TRUE, + font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', + font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size + apply_source_formatting : TRUE, + directionality : 'ltr', + forced_root_block : 'p', + hidden_input : TRUE, + padd_empty_editor : TRUE, + render_ui : TRUE, + indentation : '30px', + fix_table_elements : TRUE, + inline_styles : TRUE, + convert_fonts_to_spans : TRUE, + indent : 'simple', + indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', + indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', + validate : TRUE, + entity_encoding : 'named', + url_converter : self.convertURL, + url_converter_scope : self, + ie7_compat : TRUE + }, settings); + + self.id = self.editorId = id; + + self.isNotDirty = false; + + self.plugins = {}; + + self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { + base_uri : tinyMCE.baseURI + }); + + self.baseURI = tinymce.baseURI; + + self.contentCSS = []; + + self.contentStyles = []; + + // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic + self.setupEvents(); + + // Internal command handler objects + self.execCommands = {}; + self.queryStateCommands = {}; + self.queryValueCommands = {}; + + // Call setup + self.execCallback('setup', self); + }, + + render : function(nst) { + var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; + + // Page is not loaded yet, wait for it + if (!Event.domLoaded) { + Event.add(window, 'ready', function() { + t.render(); + }); + return; + } + + tinyMCE.settings = s; + + // Element not found, then skip initialization + if (!t.getElement()) + return; + + // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff + // here since the browser says it has contentEditable support but there is no visible caret. + if (tinymce.isIDevice && !tinymce.isIOS5) + return; + + // Add hidden input for non input elements inside form elements + if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) + DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); + + // Hide target element early to prevent content flashing + if (!s.content_editable) { + t.orgVisibility = t.getElement().style.visibility; + t.getElement().style.visibility = 'hidden'; + } + + if (tinymce.WindowManager) + t.windowManager = new tinymce.WindowManager(t); + + if (s.encoding == 'xml') { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = DOM.encode(o.content); + }); + } + + if (s.add_form_submit_trigger) { + t.onSubmit.addToTop(function() { + if (t.initialized) { + t.save(); + t.isNotDirty = 1; + } + }); + } + + if (s.add_unload_trigger) { + t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { + if (t.initialized && !t.destroyed && !t.isHidden()) + t.save({format : 'raw', no_events : true}); + }); + } + + tinymce.addUnload(t.destroy, t); + + if (s.submit_patch) { + t.onBeforeRenderUI.add(function() { + var n = t.getElement().form; + + if (!n) + return; + + // Already patched + if (n._mceOldSubmit) + return; + + // Check page uses id="submit" or name="submit" for it's submit button + if (!n.submit.nodeType && !n.submit.length) { + t.formElement = n; + n._mceOldSubmit = n.submit; + n.submit = function() { + // Save all instances + tinymce.triggerSave(); + t.isNotDirty = 1; + + return t.formElement._mceOldSubmit(t.formElement); + }; + } + + n = null; + }); + } + + // Load scripts + function loadScripts() { + if (s.language && s.language_load !== false) + sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); + + if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) + ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); + + each(explode(s.plugins), function(p) { + if (p &&!PluginManager.urls[p]) { + if (p.charAt(0) == '-') { + p = p.substr(1, p.length); + var dependencies = PluginManager.dependencies(p); + each(dependencies, function(dep) { + var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; + dep = PluginManager.createUrl(defaultSettings, dep); + PluginManager.load(dep.resource, dep); + }); + } else { + // Skip safari plugin, since it is removed as of 3.3b1 + if (p == 'safari') { + return; + } + PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); + } + } + }); + + // Init when que is loaded + sl.loadQueue(function() { + if (!t.removed) + t.init(); + }); + }; + + loadScripts(); + }, + + init : function() { + var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; + + tinymce.add(t); + + s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); + + if (s.theme) { + if (typeof s.theme != "function") { + s.theme = s.theme.replace(/-/, ''); + o = ThemeManager.get(s.theme); + t.theme = new o(); + + if (t.theme.init) + t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); + } else { + t.theme = s.theme; + } + } + + function initPlugin(p) { + var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; + if (c && tinymce.inArray(initializedPlugins,p) === -1) { + each(PluginManager.dependencies(p), function(dep){ + initPlugin(dep); + }); + po = new c(t, u); + + t.plugins[p] = po; + + if (po.init) { + po.init(t, u); + initializedPlugins.push(p); + } + } + } + + // Create all plugins + each(explode(s.plugins.replace(/\-/g, '')), initPlugin); + + // Setup popup CSS path(s) + if (s.popup_css !== false) { + if (s.popup_css) + s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); + else + s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); + } + + if (s.popup_css_add) + s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); + + t.controlManager = new tinymce.ControlManager(t); + + // Enables users to override the control factory + t.onBeforeRenderUI.dispatch(t, t.controlManager); + + // Measure box + if (s.render_ui && t.theme) { + t.orgDisplay = e.style.display; + + if (typeof s.theme != "function") { + w = s.width || e.style.width || e.offsetWidth; + h = s.height || e.style.height || e.offsetHeight; + mh = s.min_height || 100; + re = /^[0-9\.]+(|px)$/i; + + if (re.test('' + w)) + w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); + + if (re.test('' + h)) + h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); + + // Render UI + o = t.theme.renderUI({ + targetNode : e, + width : w, + height : h, + deltaWidth : s.delta_width, + deltaHeight : s.delta_height + }); + + // Resize editor + DOM.setStyles(o.sizeContainer || o.editorContainer, { + width : w, + height : h + }); + + h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); + if (h < mh) + h = mh; + } else { + o = s.theme(t, e); + + // Convert element type to id:s + if (o.editorContainer.nodeType) { + o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; + } + + // Convert element type to id:s + if (o.iframeContainer.nodeType) { + o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; + } + + // Use specified iframe height or the targets offsetHeight + h = o.iframeHeight || e.offsetHeight; + + // Store away the selection when it's changed to it can be restored later with a editor.focus() call + if (isIE) { + t.onInit.add(function(ed) { + ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() { + ed.bookmark = ed.selection.getBookmark(1); + }); + }); + + t.onNodeChange.add(function(ed) { + if (document.activeElement.id == ed.id + "_ifr") { + ed.bookmark = ed.selection.getBookmark(1); + } + }); + } + } + + t.editorContainer = o.editorContainer; + } + + // Load specified content CSS last + if (s.content_css) { + each(explode(s.content_css), function(u) { + t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); + }); + } + + // Load specified content CSS last + if (s.content_style) { + t.contentStyles.push(s.content_style); + } + + // Content editable mode ends here + if (s.content_editable) { + e = n = o = null; // Fix IE leak + return t.initContentBody(); + } + + // User specified a document.domain value + if (document.domain && location.hostname != document.domain) + tinymce.relaxedDomain = document.domain; + + t.iframeHTML = s.doctype + ''; + + // We only need to override paths if we have to + // IE has a bug where it remove site absolute urls to relative ones if this is specified + if (s.document_base_url != tinymce.documentBaseURL) + t.iframeHTML += ''; + + // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. + if (tinymce.isIE8) { + if (s.ie7_compat) + t.iframeHTML += ''; + else + t.iframeHTML += ''; + } + + t.iframeHTML += ''; + + // Load the CSS by injecting them into the HTML this will reduce "flicker" + for (i = 0; i < t.contentCSS.length; i++) { + t.iframeHTML += ''; + } + + t.contentCSS = []; + + bi = s.body_id || 'tinymce'; + if (bi.indexOf('=') != -1) { + bi = t.getParam('body_id', '', 'hash'); + bi = bi[t.id] || bi; + } + + bc = s.body_class || ''; + if (bc.indexOf('=') != -1) { + bc = t.getParam('body_class', '', 'hash'); + bc = bc[t.id] || ''; + } + + t.iframeHTML += '
    '; + + // Domain relaxing enabled, then set document domain + if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { + // We need to write the contents here in IE since multiple writes messes up refresh button and back button + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'; + } + + // Create iframe + // TODO: ACC add the appropriate description on this. + n = DOM.add(o.iframeContainer, 'iframe', { + id : t.id + "_ifr", + src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 + frameBorder : '0', + allowTransparency : "true", + title : s.aria_label, + style : { + width : '100%', + height : h, + display : 'block' // Important for Gecko to render the iframe correctly + } + }); + + t.contentAreaContainer = o.iframeContainer; + + if (o.editorContainer) { + DOM.get(o.editorContainer).style.display = t.orgDisplay; + } + + // Restore visibility on target element + e.style.visibility = t.orgVisibility; + + DOM.get(t.id).style.display = 'none'; + DOM.setAttrib(t.id, 'aria-hidden', true); + + if (!tinymce.relaxedDomain || !u) + t.initContentBody(); + + e = n = o = null; // Cleanup + }, + + initContentBody : function() { + var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; + + // Setup iframe body + if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { + doc.open(); + doc.write(self.iframeHTML); + doc.close(); + + if (tinymce.relaxedDomain) + doc.domain = tinymce.relaxedDomain; + } + + if (settings.content_editable) { + DOM.addClass(targetElm, 'mceContentBody'); + self.contentDocument = doc = settings.content_document || document; + self.contentWindow = settings.content_window || window; + self.bodyElement = targetElm; + + // Prevent leak in IE + settings.content_document = settings.content_window = null; + } + + // It will not steal focus while setting contentEditable + body = self.getBody(); + body.disabled = true; + + if (!settings.readonly) + body.contentEditable = self.getParam('content_editable_state', true); + + body.disabled = false; + + self.schema = new tinymce.html.Schema(settings); + + self.dom = new tinymce.dom.DOMUtils(doc, { + keep_values : true, + url_converter : self.convertURL, + url_converter_scope : self, + hex_colors : settings.force_hex_style_colors, + class_filter : settings.class_filter, + update_styles : true, + root_element : settings.content_editable ? self.id : null, + schema : self.schema + }); + + self.parser = new tinymce.html.DomParser(settings, self.schema); + + // Convert src and href into data-mce-src, data-mce-href and data-mce-style + self.parser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, dom = self.dom, value, internalName; + + while (i--) { + node = nodes[i]; + value = node.attr(name); + internalName = 'data-mce-' + name; + + // Add internal attribute if we need to we don't on a refresh of the document + if (!node.attributes.map[internalName]) { + if (name === "style") + node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); + else + node.attr(internalName, self.convertURL(value, name, node.name)); + } + } + }); + + // Keep scripts from executing + self.parser.addNodeFilter('script', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); + } + }); + + self.parser.addNodeFilter('#cdata', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.type = 8; + node.name = '#comment'; + node.value = '[CDATA[' + node.value + ']]'; + } + }); + + self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { + var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); + + while (i--) { + node = nodes[i]; + + if (node.isEmpty(nonEmptyElements)) + node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; + } + }); + + self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); + + self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); + + self.formatter = new tinymce.Formatter(self); + + self.undoManager = new tinymce.UndoManager(self); + + self.forceBlocks = new tinymce.ForceBlocks(self); + self.enterKey = new tinymce.EnterKey(self); + self.editorCommands = new tinymce.EditorCommands(self); + + self.onExecCommand.add(function(editor, command) { + // Don't refresh the select lists until caret move + if (!/^(FontName|FontSize)$/.test(command)) + self.nodeChanged(); + }); + + // Pass through + self.serializer.onPreProcess.add(function(se, o) { + return self.onPreProcess.dispatch(self, o, se); + }); + + self.serializer.onPostProcess.add(function(se, o) { + return self.onPostProcess.dispatch(self, o, se); + }); + + self.onPreInit.dispatch(self); + + if (!settings.browser_spellcheck && !settings.gecko_spellcheck) + doc.body.spellcheck = false; + + if (!settings.readonly) { + self.bindNativeEvents(); + } + + self.controlManager.onPostRender.dispatch(self, self.controlManager); + self.onPostRender.dispatch(self); + + self.quirks = tinymce.util.Quirks(self); + + if (settings.directionality) + body.dir = settings.directionality; + + if (settings.nowrap) + body.style.whiteSpace = "nowrap"; + + if (settings.protect) { + self.onBeforeSetContent.add(function(ed, o) { + each(settings.protect, function(pattern) { + o.content = o.content.replace(pattern, function(str) { + return ''; + }); + }); + }); + } + + // Add visual aids when new contents is added + self.onSetContent.add(function() { + self.addVisual(self.getBody()); + }); + + // Remove empty contents + if (settings.padd_empty_editor) { + self.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
    [\r\n]*)$/, ''); + }); + } + + self.load({initial : true, format : 'html'}); + self.startContent = self.getContent({format : 'raw'}); + + self.initialized = true; + + self.onInit.dispatch(self); + self.execCallback('setupcontent_callback', self.id, body, doc); + self.execCallback('init_instance_callback', self); + self.focus(true); + self.nodeChanged({initial : true}); + + // Add editor specific CSS styles + if (self.contentStyles.length > 0) { + contentCssText = ''; + + each(self.contentStyles, function(style) { + contentCssText += style + "\r\n"; + }); + + self.dom.addStyle(contentCssText); + } + + // Load specified content CSS last + each(self.contentCSS, function(url) { + self.dom.loadCSS(url); + }); + + // Handle auto focus + if (settings.auto_focus) { + setTimeout(function () { + var ed = tinymce.get(settings.auto_focus); + + ed.selection.select(ed.getBody(), 1); + ed.selection.collapse(1); + ed.getBody().focus(); + ed.getWin().focus(); + }, 100); + } + + // Clean up references for IE + targetElm = doc = body = null; + }, + + focus : function(skip_focus) { + var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; + + if (!skip_focus) { + if (self.bookmark) { + selection.moveToBookmark(self.bookmark); + self.bookmark = null; + } + + // Get selected control element + ieRng = selection.getRng(); + if (ieRng.item) { + controlElm = ieRng.item(0); + } + + self._refreshContentEditable(); + + // Focus the window iframe + if (!contentEditable) { + self.getWin().focus(); + } + + // Focus the body as well since it's contentEditable + if (tinymce.isGecko || contentEditable) { + body = self.getBody(); + + // Check for setActive since it doesn't scroll to the element + if (body.setActive && ! tinymce.isIE11) { + body.setActive(); + } else { + body.focus(); + } + + if (contentEditable) { + selection.normalize(); + } + } + + // Restore selected control element + // This is needed when for example an image is selected within a + // layer a call to focus will then remove the control selection + if (controlElm && controlElm.ownerDocument == doc) { + ieRng = doc.body.createControlRange(); + ieRng.addElement(controlElm); + ieRng.select(); + } + } + + if (tinymce.activeEditor != self) { + if ((oed = tinymce.activeEditor) != null) + oed.onDeactivate.dispatch(oed, self); + + self.onActivate.dispatch(self, oed); + } + + tinymce._setActive(self); + }, + + execCallback : function(n) { + var t = this, f = t.settings[n], s; + + if (!f) + return; + + // Look through lookup + if (t.callbackLookup && (s = t.callbackLookup[n])) { + f = s.func; + s = s.scope; + } + + if (is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + t.callbackLookup = t.callbackLookup || {}; + t.callbackLookup[n] = {func : f, scope : s}; + } + + return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); + }, + + translate : function(s) { + var c = this.settings.language || 'en', i18n = tinymce.i18n; + + if (!s) + return ''; + + return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { + return i18n[c + '.' + b] || '{#' + b + '}'; + }); + }, + + getLang : function(n, dv) { + return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); + }, + + getParam : function(n, dv, ty) { + var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; + + if (ty === 'hash') { + o = {}; + + if (is(v, 'string')) { + each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { + v = v.split('='); + + if (v.length > 1) + o[tr(v[0])] = tr(v[1]); + else + o[tr(v[0])] = tr(v); + }); + } else + o = v; + + return o; + } + + return v; + }, + + nodeChanged : function(o) { + var self = this, selection = self.selection, node; + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (self.initialized) { + o = o || {}; + + // Get start node + node = selection.getStart() || self.getBody(); + node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state + + // Get parents and add them to object + o.parents = []; + self.dom.getParent(node, function(node) { + if (node.nodeName == 'BODY') + return true; + + o.parents.push(node); + }); + + self.onNodeChange.dispatch( + self, + o ? o.controlManager || self.controlManager : self.controlManager, + node, + selection.isCollapsed(), + o + ); + } + }, + + addButton : function(name, settings) { + var self = this; + + self.buttons = self.buttons || {}; + self.buttons[name] = settings; + }, + + addCommand : function(name, callback, scope) { + this.execCommands[name] = {func : callback, scope : scope || this}; + }, + + addQueryStateHandler : function(name, callback, scope) { + this.queryStateCommands[name] = {func : callback, scope : scope || this}; + }, + + addQueryValueHandler : function(name, callback, scope) { + this.queryValueCommands[name] = {func : callback, scope : scope || this}; + }, + + addShortcut : function(pa, desc, cmd_func, sc) { + var t = this, c; + + if (t.settings.custom_shortcuts === false) + return false; + + t.shortcuts = t.shortcuts || {}; + + if (is(cmd_func, 'string')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c, false, null); + }; + } + + if (is(cmd_func, 'object')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c[0], c[1], c[2]); + }; + } + + each(explode(pa), function(pa) { + var o = { + func : cmd_func, + scope : sc || this, + desc : t.translate(desc), + alt : false, + ctrl : false, + shift : false + }; + + each(explode(pa, '+'), function(v) { + switch (v) { + case 'alt': + case 'ctrl': + case 'shift': + o[v] = true; + break; + + default: + o.charCode = v.charCodeAt(0); + o.keyCode = v.toUpperCase().charCodeAt(0); + } + }); + + t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; + }); + + return true; + }, + + execCommand : function(cmd, ui, val, a) { + var t = this, s = 0, o, st; + + if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) + t.focus(); + + a = extend({}, a); + t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); + if (a.terminate) + return false; + + // Command callback + if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Registred commands + if (o = t.execCommands[cmd]) { + st = o.func.call(o.scope, ui, val); + + // Fall through on true + if (st !== true) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return st; + } + } + + // Plugin commands + each(t.plugins, function(p) { + if (p.execCommand && p.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + s = 1; + return false; + } + }); + + if (s) + return true; + + // Theme commands + if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Editor commands + if (t.editorCommands.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Browser commands + t.getDoc().execCommand(cmd, ui, val); + t.onExecCommand.dispatch(t, cmd, ui, val, a); + }, + + queryCommandState : function(cmd) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryStateCommands[cmd]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandState(cmd); + if (o !== -1) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandState(cmd); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + queryCommandValue : function(c) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryValueCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandValue(c); + if (is(o)) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandValue(c); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + show : function() { + var self = this; + + DOM.show(self.getContainer()); + DOM.hide(self.id); + self.load(); + }, + + hide : function() { + var self = this, doc = self.getDoc(); + + // Fixed bug where IE has a blinking cursor left from the editor + if (isIE && doc) + doc.execCommand('SelectAll'); + + // We must save before we hide so Safari doesn't crash + self.save(); + + // defer the call to hide to prevent an IE9 crash #4921 + DOM.hide(self.getContainer()); + DOM.setStyle(self.id, 'display', self.orgDisplay); + }, + + isHidden : function() { + return !DOM.isHidden(this.id); + }, + + setProgressState : function(b, ti, o) { + this.onSetProgressState.dispatch(this, b, ti, o); + + return b; + }, + + load : function(o) { + var t = this, e = t.getElement(), h; + + if (e) { + o = o || {}; + o.load = true; + + // Double encode existing entities in the value + h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); + o.element = e; + + if (!o.no_events) + t.onLoadContent.dispatch(t, o); + + o.element = e = null; + + return h; + } + }, + + save : function(o) { + var t = this, e = t.getElement(), h, f; + + if (!e || !t.initialized) + return; + + o = o || {}; + o.save = true; + + o.element = e; + h = o.content = t.getContent(o); + + if (!o.no_events) + t.onSaveContent.dispatch(t, o); + + h = o.content; + + if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { + e.innerHTML = h; + + // Update hidden form element + if (f = DOM.getParent(t.id, 'form')) { + each(f.elements, function(e) { + if (e.name == t.id) { + e.value = h; + return false; + } + }); + } + } else + e.value = h; + + o.element = e = null; + + return h; + }, + + setContent : function(content, args) { + var self = this, rootNode, body = self.getBody(), forcedRootBlockName; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.set = true; + args.content = content; + + // Do preprocessing + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content + // It will also be impossible to place the caret in the editor unless there is a BR element present + if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { + forcedRootBlockName = self.settings.forced_root_block; + if (forcedRootBlockName) + content = '<' + forcedRootBlockName + '>
    '; + else + content = '
    '; + + body.innerHTML = content; + self.selection.select(body, true); + self.selection.collapse(true); + return; + } + + // Parse and serialize the html + if (args.format !== 'raw') { + content = new tinymce.html.Serializer({}, self.schema).serialize( + self.parser.parse(content) + ); + } + + // Set the new cleaned contents to the editor + args.content = tinymce.trim(content); + self.dom.setHTML(body, args.content); + + // Do post processing + if (!args.no_events) + self.onSetContent.dispatch(self, args); + + // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise + if (!self.settings.content_editable || document.activeElement === self.getBody()) { + self.selection.normalize(); + } + + return args.content; + }, + + getContent : function(args) { + var self = this, content, body = self.getBody(); + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.get = true; + args.getInner = true; + + // Do preprocessing + if (!args.no_events) + self.onBeforeGetContent.dispatch(self, args); + + // Get raw contents or by default the cleaned contents + if (args.format == 'raw') + content = body.innerHTML; + else if (args.format == 'text') + content = body.innerText || body.textContent; + else + content = self.serializer.serialize(body, args); + + // Trim whitespace in beginning/end of HTML + if (args.format != 'text') { + args.content = tinymce.trim(content); + } else { + args.content = content; + } + + // Do post processing + if (!args.no_events) + self.onGetContent.dispatch(self, args); + + return args.content; + }, + + isDirty : function() { + var self = this; + + return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; + }, + + getContainer : function() { + var self = this; + + if (!self.container) + self.container = DOM.get(self.editorContainer || self.id + '_parent'); + + return self.container; + }, + + getContentAreaContainer : function() { + return this.contentAreaContainer; + }, + + getElement : function() { + return DOM.get(this.settings.content_element || this.id); + }, + + getWin : function() { + var self = this, elm; + + if (!self.contentWindow) { + elm = DOM.get(self.id + "_ifr"); + + if (elm) + self.contentWindow = elm.contentWindow; + } + + return self.contentWindow; + }, + + getDoc : function() { + var self = this, win; + + if (!self.contentDocument) { + win = self.getWin(); + + if (win) + self.contentDocument = win.document; + } + + return self.contentDocument; + }, + + getBody : function() { + return this.bodyElement || this.getDoc().body; + }, + + convertURL : function(url, name, elm) { + var self = this, settings = self.settings; + + // Use callback instead + if (settings.urlconverter_callback) + return self.execCallback('urlconverter_callback', url, elm, true, name); + + // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs + if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) + return url; + + // Convert to relative + if (settings.relative_urls) + return self.documentBaseURI.toRelative(url); + + // Convert to absolute + url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); + + return url; + }, + + addVisual : function(elm) { + var self = this, settings = self.settings, dom = self.dom, cls; + + elm = elm || self.getBody(); + + if (!is(self.hasVisual)) + self.hasVisual = settings.visual; + + each(dom.select('table,a', elm), function(elm) { + var value; + + switch (elm.nodeName) { + case 'TABLE': + cls = settings.visual_table_class || 'mceItemTable'; + value = dom.getAttrib(elm, 'border'); + + if (!value || value == '0') { + if (self.hasVisual) + dom.addClass(elm, cls); + else + dom.removeClass(elm, cls); + } + + return; + + case 'A': + if (!dom.getAttrib(elm, 'href', false)) { + value = dom.getAttrib(elm, 'name') || elm.id; + cls = 'mceItemAnchor'; + + if (value) { + if (self.hasVisual) + dom.addClass(elm, cls); + else + dom.removeClass(elm, cls); + } + } + + return; + } + }); + + self.onVisualAid.dispatch(self, elm, self.hasVisual); + }, + + remove : function() { + var self = this, elm = self.getContainer(), doc = self.getDoc(); + + if (!self.removed) { + self.removed = 1; // Cancels post remove event execution + + // Fixed bug where IE has a blinking cursor left from the editor + if (isIE && doc) + doc.execCommand('SelectAll'); + + // We must save before we hide so Safari doesn't crash + self.save(); + + DOM.setStyle(self.id, 'display', self.orgDisplay); + + // Don't clear the window or document if content editable + // is enabled since other instances might still be present + if (!self.settings.content_editable) { + Event.unbind(self.getWin()); + Event.unbind(self.getDoc()); + } + + Event.unbind(self.getBody()); + Event.clear(elm); + + self.execCallback('remove_instance_callback', self); + self.onRemove.dispatch(self); + + // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command + self.onExecCommand.listeners = []; + + tinymce.remove(self); + DOM.remove(elm); + } + }, + + destroy : function(s) { + var t = this; + + // One time is enough + if (t.destroyed) + return; + + // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message + if (isGecko) { + Event.unbind(t.getDoc()); + Event.unbind(t.getWin()); + Event.unbind(t.getBody()); + } + + if (!s) { + tinymce.removeUnload(t.destroy); + tinyMCE.onBeforeUnload.remove(t._beforeUnload); + + // Manual destroy + if (t.theme && t.theme.destroy) + t.theme.destroy(); + + // Destroy controls, selection and dom + t.controlManager.destroy(); + t.selection.destroy(); + t.dom.destroy(); + } + + if (t.formElement) { + t.formElement.submit = t.formElement._mceOldSubmit; + t.formElement._mceOldSubmit = null; + } + + t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; + + if (t.selection) + t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; + + t.destroyed = 1; + }, + + // Internal functions + + _refreshContentEditable : function() { + var self = this, body, parent; + + // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again + if (self._isHidden()) { + body = self.getBody(); + parent = body.parentNode; + + parent.removeChild(body); + parent.appendChild(body); + + body.focus(); + } + }, + + _isHidden : function() { + var s; + + if (!isGecko) + return 0; + + // Weird, wheres that cursor selection? + s = this.selection.getSel(); + return (!s || !s.rangeCount || s.rangeCount === 0); + } + }); +})(tinymce); +(function(tinymce) { + var each = tinymce.each; + + tinymce.Editor.prototype.setupEvents = function() { + var self = this, settings = self.settings; + + // Add events to the editor + each([ + 'onPreInit', + + 'onBeforeRenderUI', + + 'onPostRender', + + 'onLoad', + + 'onInit', + + 'onRemove', + + 'onActivate', + + 'onDeactivate', + + 'onClick', + + 'onEvent', + + 'onMouseUp', + + 'onMouseDown', + + 'onDblClick', + + 'onKeyDown', + + 'onKeyUp', + + 'onKeyPress', + + 'onContextMenu', + + 'onSubmit', + + 'onReset', + + 'onPaste', + + 'onPreProcess', + + 'onPostProcess', + + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent', + + 'onLoadContent', + + 'onSaveContent', + + 'onNodeChange', + + 'onChange', + + 'onBeforeExecCommand', + + 'onExecCommand', + + 'onUndo', + + 'onRedo', + + 'onVisualAid', + + 'onSetProgressState', + + 'onSetAttrib' + ], function(name) { + self[name] = new tinymce.util.Dispatcher(self); + }); + + // Handle legacy cleanup_callback option + if (settings.cleanup_callback) { + self.onBeforeSetContent.add(function(ed, o) { + o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + }); + + self.onPreProcess.add(function(ed, o) { + if (o.set) + ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); + + if (o.get) + ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); + }); + + self.onPostProcess.add(function(ed, o) { + if (o.set) + o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + + if (o.get) + o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); + }); + } + + // Handle legacy save_callback option + if (settings.save_callback) { + self.onGetContent.add(function(ed, o) { + if (o.save) + o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); + }); + } + + // Handle legacy handle_event_callback option + if (settings.handle_event_callback) { + self.onEvent.add(function(ed, e, o) { + if (self.execCallback('handle_event_callback', e, ed, o) === false) { + e.preventDefault(); + e.stopPropagation(); + } + }); + } + + // Handle legacy handle_node_change_callback option + if (settings.handle_node_change_callback) { + self.onNodeChange.add(function(ed, cm, n) { + ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); + }); + } + + // Handle legacy save_callback option + if (settings.save_callback) { + self.onSaveContent.add(function(ed, o) { + var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); + + if (h) + o.content = h; + }); + } + + // Handle legacy onchange_callback option + if (settings.onchange_callback) { + self.onChange.add(function(ed, l) { + ed.execCallback('onchange_callback', ed, l); + }); + } + }; + + tinymce.Editor.prototype.bindNativeEvents = function() { + // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset + var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; + + nativeToDispatcherMap = { + mouseup : 'onMouseUp', + mousedown : 'onMouseDown', + click : 'onClick', + keyup : 'onKeyUp', + keydown : 'onKeyDown', + keypress : 'onKeyPress', + submit : 'onSubmit', + reset : 'onReset', + contextmenu : 'onContextMenu', + dblclick : 'onDblClick', + paste : 'onPaste' // Doesn't work in all browsers yet + }; + + // Handler that takes a native event and sends it out to a dispatcher like onKeyDown + function eventHandler(evt, args) { + var type = evt.type; + + // Don't fire events when it's removed + if (self.removed) + return; + + // Sends the native event out to a global dispatcher then to the specific event dispatcher + if (self.onEvent.dispatch(self, evt, args) !== false) { + self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); + } + }; + + // Opera doesn't support focus event for contentEditable elements so we need to fake it + function doOperaFocus(e) { + self.focus(true); + }; + + function nodeChanged(ed, e) { + // Normalize selection for example a|a becomes a|a except for Ctrl+A since it selects everything + if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { + self.selection.normalize(); + } + + self.nodeChanged(); + } + + // Add DOM events + each(nativeToDispatcherMap, function(dispatcherName, nativeName) { + var root = settings.content_editable ? self.getBody() : self.getDoc(); + + switch (nativeName) { + case 'contextmenu': + dom.bind(root, nativeName, eventHandler); + break; + + case 'paste': + dom.bind(self.getBody(), nativeName, eventHandler); + break; + + case 'submit': + case 'reset': + dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); + break; + + default: + dom.bind(root, nativeName, eventHandler); + } + }); + + // Set the editor as active when focused + dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { + self.focus(true); + }); + + if (settings.content_editable && tinymce.isOpera) { + dom.bind(self.getBody(), 'click', doOperaFocus); + dom.bind(self.getBody(), 'keydown', doOperaFocus); + } + + // Add node change handler + self.onMouseUp.add(nodeChanged); + + self.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; + + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey) + nodeChanged(ed, e); + }); + + // Add reset handler + self.onReset.add(function() { + self.setContent(self.startContent, {format : 'raw'}); + }); + + // Add shortcuts + function handleShortcut(e, execute) { + if (e.altKey || e.ctrlKey || e.metaKey) { + each(self.shortcuts, function(shortcut) { + var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; + + if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) + return; + + if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { + e.preventDefault(); + + if (execute) { + shortcut.func.call(shortcut.scope); + } + + return true; + } + }); + } + }; + + self.onKeyUp.add(function(ed, e) { + handleShortcut(e); + }); + + self.onKeyPress.add(function(ed, e) { + handleShortcut(e); + }); + + self.onKeyDown.add(function(ed, e) { + handleShortcut(e, true); + }); + + if (tinymce.isOpera) { + self.onClick.add(function(ed, e) { + e.preventDefault(); + }); + } + }; +})(tinymce); +(function(tinymce) { + // Added for compression purposes + var each = tinymce.each, undef, TRUE = true, FALSE = false; + + tinymce.EditorCommands = function(editor) { + var dom = editor.dom, + selection = editor.selection, + commands = {state: {}, exec : {}, value : {}}, + settings = editor.settings, + formatter = editor.formatter, + bookmark; + + function execCommand(command, ui, value) { + var func; + + command = command.toLowerCase(); + if (func = commands.exec[command]) { + func(command, ui, value); + return TRUE; + } + + return FALSE; + }; + + function queryCommandState(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.state[command]) + return func(command); + + return -1; + }; + + function queryCommandValue(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.value[command]) + return func(command); + + return FALSE; + }; + + function addCommands(command_list, type) { + type = type || 'exec'; + + each(command_list, function(callback, command) { + each(command.toLowerCase().split(','), function(command) { + commands[type][command] = callback; + }); + }); + }; + + // Expose public methods + tinymce.extend(this, { + execCommand : execCommand, + queryCommandState : queryCommandState, + queryCommandValue : queryCommandValue, + addCommands : addCommands + }); + + // Private methods + + function execNativeCommand(command, ui, value) { + if (ui === undef) + ui = FALSE; + + if (value === undef) + value = null; + + return editor.getDoc().execCommand(command, ui, value); + }; + + function isFormatMatch(name) { + return formatter.match(name); + }; + + function toggleFormat(name, value) { + formatter.toggle(name, value ? {value : value} : undef); + }; + + function storeSelection(type) { + bookmark = selection.getBookmark(type); + }; + + function restoreSelection() { + selection.moveToBookmark(bookmark); + }; + + // Add execCommand overrides + addCommands({ + // Ignore these, added for compatibility + 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, + + // Add undo manager logic + 'mceEndUndoLevel,mceAddUndoLevel' : function() { + editor.undoManager.add(); + }, + + 'Cut,Copy,Paste' : function(command) { + var doc = editor.getDoc(), failed; + + // Try executing the native command + try { + execNativeCommand(command); + } catch (ex) { + // Command failed + failed = TRUE; + } + + // Present alert message about clipboard access not being available + if (failed || !doc.queryCommandSupported(command)) { + if (tinymce.isGecko) { + editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { + if (state) + open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); + }); + } else + editor.windowManager.alert(editor.getLang('clipboard_no_support')); + } + }, + + // Override unlink command + unlink : function(command) { + if (selection.isCollapsed()) + selection.select(selection.getNode()); + + execNativeCommand(command); + selection.collapse(FALSE); + }, + + // Override justify commands to use the text formatter engine + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + var align = command.substring(7); + + // Remove all other alignments first + each('left,center,right,full'.split(','), function(name) { + if (align != name) + formatter.remove('align' + name); + }); + + toggleFormat('align' + align); + execCommand('mceRepaint'); + }, + + // Override list commands to fix WebKit bug + 'InsertUnorderedList,InsertOrderedList' : function(command) { + var listElm, listParent; + + execNativeCommand(command); + + // WebKit produces lists within block elements so we need to split them + // we will replace the native list creation logic to custom logic later on + // TODO: Remove this when the list creation logic is removed + listElm = dom.getParent(selection.getNode(), 'ol,ul'); + if (listElm) { + listParent = listElm.parentNode; + + // If list is within a text block then split that block + if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { + storeSelection(); + dom.split(listParent, listElm); + restoreSelection(); + } + } + }, + + // Override commands to use the text formatter engine + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + toggleFormat(command); + }, + + // Override commands to use the text formatter engine + 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { + toggleFormat(command, value); + }, + + FontSize : function(command, ui, value) { + var fontClasses, fontSizes; + + // Convert font size 1-7 to styles + if (value >= 1 && value <= 7) { + fontSizes = tinymce.explode(settings.font_size_style_values); + fontClasses = tinymce.explode(settings.font_size_classes); + + if (fontClasses) + value = fontClasses[value - 1] || value; + else + value = fontSizes[value - 1] || value; + } + + toggleFormat(command, value); + }, + + RemoveFormat : function(command) { + formatter.remove(command); + }, + + mceBlockQuote : function(command) { + toggleFormat('blockquote'); + }, + + FormatBlock : function(command, ui, value) { + return toggleFormat(value || 'p'); + }, + + mceCleanup : function() { + var bookmark = selection.getBookmark(); + + editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); + + selection.moveToBookmark(bookmark); + }, + + mceRemoveNode : function(command, ui, value) { + var node = value || selection.getNode(); + + // Make sure that the body node isn't removed + if (node != editor.getBody()) { + storeSelection(); + editor.dom.remove(node, TRUE); + restoreSelection(); + } + }, + + mceSelectNodeDepth : function(command, ui, value) { + var counter = 0; + + dom.getParent(selection.getNode(), function(node) { + if (node.nodeType == 1 && counter++ == value) { + selection.select(node); + return FALSE; + } + }, editor.getBody()); + }, + + mceSelectNode : function(command, ui, value) { + selection.select(value); + }, + + mceInsertContent : function(command, ui, value) { + var parser, serializer, parentNode, rootNode, fragment, args, + marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; + + //selection.normalize(); + + // Setup parser and serializer + parser = editor.parser; + serializer = new tinymce.html.Serializer({}, editor.schema); + bookmarkHtml = '\uFEFF'; + + // Run beforeSetContent handlers on the HTML to be inserted + args = {content: value, format: 'html'}; + selection.onBeforeSetContent.dispatch(selection, args); + value = args.content; + + // Add caret at end of contents if it's missing + if (value.indexOf('{$caret}') == -1) + value += '{$caret}'; + + // Replace the caret marker with a span bookmark element + value = value.replace(/\{\$caret\}/, bookmarkHtml); + + // Insert node maker where we will insert the new HTML and get it's parent + if (!selection.isCollapsed()) + editor.getDoc().execCommand('Delete', false, null); + + parentNode = selection.getNode(); + + // Parse the fragment within the context of the parent node + args = {context : parentNode.nodeName.toLowerCase()}; + fragment = parser.parse(value, args); + + // Move the caret to a more suitable location + node = fragment.lastChild; + if (node.attr('id') == 'mce_marker') { + marker = node; + + for (node = node.prev; node; node = node.walk(true)) { + if (node.type == 3 || !dom.isBlock(node.name)) { + node.parent.insert(marker, node, node.name === 'br'); + break; + } + } + } + + // If parser says valid we can insert the contents into that parent + if (!args.invalid) { + value = serializer.serialize(fragment); + + // Check if parent is empty or only has one BR element then set the innerHTML of that parent + node = parentNode.firstChild; + node2 = parentNode.lastChild; + if (!node || (node === node2 && node.nodeName === 'BR')) + dom.setHTML(parentNode, value); + else + selection.setContent(value); + } else { + // If the fragment was invalid within that context then we need + // to parse and process the parent it's inserted into + + // Insert bookmark node and get the parent + selection.setContent(bookmarkHtml); + parentNode = selection.getNode(); + rootNode = editor.getBody(); + + // Opera will return the document node when selection is in root + if (parentNode.nodeType == 9) + parentNode = node = rootNode; + else + node = parentNode; + + // Find the ancestor just before the root element + while (node !== rootNode) { + parentNode = node; + node = node.parentNode; + } + + // Get the outer/inner HTML depending on if we are in the root and parser and serialize that + value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); + value = serializer.serialize( + parser.parse( + // Need to replace by using a function since $ in the contents would otherwise be a problem + value.replace(//i, function() { + return serializer.serialize(fragment); + }) + ) + ); + + // Set the inner/outer HTML depending on if we are in the root or not + if (parentNode == rootNode) + dom.setHTML(rootNode, value); + else + dom.setOuterHTML(parentNode, value); + } + + marker = dom.get('mce_marker'); + + // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well + nodeRect = dom.getRect(marker); + viewPortRect = dom.getViewPort(editor.getWin()); + + // Check if node is out side the viewport if it is then scroll to it + if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || + (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { + viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); + viewportBodyElement.scrollLeft = nodeRect.x; + viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; + } + + // Move selection before marker and remove it + rng = dom.createRng(); + + // If previous sibling is a text node set the selection to the end of that node + node = marker.previousSibling; + if (node && node.nodeType == 3) { + rng.setStart(node, node.nodeValue.length); + } else { + // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node + rng.setStartBefore(marker); + rng.setEndBefore(marker); + } + + // Remove the marker node and set the new range + dom.remove(marker); + selection.setRng(rng); + + // Dispatch after event and add any visual elements needed + selection.onSetContent.dispatch(selection, args); + editor.addVisual(); + }, + + mceInsertRawHTML : function(command, ui, value) { + selection.setContent('tiny_mce_marker'); + editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); + }, + + mceToggleFormat : function(command, ui, value) { + toggleFormat(value); + }, + + mceSetContent : function(command, ui, value) { + editor.setContent(value); + }, + + 'Indent,Outdent' : function(command) { + var intentValue, indentUnit, value; + + // Setup indent level + intentValue = settings.indentation; + indentUnit = /[a-z%]+$/i.exec(intentValue); + intentValue = parseInt(intentValue); + + if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { + // If forced_root_blocks is set to false we don't have a block to indent so lets create a div + if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { + formatter.apply('div'); + } + + each(selection.getSelectedBlocks(), function(element) { + if (command == 'outdent') { + value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); + dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); + } else + dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); + }); + } else + execNativeCommand(command); + }, + + mceRepaint : function() { + var bookmark; + + if (tinymce.isGecko) { + try { + storeSelection(TRUE); + + if (selection.getSel()) + selection.getSel().selectAllChildren(editor.getBody()); + + selection.collapse(TRUE); + restoreSelection(); + } catch (ex) { + // Ignore + } + } + }, + + mceToggleFormat : function(command, ui, value) { + formatter.toggle(value); + }, + + InsertHorizontalRule : function() { + editor.execCommand('mceInsertContent', false, '
    '); + }, + + mceToggleVisualAid : function() { + editor.hasVisual = !editor.hasVisual; + editor.addVisual(); + }, + + mceReplaceContent : function(command, ui, value) { + editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); + }, + + mceInsertLink : function(command, ui, value) { + var anchor; + + if (typeof(value) == 'string') + value = {href : value}; + + anchor = dom.getParent(selection.getNode(), 'a'); + + // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. + value.href = value.href.replace(' ', '%20'); + + // Remove existing links if there could be child links or that the href isn't specified + if (!anchor || !value.href) { + formatter.remove('link'); + } + + // Apply new link to selection + if (value.href) { + formatter.apply('link', value, anchor); + } + }, + + selectAll : function() { + var root = dom.getRoot(), rng = dom.createRng(); + + // Old IE does a better job with selectall than new versions + if (selection.getRng().setStart) { + rng.setStart(root, 0); + rng.setEnd(root, root.childNodes.length); + + selection.setRng(rng); + } else { + execNativeCommand('SelectAll'); + } + } + }); + + // Add queryCommandState overrides + addCommands({ + // Override justify commands + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + var name = 'align' + command.substring(7); + var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); + var matches = tinymce.map(nodes, function(node) { + return !!formatter.matchNode(node, name); + }); + return tinymce.inArray(matches, TRUE) !== -1; + }, + + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + return isFormatMatch(command); + }, + + mceBlockQuote : function() { + return isFormatMatch('blockquote'); + }, + + Outdent : function() { + var node; + + if (settings.inline_styles) { + if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + + if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + } + + return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); + }, + + 'InsertUnorderedList,InsertOrderedList' : function(command) { + var list = dom.getParent(selection.getNode(), 'ul,ol'); + return list && + (command === 'insertunorderedlist' && list.tagName === 'UL' + || command === 'insertorderedlist' && list.tagName === 'OL'); + } + }, 'state'); + + // Add queryCommandValue overrides + addCommands({ + 'FontSize,FontName' : function(command) { + var value = 0, parent; + + if (parent = dom.getParent(selection.getNode(), 'span')) { + if (command == 'fontsize') + value = parent.style.fontSize; + else + value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + } + + return value; + } + }, 'value'); + + // Add undo manager logic + addCommands({ + Undo : function() { + editor.undoManager.undo(); + }, + + Redo : function() { + editor.undoManager.redo(); + } + }); + }; +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher; + + tinymce.UndoManager = function(editor) { + var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; + + function getContent() { + // Remove whitespace before/after and remove pure bogus nodes + return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); + }; + + function addNonTypingUndoLevel() { + self.typing = false; + self.add(); + }; + + // Create event instances + onBeforeAdd = new Dispatcher(self); + onAdd = new Dispatcher(self); + onUndo = new Dispatcher(self); + onRedo = new Dispatcher(self); + + // Pass though onAdd event from UndoManager to Editor as onChange + onAdd.add(function(undoman, level) { + if (undoman.hasUndo()) + return editor.onChange.dispatch(editor, level, undoman); + }); + + // Pass though onUndo event from UndoManager to Editor + onUndo.add(function(undoman, level) { + return editor.onUndo.dispatch(editor, level, undoman); + }); + + // Pass though onRedo event from UndoManager to Editor + onRedo.add(function(undoman, level) { + return editor.onRedo.dispatch(editor, level, undoman); + }); + + // Add initial undo level when the editor is initialized + editor.onInit.add(function() { + self.add(); + }); + + // Get position before an execCommand is processed + editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { + self.beforeChange(); + } + }); + + // Add undo level after an execCommand call was made + editor.onExecCommand.add(function(ed, cmd, ui, val, args) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { + self.add(); + } + }); + + // Add undo level on save contents, drag end and blur/focusout + editor.onSaveContent.add(addNonTypingUndoLevel); + editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); + editor.dom.bind(editor.getBody(), 'focusout', function(e) { + if (!editor.removed && self.typing) { + addNonTypingUndoLevel(); + } + }); + + editor.onKeyUp.add(function(editor, e) { + var keyCode = e.keyCode; + + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { + addNonTypingUndoLevel(); + } + }); + + editor.onKeyDown.add(function(editor, e) { + var keyCode = e.keyCode; + + // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { + if (self.typing) { + addNonTypingUndoLevel(); + } + + return; + } + + // If key isn't shift,ctrl,alt,capslock,metakey + if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { + self.beforeChange(); + self.typing = true; + self.add(); + } + }); + + editor.onMouseDown.add(function(editor, e) { + if (self.typing) { + addNonTypingUndoLevel(); + } + }); + + // Add keyboard shortcuts for undo/redo keys + editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); + editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); + + self = { + // Explose for debugging reasons + data : data, + + typing : false, + + onBeforeAdd: onBeforeAdd, + + onAdd : onAdd, + + onUndo : onUndo, + + onRedo : onRedo, + + beforeChange : function() { + beforeBookmark = editor.selection.getBookmark(2, true); + }, + + add : function(level) { + var i, settings = editor.settings, lastLevel; + + level = level || {}; + level.content = getContent(); + + self.onBeforeAdd.dispatch(self, level); + + // Add undo level if needed + lastLevel = data[index]; + if (lastLevel && lastLevel.content == level.content) + return null; + + // Set before bookmark on previous level + if (data[index]) + data[index].beforeBookmark = beforeBookmark; + + // Time to compress + if (settings.custom_undo_redo_levels) { + if (data.length > settings.custom_undo_redo_levels) { + for (i = 0; i < data.length - 1; i++) + data[i] = data[i + 1]; + + data.length--; + index = data.length; + } + } + + // Get a non intrusive normalized bookmark + level.bookmark = editor.selection.getBookmark(2, true); + + // Crop array if needed + if (index < data.length - 1) + data.length = index + 1; + + data.push(level); + index = data.length - 1; + + self.onAdd.dispatch(self, level); + editor.isNotDirty = 0; + + return level; + }, + + undo : function() { + var level, i; + + if (self.typing) { + self.add(); + self.typing = false; + } + + if (index > 0) { + level = data[--index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.beforeBookmark); + + self.onUndo.dispatch(self, level); + } + + return level; + }, + + redo : function() { + var level; + + if (index < data.length - 1) { + level = data[++index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.bookmark); + + self.onRedo.dispatch(self, level); + } + + return level; + }, + + clear : function() { + data = []; + index = 0; + self.typing = false; + }, + + hasUndo : function() { + return index > 0 || this.typing; + }, + + hasRedo : function() { + return index < data.length - 1 && !this.typing; + } + }; + + return self; + }; +})(tinymce); + +tinymce.ForceBlocks = function(editor) { + var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); + + function addRootBlocks() { + var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; + + if (!node || node.nodeType !== 1 || !settings.forced_root_block) + return; + + // Check if node is wrapped in block + while (node && node != rootNode) { + if (blockElements[node.nodeName]) + return; + + node = node.parentNode; + } + + // Get current selection + rng = selection.getRng(); + if (rng.setStart) { + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + } else { + // Force control range into text range + if (rng.item) { + node = rng.item(0); + rng = editor.getDoc().body.createTextRange(); + rng.moveToElementText(node); + } + + isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); + tmpRng = rng.duplicate(); + tmpRng.collapse(true); + startOffset = tmpRng.move('character', offset) * -1; + + if (!tmpRng.collapsed) { + tmpRng = rng.duplicate(); + tmpRng.collapse(false); + endOffset = (tmpRng.move('character', offset) * -1) - startOffset; + } + } + + // Wrap non block elements and text nodes + node = rootNode.firstChild; + while (node) { + if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { + // Remove empty text nodes + if (node.nodeType === 3 && node.nodeValue.length == 0) { + tempNode = node; + node = node.nextSibling; + dom.remove(tempNode); + continue; + } + + if (!rootBlockNode) { + rootBlockNode = dom.create(settings.forced_root_block); + node.parentNode.insertBefore(rootBlockNode, node); + wrapped = true; + } + + tempNode = node; + node = node.nextSibling; + rootBlockNode.appendChild(tempNode); + } else { + rootBlockNode = null; + node = node.nextSibling; + } + } + + if (wrapped) { + if (rng.setStart) { + rng.setStart(startContainer, startOffset); + rng.setEnd(endContainer, endOffset); + selection.setRng(rng); + } else { + // Only select if the previous selection was inside the document to prevent auto focus in quirks mode + if (isInEditorDocument) { + try { + rng = editor.getDoc().body.createTextRange(); + rng.moveToElementText(rootNode); + rng.collapse(true); + rng.moveStart('character', startOffset); + + if (endOffset > 0) + rng.moveEnd('character', endOffset); + + rng.select(); + } catch (ex) { + // Ignore + } + } + } + + editor.nodeChanged(); + } + }; + + // Force root blocks + if (settings.forced_root_block) { + editor.onKeyUp.add(addRootBlocks); + editor.onNodeChange.add(addRootBlocks); + } +}; + +(function(tinymce) { + // Shorten names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; + + tinymce.create('tinymce.ControlManager', { + ControlManager : function(ed, s) { + var t = this, i; + + s = s || {}; + t.editor = ed; + t.controls = {}; + t.onAdd = new tinymce.util.Dispatcher(t); + t.onPostRender = new tinymce.util.Dispatcher(t); + t.prefix = s.prefix || ed.id + '_'; + t._cls = {}; + + t.onPostRender.add(function() { + each(t.controls, function(c) { + c.postRender(); + }); + }); + }, + + get : function(id) { + return this.controls[this.prefix + id] || this.controls[id]; + }, + + setActive : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setActive(s); + + return c; + }, + + setDisabled : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setDisabled(s); + + return c; + }, + + add : function(c) { + var t = this; + + if (c) { + t.controls[c.id] = c; + t.onAdd.dispatch(c, t); + } + + return c; + }, + + createControl : function(name) { + var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; + + // Build control factory cache + if (!self.controlFactories) { + self.controlFactories = []; + each(editor.plugins, function(plugin) { + if (plugin.createControl) { + self.controlFactories.push(plugin); + } + }); + } + + // Create controls by asking cached factories + factories = self.controlFactories; + for (i = 0, l = factories.length; i < l; i++) { + ctrl = factories[i].createControl(name, self); + + if (ctrl) { + return self.add(ctrl); + } + } + + // Create sepearator + if (name === "|" || name === "separator") { + return self.createSeparator(); + } + + // Create control from button collection + if (editor.buttons && (ctrl = editor.buttons[name])) { + return self.createButton(name, ctrl); + } + + return self.add(ctrl); + }, + + createDropMenu : function(id, s, cc) { + var t = this, ed = t.editor, c, bm, v, cls; + + s = extend({ + 'class' : 'mceDropDown', + constrain : ed.settings.constrain_menus + }, s); + + s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; + if (v = ed.getParam('skin_variant')) + s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); + + s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; + + id = t.prefix + id; + cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; + c = t.controls[id] = new cls(id, s); + c.onAddItem.add(function(c, o) { + var s = o.settings; + + s.title = ed.getLang(s.title, s.title); + + if (!s.onclick) { + s.onclick = function(v) { + if (s.cmd) + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + }); + + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createListBox : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + + function useNativeListForAccessibility(ed) { + return ed.settings.use_accessible_selects && !tinymce.isGecko + } + + if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) + c = new tinymce.ui.NativeListBox(id, s); + else { + cls = cc || t._cls.listbox || tinymce.ui.ListBox; + c = new cls(id, s, ed); + } + + t.controls[id] = c; + + // Fix focus problem in Safari + if (tinymce.isWebKit) { + c.onPostRender.add(function(c, n) { + // Store bookmark on mousedown + Event.add(n, 'mousedown', function() { + ed.bookmark = ed.selection.getBookmark(1); + }); + + // Restore on focus, since it might be lost + Event.add(n, 'focus', function() { + ed.selection.moveToBookmark(ed.bookmark); + ed.bookmark = null; + }); + }); + } + + if (c.hideMenu) + ed.onMouseDown.add(c.hideMenu, c); + + return t.add(c); + }, + + createButton : function(id, s, cc) { + var t = this, ed = t.editor, o, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.label = ed.translate(s.label); + s.scope = s.scope || ed; + + if (!s.onclick && !s.menu_button) { + s.onclick = function() { + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + unavailable_prefix : ed.getLang('unavailable', ''), + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + if (s.menu_button) { + cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; + c = new cls(id, s, ed); + ed.onMouseDown.add(c.hideMenu, c); + } else { + cls = t._cls.button || tinymce.ui.Button; + c = new cls(id, s, ed); + } + + return t.add(c); + }, + + createMenuButton : function(id, s, cc) { + s = s || {}; + s.menu_button = 1; + + return this.createButton(id, s, cc); + }, + + createSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; + c = t.add(new cls(id, s, ed)); + ed.onMouseDown.add(c.hideMenu, c); + + return c; + }, + + createColorSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls, bm; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + if (tinymce.isIE) + bm = ed.selection.getBookmark(1); + + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + 'menu_class' : ed.getParam('skin') + 'Skin', + scope : s.scope, + more_colors_title : ed.getLang('more_colors') + }, s); + + id = t.prefix + id; + cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; + c = new cls(id, s, ed); + ed.onMouseDown.add(c.hideMenu, c); + + // Remove the menu element when the editor is removed + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createToolbar : function(id, s, cc) { + var c, t = this, cls; + + id = t.prefix + id; + cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createToolbarGroup : function(id, s, cc) { + var c, t = this, cls; + id = t.prefix + id; + cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createSeparator : function(cc) { + var cls = cc || this._cls.separator || tinymce.ui.Separator; + + return new cls(); + }, + + setControlType : function(n, c) { + return this._cls[n.toLowerCase()] = c; + }, + + destroy : function() { + each(this.controls, function(c) { + c.destroy(); + }); + + this.controls = null; + } + }); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + tinymce.create('tinymce.WindowManager', { + WindowManager : function(ed) { + var t = this; + + t.editor = ed; + t.onOpen = new Dispatcher(t); + t.onClose = new Dispatcher(t); + t.params = {}; + t.features = {}; + }, + + open : function(s, p) { + var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; + + // Default some options + s = s || {}; + p = p || {}; + sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window + sh = isOpera ? vp.h : screen.height; + s.name = s.name || 'mc_' + new Date().getTime(); + s.width = parseInt(s.width || 320); + s.height = parseInt(s.height || 240); + s.resizable = true; + s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); + s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); + p.inline = false; + p.mce_width = s.width; + p.mce_height = s.height; + p.mce_auto_focus = s.auto_focus; + + if (mo) { + if (isIE) { + s.center = true; + s.help = false; + s.dialogWidth = s.width + 'px'; + s.dialogHeight = s.height + 'px'; + s.scroll = s.scrollbars || false; + } + } + + // Build features string + each(s, function(v, k) { + if (tinymce.is(v, 'boolean')) + v = v ? 'yes' : 'no'; + + if (!/^(name|url)$/.test(k)) { + if (isIE && mo) + f += (f ? ';' : '') + k + ':' + v; + else + f += (f ? ',' : '') + k + '=' + v; + } + }); + + t.features = s; + t.params = p; + t.onOpen.dispatch(t, s, p); + + u = s.url || s.file; + u = tinymce._addVer(u); + + try { + if (isIE && mo) { + w = 1; + window.showModalDialog(u, window, f); + } else + w = window.open(u, s.name, f); + } catch (ex) { + // Ignore + } + + if (!w) + alert(t.editor.getLang('popup_blocked')); + }, + + close : function(w) { + w.close(); + this.onClose.dispatch(this); + }, + + createInstance : function(cl, a, b, c, d, e) { + var f = tinymce.resolve(cl); + + return new f(a, b, c, d, e); + }, + + confirm : function(t, cb, s, w) { + w = w || window; + + cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); + }, + + alert : function(tx, cb, s, w) { + var t = this; + + w = w || window; + w.alert(t._decode(t.editor.getLang(tx, tx))); + + if (cb) + cb.call(s || t); + }, + + resizeBy : function(dw, dh, win) { + win.resizeBy(dw, dh); + }, + + // Internal functions + + _decode : function(s) { + return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); + } + }); +}(tinymce)); +(function(tinymce) { + tinymce.Formatter = function(ed) { + var formats = {}, + each = tinymce.each, + dom = ed.dom, + selection = ed.selection, + TreeWalker = tinymce.dom.TreeWalker, + rangeUtils = new tinymce.dom.RangeUtils(dom), + isValid = ed.schema.isValidChild, + isArray = tinymce.isArray, + isBlock = dom.isBlock, + forcedRootBlock = ed.settings.forced_root_block, + nodeIndex = dom.nodeIndex, + INVISIBLE_CHAR = '\uFEFF', + MCE_ATTR_RE = /^(src|href|style)$/, + FALSE = false, + TRUE = true, + formatChangeData, + undef, + getContentEditable = dom.getContentEditable; + + function isTextBlock(name) { + if (name.nodeType) { + name = name.nodeName; + } + + return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; + } + + function getParents(node, selector) { + return dom.getParents(node, selector, dom.getRoot()); + }; + + function isCaretNode(node) { + return node.nodeType === 1 && node.id === '_mce_caret'; + }; + + function defaultFormats() { + register({ + alignleft : [ + {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} + ], + + aligncenter : [ + {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, + {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, + {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} + ], + + alignright : [ + {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} + ], + + alignfull : [ + {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} + ], + + bold : [ + {inline : 'strong', remove : 'all'}, + {inline : 'span', styles : {fontWeight : 'bold'}}, + {inline : 'b', remove : 'all'} + ], + + italic : [ + {inline : 'em', remove : 'all'}, + {inline : 'span', styles : {fontStyle : 'italic'}}, + {inline : 'i', remove : 'all'} + ], + + underline : [ + {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, + {inline : 'u', remove : 'all'} + ], + + strikethrough : [ + {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, + {inline : 'strike', remove : 'all'} + ], + + forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, + hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, + fontname : {inline : 'span', styles : {fontFamily : '%value'}}, + fontsize : {inline : 'span', styles : {fontSize : '%value'}}, + fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, + blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, + subscript : {inline : 'sub'}, + superscript : {inline : 'sup'}, + + link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, + onmatch : function(node) { + return true; + }, + + onformat : function(elm, fmt, vars) { + each(vars, function(value, key) { + dom.setAttrib(elm, key, value); + }); + } + }, + + removeformat : [ + {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, + {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, + {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} + ] + }); + + // Register default block formats + each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { + register(name, {block : name, remove : 'all'}); + }); + + // Register user defined formats + register(ed.settings.formats); + }; + + function addKeyboardShortcuts() { + // Add some inline shortcuts + ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); + ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); + ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); + + // BlockFormat shortcuts keys + for (var i = 1; i <= 6; i++) { + ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); + } + + ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); + ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); + ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); + }; + + // Public functions + + function get(name) { + return name ? formats[name] : formats; + }; + + function register(name, format) { + if (name) { + if (typeof(name) !== 'string') { + each(name, function(format, name) { + register(name, format); + }); + } else { + // Force format into array and add it to internal collection + format = format.length ? format : [format]; + + each(format, function(format) { + // Set deep to false by default on selector formats this to avoid removing + // alignment on images inside paragraphs when alignment is changed on paragraphs + if (format.deep === undef) + format.deep = !format.selector; + + // Default to true + if (format.split === undef) + format.split = !format.selector || format.inline; + + // Default to true + if (format.remove === undef && format.selector && !format.inline) + format.remove = 'none'; + + // Mark format as a mixed format inline + block level + if (format.selector && format.inline) { + format.mixed = true; + format.block_expand = true; + } + + // Split classes if needed + if (typeof(format.classes) === 'string') + format.classes = format.classes.split(/\s+/); + }); + + formats[name] = format; + } + } + }; + + var getTextDecoration = function(node) { + var decoration; + + ed.dom.getParent(node, function(n) { + decoration = ed.dom.getStyle(n, 'text-decoration'); + return decoration && decoration !== 'none'; + }); + + return decoration; + }; + + var processUnderlineAndColor = function(node) { + var textDecoration; + if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { + textDecoration = getTextDecoration(node.parentNode); + if (ed.dom.getStyle(node, 'color') && textDecoration) { + ed.dom.setStyle(node, 'text-decoration', textDecoration); + } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { + ed.dom.setStyle(node, 'text-decoration', null); + } + } + }; + + function apply(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); + + function setElementFormat(elm, fmt) { + fmt = fmt || format; + + if (elm) { + if (fmt.onformat) { + fmt.onformat(elm, fmt, vars, node); + } + + each(fmt.styles, function(value, name) { + dom.setStyle(elm, name, replaceVars(value, vars)); + }); + + each(fmt.attributes, function(value, name) { + dom.setAttrib(elm, name, replaceVars(value, vars)); + }); + + each(fmt.classes, function(value) { + value = replaceVars(value, vars); + + if (!dom.hasClass(elm, value)) + dom.addClass(elm, value); + }); + } + }; + function adjustSelectionToVisibleSelection() { + function findSelectionEnd(start, end) { + var walker = new TreeWalker(end); + for (node = walker.current(); node; node = walker.prev()) { + if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { + return node; + } + } + }; + + // Adjust selection so that a end container with a end offset of zero is not included in the selection + // as this isn't visible to the user. + var rng = ed.selection.getRng(); + var start = rng.startContainer; + var end = rng.endContainer; + + if (start != end && rng.endOffset === 0) { + var newEnd = findSelectionEnd(start, end); + var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; + + rng.setEnd(newEnd, endOffset); + } + + return rng; + } + + function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ + var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; + + // find the index of the first child list. + each(node.childNodes, function(n, index) { + if (n.nodeName === "UL" || n.nodeName === "OL") { + listIndex = index; + list = n; + return false; + } + }); + + // get the index of the bookmarks + each(node.childNodes, function(n, index) { + if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { + if (n.id == bookmark.id + "_start") { + startIndex = index; + } else if (n.id == bookmark.id + "_end") { + endIndex = index; + } + } + }); + + // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally + if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { + each(tinymce.grep(node.childNodes), process); + return 0; + } else { + currentWrapElm = dom.clone(wrapElm, FALSE); + + // create a list of the nodes on the same side of the list as the selection + each(tinymce.grep(node.childNodes), function(n, index) { + if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { + nodes.push(n); + n.parentNode.removeChild(n); + } + }); + + // insert the wrapping element either before or after the list. + if (startIndex < listIndex) { + node.insertBefore(currentWrapElm, list); + } else if (startIndex > listIndex) { + node.insertBefore(currentWrapElm, list.nextSibling); + } + + // add the new nodes to the list. + newWrappers.push(currentWrapElm); + + each(nodes, function(node) { + currentWrapElm.appendChild(node); + }); + + return currentWrapElm; + } + }; + + function applyRngStyle(rng, bookmark, node_specific) { + var newWrappers = [], wrapName, wrapElm, contentEditable = true; + + // Setup wrapper element + wrapName = format.inline || format.block; + wrapElm = dom.create(wrapName); + setElementFormat(wrapElm); + + rangeUtils.walk(rng, function(nodes) { + var currentWrapElm; + + function process(node) { + var nodeName, parentName, found, hasContentEditableState, lastContentEditable; + + lastContentEditable = contentEditable; + nodeName = node.nodeName.toLowerCase(); + parentName = node.parentNode.nodeName.toLowerCase(); + + // Node has a contentEditable value + if (node.nodeType === 1 && getContentEditable(node)) { + lastContentEditable = contentEditable; + contentEditable = getContentEditable(node) === "true"; + hasContentEditableState = true; // We don't want to wrap the container only it's children + } + + // Stop wrapping on br elements + if (isEq(nodeName, 'br')) { + currentWrapElm = 0; + + // Remove any br elements when we wrap things + if (format.block) + dom.remove(node); + + return; + } + + // If node is wrapper type + if (format.wrapper && matchNode(node, name, vars)) { + currentWrapElm = 0; + return; + } + + // Can we rename the block + if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { + node = dom.rename(node, wrapName); + setElementFormat(node); + newWrappers.push(node); + currentWrapElm = 0; + return; + } + + // Handle selector patterns + if (format.selector) { + // Look for matching formats + each(formatList, function(format) { + // Check collapsed state if it exists + if ('collapsed' in format && format.collapsed !== isCollapsed) { + return; + } + + if (dom.is(node, format.selector) && !isCaretNode(node)) { + setElementFormat(node, format); + found = true; + } + }); + + // Continue processing if a selector match wasn't found and a inline element is defined + if (!format.inline || found) { + currentWrapElm = 0; + return; + } + } + + // Is it valid to wrap this item + if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && + !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) { + // Start wrapping + if (!currentWrapElm) { + // Wrap the node + currentWrapElm = dom.clone(wrapElm, FALSE); + node.parentNode.insertBefore(currentWrapElm, node); + newWrappers.push(currentWrapElm); + } + + currentWrapElm.appendChild(node); + } else if (nodeName == 'li' && bookmark) { + // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. + currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); + } else { + // Start a new wrapper for possible children + currentWrapElm = 0; + + each(tinymce.grep(node.childNodes), process); + + if (hasContentEditableState) { + contentEditable = lastContentEditable; // Restore last contentEditable state from stack + } + + // End the last wrapper + currentWrapElm = 0; + } + }; + + // Process siblings from range + each(nodes, process); + }); + + // Wrap links inside as well, for example color inside a link when the wrapper is around the link + if (format.wrap_links === false) { + each(newWrappers, function(node) { + function process(node) { + var i, currentWrapElm, children; + + if (node.nodeName === 'A') { + currentWrapElm = dom.clone(wrapElm, FALSE); + newWrappers.push(currentWrapElm); + + children = tinymce.grep(node.childNodes); + for (i = 0; i < children.length; i++) + currentWrapElm.appendChild(children[i]); + + node.appendChild(currentWrapElm); + } + + each(tinymce.grep(node.childNodes), process); + }; + + process(node); + }); + } + + // Cleanup + + each(newWrappers, function(node) { + var childCount; + + function getChildCount(node) { + var count = 0; + + each(node.childNodes, function(node) { + if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) + count++; + }); + + return count; + }; + + function mergeStyles(node) { + var child, clone; + + each(node.childNodes, function(node) { + if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { + child = node; + return FALSE; // break loop + } + }); + + // If child was found and of the same type as the current node + if (child && matchName(child, format)) { + clone = dom.clone(child, FALSE); + setElementFormat(clone); + + dom.replace(clone, node, TRUE); + dom.remove(child, 1); + } + + return clone || node; + }; + + childCount = getChildCount(node); + + // Remove empty nodes but only if there is multiple wrappers and they are not block + // elements so never remove single

    since that would remove the currrent empty block element where the caret is at + if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { + dom.remove(node, 1); + return; + } + + if (format.inline || format.wrapper) { + // Merges the current node with it's children of similar type to reduce the number of elements + if (!format.exact && childCount === 1) + node = mergeStyles(node); + + // Remove/merge children + each(formatList, function(format) { + // Merge all children of similar type will move styles from child to parent + // this: text + // will become: text + each(dom.select(format.inline, node), function(child) { + var parent; + + // When wrap_links is set to false we don't want + // to remove the format on children within links + if (format.wrap_links === false) { + parent = child.parentNode; + + do { + if (parent.nodeName === 'A') + return; + } while (parent = parent.parentNode); + } + + removeFormat(format, vars, child, format.exact ? child : null); + }); + }); + + // Remove child if direct parent is of same type + if (matchNode(node.parentNode, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + + // Look for parent with similar style format + if (format.merge_with_parents) { + dom.getParent(node.parentNode, function(parent) { + if (matchNode(parent, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + }); + } + + // Merge next and previous siblings if they are similar texttext becomes texttext + if (node && format.merge_siblings !== false) { + node = mergeSiblings(getNonWhiteSpaceSibling(node), node); + node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); + } + } + }); + }; + + if (format) { + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + applyRngStyle(expandRng(rng, formatList), null, true); + } else { + applyRngStyle(node, null, true); + } + } else { + if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + // Obtain selection node before selection is unselected by applyRngStyle() + var curSelNode = ed.selection.getNode(); + + // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false + // It's kind of a hack but people should be using the default block type P since all desktop editors work that way + if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { + apply(formatList[0].defaultBlock); + } + + // Apply formatting to selection + ed.selection.setRng(adjustSelectionToVisibleSelection()); + bookmark = selection.getBookmark(); + applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); + + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (format.styles && (format.styles.color || format.styles.textDecoration)) { + tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); + processUnderlineAndColor(curSelNode); + } + + selection.moveToBookmark(bookmark); + moveStart(selection.getRng(TRUE)); + ed.nodeChanged(); + } else + performCaretAction('apply', name, vars); + } + } + }; + + function remove(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; + + // Merges the styles for each node + function process(node) { + var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; + + // Skip on text nodes as they have neither format to remove nor children + if (node.nodeType === 3) { + return; + } + + // Node has a contentEditable value + if (node.nodeType === 1 && getContentEditable(node)) { + lastContentEditable = contentEditable; + contentEditable = getContentEditable(node) === "true"; + hasContentEditableState = true; // We don't want to wrap the container only it's children + } + + // Grab the children first since the nodelist might be changed + children = tinymce.grep(node.childNodes); + + // Process current node + if (contentEditable && !hasContentEditableState) { + for (i = 0, l = formatList.length; i < l; i++) { + if (removeFormat(formatList[i], vars, node, node)) + break; + } + } + + // Process the children + if (format.deep) { + if (children.length) { + for (i = 0, l = children.length; i < l; i++) + process(children[i]); + + if (hasContentEditableState) { + contentEditable = lastContentEditable; // Restore last contentEditable state from stack + } + } + } + }; + + function findFormatRoot(container) { + var formatRoot; + + // Find format root + each(getParents(container.parentNode).reverse(), function(parent) { + var format; + + // Find format root element + if (!formatRoot && parent.id != '_start' && parent.id != '_end') { + // Is the node matching the format we are looking for + format = matchNode(parent, name, vars); + if (format && format.split !== false) + formatRoot = parent; + } + }); + + return formatRoot; + }; + + function wrapAndSplit(format_root, container, target, split) { + var parent, clone, lastClone, firstClone, i, formatRootParent; + + // Format root found then clone formats and split it + if (format_root) { + formatRootParent = format_root.parentNode; + + for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { + clone = dom.clone(parent, FALSE); + + for (i = 0; i < formatList.length; i++) { + if (removeFormat(formatList[i], vars, clone, clone)) { + clone = 0; + break; + } + } + + // Build wrapper node + if (clone) { + if (lastClone) + clone.appendChild(lastClone); + + if (!firstClone) + firstClone = clone; + + lastClone = clone; + } + } + + // Never split block elements if the format is mixed + if (split && (!format.mixed || !isBlock(format_root))) + container = dom.split(format_root, container); + + // Wrap container in cloned formats + if (lastClone) { + target.parentNode.insertBefore(lastClone, target); + firstClone.appendChild(target); + } + } + + return container; + }; + + function splitToFormatRoot(container) { + return wrapAndSplit(findFormatRoot(container), container, container, true); + }; + + function unwrap(start) { + var node = dom.get(start ? '_start' : '_end'), + out = node[start ? 'firstChild' : 'lastChild']; + + // If the end is placed within the start the result will be removed + // So this checks if the out node is a bookmark node if it is it + // checks for another more suitable node + if (isBookmarkNode(out)) + out = out[start ? 'firstChild' : 'lastChild']; + + dom.remove(node, true); + + return out; + }; + + function removeRngStyle(rng) { + var startContainer, endContainer, node; + + rng = expandRng(rng, formatList, TRUE); + + if (format.split) { + startContainer = getContainer(rng, TRUE); + endContainer = getContainer(rng); + + if (startContainer != endContainer) { + // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead + // This will happen if you tripple click a table cell and use remove formatting + if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { + startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; + } + + // Wrap start/end nodes in span element since these might be cloned/moved + startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); + endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); + + // Split start/end + splitToFormatRoot(startContainer); + splitToFormatRoot(endContainer); + + // Unwrap start/end to get real elements again + startContainer = unwrap(TRUE); + endContainer = unwrap(); + } else + startContainer = endContainer = splitToFormatRoot(startContainer); + + // Update range positions since they might have changed after the split operations + rng.startContainer = startContainer.parentNode; + rng.startOffset = nodeIndex(startContainer); + rng.endContainer = endContainer.parentNode; + rng.endOffset = nodeIndex(endContainer) + 1; + } + + // Remove items between start/end + rangeUtils.walk(rng, function(nodes) { + each(nodes, function(node) { + process(node); + + // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. + if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { + removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); + } + }); + }); + }; + + // Handle node + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + removeRngStyle(rng); + } else { + removeRngStyle(node); + } + + return; + } + + if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + bookmark = selection.getBookmark(); + removeRngStyle(selection.getRng(TRUE)); + selection.moveToBookmark(bookmark); + + // Check if start element still has formatting then we are at: "text|text" and need to move the start into the next text node + if (format.inline && match(name, vars, selection.getStart())) { + moveStart(selection.getRng(true)); + } + + ed.nodeChanged(); + } else + performCaretAction('remove', name, vars); + }; + + function toggle(name, vars, node) { + var fmt = get(name); + + if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) + remove(name, vars, node); + else + apply(name, vars, node); + }; + + function matchNode(node, name, vars, similar) { + var formatList = get(name), format, i, classes; + + function matchItems(node, format, item_name) { + var key, value, items = format[item_name], i; + + // Custom match + if (format.onmatch) { + return format.onmatch(node, format, item_name); + } + + // Check all items + if (items) { + // Non indexed object + if (items.length === undef) { + for (key in items) { + if (items.hasOwnProperty(key)) { + if (item_name === 'attributes') + value = dom.getAttrib(node, key); + else + value = getStyle(node, key); + + if (similar && !value && !format.exact) + return; + + if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) + return; + } + } + } else { + // Only one match needed for indexed arrays + for (i = 0; i < items.length; i++) { + if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) + return format; + } + } + } + + return format; + }; + + if (formatList && node) { + // Check each format in list + for (i = 0; i < formatList.length; i++) { + format = formatList[i]; + + // Name name, attributes, styles and classes + if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { + // Match classes + if (classes = format.classes) { + for (i = 0; i < classes.length; i++) { + if (!dom.hasClass(node, classes[i])) + return; + } + } + + return format; + } + } + } + }; + + function match(name, vars, node) { + var startNode; + + function matchParents(node) { + // Find first node with similar format settings + node = dom.getParent(node, function(node) { + return !!matchNode(node, name, vars, true); + }); + + // Do an exact check on the similar format element + return matchNode(node, name, vars); + }; + + // Check specified node + if (node) + return matchParents(node); + + // Check selected node + node = selection.getNode(); + if (matchParents(node)) + return TRUE; + + // Check start node if it's different + startNode = selection.getStart(); + if (startNode != node) { + if (matchParents(startNode)) + return TRUE; + } + + return FALSE; + }; + + function matchAll(names, vars) { + var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; + + // Check start of selection for formats + startElement = selection.getStart(); + dom.getParent(startElement, function(node) { + var i, name; + + for (i = 0; i < names.length; i++) { + name = names[i]; + + if (!checkedMap[name] && matchNode(node, name, vars)) { + checkedMap[name] = true; + matchedFormatNames.push(name); + } + } + }, dom.getRoot()); + + return matchedFormatNames; + }; + + function canApply(name) { + var formatList = get(name), startNode, parents, i, x, selector; + + if (formatList) { + startNode = selection.getStart(); + parents = getParents(startNode); + + for (x = formatList.length - 1; x >= 0; x--) { + selector = formatList[x].selector; + + // Format is not selector based, then always return TRUE + if (!selector) + return TRUE; + + for (i = parents.length - 1; i >= 0; i--) { + if (dom.is(parents[i], selector)) + return TRUE; + } + } + } + + return FALSE; + }; + + function formatChanged(formats, callback, similar) { + var currentFormats; + + // Setup format node change logic + if (!formatChangeData) { + formatChangeData = {}; + currentFormats = {}; + + ed.onNodeChange.addToTop(function(ed, cm, node) { + var parents = getParents(node), matchedFormats = {}; + + // Check for new formats + each(formatChangeData, function(callbacks, format) { + each(parents, function(node) { + if (matchNode(node, format, {}, callbacks.similar)) { + if (!currentFormats[format]) { + // Execute callbacks + each(callbacks, function(callback) { + callback(true, {node: node, format: format, parents: parents}); + }); + + currentFormats[format] = callbacks; + } + + matchedFormats[format] = callbacks; + return false; + } + }); + }); + + // Check if current formats still match + each(currentFormats, function(callbacks, format) { + if (!matchedFormats[format]) { + delete currentFormats[format]; + + each(callbacks, function(callback) { + callback(false, {node: node, format: format, parents: parents}); + }); + } + }); + }); + } + + // Add format listeners + each(formats.split(','), function(format) { + if (!formatChangeData[format]) { + formatChangeData[format] = []; + formatChangeData[format].similar = similar; + } + + formatChangeData[format].push(callback); + }); + + return this; + }; + + // Expose to public + tinymce.extend(this, { + get : get, + register : register, + apply : apply, + remove : remove, + toggle : toggle, + match : match, + matchAll : matchAll, + matchNode : matchNode, + canApply : canApply, + formatChanged: formatChanged + }); + + // Initialize + defaultFormats(); + addKeyboardShortcuts(); + + // Private functions + + function matchName(node, format) { + // Check for inline match + if (isEq(node, format.inline)) + return TRUE; + + // Check for block match + if (isEq(node, format.block)) + return TRUE; + + // Check for selector match + if (format.selector) + return dom.is(node, format.selector); + }; + + function isEq(str1, str2) { + str1 = str1 || ''; + str2 = str2 || ''; + + str1 = '' + (str1.nodeName || str1); + str2 = '' + (str2.nodeName || str2); + + return str1.toLowerCase() == str2.toLowerCase(); + }; + + function getStyle(node, name) { + var styleVal = dom.getStyle(node, name); + + // Force the format to hex + if (name == 'color' || name == 'backgroundColor') + styleVal = dom.toHex(styleVal); + + // Opera will return bold as 700 + if (name == 'fontWeight' && styleVal == 700) + styleVal = 'bold'; + + return '' + styleVal; + }; + + function replaceVars(value, vars) { + if (typeof(value) != "string") + value = value(vars); + else if (vars) { + value = value.replace(/%(\w+)/g, function(str, name) { + return vars[name] || str; + }); + } + + return value; + }; + + function isWhiteSpaceNode(node) { + return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); + }; + + function wrap(node, name, attrs) { + var wrapper = dom.create(name, attrs); + + node.parentNode.insertBefore(wrapper, node); + wrapper.appendChild(node); + + return wrapper; + }; + + function expandRng(rng, format, remove) { + var sibling, lastIdx, leaf, endPoint, + startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset; + + // This function walks up the tree if there is no siblings before/after the node + function findParentContainer(start) { + var container, parent, child, sibling, siblingName, root; + + container = parent = start ? startContainer : endContainer; + siblingName = start ? 'previousSibling' : 'nextSibling'; + root = dom.getRoot(); + + function isBogusBr(node) { + return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; + }; + + // If it's a text node and the offset is inside the text + if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { + if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { + return container; + } + } + + for (;;) { + // Stop expanding on block elements + if (!format[0].block_expand && isBlock(parent)) + return parent; + + // Walk left/right + for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { + if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { + return parent; + } + } + + // Check if we can move up are we at root level or body level + if (parent.parentNode == root) { + container = parent; + break; + } + + parent = parent.parentNode; + } + + return container; + }; + + // This function walks down the tree to find the leaf at the selection. + // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. + function findLeaf(node, offset) { + if (offset === undef) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + while (node && node.hasChildNodes()) { + node = node.childNodes[offset]; + if (node) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + return { node: node, offset: offset }; + } + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { + lastIdx = startContainer.childNodes.length - 1; + startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { + lastIdx = endContainer.childNodes.length - 1; + endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; + + if (endContainer.nodeType == 3) + endOffset = endContainer.nodeValue.length; + } + + // Expands the node to the closes contentEditable false element if it exists + function findParentContentEditable(node) { + var parent = node; + + while (parent) { + if (parent.nodeType === 1 && getContentEditable(parent)) { + return getContentEditable(parent) === "false" ? parent : node; + } + + parent = parent.parentNode; + } + + return node; + }; + + function findWordEndPoint(container, offset, start) { + var walker, node, pos, lastTextNode; + + function findSpace(node, offset) { + var pos, pos2, str = node.nodeValue; + + if (typeof(offset) == "undefined") { + offset = start ? str.length : 0; + } + + if (start) { + pos = str.lastIndexOf(' ', offset); + pos2 = str.lastIndexOf('\u00a0', offset); + pos = pos > pos2 ? pos : pos2; + + // Include the space on remove to avoid tag soup + if (pos !== -1 && !remove) { + pos++; + } + } else { + pos = str.indexOf(' ', offset); + pos2 = str.indexOf('\u00a0', offset); + pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; + } + + return pos; + }; + + if (container.nodeType === 3) { + pos = findSpace(container, offset); + + if (pos !== -1) { + return {container : container, offset : pos}; + } + + lastTextNode = container; + } + + // Walk the nodes inside the block + walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); + while (node = walker[start ? 'prev' : 'next']()) { + if (node.nodeType === 3) { + lastTextNode = node; + pos = findSpace(node); + + if (pos !== -1) { + return {container : node, offset : pos}; + } + } else if (isBlock(node)) { + break; + } + } + + if (lastTextNode) { + if (start) { + offset = 0; + } else { + offset = lastTextNode.length; + } + + return {container: lastTextNode, offset: offset}; + } + }; + + function findSelectorEndPoint(container, sibling_name) { + var parents, i, y, curFormat; + + if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) + container = container[sibling_name]; + + parents = getParents(container); + for (i = 0; i < parents.length; i++) { + for (y = 0; y < format.length; y++) { + curFormat = format[y]; + + // If collapsed state is set then skip formats that doesn't match that + if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) + continue; + + if (dom.is(parents[i], curFormat.selector)) + return parents[i]; + } + } + + return container; + }; + + function findBlockEndPoint(container, sibling_name, sibling_name2) { + var node; + + // Expand to block of similar type + if (!format[0].wrapper) + node = dom.getParent(container, format[0].block); + + // Expand to first wrappable block element or any block element + if (!node) + node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock); + + // Exclude inner lists from wrapping + if (node && format[0].wrapper) + node = getParents(node, 'ul,ol').reverse()[0] || node; + + // Didn't find a block element look for first/last wrappable element + if (!node) { + node = container; + + while (node[sibling_name] && !isBlock(node[sibling_name])) { + node = node[sibling_name]; + + // Break on BR but include it will be removed later on + // we can't remove it now since we need to check if it can be wrapped + if (isEq(node, 'br')) + break; + } + } + + return node || container; + }; + + // Expand to closest contentEditable element + startContainer = findParentContentEditable(startContainer); + endContainer = findParentContentEditable(endContainer); + + // Exclude bookmark nodes if possible + if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { + startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; + startContainer = startContainer.nextSibling || startContainer; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { + endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; + endContainer = endContainer.previousSibling || endContainer; + + if (endContainer.nodeType == 3) + endOffset = endContainer.length; + } + + if (format[0].inline) { + if (rng.collapsed) { + // Expand left to closest word boundery + endPoint = findWordEndPoint(startContainer, startOffset, true); + if (endPoint) { + startContainer = endPoint.container; + startOffset = endPoint.offset; + } + + // Expand right to closest word boundery + endPoint = findWordEndPoint(endContainer, endOffset); + if (endPoint) { + endContainer = endPoint.container; + endOffset = endPoint.offset; + } + } + + // Avoid applying formatting to a trailing space. + leaf = findLeaf(endContainer, endOffset); + if (leaf.node) { + while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) + leaf = findLeaf(leaf.node.previousSibling); + + if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && + leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { + + if (leaf.offset > 1) { + endContainer = leaf.node; + endContainer.splitText(leaf.offset - 1); + } + } + } + } + + // Move start/end point up the tree if the leaves are sharp and if we are in different containers + // Example * becomes !: !

    *texttext*

    ! + // This will reduce the number of wrapper elements that needs to be created + // Move start point up the tree + if (format[0].inline || format[0].block_expand) { + if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { + startContainer = findParentContainer(true); + } + + if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { + endContainer = findParentContainer(); + } + } + + // Expand start/end container to matching selector + if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { + // Find new startContainer/endContainer if there is better one + startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); + endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); + } + + // Expand start/end container to matching block element or text node + if (format[0].block || format[0].selector) { + // Find new startContainer/endContainer if there is better one + startContainer = findBlockEndPoint(startContainer, 'previousSibling'); + endContainer = findBlockEndPoint(endContainer, 'nextSibling'); + + // Non block element then try to expand up the leaf + if (format[0].block) { + if (!isBlock(startContainer)) + startContainer = findParentContainer(true); + + if (!isBlock(endContainer)) + endContainer = findParentContainer(); + } + } + + // Setup index for startContainer + if (startContainer.nodeType == 1) { + startOffset = nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + + // Setup index for endContainer + if (endContainer.nodeType == 1) { + endOffset = nodeIndex(endContainer) + 1; + endContainer = endContainer.parentNode; + } + + // Return new range like object + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + } + + function removeFormat(format, vars, node, compare_node) { + var i, attrs, stylesModified; + + // Check if node matches format + if (!matchName(node, format)) + return FALSE; + + // Should we compare with format attribs and styles + if (format.remove != 'all') { + // Remove styles + each(format.styles, function(value, name) { + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(getStyle(compare_node, name), value)) + dom.setStyle(node, name, ''); + + stylesModified = 1; + }); + + // Remove style attribute if it's empty + if (stylesModified && dom.getAttrib(node, 'style') == '') { + node.removeAttribute('style'); + node.removeAttribute('data-mce-style'); + } + + // Remove attributes + each(format.attributes, function(value, name) { + var valueOut; + + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { + // Keep internal classes + if (name == 'class') { + value = dom.getAttrib(node, name); + if (value) { + // Build new class value where everything is removed except the internal prefixed classes + valueOut = ''; + each(value.split(/\s+/), function(cls) { + if (/mce\w+/.test(cls)) + valueOut += (valueOut ? ' ' : '') + cls; + }); + + // We got some internal classes left + if (valueOut) { + dom.setAttrib(node, name, valueOut); + return; + } + } + } + + // IE6 has a bug where the attribute doesn't get removed correctly + if (name == "class") + node.removeAttribute('className'); + + // Remove mce prefixed attributes + if (MCE_ATTR_RE.test(name)) + node.removeAttribute('data-mce-' + name); + + node.removeAttribute(name); + } + }); + + // Remove classes + each(format.classes, function(value) { + value = replaceVars(value, vars); + + if (!compare_node || dom.hasClass(compare_node, value)) + dom.removeClass(node, value); + }); + + // Check for non internal attributes + attrs = dom.getAttribs(node); + for (i = 0; i < attrs.length; i++) { + if (attrs[i].nodeName.indexOf('_') !== 0) + return FALSE; + } + } + + // Remove the inline child if it's empty for example or + if (format.remove != 'none') { + removeNode(node, format); + return TRUE; + } + }; + + function removeNode(node, format) { + var parentNode = node.parentNode, rootBlockElm; + + function find(node, next, inc) { + node = getNonWhiteSpaceSibling(node, next, inc); + + return !node || (node.nodeName == 'BR' || isBlock(node)); + }; + + if (format.block) { + if (!forcedRootBlock) { + // Append BR elements if needed before we remove the block + if (isBlock(node) && !isBlock(parentNode)) { + if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) + node.insertBefore(dom.create('br'), node.firstChild); + + if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) + node.appendChild(dom.create('br')); + } + } else { + // Wrap the block in a forcedRootBlock if we are at the root of document + if (parentNode == dom.getRoot()) { + if (!format.list_block || !isEq(node, format.list_block)) { + each(tinymce.grep(node.childNodes), function(node) { + if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { + if (!rootBlockElm) + rootBlockElm = wrap(node, forcedRootBlock); + else + rootBlockElm.appendChild(node); + } else + rootBlockElm = 0; + }); + } + } + } + } + + // Never remove nodes that isn't the specified inline element if a selector is specified too + if (format.selector && format.inline && !isEq(format.inline, node)) + return; + + dom.remove(node, 1); + }; + + function getNonWhiteSpaceSibling(node, next, inc) { + if (node) { + next = next ? 'nextSibling' : 'previousSibling'; + + for (node = inc ? node : node[next]; node; node = node[next]) { + if (node.nodeType == 1 || !isWhiteSpaceNode(node)) + return node; + } + } + }; + + function isBookmarkNode(node) { + return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; + }; + + function mergeSiblings(prev, next) { + var marker, sibling, tmpSibling; + + function compareElements(node1, node2) { + // Not the same name + if (node1.nodeName != node2.nodeName) + return FALSE; + + function getAttribs(node) { + var attribs = {}; + + each(dom.getAttribs(node), function(attr) { + var name = attr.nodeName.toLowerCase(); + + // Don't compare internal attributes or style + if (name.indexOf('_') !== 0 && name !== 'style') + attribs[name] = dom.getAttrib(node, name); + }); + + return attribs; + }; + + function compareObjects(obj1, obj2) { + var value, name; + + for (name in obj1) { + // Obj1 has item obj2 doesn't have + if (obj1.hasOwnProperty(name)) { + value = obj2[name]; + + // Obj2 doesn't have obj1 item + if (value === undef) + return FALSE; + + // Obj2 item has a different value + if (obj1[name] != value) + return FALSE; + + // Delete similar value + delete obj2[name]; + } + } + + // Check if obj 2 has something obj 1 doesn't have + for (name in obj2) { + // Obj2 has item obj1 doesn't have + if (obj2.hasOwnProperty(name)) + return FALSE; + } + + return TRUE; + }; + + // Attribs are not the same + if (!compareObjects(getAttribs(node1), getAttribs(node2))) + return FALSE; + + // Styles are not the same + if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) + return FALSE; + + return TRUE; + }; + + function findElementSibling(node, sibling_name) { + for (sibling = node; sibling; sibling = sibling[sibling_name]) { + if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) + return node; + + if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) + return sibling; + } + + return node; + }; + + // Check if next/prev exists and that they are elements + if (prev && next) { + // If previous sibling is empty then jump over it + prev = findElementSibling(prev, 'previousSibling'); + next = findElementSibling(next, 'nextSibling'); + + // Compare next and previous nodes + if (compareElements(prev, next)) { + // Append nodes between + for (sibling = prev.nextSibling; sibling && sibling != next;) { + tmpSibling = sibling; + sibling = sibling.nextSibling; + prev.appendChild(tmpSibling); + } + + // Remove next node + dom.remove(next); + + // Move children into prev node + each(tinymce.grep(next.childNodes), function(node) { + prev.appendChild(node); + }); + + return prev; + } + } + + return next; + }; + + function getContainer(rng, start) { + var container, offset, lastIdx, walker; + + container = rng[start ? 'startContainer' : 'endContainer']; + offset = rng[start ? 'startOffset' : 'endOffset']; + + if (container.nodeType == 1) { + lastIdx = container.childNodes.length - 1; + + if (!start && offset) + offset--; + + container = container.childNodes[offset > lastIdx ? lastIdx : offset]; + } + + // If start text node is excluded then walk to the next node + if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { + container = new TreeWalker(container, ed.getBody()).next() || container; + } + + // If end text node is excluded then walk to the previous node + if (container.nodeType === 3 && !start && offset === 0) { + container = new TreeWalker(container, ed.getBody()).prev() || container; + } + + return container; + }; + + function performCaretAction(type, name, vars) { + var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; + + // Creates a caret container bogus element + function createCaretContainer(fill) { + var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); + + if (fill) { + caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); + } + + return caretContainer; + }; + + function isCaretContainerEmpty(node, nodes) { + while (node) { + if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { + return false; + } + + // Collect nodes + if (nodes && node.nodeType === 1) { + nodes.push(node); + } + + node = node.firstChild; + } + + return true; + }; + + // Returns any parent caret container element + function getParentCaretContainer(node) { + while (node) { + if (node.id === caretContainerId) { + return node; + } + + node = node.parentNode; + } + }; + + // Finds the first text node in the specified node + function findFirstTextNode(node) { + var walker; + + if (node) { + walker = new TreeWalker(node, node); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType === 3) { + return node; + } + } + } + }; + + // Removes the caret container for the specified node or all on the current document + function removeCaretContainer(node, move_caret) { + var child, rng; + + if (!node) { + node = getParentCaretContainer(selection.getStart()); + + if (!node) { + while (node = dom.get(caretContainerId)) { + removeCaretContainer(node, false); + } + } + } else { + rng = selection.getRng(true); + + if (isCaretContainerEmpty(node)) { + if (move_caret !== false) { + rng.setStartBefore(node); + rng.setEndBefore(node); + } + + dom.remove(node); + } else { + child = findFirstTextNode(node); + + if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { + child = child.deleteData(0, 1); + } + + dom.remove(node, 1); + } + + selection.setRng(rng); + } + }; + + // Applies formatting to the caret postion + function applyCaretFormat() { + var rng, caretContainer, textNode, offset, bookmark, container, text; + + rng = selection.getRng(true); + offset = rng.startOffset; + container = rng.startContainer; + text = container.nodeValue; + + caretContainer = getParentCaretContainer(selection.getStart()); + if (caretContainer) { + textNode = findFirstTextNode(caretContainer); + } + + // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character + if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name)); + rng = rangeUtils.split(rng); + + // Apply the format to the range + apply(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { + caretContainer = createCaretContainer(true); + textNode = caretContainer.firstChild; + + rng.insertNode(caretContainer); + offset = 1; + + apply(name, vars, caretContainer); + } else { + apply(name, vars, caretContainer); + } + + // Move selection to text node + selection.setCursorLocation(textNode, offset); + } + }; + + function removeCaretFormat() { + var rng = selection.getRng(true), container, offset, bookmark, + hasContentAfter, node, formatNode, parents = [], i, caretContainer; + + container = rng.startContainer; + offset = rng.startOffset; + node = container; + + if (container.nodeType == 3) { + if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { + hasContentAfter = true; + } + + node = node.parentNode; + } + + while (node) { + if (matchNode(node, name, vars)) { + formatNode = node; + break; + } + + if (node.nextSibling) { + hasContentAfter = true; + } + + parents.push(node); + node = node.parentNode; + } + + // Node doesn't have the specified format + if (!formatNode) { + return; + } + + // Is there contents after the caret then remove the format on the element + if (hasContentAfter) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name), true); + rng = rangeUtils.split(rng); + + // Remove the format from the range + remove(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + caretContainer = createCaretContainer(); + + node = caretContainer; + for (i = parents.length - 1; i >= 0; i--) { + node.appendChild(dom.clone(parents[i], false)); + node = node.firstChild; + } + + // Insert invisible character into inner most format element + node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); + node = node.firstChild; + + var block = dom.getParent(formatNode, isTextBlock); + + if (block && dom.isEmpty(block)) { + // Replace formatNode with caretContainer when removing format from empty block like

    |

    + formatNode.parentNode.replaceChild(caretContainer, formatNode); + } else { + // Insert caret container after the formated node + dom.insertAfter(caretContainer, formatNode); + } + + // Move selection to text node + selection.setCursorLocation(node, 1); + + // If the formatNode is empty, we can remove it safely. + if (dom.isEmpty(formatNode)) { + dom.remove(formatNode); + } + } + }; + + // Checks if the parent caret container node isn't empty if that is the case it + // will remove the bogus state on all children that isn't empty + function unmarkBogusCaretParents() { + var i, caretContainer, node; + + caretContainer = getParentCaretContainer(selection.getStart()); + if (caretContainer && !dom.isEmpty(caretContainer)) { + tinymce.walk(caretContainer, function(node) { + if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { + dom.setAttrib(node, 'data-mce-bogus', null); + } + }, 'childNodes'); + } + }; + + // Only bind the caret events once + if (!self._hasCaretEvents) { + // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements + ed.onBeforeGetContent.addToTop(function() { + var nodes = [], i; + + if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { + // Mark children + i = nodes.length; + while (i--) { + dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); + } + } + }); + + // Remove caret container on mouse up and on key up + tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { + ed[name].addToTop(function() { + removeCaretContainer(); + unmarkBogusCaretParents(); + }); + }); + + // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys + ed.onKeyDown.addToTop(function(ed, e) { + var keyCode = e.keyCode; + + if (keyCode == 8 || keyCode == 37 || keyCode == 39) { + removeCaretContainer(getParentCaretContainer(selection.getStart())); + } + + unmarkBogusCaretParents(); + }); + + // Remove bogus state if they got filled by contents using editor.selection.setContent + selection.onSetContent.add(unmarkBogusCaretParents); + + self._hasCaretEvents = true; + } + + // Do apply or remove caret format + if (type == "apply") { + applyCaretFormat(); + } else { + removeCaretFormat(); + } + }; + + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, isAtEndOfText, + walker, node, nodes, tmpNode; + + // Convert text node into index if possible + if (container.nodeType == 3 && offset >= container.nodeValue.length) { + // Get the parent container location and walk from there + offset = nodeIndex(container); + container = container.parentNode; + isAtEndOfText = true; + } + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1) { + nodes = container.childNodes; + container = nodes[Math.min(offset, nodes.length - 1)]; + walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); + + // If offset is at end of the parent node walk to the next one + if (offset > nodes.length - 1 || isAtEndOfText) + walker.next(); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + // IE has a "neat" feature where it moves the start node into the closest element + // we can avoid this by inserting an element before it and then remove it after we set the selection + tmpNode = dom.create('a', null, INVISIBLE_CHAR); + node.parentNode.insertBefore(tmpNode, node); + + // Set selection and remove tmpNode + rng.setStart(node, 0); + selection.setRng(rng); + dom.remove(tmpNode); + + return; + } + } + } + }; + }; +})(tinymce); + +tinymce.onAddEditor.add(function(tinymce, ed) { + var filters, fontSizes, dom, settings = ed.settings; + + function replaceWithSpan(node, styles) { + tinymce.each(styles, function(value, name) { + if (value) + dom.setStyle(node, name, value); + }); + + dom.rename(node, 'span'); + }; + + function convert(editor, params) { + dom = editor.dom; + + if (settings.convert_fonts_to_spans) { + tinymce.each(dom.select('font,u,strike', params.node), function(node) { + filters[node.nodeName.toLowerCase()](ed.dom, node); + }); + } + }; + + if (settings.inline_styles) { + fontSizes = tinymce.explode(settings.font_size_legacy_values); + + filters = { + font : function(dom, node) { + replaceWithSpan(node, { + backgroundColor : node.style.backgroundColor, + color : node.color, + fontFamily : node.face, + fontSize : fontSizes[parseInt(node.size, 10) - 1] + }); + }, + + u : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'underline' + }); + }, + + strike : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'line-through' + }); + } + }; + + ed.onPreProcess.add(convert); + ed.onSetContent.add(convert); + + ed.onInit.add(function() { + ed.selection.onSetContent.add(convert); + }); + } +}); + +(function(tinymce) { + var TreeWalker = tinymce.dom.TreeWalker; + + tinymce.EnterKey = function(editor) { + var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); + + function handleEnterKey(evt) { + var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, + newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; + + // Returns true if the block can be split into two blocks or not + function canSplitBlock(node) { + return node && + dom.isBlock(node) && + !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && + !/^(fixed|absolute)/i.test(node.style.position) && + dom.getContentEditable(node) !== "true"; + }; + + // Renders empty block on IE + function renderBlockOnIE(block) { + var oldRng; + + if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) { + oldRng = selection.getRng(); + block.appendChild(dom.create('span', null, '\u00a0')); + selection.select(block); + block.lastChild.outerHTML = ''; + selection.setRng(oldRng); + } + }; + + // Remove the first empty inline element of the block so this:

    x

    becomes this:

    x

    + function trimInlineElementsOnLeftSideOfBlock(block) { + var node = block, firstChilds = [], i; + + // Find inner most first child ex:

    *

    + while (node = node.firstChild) { + if (dom.isBlock(node)) { + return; + } + + if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { + firstChilds.push(node); + } + } + + i = firstChilds.length; + while (i--) { + node = firstChilds[i]; + if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { + dom.remove(node); + } else { + // Remove see #5381 + if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { + dom.remove(node); + } + } + } + }; + + // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image + function moveToCaretPosition(root) { + var walker, node, rng, y, viewPort, lastNode = root, tempElm; + + rng = dom.createRng(); + + if (root.hasChildNodes()) { + walker = new TreeWalker(root, root); + + while (node = walker.current()) { + if (node.nodeType == 3) { + rng.setStart(node, 0); + rng.setEnd(node, 0); + break; + } + + if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { + rng.setStartBefore(node); + rng.setEndBefore(node); + break; + } + + lastNode = node; + node = walker.next(); + } + + if (!node) { + rng.setStart(lastNode, 0); + rng.setEnd(lastNode, 0); + } + } else { + if (root.nodeName == 'BR') { + if (root.nextSibling && dom.isBlock(root.nextSibling)) { + // Trick on older IE versions to render the caret before the BR between two lists + if (!documentMode || documentMode < 9) { + tempElm = dom.create('br'); + root.parentNode.insertBefore(tempElm, root); + } + + rng.setStartBefore(root); + rng.setEndBefore(root); + } else { + rng.setStartAfter(root); + rng.setEndAfter(root); + } + } else { + rng.setStart(root, 0); + rng.setEnd(root, 0); + } + } + + selection.setRng(rng); + + // Remove tempElm created for old IE:s + dom.remove(tempElm); + + viewPort = dom.getViewPort(editor.getWin()); + + // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs + y = dom.getPos(root).y; + if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { + editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks + } + }; + + // Creates a new block element by cloning the current one or creating a new one if the name is specified + // This function will also copy any text formatting from the parent block and add it to the new one + function createNewBlock(name) { + var node = container, block, clonedNode, caretNode; + + block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); + caretNode = block; + + // Clone any parent styles + if (settings.keep_styles !== false) { + do { + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + // Never clone a caret containers + if (node.id == '_mce_caret') { + continue; + } + + clonedNode = node.cloneNode(false); + dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique + + if (block.hasChildNodes()) { + clonedNode.appendChild(block.firstChild); + block.appendChild(clonedNode); + } else { + caretNode = clonedNode; + block.appendChild(clonedNode); + } + } + } while (node = node.parentNode); + } + + // BR is needed in empty blocks on non IE browsers + if (!tinymce.isIE || tinymce.isIE11) { + caretNode.innerHTML = '
    '; + } + + return block; + }; + + // Returns true/false if the caret is at the start/end of the parent block element + function isCaretAtStartOrEndOfBlock(start) { + var walker, node, name; + + // Caret is in the middle of a text node like "a|b" + if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { + return false; + } + + // If after the last element in block node edge case for #5091 + if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { + return true; + } + + // If the caret if before the first element in parentBlock + if (start && container.nodeType == 1 && container == parentBlock.firstChild) { + return true; + } + + // Caret can be before/after a table + if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { + return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); + } + + // Walk the DOM and look for text nodes or non empty elements + walker = new TreeWalker(container, parentBlock); + + // If caret is in beginning or end of a text block then jump to the next/previous node + if (container.nodeType == 3) { + if (start && offset == 0) { + walker.prev(); + } else if (!start && offset == container.nodeValue.length) { + walker.next(); + } + } + + while (node = walker.current()) { + if (node.nodeType === 1) { + // Ignore bogus elements + if (!node.getAttribute('data-mce-bogus')) { + // Keep empty elements like but not trailing br:s like

    text|

    + name = node.nodeName.toLowerCase(); + if (nonEmptyElementsMap[name] && name !== 'br') { + return false; + } + } + } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { + return false; + } + + if (start) { + walker.prev(); + } else { + walker.next(); + } + } + + return true; + }; + + // Wraps any text nodes or inline elements in the specified forced root block name + function wrapSelfAndSiblingsInDefaultBlock(container, offset) { + var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; + + // Not in a block element or in a table cell or caption + parentBlock = dom.getParent(container, dom.isBlock); + if (!parentBlock || !canSplitBlock(parentBlock)) { + parentBlock = parentBlock || editableRoot; + + if (!parentBlock.hasChildNodes()) { + newBlock = dom.create(blockName); + parentBlock.appendChild(newBlock); + rng.setStart(newBlock, 0); + rng.setEnd(newBlock, 0); + return newBlock; + } + + // Find parent that is the first child of parentBlock + node = container; + while (node.parentNode != parentBlock) { + node = node.parentNode; + } + + // Loop left to find start node start wrapping at + while (node && !dom.isBlock(node)) { + startNode = node; + node = node.previousSibling; + } + + if (startNode) { + newBlock = dom.create(blockName); + startNode.parentNode.insertBefore(newBlock, startNode); + + // Start wrapping until we hit a block + node = startNode; + while (node && !dom.isBlock(node)) { + next = node.nextSibling; + newBlock.appendChild(node); + node = next; + } + + // Restore range to it's past location + rng.setStart(container, offset); + rng.setEnd(container, offset); + } + } + + return container; + }; + + // Inserts a block or br before/after or in the middle of a split list of the LI is empty + function handleEmptyListItem() { + function isFirstOrLastLi(first) { + var node = containerBlock[first ? 'firstChild' : 'lastChild']; + + // Find first/last element since there might be whitespace there + while (node) { + if (node.nodeType == 1) { + break; + } + + node = node[first ? 'nextSibling' : 'previousSibling']; + } + + return node === parentBlock; + }; + + newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); + + if (isFirstOrLastLi(true) && isFirstOrLastLi()) { + // Is first and last list item then replace the OL/UL with a text block + dom.replace(newBlock, containerBlock); + } else if (isFirstOrLastLi(true)) { + // First LI in list then remove LI and add text block before list + containerBlock.parentNode.insertBefore(newBlock, containerBlock); + } else if (isFirstOrLastLi()) { + // Last LI in list then temove LI and add text block after list + dom.insertAfter(newBlock, containerBlock); + renderBlockOnIE(newBlock); + } else { + // Middle LI in list the split the list and insert a text block in the middle + // Extract after fragment and insert it after the current block + tmpRng = rng.cloneRange(); + tmpRng.setStartAfter(parentBlock); + tmpRng.setEndAfter(containerBlock); + fragment = tmpRng.extractContents(); + dom.insertAfter(fragment, containerBlock); + dom.insertAfter(newBlock, containerBlock); + } + + dom.remove(parentBlock); + moveToCaretPosition(newBlock); + undoManager.add(); + }; + + // Walks the parent block to the right and look for any contents + function hasRightSideContent() { + var walker = new TreeWalker(container, parentBlock), node; + + while (node = walker.next()) { + if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { + return true; + } + } + } + + // Inserts a BR element if the forced_root_block option is set to false or empty string + function insertBr() { + var brElm, extraBr, marker; + + if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { + // Insert extra BR element at the end block elements + if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) { + brElm = dom.create('br'); + rng.insertNode(brElm); + rng.setStartAfter(brElm); + rng.setEndAfter(brElm); + extraBr = true; + } + } + + brElm = dom.create('br'); + rng.insertNode(brElm); + + // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it + if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { + brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); + } + + // Insert temp marker and scroll to that + marker = dom.create('span', {}, ' '); + brElm.parentNode.insertBefore(marker, brElm); + selection.scrollIntoView(marker); + dom.remove(marker); + + if (!extraBr) { + rng.setStartAfter(brElm); + rng.setEndAfter(brElm); + } else { + rng.setStartBefore(brElm); + rng.setEndBefore(brElm); + } + + selection.setRng(rng); + undoManager.add(); + }; + + // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element + function trimLeadingLineBreaks(node) { + do { + if (node.nodeType === 3) { + node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); + } + + node = node.firstChild; + } while (node); + }; + + function getEditableRoot(node) { + var root = dom.getRoot(), parent, editableRoot; + + // Get all parents until we hit a non editable parent or the root + parent = node; + while (parent !== root && dom.getContentEditable(parent) !== "false") { + if (dom.getContentEditable(parent) === "true") { + editableRoot = parent; + } + + parent = parent.parentNode; + } + + return parent !== root ? editableRoot : root; + }; + + // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block + function addBrToBlockIfNeeded(block) { + var lastChild; + + // IE will render the blocks correctly other browsers needs a BR + if (!tinymce.isIE || tinymce.isIE11) { + block.normalize(); // Remove empty text nodes that got left behind by the extract + + // Check if the block is empty or contains a floated last child + lastChild = block.lastChild; + if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { + dom.add(block, 'br'); + } + } + }; + + // Delete any selected contents + if (!rng.collapsed) { + editor.execCommand('Delete'); + return; + } + + // Event is blocked by some other handler for example the lists plugin + if (evt.isDefaultPrevented()) { + return; + } + + // Setup range items and newBlockName + container = rng.startContainer; + offset = rng.startOffset; + newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block; + newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; + documentMode = dom.doc.documentMode; + shiftKey = evt.shiftKey; + + // Resolve node index + if (container.nodeType == 1 && container.hasChildNodes()) { + isAfterLastNodeInContainer = offset > container.childNodes.length - 1; + container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; + if (isAfterLastNodeInContainer && container.nodeType == 3) { + offset = container.nodeValue.length; + } else { + offset = 0; + } + } + + // Get editable root node normaly the body element but sometimes a div or span + editableRoot = getEditableRoot(container); + + // If there is no editable root then enter is done inside a contentEditable false element + if (!editableRoot) { + return; + } + + undoManager.beforeChange(); + + // If editable root isn't block nor the root of the editor + if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { + if (!newBlockName || shiftKey) { + insertBr(); + } + + return; + } + + // Wrap the current node and it's sibling in a default block if it's needed. + // for example this text|text2 will become this

    text|text2

    + // This won't happen if root blocks are disabled or the shiftKey is pressed + if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) { + container = wrapSelfAndSiblingsInDefaultBlock(container, offset); + } + + // Find parent block and setup empty block paddings + parentBlock = dom.getParent(container, dom.isBlock); + containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; + + // Setup block names + parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 + containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 + + // Enter inside block contained within a LI then split or insert before/after LI + if (containerBlockName == 'LI' && !evt.ctrlKey) { + parentBlock = containerBlock; + parentBlockName = containerBlockName; + } + + // Handle enter in LI + if (parentBlockName == 'LI') { + if (!newBlockName && shiftKey) { + insertBr(); + return; + } + + // Handle enter inside an empty list item + if (dom.isEmpty(parentBlock)) { + // Let the list plugin or browser handle nested lists for now + if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { + return false; + } + + handleEmptyListItem(); + return; + } + } + + // Don't split PRE tags but insert a BR instead easier when writing code samples etc + if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { + if (!shiftKey) { + insertBr(); + return; + } + } else { + // If no root block is configured then insert a BR by default or if the shiftKey is pressed + if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) { + insertBr(); + return; + } + } + + // Default block name if it's not configured + newBlockName = newBlockName || 'P'; + + // Insert new block before/after the parent block depending on caret location + if (isCaretAtStartOrEndOfBlock()) { + // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup + if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { + newBlock = createNewBlock(newBlockName); + } else { + newBlock = createNewBlock(); + } + + // Split the current container block element if enter is pressed inside an empty inner block element + if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { + // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P + newBlock = dom.split(containerBlock, parentBlock); + } else { + dom.insertAfter(newBlock, parentBlock); + } + + moveToCaretPosition(newBlock); + } else if (isCaretAtStartOrEndOfBlock(true)) { + // Insert new block before + newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); + renderBlockOnIE(newBlock); + } else { + // Extract after fragment and insert it after the current block + tmpRng = rng.cloneRange(); + tmpRng.setEndAfter(parentBlock); + fragment = tmpRng.extractContents(); + trimLeadingLineBreaks(fragment); + newBlock = fragment.firstChild; + dom.insertAfter(fragment, parentBlock); + trimInlineElementsOnLeftSideOfBlock(newBlock); + addBrToBlockIfNeeded(parentBlock); + moveToCaretPosition(newBlock); + } + + dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique + undoManager.add(); + } + + editor.onKeyDown.add(function(ed, evt) { + if (evt.keyCode == 13) { + if (handleEnterKey(evt) !== false) { + evt.preventDefault(); + } + } + }); + }; +})(tinymce); + diff --git a/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/utils/editable_selects.js b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/utils/editable_selects.js new file mode 100644 index 0000000..4d9ffe2 --- /dev/null +++ b/data/treeio/treeio/static/js/tinymce/jscripts/tiny_mce/utils/editable_selects.js @@ -0,0 +1,70 @@ +/** + * editable_selects.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +var TinyMCE_EditableSelects = { + editSelectElm : null, + + init : function() { + var nl = document.getElementsByTagName("select"), i, d = document, o; + + for (i=0; i'; + h += ' '; + + return h; +} + +function updateColor(img_id, form_element_id) { + document.getElementById(img_id).style.backgroundColor = document.forms[0].elements[form_element_id].value; +} + +function setBrowserDisabled(id, state) { + var img = document.getElementById(id); + var lnk = document.getElementById(id + "_link"); + + if (lnk) { + if (state) { + lnk.setAttribute("realhref", lnk.getAttribute("href")); + lnk.removeAttribute("href"); + tinyMCEPopup.dom.addClass(img, 'disabled'); + } else { + if (lnk.getAttribute("realhref")) + lnk.setAttribute("href", lnk.getAttribute("realhref")); + + tinyMCEPopup.dom.removeClass(img, 'disabled'); + } + } +} + +function getBrowserHTML(id, target_form_element, type, prefix) { + var option = prefix + "_" + type + "_browser_callback", cb, html; + + cb = tinyMCEPopup.getParam(option, tinyMCEPopup.getParam("file_browser_callback")); + + if (!cb) + return ""; + + html = ""; + html += ''; + html += ' '; + + return html; +} + +function openBrowser(img_id, target_form_element, type, option) { + var img = document.getElementById(img_id); + + if (img.className != "mceButtonDisabled") + tinyMCEPopup.openBrowser(target_form_element, type, option); +} + +function selectByValue(form_obj, field_name, value, add_custom, ignore_case) { + if (!form_obj || !form_obj.elements[field_name]) + return; + + if (!value) + value = ""; + + var sel = form_obj.elements[field_name]; + + var found = false; + for (var i=0; i parseInt(v)) + st = this.mark(f, n); + } + } + + return st; + }, + + hasClass : function(n, c, d) { + return new RegExp('\\b' + c + (d ? '[0-9]+' : '') + '\\b', 'g').test(n.className); + }, + + getNum : function(n, c) { + c = n.className.match(new RegExp('\\b' + c + '([0-9]+)\\b', 'g'))[0]; + c = c.replace(/[^0-9]/g, ''); + + return c; + }, + + addClass : function(n, c, b) { + var o = this.removeClass(n, c); + n.className = b ? c + (o != '' ? (' ' + o) : '') : (o != '' ? (o + ' ') : '') + c; + }, + + removeClass : function(n, c) { + c = n.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)"), ' '); + return n.className = c != ' ' ? c : ''; + }, + + tags : function(f, s) { + return f.getElementsByTagName(s); + }, + + mark : function(f, n) { + var s = this.settings; + + this.addClass(n, s.invalid_cls); + n.setAttribute('aria-invalid', 'true'); + this.markLabels(f, n, s.invalid_cls); + + return false; + }, + + markLabels : function(f, n, ic) { + var nl, i; + + nl = this.tags(f, "label"); + for (i=0; i 1) { + $child = this._$clip.wrapInner("
    ").children(); + } + this._$view = $child.addClass("ui-scrollview-view"); + + this._$clip.css("overflow", this.options.scrollMethod === "scroll" ? "scroll" : "hidden"); + this._makePositioned(this._$clip); + + this._$view.css("overflow", "hidden"); + + // Turn off our faux scrollbars if we are using native scrolling + // to position the view. + + this.options.showScrollBars = this.options.scrollMethod === "scroll" ? false : this.options.showScrollBars; + + // We really don't need this if we are using a translate transformation + // for scrolling. We set it just in case the user wants to switch methods + // on the fly. + + this._makePositioned(this._$view); + this._$view.css({ left: 0, top: 0 }); + + this._sx = 0; + this._sy = 0; + + var direction = this.options.direction; + this._hTracker = (direction !== "y") ? new MomentumTracker(this.options) : null; + this._vTracker = (direction !== "x") ? new MomentumTracker(this.options) : null; + + this._timerInterval = 1000/this.options.fps; + this._timerID = 0; + + var self = this; + this._timerCB = function(){ self._handleMomentumScroll(); }; + + this._addBehaviors(); + }, + + _startMScroll: function(speedX, speedY) + { + this._stopMScroll(); + this._showScrollBars(); + + var keepGoing = false; + var duration = this.options.scrollDuration; + + this._$clip.trigger(this.options.startEventName); + + var ht = this._hTracker; + if (ht) + { + var c = this._$clip.width(); + var v = this._$view.width(); + ht.start(this._sx, speedX, duration, (v > c) ? -(v - c) : 0, 0); + keepGoing = !ht.done(); + } + + var vt = this._vTracker; + if (vt) + { + var c = this._$clip.height(); + var v = this._$view.height(); + vt.start(this._sy, speedY, duration, (v > c) ? -(v - c) : 0, 0); + keepGoing = keepGoing || !vt.done(); + } + + if (keepGoing) + this._timerID = setTimeout(this._timerCB, this._timerInterval); + else + this._stopMScroll(); + }, + + _stopMScroll: function() + { + if (this._timerID) + { + this._$clip.trigger(this.options.stopEventName); + clearTimeout(this._timerID); + } + this._timerID = 0; + + if (this._vTracker) + this._vTracker.reset(); + + if (this._hTracker) + this._hTracker.reset(); + + this._hideScrollBars(); + }, + + _handleMomentumScroll: function() + { + var keepGoing = false; + var v = this._$view; + + var x = 0, y = 0; + + var vt = this._vTracker; + if (vt) + { + vt.update(); + y = vt.getPosition(); + keepGoing = !vt.done(); + } + + var ht = this._hTracker; + if (ht) + { + ht.update(); + x = ht.getPosition(); + keepGoing = keepGoing || !ht.done(); + } + + this._setScrollPosition(x, y); + this._$clip.trigger(this.options.updateEventName, { x: x, y: y }); + + if (keepGoing) + this._timerID = setTimeout(this._timerCB, this._timerInterval); + else + this._stopMScroll(); + }, + + _setScrollPosition: function(x, y) + { + this._sx = x; + this._sy = y; + + var $v = this._$view; + + var sm = this.options.scrollMethod; + + switch (sm) + { + case "translate": + setElementTransform($v, x + "px", y + "px"); + break; + case "position": + $v.css({left: x + "px", top: y + "px"}); + break; + case "scroll": + var c = this._$clip[0]; + c.scrollLeft = -x; + c.scrollTop = -y; + break; + } + + var $vsb = this._$vScrollBar; + var $hsb = this._$hScrollBar; + + if ($vsb) + { + var $sbt = $vsb.find(".ui-scrollbar-thumb"); + if (sm === "translate") + setElementTransform($sbt, "0px", -y/$v.height() * $sbt.parent().height() + "px"); + else + $sbt.css("top", -y/$v.height()*100 + "%"); + } + + if ($hsb) + { + var $sbt = $hsb.find(".ui-scrollbar-thumb"); + if (sm === "translate") + setElementTransform($sbt, -x/$v.width() * $sbt.parent().width() + "px", "0px"); + else + $sbt.css("left", -x/$v.width()*100 + "%"); + } + }, + + scrollTo: function(x, y, duration) + { + this._stopMScroll(); + if (!duration) + return this._setScrollPosition(x, y); + + x = -x; + y = -y; + + var self = this; + var start = getCurrentTime(); + var efunc = $.easing["easeOutQuad"]; + var sx = this._sx; + var sy = this._sy; + var dx = x - sx; + var dy = y - sy; + var tfunc = function(){ + var elapsed = getCurrentTime() - start; + if (elapsed >= duration) + { + self._timerID = 0; + self._setScrollPosition(x, y); + } + else + { + var ec = efunc(elapsed/duration, elapsed, 0, 1, duration); + self._setScrollPosition(sx + (dx * ec), sy + (dy * ec)); + self._timerID = setTimeout(tfunc, self._timerInterval); + } + }; + + this._timerID = setTimeout(tfunc, this._timerInterval); + }, + + getScrollPosition: function() + { + return { x: -this._sx, y: -this._sy }; + }, + + _getScrollHierarchy: function() + { + var svh = []; + this._$clip.parents(".ui-scrollview-clip").each(function(){ + var d = $(this).data("scrollview"); + if (d) svh.unshift(d); + }); + return svh; + }, + + _getAncestorByDirection: function(dir) + { + var svh = this._getScrollHierarchy(); + var n = svh.length; + while (0 < n--) + { + var sv = svh[n]; + var svdir = sv.options.direction; + + if (!svdir || svdir == dir) + return sv; + } + return null; + }, + + _handleDragStart: function(e, ex, ey) + { + // Stop any scrolling of elements in our parent hierarcy. + $.each(this._getScrollHierarchy(),function(i,sv){ sv._stopMScroll(); }); + this._stopMScroll(); + + var c = this._$clip; + var v = this._$view; + + if (this.options.delayedClickEnabled) { + this._$clickEle = $(e.target).closest(this.options.delayedClickSelector); + } + this._lastX = ex; + this._lastY = ey; + this._doSnapBackX = false; + this._doSnapBackY = false; + this._speedX = 0; + this._speedY = 0; + this._directionLock = ""; + this._didDrag = false; + + if (this._hTracker) + { + var cw = parseInt(c.css("width"), 10); + var vw = parseInt(v.css("width"), 10); + this._maxX = cw - vw; + if (this._maxX > 0) this._maxX = 0; + if (this._$hScrollBar) + this._$hScrollBar.find(".ui-scrollbar-thumb").css("width", (cw >= vw ? "100%" : Math.floor(cw/vw*100)+ "%")); + } + + if (this._vTracker) + { + var ch = parseInt(c.css("height"), 10); + var vh = parseInt(v.css("height"), 10); + this._maxY = ch - vh; + if (this._maxY > 0) this._maxY = 0; + if (this._$vScrollBar) + this._$vScrollBar.find(".ui-scrollbar-thumb").css("height", (ch >= vh ? "100%" : Math.floor(ch/vh*100)+ "%")); + } + + var svdir = this.options.direction; + + this._pageDelta = 0; + this._pageSize = 0; + this._pagePos = 0; + + if (this.options.pagingEnabled && (svdir === "x" || svdir === "y")) + { + this._pageSize = svdir === "x" ? cw : ch; + this._pagePos = svdir === "x" ? this._sx : this._sy; + this._pagePos -= this._pagePos % this._pageSize; + } + this._lastMove = 0; + this._enableTracking(); + + // If we're using mouse events, we need to prevent the default + // behavior to suppress accidental selection of text, etc. We + // can't do this on touch devices because it will disable the + // generation of "click" events. + // + // XXX: We should test if this has an effect on links! - kin + + if (this.options.eventType == "mouse" || this.options.delayedClickEnabled) + e.preventDefault(); + e.stopPropagation(); + }, + + _propagateDragMove: function(sv, e, ex, ey, dir) + { + this._hideScrollBars(); + this._disableTracking(); + sv._handleDragStart(e,ex,ey); + sv._directionLock = dir; + sv._didDrag = this._didDrag; + }, + + _handleDragMove: function(e, ex, ey) + { + this._lastMove = getCurrentTime(); + + var v = this._$view; + + var dx = ex - this._lastX; + var dy = ey - this._lastY; + var svdir = this.options.direction; + + if (!this._directionLock) + { + var x = Math.abs(dx); + var y = Math.abs(dy); + var mt = this.options.moveThreshold; + + if (x < mt && y < mt) { + return false; + } + + var dir = null; + var r = 0; + if (x < y && (x/y) < 0.5) { + dir = "y"; + } + else if (x > y && (y/x) < 0.5) { + dir = "x"; + } + + if (svdir && dir && svdir != dir) + { + // This scrollview can't handle the direction the user + // is attempting to scroll. Find an ancestor scrollview + // that can handle the request. + + var sv = this._getAncestorByDirection(dir); + if (sv) + { + this._propagateDragMove(sv, e, ex, ey, dir); + return false; + } + } + + this._directionLock = svdir ? svdir : (dir ? dir : "none"); + } + + var newX = this._sx; + var newY = this._sy; + + if (this._directionLock !== "y" && this._hTracker) + { + var x = this._sx; + this._speedX = dx; + newX = x + dx; + + // Simulate resistance. + + this._doSnapBackX = false; + if (newX > 0 || newX < this._maxX) + { + if (this._directionLock === "x") + { + var sv = this._getAncestorByDirection("x"); + if (sv) + { + this._setScrollPosition(newX > 0 ? 0 : this._maxX, newY); + this._propagateDragMove(sv, e, ex, ey, dir); + return false; + } + } + newX = x + (dx/2); + this._doSnapBackX = true; + } + } + + if (this._directionLock !== "x" && this._vTracker) + { + var y = this._sy; + this._speedY = dy; + newY = y + dy; + + // Simulate resistance. + + this._doSnapBackY = false; + if (newY > 0 || newY < this._maxY) + { + if (this._directionLock === "y") + { + var sv = this._getAncestorByDirection("y"); + if (sv) + { + this._setScrollPosition(newX, newY > 0 ? 0 : this._maxY); + this._propagateDragMove(sv, e, ex, ey, dir); + return false; + } + } + + newY = y + (dy/2); + this._doSnapBackY = true; + } + + } + + if (this.options.pagingEnabled && (svdir === "x" || svdir === "y")) + { + if (this._doSnapBackX || this._doSnapBackY) + this._pageDelta = 0; + else + { + var opos = this._pagePos; + var cpos = svdir === "x" ? newX : newY; + var delta = svdir === "x" ? dx : dy; + + this._pageDelta = (opos > cpos && delta < 0) ? this._pageSize : ((opos < cpos && delta > 0) ? -this._pageSize : 0); + } + } + + this._didDrag = true; + this._lastX = ex; + this._lastY = ey; + + this._setScrollPosition(newX, newY); + + this._showScrollBars(); + + // Call preventDefault() to prevent touch devices from + // scrolling the main window. + + // e.preventDefault(); + + return false; + }, + + _handleDragStop: function(e) + { + var l = this._lastMove; + var t = getCurrentTime(); + var doScroll = l && (t - l) <= this.options.moveIntervalThreshold; + + var sx = (this._hTracker && this._speedX && doScroll) ? this._speedX : (this._doSnapBackX ? 1 : 0); + var sy = (this._vTracker && this._speedY && doScroll) ? this._speedY : (this._doSnapBackY ? 1 : 0); + + var svdir = this.options.direction; + if (this.options.pagingEnabled && (svdir === "x" || svdir === "y") && !this._doSnapBackX && !this._doSnapBackY) + { + var x = this._sx; + var y = this._sy; + if (svdir === "x") + x = -this._pagePos + this._pageDelta; + else + y = -this._pagePos + this._pageDelta; + + this.scrollTo(x, y, this.options.snapbackDuration); + } + else if (sx || sy) + this._startMScroll(sx, sy); + else + this._hideScrollBars(); + + this._disableTracking(); + + if (!this._didDrag && this.options.delayedClickEnabled && this._$clickEle.length) { + this._$clickEle + .trigger("mousedown") + //.trigger("focus") + .trigger("mouseup") + .trigger("click"); + } + + // If a view scrolled, then we need to absorb + // the event so that links etc, underneath our + // cursor/finger don't fire. + + return this._didDrag ? false : undefined; + }, + + _enableTracking: function() + { + $(document).bind(this._dragMoveEvt, this._dragMoveCB); + $(document).bind(this._dragStopEvt, this._dragStopCB); + }, + + _disableTracking: function() + { + $(document).unbind(this._dragMoveEvt, this._dragMoveCB); + $(document).unbind(this._dragStopEvt, this._dragStopCB); + }, + + _showScrollBars: function() + { + var vclass = "ui-scrollbar-visible"; + if (this._$vScrollBar) this._$vScrollBar.addClass(vclass); + if (this._$hScrollBar) this._$hScrollBar.addClass(vclass); + }, + + _hideScrollBars: function() + { + var vclass = "ui-scrollbar-visible"; + if (this._$vScrollBar) this._$vScrollBar.removeClass(vclass); + if (this._$hScrollBar) this._$hScrollBar.removeClass(vclass); + }, + + _addBehaviors: function() + { + var self = this; + if (this.options.eventType === "mouse") + { + this._dragStartEvt = "mousedown"; + this._dragStartCB = function(e){ return self._handleDragStart(e, e.clientX, e.clientY); }; + + this._dragMoveEvt = "mousemove"; + this._dragMoveCB = function(e){ return self._handleDragMove(e, e.clientX, e.clientY); }; + + this._dragStopEvt = "mouseup"; + this._dragStopCB = function(e){ return self._handleDragStop(e); }; + } + else // "touch" + { + this._dragStartEvt = "touchstart"; + this._dragStartCB = function(e) + { + var t = e.originalEvent.targetTouches[0]; + return self._handleDragStart(e, t.pageX, t.pageY); + }; + + this._dragMoveEvt = "touchmove"; + this._dragMoveCB = function(e) + { + var t = e.originalEvent.targetTouches[0]; + return self._handleDragMove(e, t.pageX, t.pageY); + }; + + this._dragStopEvt = "touchend"; + this._dragStopCB = function(e){ return self._handleDragStop(e); }; + } + + this._$view.bind(this._dragStartEvt, this._dragStartCB); + + if (this.options.showScrollBars) + { + var $c = this._$clip; + var prefix = "
    "; + if (this._vTracker) + { + $c.append(prefix + "y" + suffix); + this._$vScrollBar = $c.children(".ui-scrollbar-y"); + } + if (this._hTracker) + { + $c.append(prefix + "x" + suffix); + this._$hScrollBar = $c.children(".ui-scrollbar-x"); + } + } + } +}); + +function setElementTransform($ele, x, y) +{ + var v = "translate3d(" + x + "," + y + ", 0px)"; + $ele.css({ + "-moz-transform": v, + "-webkit-transform": v, + "transform": v + }); +} + + +function MomentumTracker(options) +{ + this.options = $.extend({}, options); + this.easing = "easeOutQuad"; + this.reset(); +} + +var tstates = { + scrolling: 0, + overshot: 1, + snapback: 2, + done: 3 +}; + +function getCurrentTime() { return (new Date()).getTime(); } + +$.extend(MomentumTracker.prototype, { + start: function(pos, speed, duration, minPos, maxPos) + { + this.state = (speed != 0) ? ((pos < minPos || pos > maxPos) ? tstates.snapback : tstates.scrolling) : tstates.done; + this.pos = pos; + this.speed = speed; + this.duration = (this.state == tstates.snapback) ? this.options.snapbackDuration : duration; + this.minPos = minPos; + this.maxPos = maxPos; + + this.fromPos = (this.state == tstates.snapback) ? this.pos : 0; + this.toPos = (this.state == tstates.snapback) ? ((this.pos < this.minPos) ? this.minPos : this.maxPos) : 0; + + this.startTime = getCurrentTime(); + }, + + reset: function() + { + this.state = tstates.done; + this.pos = 0; + this.speed = 0; + this.minPos = 0; + this.maxPos = 0; + this.duration = 0; + }, + + update: function() + { + var state = this.state; + if (state == tstates.done) + return this.pos; + + var duration = this.duration; + var elapsed = getCurrentTime() - this.startTime; + elapsed = elapsed > duration ? duration : elapsed; + + if (state == tstates.scrolling || state == tstates.overshot) + { + var dx = this.speed * (1 - $.easing[this.easing](elapsed/duration, elapsed, 0, 1, duration)); + + var x = this.pos + dx; + + var didOverShoot = (state == tstates.scrolling) && (x < this.minPos || x > this.maxPos); + if (didOverShoot) + x = (x < this.minPos) ? this.minPos : this.maxPos; + + this.pos = x; + + if (state == tstates.overshot) + { + if (elapsed >= duration) + { + this.state = tstates.snapback; + this.fromPos = this.pos; + this.toPos = (x < this.minPos) ? this.minPos : this.maxPos; + this.duration = this.options.snapbackDuration; + this.startTime = getCurrentTime(); + elapsed = 0; + } + } + else if (state == tstates.scrolling) + { + if (didOverShoot) + { + this.state = tstates.overshot; + this.speed = dx / 2; + this.duration = this.options.overshootDuration; + this.startTime = getCurrentTime(); + } + else if (elapsed >= duration) + this.state = tstates.done; + } + } + else if (state == tstates.snapback) + { + if (elapsed >= duration) + { + this.pos = this.toPos; + this.state = tstates.done; + } + else + this.pos = this.fromPos + ((this.toPos - this.fromPos) * $.easing[this.easing](elapsed/duration, elapsed, 0, 1, duration)); + } + + return this.pos; + }, + + done: function() { return this.state == tstates.done; }, + getPosition: function(){ return this.pos; } +}); + +jQuery.widget( "mobile.scrolllistview", jQuery.mobile.scrollview, { + options: { + direction: "y" + }, + + _create: function() { + $.mobile.scrollview.prototype._create.call(this); + + // Cache the dividers so we don't have to search for them everytime the + // view is scrolled. + // + // XXX: Note that we need to update this cache if we ever support lists + // that can dynamically update their content. + + this._$dividers = this._$view.find("[data-role=list-divider]"); + this._lastDivider = null; + }, + + _setScrollPosition: function(x, y) + { + // Let the view scroll like it normally does. + + $.mobile.scrollview.prototype._setScrollPosition.call(this, x, y); + + y = -y; + + // Find the dividers for the list. + + var $divs = this._$dividers; + var cnt = $divs.length; + var d = null; + var dy = 0; + var nd = null; + + for (var i = 0; i < cnt; i++) + { + nd = $divs.get(i); + var t = nd.offsetTop; + if (y >= t) + { + d = nd; + dy = t; + } + else if (d) + break; + } + + // If we found a divider to move position it at the top of the + // clip view. + + if (d) + { + var h = d.offsetHeight; + var mxy = (d != nd) ? nd.offsetTop : (this._$view.get(0).offsetHeight); + if (y + h >= mxy) + y = (mxy - h) - dy; + else + y = y - dy; + + // XXX: Need to convert this over to using $().css() and supporting the non-transform case. + + var ld = this._lastDivider; + if (ld && d != ld) { + setElementTransform($(ld), 0, 0); + } + setElementTransform($(d), 0, y + "px"); + this._lastDivider = d; + + } + } +}); + +})(jQuery,window,document); // End Component + diff --git a/data/treeio/treeio/templates/html/core/administration/settings_view.html b/data/treeio/treeio/templates/html/core/administration/settings_view.html new file mode 100644 index 0000000..d5effbb --- /dev/null +++ b/data/treeio/treeio/templates/html/core/administration/settings_view.html @@ -0,0 +1,50 @@ +{% load i18n %} +{% extends "html/core/page.html" %} + +{% block title %}{% trans %}Settings{% endtrans %} | {% trans %}Administration{% endtrans %}{% endblock %} + +{% block class_settings %}sidebar-link-active{% endblock %} + +{% block module_title %}{% trans %}Settings{% endtrans %}{% endblock %} + +{% block module_topmenu %} +{% trans %}View{% endtrans %} +{% trans %}Edit{% endtrans %} +{% endblock %} + +{% block module_content %} + +
    + + + {% if default_perspective %} + {{ default_perspective }} + {% endif %} + +
    + + + {{ default_permissions_display }} + +
    +
    + + + {% for code, name in all_languages %} + {% if code == language %}{{ name }}{% endif %} + {% endfor %} + +
    +
    + + + {% for zone, title in all_timezones %} + {% if zone == default_timezone %}{{ title }}{% endif %} + {% endfor %} + +
    +
    + + {% if logopath %}{{ logopath }}{% else %}{% trans %}Default{% endtrans %}{% endif %} +
    +{% endblock %} diff --git a/data/treeio/treeio/templates/html/core/billing/upgrade.html b/data/treeio/treeio/templates/html/core/billing/upgrade.html new file mode 100644 index 0000000..f742f9c --- /dev/null +++ b/data/treeio/treeio/templates/html/core/billing/upgrade.html @@ -0,0 +1,104 @@ +{% load i18n %} +{% extends "html/core/page.html" %} + +{% block title %}{% trans %}Upgrade{% endtrans %} | {% trans %}Billing{% endtrans %} | {% trans %}Administration{% endtrans %}{% endblock %} + +{% block module_title %}{% trans %}Billing{% endtrans %}{% endblock %} + +{% block module_topmenu %} + {% trans %}Upgrade Account{% endtrans %} + {% trans %}Billing Information{% endtrans %} +{% endblock %} + +{% block class_billing %}sidebar-link-active{% endblock %} + + +{% block module_content %} + + + +
    + {% if messages %} +

    + {% for message in messages %} + {{ message|htsafe }} + {% endfor %} +

    + {% endif %} + +

    + {% if not has_billing %} +

    {% trans %}Please{% endtrans %} {% trans %}update your billing details{% endtrans %} {% trans %}in order to upgrade.{% endtrans %}

    + {% endif %} +

    + + +
    +
    + +
    + {% trans %}Account Information{% endtrans %} +
    +

    You have {{ quota - active_users }} of your {{ quota }} available users left. ({{ disabled_users }} currently disabled).

    +

    Additional users are priced at $15 per user/month.

    +
    +
    +
    +
    +
    + + + +
    +
    +
    + +
    + {% trans %}Add Users{% endtrans %} +
    +
    + {% csrf_token %} + +
      + {{ form.as_ul()|htsafe }} +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +{% endblock %} diff --git a/data/treeio/treeio/templates/html/core/database_setup.html b/data/treeio/treeio/templates/html/core/database_setup.html new file mode 100644 index 0000000..f626823 --- /dev/null +++ b/data/treeio/treeio/templates/html/core/database_setup.html @@ -0,0 +1,26 @@ +{% load i18n %} +{% extends "html/base_login.html" %} + +{% block title %}{% trans %}Set domain up{% endtrans %}{% endblock %} + +{% block page %} + +{% endblock %} diff --git a/data/treeio/treeio/treeio/account/ajax.py b/data/treeio/treeio/treeio/account/ajax.py new file mode 100644 index 0000000..8afc657 --- /dev/null +++ b/data/treeio/treeio/treeio/account/ajax.py @@ -0,0 +1,272 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core Ajax views +""" + +from django.template import RequestContext +import re +from treeio.core.mail import EmailInvitation +from django.contrib.sites.models import RequestSite +from treeio.core.models import Attachment, Invitation +from treeio.core.views import user_denied +from treeio.core.rendering import render_to_string +from treeio.core.models import Comment, Object, UpdateRecord, Tag +from treeio.core.forms import TagsForm +from treeio.core.ajax import converter +from dajaxice.core import dajaxice_functions +from dajax.core import Dajax + + +def comments_likes(request, target, form, expand=True): + dajax = Dajax() + + response_format = 'html' + + object_id = form.get('object_id', 0) + update = form.get('update', 0) + object = None + if update: + object = UpdateRecord.objects.get(pk=object_id) + else: + object = Object.objects.get(pk=object_id) + + profile = request.user.profile + + if object: + if form.get('like', 0) == unicode(object.id): + object.likes.add(profile) + if hasattr(object, 'score'): + object.score += 1 + object.save() + + elif form.get('unlike', 0) == unicode(object.id): + object.likes.remove(profile) + if hasattr(object, 'score'): + object.score -= 1 + object.save() + + elif form.get('dislike', 0) == unicode(object.id): + object.dislikes.add(profile) + if hasattr(object, 'score'): + object.score += 1 + object.save() + + elif form.get('undislike', 0) == unicode(object.id): + object.dislikes.remove(profile) + if hasattr(object, 'score'): + object.score -= 1 + object.save() + + elif form.get('commentobject', 0) == unicode(object.id) and 'comment' in form: + comment = Comment(author=profile, + body=form.get('comment')) + comment.save() + if hasattr(object, 'score'): + object.score += 1 + object.save() + object.comments.add(comment) + + likes = object.likes.all() + dislikes = object.dislikes.all() + comments = object.comments.all() + + ilike = profile in likes + idislike = profile in dislikes + icommented = comments.filter(author=profile).exists() or \ + comments.filter(author__default_group__in=[ + profile.default_group_id] + [i.id for i in profile.other_groups.all().only('id')]).exists() + + output = render_to_string('core/tags/comments_likes', + {'object': object, + 'is_update': update, + 'profile': profile, + 'likes': likes, + 'dislikes': dislikes, + 'comments': comments, + 'ilike': ilike, + 'idislike': idislike, + 'icommented': icommented, + 'expand': expand}, + context_instance=RequestContext(request), + response_format=response_format) + + dajax.add_data({'target': target, 'content': output}, 'treeio.add_data') + return dajax.json() + +dajaxice_functions.register(comments_likes) + + +def tags(request, target, object_id, edit=False, formdata=None): + if formdata is None: + formdata = {} + dajax = Dajax() + + response_format = 'html' + object = Object.objects.get(pk=object_id) + + tags = object.tags.all() + form = None + if 'tags' in formdata and not type(formdata['tags']) == list: + formdata['tags'] = [formdata['tags']] + + if edit or formdata: + if formdata.get('tags_object', 0) == unicode(object.id): + form = TagsForm(tags, formdata) + if form.is_valid(): + if 'multicomplete_tags' in formdata: + tag_names = formdata.get('multicomplete_tags').split(',') + new_tags = [] + for name in tag_names: + name = name.strip() + if name: + try: + tag = Tag.objects.get(name=name) + except Tag.DoesNotExist: + tag = Tag(name=name) + tag.save() + new_tags.append(tag) + else: + new_tags = form.is_valid() + + object.tags.clear() + for tag in new_tags: + object.tags.add(tag) + tags = object.tags.all() + form = None + else: + form = TagsForm(tags) + + context = {'object': object, + 'tags': tags, + 'form': form} + + context = converter.preprocess_context(context) + + output = render_to_string('core/ajax/tags_box', context, + context_instance=RequestContext(request), + response_format=response_format) + + dajax.add_data({'target': target, 'content': output}, 'treeio.add_data') + return dajax.json() + +dajaxice_functions.register(tags) + + +def attachment(request, object_id, update_id=None): + dajax = Dajax() + + try: + + if object_id: + attachments = Attachment.objects.filter( + attached_object__id=object_id) + template = 'core/tags/attachments_block' + + object_markup = render_to_string(template, + {'object_id': object_id, + 'attachments': attachments}, + context_instance=RequestContext( + request), + response_format='html') + + dajax.add_data( + {'target': 'div.attachment-block[object="%s"]' % object_id, 'content': object_markup}, 'treeio.add_data') + + if update_id: + attachments = Attachment.objects.filter( + attached_record__id=update_id) + template = 'core/tags/attachments_record_block' + update_markup = render_to_string(template, + {'update_id': update_id, + 'attachments': attachments}, + context_instance=RequestContext( + request), + response_format='html') + dajax.add_data( + {'target': 'div.attachment-record-block[object="%s"]' % update_id, 'content': update_markup}, 'treeio.add_data') + + except Exception, e: + pass + + return dajax.json() + +dajaxice_functions.register(attachment) + + +def attachment_delete(request, attachment_id): + + try: + a = Attachment.objects.get(pk=attachment_id) + except Attachment.DoesNotExist: + return + + profile = request.user.profile + + if a.attached_object: + object_id = a.attached_object.id + object = Object.objects.get(pk=object_id) + else: + object_id = None + + update_id = None + if a.attached_record: + update_id = a.attached_record.id + update = UpdateRecord.objects.get(pk=update_id) + if not update.author == profile: + return user_denied(request, message="Only the author of this Update can delete attachments.") + + elif not profile.has_permission(object, mode='w'): + return user_denied(request, message="You don't have full access to this Object") + + a.delete() + + return attachment(request, object_id, update_id) + +dajaxice_functions.register(attachment_delete) + + +def easy_invite(request, emails=None): + + dajax = Dajax() + + try: + emails_original = emails + emails = emails.split(',') + + sender = request.user.profile + default_group = sender.default_group + domain = RequestSite(request).domain + + invited = [] + + for email in emails: + email = email.strip() + if len(email) > 7 and re.match("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$", email) is not None: + invitation = Invitation( + sender=request.user.profile, email=email, default_group=default_group) + invitation.save() + EmailInvitation( + invitation=invitation, sender=sender, domain=domain).send_email() + invited.append(email) + + if invited: + template = 'core/tags/easy_invite_success' + else: + template = 'core/tags/easy_invite_failure' + except: + template = 'core/tags/easy_invite_failure' + + invite_markup = render_to_string(template, + {}, + context_instance=RequestContext(request), + response_format='html') + + dajax.add_data({'target': "div.easy-invite[emails='%s']" % + (emails_original), 'content': invite_markup}, 'treeio.add_data') + return dajax.json() + +dajaxice_functions.register(easy_invite) diff --git a/data/treeio/treeio/treeio/account/cron.py b/data/treeio/treeio/treeio/account/cron.py new file mode 100644 index 0000000..a1f1cac --- /dev/null +++ b/data/treeio/treeio/treeio/account/cron.py @@ -0,0 +1,88 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Account cron jobs +""" + +import codecs + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +from django.db.models import Q +from django.utils.html import strip_tags +from django.contrib.sites.models import Site + +from datetime import datetime, timedelta +from treeio.core.mail import SystemEmail +from treeio.core.models import UpdateRecord +from treeio.account.models import NotificationSetting, Notification + + +class CronNotifier: + def __init__(self): + self.next_daily = datetime.now() + + def send_notification(self, note, records): + message_html = codecs.getwriter("utf8")(StringIO()) + message_html.write(note.title()) + current_url = None + for record in records: + if current_url != record.url: + current_url = record.url + message_html.write( + u'

    \n\n' % unicode(record.url)) + if record.sender: + message_html.write(u'%s (%s):
    \n' % + (unicode(record.sender), unicode(record.sender.get_human_type()))) + else: + message_html.write( + u'%s:
    \n' % unicode(record.url)) + message_html.write('-' * 30) + message_html.write('

    \n\n') + message_html.write(u'%s:
    \n%s - %s

    \n\n' % + (unicode(record.author), unicode(record.date_created.isoformat()), + record.get_full_message())) + signature = "This is an automated message from Tree.io service (http://tree.io). Please do not reply to this e-mail." + subject = "%s summary of [Tree.io] %s" % ( + note.get_ntype_display(), unicode(note.owner),) + + # send email notification to recipient + try: + toaddr = note.owner.get_contact().get_email() + except: + toaddr = None + if toaddr: + html = message_html.getvalue() + html = html.replace( + 'href="', 'href="http://' + Site.objects.get_current().domain) + body = strip_tags(html) + SystemEmail( + toaddr, subject, body, signature, html + signature).send_email() + Notification( + recipient=note.owner, body=html, ntype=note.ntype).save() + + def send_notifications(self): + "Run sending some notifications" + now = datetime.now() + + if self.next_daily <= now: + notes = NotificationSetting.objects.filter( + next_date__lte=now.date(), enabled=True) + for note in notes: + query = Q() + for module in note.modules.all(): + query = query | Q( + about__object_type__icontains=module.name) + query = query & Q(date_created__gte=note.last_datetime) \ + & (Q(author=note.owner_id) | Q(recipients=note.owner_id)) + self.send_notification(note, UpdateRecord.objects.filter( + query).distinct().order_by('url', '-date_created')) + note.update_date(now) + self.next_daily = datetime( + now.year, now.month, now.day) + timedelta(days=1) diff --git a/data/treeio/treeio/treeio/account/forms.py b/data/treeio/treeio/treeio/account/forms.py new file mode 100644 index 0000000..879ed79 --- /dev/null +++ b/data/treeio/treeio/treeio/account/forms.py @@ -0,0 +1,237 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core forms +""" +from django import forms +from treeio.account.models import NotificationSetting, notification_types +from treeio.core.models import User, Object, ModuleSetting, Perspective, Module +from django.utils.translation import ugettext as _ +from treeio.core.decorators import preprocess_form +from treeio.core.conf import settings +from datetime import date + +preprocess_form() + + +class MassActionForm(forms.Form): + """ Mass action form for Accounts """ + + delete = forms.ChoiceField(label=_("With selected"), + choices=(('', '-----'), ('delete', _('Delete Completely')), + ('trash', _('Move to Trash'))), required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(MassActionForm, self).__init__(*args, **kwargs) + self.fields['delete'] = forms.ChoiceField(label=_("With selected"), + choices=(('', '-----'), ('delete', _('Delete Completely')), + ('trash', _('Move to Trash'))), required=False) + + def save(self, *args, **kwargs): + "Process form" + if self.instance: + if self.is_valid(): + if self.cleaned_data['delete']: + if self.cleaned_data['delete'] == 'delete': + self.instance.delete() + if self.cleaned_data['delete'] == 'trash': + self.instance.trash = True + self.instance.save() + + +class AccountForm(forms.ModelForm): + """ Account form """ + + def __init__(self, *args, **kwargs): + self.instance = kwargs['instance'] + super(AccountForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Username") + + def save(self, *args, **kwargs): + "Form processor" + super(AccountForm, self).save(*args, **kwargs) + + self.instance.user.username = self.instance.name + self.instance.user.save() + + def clean_name(self): + "Clean name" + data = self.cleaned_data['name'] + existing = User.objects.filter(name=data).exclude(pk=self.instance.id) + if existing: + raise forms.ValidationError( + _("User with username %s already exists.") % data) + return data + + class Meta: + "Account Form" + model = User + fields = ('name', 'default_group', 'other_groups') + + +class AccountPasswordForm(forms.Form): + """ Password form """ + + old_password = forms.CharField(max_length=255, + widget=forms.PasswordInput(render_value=False)) + new_password = forms.CharField(max_length=255, + widget=forms.PasswordInput(render_value=False)) + new_password_again = forms.CharField(max_length=255, + widget=forms.PasswordInput(render_value=False)) + + user = None + + def __init__(self, user, *args, **kwargs): + super(AccountPasswordForm, self).__init__(*args, **kwargs) + self.fields['old_password'].label = _("Current Password") + self.fields['new_password'].label = _("New Password") + self.fields['new_password_again'].label = _("Confirm Password") + + self.user = user + + def clean_old_password(self): + "Clean old password" + data = self.cleaned_data['old_password'] + if not self.user.check_password(data): + raise forms.ValidationError(_("Current password is wrong")) + return data + + def clean_new_password_again(self): + "Clean new password again" + password1 = self.cleaned_data['new_password'] + password2 = self.cleaned_data['new_password_again'] + if not password1 == password2: + raise forms.ValidationError(_("Passwords do not match")) + return password2 + + def save(self): + "Save" + password1 = self.cleaned_data['new_password'] + self.user.set_password(password1) + return self.user.save() + + +class SettingsForm(forms.Form): + """ User Account settings form """ + + default_perspective = forms.ModelChoiceField( + label='Default Perspective', queryset=[]) + language = forms.ChoiceField(label='Language', choices=[]) + default_timezone = forms.ChoiceField(label='Time Zone', choices=[]) + user = None + email_notifications = forms.ChoiceField( + label="E-mail Notifications", + choices=(('never', _('Never (disabled)')), ('True', _('As-it-happens'))) + notification_types, required=False) + notifications_for_modules = forms.MultipleChoiceField( + label="Receive notifications for modules", required=False) + + def __init__(self, user, *args, **kwargs): + "Sets choices and initial value" + super(SettingsForm, self).__init__(*args, **kwargs) + + self.fields['default_perspective'].label = _("Default Perspective") + self.fields['language'].label = _("Language") + self.fields['default_timezone'].label = _("Time Zone") + self.fields['email_notifications'].label = _("E-mail Notifications") + + self.user = user + + self.fields['default_perspective'].queryset = Object.filter_permitted( + user, Perspective.objects) + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_perspective', user=self.user)[0] + default_perspective = Perspective.objects.get(pk=long(conf.value)) + self.fields['default_perspective'].initial = default_perspective.id + except: + pass + + self.fields['default_timezone'].choices = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE') + timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get('default_timezone', user=user)[0] + timezone = conf.value + except: + pass + self.fields['default_timezone'].initial = timezone + + self.fields['language'].choices = getattr( + settings, 'HARDTREE_LANGUAGES', [('en', 'English')]) + language = getattr(settings, 'HARDTREE_LANGUAGES_DEFAULT', '') + try: + conf = ModuleSetting.get('language', user=user)[0] + language = conf.value + except IndexError: + pass + self.fields['language'].initial = language + + try: + conf = ModuleSetting.get('email_notifications', user=user)[0] + self.fields['email_notifications'].initial = conf.value + except: + self.fields[ + 'email_notifications'].initial = settings.HARDTREE_ALLOW_EMAIL_NOTIFICATIONS + + perspective = user.get_perspective() + + modules = perspective.modules.filter(display=True).order_by('title') + if not modules: + modules = Module.objects.filter(display=True).order_by('title') + self.fields['notifications_for_modules'].choices = [ + (module.pk, module.title) for module in modules] + + try: + modules = NotificationSetting.objects.get( + owner=self.user).modules.all() + self.fields['notifications_for_modules'].initial = [ + m.pk for m in modules] + except (NotificationSetting.DoesNotExist, NotificationSetting.MultipleObjectsReturned): + pass + + def save(self): + "Form processor" + try: + ModuleSetting.set_for_module('default_perspective', + self.cleaned_data[ + 'default_perspective'].id, + 'treeio.core', user=self.user) + ModuleSetting.set_for_module('default_timezone', + self.cleaned_data['default_timezone'], + 'treeio.core', user=self.user) + ModuleSetting.set_for_module('language', + self.cleaned_data['language'], + 'treeio.core', user=self.user) + # notification settings + email_notifications = self.cleaned_data['email_notifications'] + notification, created = NotificationSetting.objects.get_or_create( + owner=self.user) + if email_notifications in ('d', 'w', 'm'): + notification.ntype = email_notifications + if not notification.enabled: + notification.enabled = True + notification.update_date(date.today()) + notification.save() + notification.modules.clear() + for m in Module.objects.filter(pk__in=self.cleaned_data['notifications_for_modules']): + notification.modules.add(m) + else: + notification.enabled = False + notification.save() + ModuleSetting.set_for_module('email_notifications', + email_notifications, + 'treeio.core', user=self.user) + return True + + except: + return False diff --git a/data/treeio/treeio/treeio/account/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/account/south_migrations/0001_initial.py new file mode 100644 index 0000000..6e560a5 --- /dev/null +++ b/data/treeio/treeio/treeio/account/south_migrations/0001_initial.py @@ -0,0 +1,25 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + pass + + def backwards(self, orm): + pass + + models = { + + } + + complete_apps = ['account'] diff --git a/data/treeio/treeio/treeio/account/views.py b/data/treeio/treeio/treeio/account/views.py new file mode 100644 index 0000000..26d2f86 --- /dev/null +++ b/data/treeio/treeio/treeio/account/views.py @@ -0,0 +1,223 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core module views +""" + +from treeio.core.rendering import render_to_response +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse +from treeio.account.forms import AccountForm, AccountPasswordForm, SettingsForm, MassActionForm +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.core.models import ModuleSetting, Perspective +from treeio.account.models import NotificationSetting +from treeio.core.conf import settings +from jinja2 import Markup + + +@treeio_login_required +def account_view(request, response_format='html'): + "Account view" + + profile = request.user.profile + try: + contacts = profile.contact_set.exclude(trash=True) + except: + contacts = [] + + return render_to_response('account/account_view', + {'profile': profile, 'contacts': contacts}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def watchlist(request, response_format='html'): + "Displays all objects a User is subscribed to" + + profile = request.user.profile + watchlist = profile.subscriptions.all() + + context = {'profile': profile, 'watchlist': watchlist} + + return render_to_response('account/watchlist', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def account_edit(request, response_format='html'): + "Account edit" + + profile = request.user.profile + if request.POST: + form = AccountForm(request.POST, instance=profile) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('account_view')) + else: + form = AccountForm(instance=profile) + + return render_to_response('account/account_edit', + {'profile': profile, + 'form': Markup(form.as_ul())}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def account_password(request, response_format='html'): + "Change password form" + + profile = request.user.profile + if request.POST: + if 'cancel' not in request.POST: + form = AccountPasswordForm(request.user, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('account_view')) + else: + return HttpResponseRedirect(reverse('account_view')) + else: + form = AccountPasswordForm(request.user) + + return render_to_response('account/account_password', + {'profile': profile, + 'form': Markup(form.as_ul())}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Settings +# +@treeio_login_required +def settings_view(request, response_format='html'): + "Settings view" + user = request.user.profile + + # default permissions + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_permissions', user=user)[0] + default_permissions = conf.value + except: + default_permissions = settings.HARDTREE_DEFAULT_PERMISSIONS + + # default perspective + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_perspective', user=user)[0] + default_perspective = Perspective.objects.get(pk=long(conf.value)) + except: + default_perspective = None + + # language + language = getattr(settings, 'HARDTREE_LANGUAGES_DEFAULT', '') + try: + conf = ModuleSetting.get('language', user=user)[0] + language = conf.value + except IndexError: + pass + all_languages = getattr( + settings, 'HARDTREE_LANGUAGES', [('en', 'English')]) + + # time zone + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get('default_timezone')[0] + default_timezone = conf.value + except: + pass + + try: + conf = ModuleSetting.get('default_timezone', user=user)[0] + default_timezone = conf.value + except: + default_timezone = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE')[default_timezone][0] + + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE') + + # email notifications e.g. new task assigned to you + email_notifications = getattr( + settings, 'HARDTREE_ALLOW_EMAIL_NOTIFICATIONS', False) + try: + conf = ModuleSetting.get('email_notifications', user=user)[0] + email_notifications = conf.value + except: + pass + + try: + ns = NotificationSetting.objects.get(owner=user, enabled=True) + notifications_for_modules = [m.title for m in ns.modules.all()] + except NotificationSetting.DoesNotExist: + notifications_for_modules = [] + + return render_to_response('account/settings_view', + { + 'default_permissions': default_permissions, + 'default_perspective': default_perspective, + 'language': language, + 'all_languages': all_languages, + 'default_timezone': default_timezone, + 'all_timezones': all_timezones, + 'email_notifications': email_notifications, + 'notifications_for_modules': notifications_for_modules, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def settings_edit(request, response_format='html'): + "Settings edit" + + if request.POST: + if 'cancel' not in request.POST: + form = SettingsForm(request.user.profile, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('account_settings_view')) + else: + return HttpResponseRedirect(reverse('account_settings_view')) + else: + form = SettingsForm(request.user.profile) + + return render_to_response('account/settings_edit', + {'form': Markup(form.as_ul())}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Notification settings +# + + +def _process_mass_form(f): + "Pre-process request to handle mass action form for NotificationSetting" + + def wrap(request, *args, **kwargs): + "Wrap" + user = request.user.profile + if 'massform' in request.POST: + for key in request.POST: + if 'mass-setting' in key: + try: + report = NotificationSetting.objects.get(pk=request.POST[key]) + form = MassActionForm( + user, request.POST, instance=report) + if form.is_valid() and user.has_permission(report, mode='w'): + form.save() + except: + pass + + return f(request, *args, **kwargs) + + # can use functools.update_wrapper instead + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + + return wrap diff --git a/data/treeio/treeio/treeio/changes/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/changes/south_migrations/0001_initial.py new file mode 100644 index 0000000..c93b405 --- /dev/null +++ b/data/treeio/treeio/treeio/changes/south_migrations/0001_initial.py @@ -0,0 +1,194 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'ChangeSetStatus' + db.create_table('changes_changesetstatus', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('active', self.gf('django.db.models.fields.BooleanField') + (default=True)), + ('hidden', self.gf('django.db.models.fields.BooleanField') + (default=False)), + )) + db.send_create_signal('changes', ['ChangeSetStatus']) + + # Adding model 'ChangeSet' + db.create_table('changes_changeset', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('object', self.gf('django.db.models.fields.related.ForeignKey') + (related_name='changeset_object_set', to=orm['core.Object'])), + ('author', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='author', null=True, to=orm['core.User'])), + ('resolved_by', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.User'], null=True, blank=True)), + ('resolved_on', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + ('status', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['changes.ChangeSetStatus'])), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('date_created', self.gf('django.db.models.fields.DateTimeField') + (default=datetime.datetime.now)), + )) + db.send_create_signal('changes', ['ChangeSet']) + + # Adding model 'Change' + db.create_table('changes_change', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('change_set', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['changes.ChangeSet'])), + ('change_type', self.gf('django.db.models.fields.CharField') + (max_length=255, null=True, blank=True)), + ('field', self.gf('django.db.models.fields.CharField') + (max_length=255, null=True, blank=True)), + ('change_from', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('change_to', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('date_created', self.gf('django.db.models.fields.DateTimeField') + (default=datetime.datetime.now)), + )) + db.send_create_signal('changes', ['Change']) + + def backwards(self, orm): + + # Deleting model 'ChangeSetStatus' + db.delete_table('changes_changesetstatus') + + # Deleting model 'ChangeSet' + db.delete_table('changes_changeset') + + # Deleting model 'Change' + db.delete_table('changes_change') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'changes.change': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Change'}, + 'change_from': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'change_set': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['changes.ChangeSet']"}), + 'change_to': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'change_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'field': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'changes.changeset': { + 'Meta': {'ordering': "('-date_created', 'name')", 'object_name': 'ChangeSet'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'author'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'changeset_object_set'", 'to': "orm['core.Object']"}), + 'resolved_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'resolved_on': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['changes.ChangeSetStatus']"}) + }, + 'changes.changesetstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'ChangeSetStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['changes'] diff --git a/data/treeio/treeio/treeio/core/administration/api/handlers.py b/data/treeio/treeio/treeio/core/administration/api/handlers.py new file mode 100644 index 0000000..e0d2a3e --- /dev/null +++ b/data/treeio/treeio/treeio/core/administration/api/handlers.py @@ -0,0 +1,217 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, with_statement + +__all__ = ['GroupHandler', 'UserHandler', 'ModuleHandler', + 'PerspectiveHandler', 'PageFolderHandler', 'PageHandler'] + +from django.utils.translation import ugettext as _ + +from treeio.core.api.utils import rc +from piston3.handler import BaseHandler +from django.db.models import Q +from treeio.core.api.handlers import AccessHandler, ObjectHandler +from treeio.core.api.decorators import module_admin_required +from treeio.core.models import User, Group, Perspective, Module, Page, PageFolder +from treeio.core.administration.forms import PerspectiveForm, UserForm, GroupForm, PageForm, PageFolderForm + + +class GroupHandler(AccessHandler): + "Entrypoint for Group model." + model = Group + form = GroupForm + fields = ('id', 'name', 'parent', 'perspective', 'details') + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_admin_groups', [object_id]) + + @staticmethod + def perspective(data): + return data.get_perspective() + + +class UserHandler(AccessHandler): + "Entrypoint for User model." + model = User + form = UserForm + allowed_methods = ('GET', 'DELETE') + fields = ('id', 'name', 'default_group', 'other_groups', + 'disabled', 'last_access', 'perspective') + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_admin_users', [object_id]) + + @staticmethod + def perspective(data): + return data.get_perspective() + + def create(self, request, *args, **kwargs): + return rc.NOT_IMPLEMENTED + + def update(self, request, *args, **kwargs): + return rc.NOT_IMPLEMENTED + + @module_admin_required() + def delete(self, request, *args, **kwargs): + pkfield = self.model._meta.pk.name + + if pkfield in kwargs: + try: + profile = self.model.objects.get(pk=kwargs.get(pkfield)) + + if profile == request.user.profile: + self.status = 401 + return _("This is you!") + else: + profile.delete() + return rc.DELETED + except self.model.MultipleObjectsReturned: + return rc.DUPLICATE_ENTRY + except self.model.DoesNotExist: + return rc.NOT_HERE + else: + return rc.BAD_REQUEST + + +class ModuleHandler(BaseHandler): + "Entrypoint for Module model." + allowed_methods = ('GET',) + model = Module + exclude = ('object_type', 'object_ptr', 'object_name') + + read = module_admin_required()(BaseHandler.read) + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_admin_modules', [object_id]) + + +class PerspectiveHandler(ObjectHandler): + "Entrypoint for Perspective model." + model = Perspective + form = PerspectiveForm + + fields = ('id',) + form._meta.fields + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_admin_perspectives', [object_id]) + + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.core') + + def check_instance_permission(self, request, inst, mode): + return request.user.profile.is_admin('treeio.core') + + @module_admin_required() + def delete_instance(self, request, inst): + # Don't let users delete their last perspective + other_perspectives = Perspective.objects.filter( + trash=False).exclude(id=inst.id) + admin_module = Module.objects.all().filter(name='treeio.core')[0] + if not other_perspectives: + self.status = 401 + return _("This is your only Perspective.") + elif not other_perspectives.filter(Q(modules=admin_module) | Q(modules__isnull=True)): + self.status = 401 + return _("This is your only Perspective with Administration module. You would be locked out!") + elif 'trash' in request.REQUEST: + inst.trash = True + inst.save() + return inst + else: + inst.delete() + return rc.DELETED + + @module_admin_required() + def update(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + pkfield = kwargs.get(self.model._meta.pk.name) or request.data.get( + self.model._meta.pk.name) + + if not pkfield: + return rc.BAD_REQUEST + + try: + obj = self.model.objects.get(pk=pkfield) + except self.model.ObjectDoesNotExist: + return rc.NOT_FOUND + + attrs = self.flatten_dict(request) + + form = self.form(instance=obj, **attrs) + if form.is_valid(): + perspective = form.save() + + admin_module = Module.objects.filter(name='treeio.core')[0] + other_perspectives = Perspective.objects.filter( + trash=False).exclude(id=perspective.id) + modules = perspective.modules.all() + if modules and admin_module not in modules: + if not other_perspectives.filter(Q(modules=admin_module) | Q(modules__isnull=True)): + perspective.modules.add(admin_module) + request.session['message'] = _( + "This is your only Perspective with Administration module. You would be locked out!") + return obj + else: + self.status = 400 + return form.errors + + +class PageFolderHandler(ObjectHandler): + "Entrypoint for PageFolder model." + model = PageFolder + form = PageFolderForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_admin_folders', [object_id]) + + def check_instance_permission(self, request, inst, mode): + return request.user.profile.is_admin('treeio.core') + + def flatten_dict(self, request): + return {'data': super(ObjectHandler, self).flatten_dict(request.data)} + + +class PageHandler(ObjectHandler): + "Entrypoint for Page model." + model = Page + form = PageForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_admin_pages', [object_id]) + + def check_instance_permission(self, request, inst, mode): + return request.user.profile.is_admin('treeio.core') + + def flatten_dict(self, request): + return {'data': super(ObjectHandler, self).flatten_dict(request.data)} diff --git a/data/treeio/treeio/treeio/core/administration/forms.py b/data/treeio/treeio/treeio/core/administration/forms.py new file mode 100644 index 0000000..4f7e3cd --- /dev/null +++ b/data/treeio/treeio/treeio/core/administration/forms.py @@ -0,0 +1,474 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Administration module forms +""" +from django.db import models +from django import forms +from django.forms import ModelChoiceField +from treeio.core.conf import settings +from django.db.models import Q +from django.core.files.storage import default_storage +import django.contrib.auth.models as django_auth +from django.utils.translation import ugettext as _ +from treeio.core.decorators import preprocess_form +from treeio.identities.forms import ContactForm +from treeio.core.models import User, Group, Perspective, ModuleSetting, Page, PageFolder, user_autocreate_handler +import hashlib +import random +import re + +preprocess_form() + +PERMISSION_CHOICES = ( + ('everyone', 'Everyone'), + ('usergroup', 'Automatic, User and Default Group'), + ('usergroupreadonly', 'Automatic, User and Default Group. READ ONLY'), + ('userallgroups', 'Automatic, User and All Their Groups'), + ('userallgroupsreadonly', + 'Automatic, User and All Their Groups. READ ONLY'), + ('user', 'Automatic, User Only'), + ('userreadonly', 'Automatic, User Only. READ ONLY'), + ('nomoduleusergroup', 'Automatic, Skip Module, User and Default Group'), + ('nomoduleusergroupreadonly', + 'Automatic, Skip Module, User and Default Group. READ ONLY'), + ('nomoduleuserallgroups', + 'Automatic, Skip Module, User and All Their Groups'), + ('nomoduleuserallgroupsreadonly', + 'Automatic, Skip Module, User and All Their Groups. READ ONLY'), + ('nomoduleuser', 'Automatic, Skip Module, User Only'), + ('nomoduleuserreadonly', 'Automatic, Skip Module, User Only. READ ONLY'), + ('forceusergroup', 'Force User and Default Group'), + ('forceuserallgroups', 'Force User and All Their Groups'), + ('forceuser', 'Force User Only'), +) + + +class SettingsForm(forms.Form): + + """ Global settings form """ + + default_perspective = forms.ModelChoiceField( + label='Default Perspective', queryset=[]) + default_permissions = forms.ChoiceField(label='Default Permissions', + choices=PERMISSION_CHOICES) + language = forms.ChoiceField(label='Language', choices=[]) + default_timezone = forms.ChoiceField(label='Time Zone', choices=[]) + logo = forms.ImageField( + label='Logo', required=False, widget=forms.FileInput) + + def __init__(self, user, *args, **kwargs): + "Sets choices and initial value" + super(SettingsForm, self).__init__(*args, **kwargs) + + self.fields['default_perspective'].queryset = Perspective.objects.all() + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_perspective')[0] + default_perspective = Perspective.objects.get(pk=long(conf.value)) + self.fields['default_perspective'].initial = default_perspective.id + except: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_permissions')[0] + self.fields['default_permissions'].initial = conf.value + except: + self.fields['default_permissions'].initial = getattr( + settings, 'HARDTREE_DEFAULT_PERMISSIONS', 'everyone') + + self.fields['default_timezone'].choices = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE') + timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_timezone')[0] + timezone = conf.value + except Exception: + pass + self.fields['default_timezone'].initial = timezone + + self.fields['language'].choices = getattr( + settings, 'HARDTREE_LANGUAGES', [('en', 'English')]) + language = getattr(settings, 'HARDTREE_LANGUAGES_DEFAULT', '') + try: + conf = ModuleSetting.get_for_module('treeio.core', 'language')[0] + language = conf.value + except IndexError: + pass + self.fields['language'].initial = language + + if getattr(settings, 'HARDTREE_SUBSCRIPTION_CUSTOMIZATION', True): + logopath = '' + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'logopath')[0] + logopath = conf.value + except: + pass + + if logopath: + match = re.match('.*[a-z0-9]{32}__(?P.+)$', logopath) + if match: + logopath = match.group('filename') + form_field = forms.ChoiceField( + label=_("Logo"), widget=forms.RadioSelect()) + form_field.choices = ((logopath, _("Keep existing: ") + unicode(logopath)), + ('delete', "Delete ")) + form_field.initial = logopath + form_field.required = False + self.fields['logo'] = form_field + self.fields['logo'].label = _("Logo") + else: + del self.fields['logo'] + + self.fields['default_perspective'].label = _("Default Perspective") + self.fields['default_permissions'].label = _("Default Permissions") + self.fields['default_timezone'].label = _("Time Zone") + self.fields['language'].label = _("Language") + + def _get_upload_name(self, filename): + "Returns an upload_to path to a new file" + while True: + hasher = hashlib.md5() + hasher.update(str(random.random())) + filepath = u"core/" + hasher.hexdigest() + u"__" + filename + fullpath = settings.MEDIA_ROOT + filepath + if not default_storage.exists(fullpath): + return filepath + + def _handle_uploaded_file(self, field_name): + "Process an uploaded file" + try: + file = self.files[field_name] + filepath = self._get_upload_name(file.name) + except KeyError: + return '' + destination = open(settings.MEDIA_ROOT + filepath, 'wb+') + for chunk in file.chunks(): + destination.write(chunk) + destination.close() + return filepath + + def save(self): + "Form processor" + try: + ModuleSetting.set_for_module('default_perspective', + self.cleaned_data[ + 'default_perspective'].id, + 'treeio.core') + ModuleSetting.set_for_module('default_permissions', + self.cleaned_data[ + 'default_permissions'], + 'treeio.core') + ModuleSetting.set_for_module('default_timezone', + self.cleaned_data['default_timezone'], + 'treeio.core') + ModuleSetting.set_for_module('language', + self.cleaned_data['language'], + 'treeio.core') + if getattr(settings, 'HARDTREE_SUBSCRIPTION_CUSTOMIZATION', True): + if isinstance(self.fields['logo'], forms.FileField): + logopath = self._handle_uploaded_file('logo') + ModuleSetting.set_for_module( + 'logopath', logopath, 'treeio.core') + + elif isinstance(self.fields['logo'], forms.ChoiceField): + if self.cleaned_data['logo'] == 'delete': + try: + ModuleSetting.get_for_module( + 'treeio.core', 'logopath').delete() + except: + pass + + return True + + except: + return False + + +class PerspectiveForm(forms.ModelForm): + + """ Perspective form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '50'})) + + def __init__(self, user, *args, **kwargs): + super(PerspectiveForm, self).__init__(*args, **kwargs) + + self.fields['modules'].help_text = "" + self.fields['name'].label = _("Name") + self.fields['modules'].label = _("Modules") + self.fields['details'].label = _("Details") + + class Meta: + + "Perspective Form" + model = Perspective + fields = ('name', 'modules', 'details') + + +class UserForm(forms.ModelForm): + + """ User form """ + perspective = ModelChoiceField( + label='Perspective', queryset=[], required=False) + + def __init__(self, *args, **kwargs): + + super(UserForm, self).__init__(*args, **kwargs) + + if 'instance' in kwargs: + self.instance = kwargs['instance'] + else: + self.fields['password'] = forms.CharField(max_length=255, label=_("Password"), + widget=forms.PasswordInput(render_value=False)) + self.fields['password_again'] = forms.CharField(max_length=255, label=_("Confirm Password"), + widget=forms.PasswordInput(render_value=False)) + + self.fields['name'].label = _("Username") + self.fields['name'].help_text = _("Used to log in") + self.fields['default_group'].label = _("Default group") + self.fields['other_groups'].label = _("Other groups") + self.fields['other_groups'].help_text = "" + self.fields['perspective'].label = _("Perspective") + self.fields['perspective'].queryset = Perspective.objects.all() + if self.instance: + try: + self.fields[ + 'perspective'].initial = self.instance.get_perspective() + except: + pass + + def clean_name(self): + "Clean Name" + data = self.cleaned_data['name'] + query = Q(name=data) + if self.instance and self.instance.id: + query = query & ~Q(id=self.instance.id) + existing = User.objects.filter(query) + if existing: + raise forms.ValidationError( + _("User with username %s already exists.") % data) + if self.instance and not self.instance.id: + # Check Hardtree Subscription user limit + user_limit = getattr( + settings, 'HARDTREE_SUBSCRIPTION_USER_LIMIT', 0) + if user_limit > 0: + user_number = User.objects.filter(disabled=False).count() + if user_number >= user_limit: + raise forms.ValidationError( + _("Sorry, but your subscription does not allow more than %d users. You're currently at your limit.") % (user_limit)) + return data + + def clean_password_again(self): + "Clean password again" + password1 = self.cleaned_data['password'] + password2 = self.cleaned_data['password_again'] + if not password1 == password2: + raise forms.ValidationError(_("Passwords do not match")) + return password2 + + def clean_disabled(self): + "Ensure the admin does not go over subscription limit by re-enabling users" + enable = not self.cleaned_data['disabled'] + if self.instance and self.instance.id and enable and self.instance.disabled: + user_limit = getattr( + settings, 'HARDTREE_SUBSCRIPTION_USER_LIMIT', 0) + if user_limit > 0: + user_number = User.objects.filter(disabled=False).count() + if user_number >= user_limit: + raise forms.ValidationError( + _("Sorry, but your subscription does not allow more than %d users. You're currently at your limit.") % (user_limit)) + return self.cleaned_data['disabled'] + + def save(self, *args, **kwargs): + "Form processor" + + if self.instance.id: + self.instance.user.username = self.instance.name + self.instance.user.save() + super(UserForm, self).save(*args, **kwargs) + else: + new_user = django_auth.User( + username=self.cleaned_data['name'], password='') + new_user.set_password(self.cleaned_data['password']) + models.signals.post_save.disconnect(user_autocreate_handler, sender=django_auth.User) + new_user.save() + if getattr(settings, 'HARDTREE_SIGNALS_AUTOCREATE_USER', False): + models.signals.post_save.connect(user_autocreate_handler, sender=django_auth.User) + self.instance.user = new_user + super(UserForm, self).save(*args, **kwargs) + + if self.cleaned_data['perspective']: + self.instance.set_perspective(self.cleaned_data['perspective']) + + return self.instance + + class Meta: + + "User Form" + model = User + fields = ('name', 'default_group', 'other_groups', 'disabled') + + +class PasswordForm(forms.Form): + + """ Password form """ + + new_password = forms.CharField(max_length=255, label=_("New Password"), + widget=forms.PasswordInput(render_value=False)) + new_password_again = forms.CharField(max_length=255, label=_("Confirm Password"), + widget=forms.PasswordInput(render_value=False)) + + user = None + + def __init__(self, user, *args, **kwargs): + super(PasswordForm, self).__init__(*args, **kwargs) + self.user = user + self.fields['new_password'].label = _("New Password") + self.fields['new_password_again'].label = _("Confirm Password") + + def clean_new_password_again(self): + "Clean New Password Again" + password1 = self.cleaned_data['new_password'] + password2 = self.cleaned_data['new_password_again'] + if not password1 == password2: + raise forms.ValidationError(_("Passwords do not match")) + return password2 + + def save(self): + "Save" + password1 = self.cleaned_data['new_password'] + self.user.set_password(password1) + return self.user.save() + + +class GroupForm(forms.ModelForm): + + """ Group form """ + perspective = ModelChoiceField( + label=_('Perspective'), queryset=[], required=False) + + def __init__(self, *args, **kwargs): + super(GroupForm, self).__init__(*args, **kwargs) + + self.fields['perspective'].label = _('Perspective') + self.fields['perspective'].queryset = Perspective.objects.all() + if self.instance: + try: + self.fields[ + 'perspective'].initial = self.instance.get_perspective() + except: + pass + + def save(self, *args, **kwargs): + instance = super(GroupForm, self).save(*args, **kwargs) + if instance.id and self.cleaned_data['perspective']: + instance.set_perspective(self.cleaned_data['perspective']) + return instance + + class Meta: + + "Group Form" + model = Group + fields = ('name', 'parent', 'details') + + +class PageForm(forms.ModelForm): + + """ Static Page form """ + title = forms.CharField(widget=forms.TextInput(attrs={'size': '50'})) + + def __init__(self, *args, **kwargs): + super(PageForm, self).__init__(*args, **kwargs) + + class Meta: + + "Page Form" + model = Page + fields = ('name', 'title', 'folder', 'published', 'body') + + +class PageFolderForm(forms.ModelForm): + + """ PageFolder for Static Pages form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '50'})) + + def __init__(self, *args, **kwargs): + super(PageFolderForm, self).__init__(*args, **kwargs) + + class Meta: + + "Page Folder Form" + model = PageFolder + fields = ('name', 'details') + + +class FilterForm(forms.ModelForm): + + """ Filter form for Modules definition """ + + def __init__(self, user, _type=None, *args, **kwargs): + if _type is None: + _type = [] + super(FilterForm, self).__init__(*args, **kwargs) + + if 'perspective' in _type: + del self.fields['name'] + self.fields['modules'].help_text = "" + + if 'module' in _type: + del self.fields['name'] + self.fields['modules'].help_text = "" + + class Meta: + + "Filter" + model = Perspective + fields = ('name', 'modules') + + +class ContactSetupForm(ContactForm): + + """ ContactSetupForm """ + + name = forms.CharField( + max_length=256, widget=forms.TextInput(attrs={'size': '50'})) + instance = None + files = {} + + def __init__(self, contact_type, instance=None, *args, **kwargs): + "Populates form with fields from given ContactType" + + if instance: + self.instance = instance + values = instance.contactvalue_set.all() + + super(ContactForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _('Name') + + if 'files' in kwargs: + self.files = kwargs['files'] + + for field in contact_type.fields.all(): + if self.instance: + initial_field_name = self._get_free_field_name(field) + self.fields[initial_field_name] = self._get_form_field(field) + for value in values: + if value.field == field: + field_name = self._get_free_field_name(field) + self.fields[field_name] = self._get_form_field( + field, value) + if initial_field_name in self.fields: + del self.fields[initial_field_name] + else: + field_name = self._get_free_field_name(field) + self.fields[field_name] = self._get_form_field(field) + + if self.instance: + self.fields['name'].initial = self.instance.name diff --git a/data/treeio/treeio/treeio/core/administration/views.py b/data/treeio/treeio/treeio/core/administration/views.py new file mode 100644 index 0000000..8f65128 --- /dev/null +++ b/data/treeio/treeio/treeio/core/administration/views.py @@ -0,0 +1,848 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core module views +""" + +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse +from django.contrib.sites.models import RequestSite +from django.utils.translation import ugettext as _ +from treeio.core.conf import settings +from django.db.models import Q +from treeio.core.rendering import render_to_response +from treeio.core.models import User, Group, Invitation, Perspective, Module, ModuleSetting, Page, PageFolder +from treeio.core.administration.forms import PerspectiveForm, UserForm, PasswordForm, \ + GroupForm, PageForm, PageFolderForm, FilterForm, SettingsForm, PERMISSION_CHOICES +from treeio.core.mail import EmailInvitation +from treeio.core.decorators import module_admin_required, treeio_login_required, handle_response_format + +from treeio.identities.models import ContactType +from treeio.core.administration.forms import ContactSetupForm +import re + + +def _get_filter_query(args): + "Creates a query to filter Modules based on FilterForm arguments" + query = Q(trash=False) + + for arg in args: + if hasattr(Perspective, arg) and args[arg]: + kwargs = {unicode(arg + '__id'): long(args[arg])} + query = query & Q(**kwargs) + + return query + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def index_perspectives(request, response_format='html'): + "Perspective list" + + query = _get_filter_query(request.GET) + perspectives = Perspective.objects.filter(query).order_by('name') + + filters = FilterForm( + request.user.profile, 'perspective', request.GET) + + message = request.session.pop('message', '') + + return render_to_response('core/administration/index_perspectives', + {'perspectives': perspectives, + 'filters': filters, + 'message': message}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def index_modules(request, response_format='html'): + "Module list" + modules = Module.objects.all().order_by('title') + + return render_to_response('core/administration/index_modules', + {'modules': modules}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Perspectives +# + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def perspective_view(request, perspective_id, response_format='html'): + "Perspective view" + perspective = get_object_or_404(Perspective, pk=perspective_id) + + all_modules = Module.objects.all() + + message = request.session.pop('message', '') + + return render_to_response('core/administration/perspective_view', + {'perspective': perspective, + 'all_modules': all_modules, + 'message': message}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def perspective_edit(request, perspective_id, response_format='html'): + "Perspective edit" + perspective = get_object_or_404(Perspective, pk=perspective_id) + # Don't let users delete their last perspective + other_perspectives = Perspective.objects.filter( + trash=False).exclude(id=perspective_id) + admin_module = Module.objects.filter(name='treeio.core')[0] + + if request.POST: + if 'cancel' not in request.POST: + form = PerspectiveForm( + request.user.profile, request.POST, instance=perspective) + if form.is_valid(): + perspective = form.save() + modules = perspective.modules.all() + if modules and admin_module not in modules: + if not other_perspectives.filter(Q(modules=admin_module) | Q(modules__isnull=True)): + perspective.modules.add(admin_module) + request.session['message'] = _( + "This is your only Perspective with Administration module. You would be locked out!") + return HttpResponseRedirect(reverse('core_admin_perspective_view', args=[perspective.id])) + else: + return HttpResponseRedirect(reverse('core_admin_perspective_view', args=[perspective.id])) + else: + form = PerspectiveForm( + request.user.profile, instance=perspective) + + request.session.pop('message', '') + + return render_to_response('core/administration/perspective_edit', + {'perspective': perspective, + 'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def perspective_delete(request, perspective_id, response_format='html'): + "Perspective delete" + + perspective = get_object_or_404(Perspective, pk=perspective_id) + all_modules = Module.objects.all() + message = "" + + # Don't let users delete their last perspective + other_perspectives = Perspective.objects.filter( + trash=False).exclude(id=perspective_id) + admin_module = all_modules.filter(name='treeio.core')[0] + if not other_perspectives: + message = _("This is your only Perspective.") + elif not other_perspectives.filter(Q(modules=admin_module) | Q(modules__isnull=True)): + message = _( + "This is your only Perspective with Administration module. You would be locked out!") + else: + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + perspective.trash = True + perspective.save() + else: + perspective.delete() + return HttpResponseRedirect(reverse('core_admin_index_perspectives')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('core_admin_perspective_view', args=[perspective.id])) + + return render_to_response('core/administration/perspective_delete', + {'perspective': perspective, + 'all_modules': all_modules, + 'message': message}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def perspective_add(request, response_format='html'): + "Perspective add" + + if request.POST: + if 'cancel' not in request.POST: + perspective = Perspective() + form = PerspectiveForm( + request.user.profile, request.POST, instance=perspective) + if form.is_valid(): + perspective = form.save() + perspective.set_user_from_request(request) + return HttpResponseRedirect(reverse('core_admin_perspective_view', args=[perspective.id])) + else: + return HttpResponseRedirect(reverse('core_admin_index_perspectives')) + else: + form = PerspectiveForm(request.user.profile) + + return render_to_response('core/administration/perspective_add', + {'form': form.as_ul()}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Modules +# + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def module_view(request, module_id, response_format='html'): + "Module view" + module = get_object_or_404(Module, pk=module_id) + + return render_to_response('core/administration/module_view', + {'module': module}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Users +# + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def index_users(request, response_format='html'): + "User List" + + users = User.objects.order_by('user__username') + + return render_to_response('core/administration/index_users', + {'users': users}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def user_view(request, user_id, response_format='html'): + "User view" + + profile = get_object_or_404(User, pk=user_id) + try: + contacts = profile.contact_set.exclude(trash=True) + except: + contacts = [] + + modules = profile.get_perspective().get_modules() + + return render_to_response('core/administration/user_view', + {'profile': profile, 'contacts': contacts, + 'modules': modules}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def user_edit(request, user_id, response_format='html'): + "User edit" + + profile = get_object_or_404(User, pk=user_id) + if request.POST: + if 'cancel' not in request.POST: + form = UserForm(request.POST, instance=profile) + if form.is_valid(): + profile = form.save() + return HttpResponseRedirect(reverse('core_admin_user_view', args=[profile.id])) + else: + return HttpResponseRedirect(reverse('core_admin_user_view', args=[profile.id])) + else: + form = UserForm(instance=profile) + + return render_to_response('core/administration/user_edit', + {'profile': profile, + 'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def contact_setup(request, response_format='html'): + + profile = request.user.profile + contact = profile.get_contact() + + def get_contact_type(description): + c_type = None + slug = description.lower() + name = slug.capitalize() + try: + c_type = ContactType.objects.get(Q(name=name) | Q(slug=slug)) + except: + contact_types = ContactType.objects.all() + if contact_types.count(): + c_type = contact_types[0] + return c_type + + company_type = get_contact_type('company') + + company = None + if contact: + person_type = contact.contact_type + if contact.parent and contact.parent.contact_type == company_type: + company = contact.parent + else: + person_type = get_contact_type('person') + + if person_type and request.POST: + contact_form = ContactSetupForm( + person_type, instance=contact, data=request.POST, files=request.FILES, prefix='person') + company_form = ContactSetupForm( + company_type, instance=company, data=request.POST, files=request.FILES, prefix='company') + if contact_form.is_valid() and company_form.is_valid(): + company = company_form.save(request, company_type) + contact_form.cleaned_data['parent'] = company + contact_form.cleaned_data['related_user'] = profile + contact_form.save(request, person_type) + return HttpResponseRedirect(reverse('identities_contact_me')) + else: + contact_form = ContactSetupForm( + person_type, instance=contact, prefix='person') if person_type else None + company_form = ContactSetupForm( + company_type, instance=company, prefix='company') if company_type else None + + return render_to_response('core/administration/contact_settings', + {'contact_form': contact_form, + 'company_form': company_form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def user_password(request, user_id, response_format='html'): + "User change password form" + + profile = get_object_or_404(User, pk=user_id) + if request.POST: + if 'cancel' not in request.POST: + form = PasswordForm(profile.user, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('core_admin_user_view', args=[profile.id])) + else: + return HttpResponseRedirect(reverse('core_admin_user_view', args=[profile.id])) + else: + form = PasswordForm(profile.user) + + return render_to_response('core/administration/user_password', + {'profile': profile, 'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def user_delete(request, user_id, response_format='html'): + "User delete" + + profile = get_object_or_404(User, pk=user_id) + message = "" + + if profile == request.user.profile: + message = _("This is you!") + else: + if request.POST: + if 'delete' in request.POST: + profile.delete() + return HttpResponseRedirect(reverse('core_admin_index_users')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('core_admin_user_view', args=[profile.id])) + + return render_to_response('core/administration/user_delete', + {'profile': profile, + 'message': message}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def user_add(request, response_format='html'): + "User add" + + user_limit = getattr(settings, 'HARDTREE_SUBSCRIPTION_USER_LIMIT', 0) + + if user_limit > 0: + user_number = User.objects.filter(disabled=False).count() + if user_number >= user_limit: + return HttpResponseRedirect(reverse('core_billing_upgrade')) + + if request.POST: + if 'cancel' not in request.POST: + form = UserForm(request.POST) + if form.is_valid(): + profile = form.save() + return HttpResponseRedirect(reverse('core_admin_user_view', args=[profile.id])) + else: + return HttpResponseRedirect(reverse('core_admin_index_users')) + else: + form = UserForm() + + return render_to_response('core/administration/user_add', + {'form': form}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Invites +# + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def user_invite(request, emails=None, response_format='html'): + "Invite people to Hardtree" + + user_limit = getattr(settings, 'HARDTREE_SUBSCRIPTION_USER_LIMIT', 0) + + # Check whether any invites can be made at all + if user_limit > 0: + user_number = User.objects.filter(disabled=False).count() + invites_left = user_limit - user_number + if user_number >= user_limit: + return HttpResponseRedirect(reverse('core_billing_upgrade')) + else: + invites_left = 100000000000000 + + invited = [] + if request.POST or emails: + sender = request.user.profile + default_group = sender.default_group + domain = RequestSite(request).domain + if not emails: + emails = request.POST.get('emails').split(',') + + # Check whether the number of invites + current users exceeds the limit + if user_limit > 0: + user_number = User.objects.filter(disabled=False).count() + if len(emails) + user_number > user_limit: + return HttpResponseRedirect(reverse('core_billing_upgrade')) + + for email in emails: + email = email.strip() + if len(email) > 7 and re.match("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$", email) is not None: + if user_limit > 0 and user_number >= user_limit: + break + invitation = Invitation( + sender=request.user.profile, email=email, default_group=default_group) + invitation.save() + EmailInvitation( + invitation=invitation, sender=sender, domain=domain).send_email() + invited.append(email) + + return render_to_response('core/administration/user_invite', + {'user_limit': user_limit, + 'emails': emails, + 'invites_left': invites_left, + 'invited': invited}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Groups +# + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def index_groups(request, response_format='html'): + "Group List" + + groups = Group.objects.order_by('parent', 'name') + + return render_to_response('core/administration/index_groups', + {'groups': groups}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def group_view(request, group_id, response_format='html'): + "Group view" + + group = get_object_or_404(Group, pk=group_id) + members = User.objects.filter( + Q(default_group=group) | Q(other_groups=group)).distinct() + subgroups = Group.objects.filter(parent=group) + + return render_to_response('core/administration/group_view', + {'group': group, + 'subgroups': subgroups, + 'members': members}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def group_edit(request, group_id, response_format='html'): + "Group edit" + + group = get_object_or_404(Group, pk=group_id) + + if request.POST: + if 'cancel' not in request.POST: + form = GroupForm(request.POST, instance=group) + if form.is_valid(): + group = form.save() + return HttpResponseRedirect(reverse('core_admin_group_view', args=[group.id])) + else: + return HttpResponseRedirect(reverse('core_admin_group_view', args=[group.id])) + else: + form = GroupForm(instance=group) + + return render_to_response('core/administration/group_edit', + {'group': group, + 'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def group_delete(request, group_id, response_format='html'): + "Group delete" + + group = get_object_or_404(Group, pk=group_id) + members = User.objects.filter( + Q(default_group=group) | Q(other_groups=group)).distinct() + subgroups = Group.objects.filter(parent=group) + + if request.POST: + if 'delete' in request.POST: + group.delete() + return HttpResponseRedirect(reverse('core_admin_index_groups')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('core_admin_group_view', args=[group.id])) + + return render_to_response('core/administration/group_delete', + {'group': group, + 'members': members, + 'subgroups': subgroups}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def group_add(request, response_format='html'): + "Group add" + + if request.POST: + if 'cancel' not in request.POST: + form = GroupForm(request.POST) + if form.is_valid(): + group = form.save() + return HttpResponseRedirect(reverse('core_admin_group_view', args=[group.id])) + else: + return HttpResponseRedirect(reverse('core_admin_index_groups')) + + else: + form = GroupForm() + + return render_to_response('core/administration/group_add', + {'form': form}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Pages +# + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def index_pages(request, response_format='html'): + "Static Pages list" + pages = Page.objects.all().order_by('name') + folders = PageFolder.objects.all().order_by('name') + + return render_to_response('core/administration/index_pages', + {'pages': pages, 'folders': folders}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def pagefolder_view(request, folder_id, response_format='html'): + "Folder for Static Pages view" + + folder = get_object_or_404(PageFolder, pk=folder_id) + pages = Page.objects.filter(folder=folder).order_by('name') + + return render_to_response('core/administration/pagefolder_view', + {'folder': folder, 'pages': pages}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def pagefolder_edit(request, folder_id, response_format='html'): + "Folder for Static Pages edit" + + folder = get_object_or_404(PageFolder, pk=folder_id) + if request.POST: + form = PageFolderForm(request.POST, instance=folder) + if form.is_valid(): + folder = form.save() + return HttpResponseRedirect(reverse('core_admin_pagefolder_view', args=[folder.id])) + else: + form = PageFolderForm(instance=folder) + + return render_to_response('core/administration/pagefolder_edit', + {'folder': folder, 'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def pagefolder_delete(request, folder_id, response_format='html'): + "Folder for Static Pages delete" + + folder = get_object_or_404(PageFolder, pk=folder_id) + pages = Page.objects.filter(folder=folder).order_by('name') + if request.POST: + if 'delete' in request.POST: + folder.delete() + return HttpResponseRedirect(reverse('core_admin_index_pages')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('core_admin_pagefolder_view', args=[folder.id])) + + return render_to_response('core/administration/pagefolder_delete', + {'folder': folder, 'pages': pages}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def pagefolder_add(request, response_format='html'): + "Folder for Static Pages add" + + if request.POST: + form = PageFolderForm(request.POST) + if form.is_valid(): + folder = form.save() + return HttpResponseRedirect(reverse('core_admin_pagefolder_view', args=[folder.id])) + else: + form = PageFolderForm() + + return render_to_response('core/administration/pagefolder_add', + {'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@module_admin_required() +def page_view(request, page_id, response_format='html'): + "Static Page view" + page = get_object_or_404(Page, pk=page_id) + + return render_to_response('core/administration/page_view', + {'page': page}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def page_edit(request, page_id, response_format='html'): + "Static Page edit" + + page = get_object_or_404(Page, pk=page_id) + if request.POST: + form = PageForm(request.POST, instance=page) + if form.is_valid(): + page = form.save() + return HttpResponseRedirect(reverse('core_admin_page_view', args=[page.id])) + else: + form = PageForm(instance=page) + + return render_to_response('core/administration/page_edit', + {'page': page, 'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def page_delete(request, page_id, response_format='html'): + "Static Page delete" + + page = get_object_or_404(Page, pk=page_id) + if request.POST: + if 'delete' in request.POST: + page.delete() + return HttpResponseRedirect(reverse('core_admin_index_pages')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('core_admin_page_view', args=[page.id])) + + return render_to_response('core/administration/page_delete', + {'page': page}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def page_add(request, response_format='html'): + "Static Page add" + + if request.POST: + form = PageForm(request.POST) + if form.is_valid(): + page = form.save() + return HttpResponseRedirect(reverse('core_admin_page_view', args=[page.id])) + else: + form = PageForm() + + return render_to_response('core/administration/page_add', + {'form': form}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Setup Guide +# + + +@treeio_login_required +@module_admin_required() +def setup(request, response_format='html'): + "Quick set-up page" + + modules = Module.objects.all() + + for module in modules: + if module.name in ('treeio.projects', 'treeio.sales', 'treeio.services'): + module.major = True + else: + module.major = False + + context = {'modules': modules} + + return render_to_response('core/administration/setup', context, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Settings +# +@handle_response_format +@treeio_login_required +@module_admin_required() +def settings_view(request, response_format='html'): + "Settings view" + + # default permissions + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_permissions')[0] + default_permissions = conf.value + except: + default_permissions = settings.HARDTREE_DEFAULT_PERMISSIONS + + default_permissions_display = default_permissions + for key, value in PERMISSION_CHOICES: + if key == default_permissions: + default_permissions_display = _(value) + + # default perspective + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_perspective')[0] + default_perspective = Perspective.objects.get(pk=long(conf.value)) + except: + default_perspective = None + + # language + language = getattr(settings, 'HARDTREE_LANGUAGES_DEFAULT', '') + try: + conf = ModuleSetting.get_for_module('treeio.core', 'language')[0] + language = conf.value + except IndexError: + pass + all_languages = getattr( + settings, 'HARDTREE_LANGUAGES', [('en', 'English')]) + + logopath = '' + try: + conf = ModuleSetting.get_for_module('treeio.core', 'logopath')[0] + logopath = conf.value + match = re.match('.*[a-z0-9]{32}__(?P.+)$', logopath) + if match: + logopath = match.group('filename') + except: + pass + + # time zone + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_timezone')[0] + default_timezone = conf.value + except Exception: + default_timezone = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE')[default_timezone][0] + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE', [ + (1, '(GMT-11:00) International Date Line West')]) + + return render_to_response('core/administration/settings_view', + { + 'default_permissions': default_permissions, + 'default_permissions_display': default_permissions_display, + 'default_perspective': default_perspective, + 'language': language, + 'all_languages': all_languages, + 'logopath': logopath, + 'default_timezone': default_timezone, + 'all_timezones': all_timezones + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@module_admin_required() +def settings_edit(request, response_format='html'): + "Settings edit" + + if request.POST: + if 'cancel' not in request.POST: + form = SettingsForm( + request.user.profile, request.POST, request.FILES) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('core_settings_view')) + else: + return HttpResponseRedirect(reverse('core_settings_view')) + + else: + form = SettingsForm(request.user.profile) + + return render_to_response('core/administration/settings_edit', + { + 'form': form, + }, + context_instance=RequestContext(request), response_format=response_format) diff --git a/data/treeio/treeio/treeio/core/ajax/converter.py b/data/treeio/treeio/treeio/core/ajax/converter.py new file mode 100644 index 0000000..6d7c443 --- /dev/null +++ b/data/treeio/treeio/treeio/core/ajax/converter.py @@ -0,0 +1,225 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Converter for AJAX response + +Takes HTML rendered response from Django and return JSON-serializable dict +""" +from collections import OrderedDict +from django.forms import BaseForm, TextInput, CharField, HiddenInput, MultiValueField, MultiWidget +from django.utils.safestring import mark_safe + +from treeio.core.ajax.rules import apply_rules + + +class MultiHiddenWidget(MultiWidget): + "Renders multiple hidden widgets for initial values to use in autocomplete" + + def __init__(self, initial=None, choices=None, attrs=None): + if initial is None: + initial = [] + if choices is None: + choices = [] + if attrs is None: + attrs = {} + widgets = [] + if initial: + for i in initial: + if i: + initial_label = '' + for choice in choices: + if choice[0] == i: + initial_label = choice[1] + break + widgets.append( + HiddenInput(attrs=dict(attrs, label=initial_label))) + super(MultiHiddenWidget, self).__init__(widgets=widgets, attrs=attrs) + + def render(self, name, value, attrs=None): + if self.is_localized: + for widget in self.widgets: + widget.is_localized = self.is_localized + # value is a list of values, each corresponding to a widget + # in self.widgets. + if not isinstance(value, list): + value = self.decompress(value) + output = [u'' % (u'multi', name)] + final_attrs = self.build_attrs(attrs) + id_ = final_attrs.get('id', None) + for i, widget in enumerate(self.widgets): + try: + widget_value = value[i] + except IndexError: + widget_value = None + if id_: + final_attrs = dict(final_attrs, id=id_) + output.append(widget.render(name, widget_value, final_attrs)) + output.append(u'') + return mark_safe(self.format_output(output)) + + def decompress(self, value): + if not value: + value = [] + else: + value = [value] + return value + + +class MultiHiddenField(MultiValueField): + "Multiple choice hidden field" + widget = MultiHiddenWidget + + def __init__(self, required=False, widget=None, initial=None, choices=None, fields=None): + if choices is None: + choices = [] + if fields is None: + fields = [] + widget = MultiHiddenWidget(initial=initial, choices=choices) + super(MultiHiddenField, self).__init__(fields=fields, widget=widget, initial=initial, + required=required, label='', help_text='') + + def compress(self, data_list): + return data_list + + +def convert_to_ajax(page, context_instance): + "Converts Django HTML response into AJAX response represented by a dict()" + + response = apply_rules(page) + + # The following is Deprecated for Django 1.3 + # if 'module_content' in response: + # module_content = HttpResponse(response['module_content'], mimetype='text/html') + # response['module_content'] = csrf().process_response(context_instance['request'], module_content).content + + return response + + +def preprocess_context(context): + "Prepares context to be rendered for AJAX" + + # Process autocomplete-multiple fields + for key in context: + if isinstance(context[key], BaseForm): + form = context[key] + for fname in form.fields: + # skip newly added fields to avoid looping infinitely + if "autocomplete" not in fname: + field = form.fields[fname] + try: + # find autocomplete fields + if field.widget.attrs and 'callback' in field.widget.attrs and 'autocomplete' in \ + field.widget.attrs['class']: + + # save existing attributes + old_attrs = field.widget.attrs + + # replace current widget with hidden input + field.widget = HiddenInput() + + # get the current field value + if not field.initial and fname in form.initial: + field.initial = form.initial[fname] + + # if the field has choices replace value with it's + # actual label + initial_name = field.initial + if field.initial and field.choices: + for choice in field.choices: + if choice[0] == field.initial: + initial_name = choice[1] + break + + # create new field + new_field = CharField(widget=TextInput(attrs=old_attrs), + label=field.label, + required=field.required, + help_text=field.help_text, + initial=initial_name) + + # update fields in context + form.fields.update( + {fname: field, "autocomplete_" + fname: new_field}) + if fname in form.errors: + form.errors[ + "autocomplete_" + fname] = form.errors[fname] + del form.errors[fname] + + # restore original field order + order = form.fields.keyOrder + order.insert(order.index(fname), + order.pop(order.index("autocomplete_" + fname))) + + except: + pass + + # Process autocomplete fields + for key in context: + if isinstance(context[key], BaseForm): + form = context[key] + for fname in form.fields: + # skip newly added fields to avoid looping infinitely + if not "autocomplete" in fname: + field = form.fields[fname] + try: + # find autocomplete fields + if field.widget.attrs and 'callback' in field.widget.attrs and 'multicomplete' in \ + field.widget.attrs['class']: + + # save existing attributes + old_attrs = field.widget.attrs + + # replace current widget with hidden input + field.widget = HiddenInput() + + # get the current field value + if not field.initial and fname in form.initial: + field.initial = form.initial[fname] + + # if the field has choices replace value with it's + # actual label + initial_name = '' + if field.initial and field.choices: + for choice in field.choices: + if choice[0] in field.initial: + initial_name += choice[1] + u', ' + + # create new field + new_field = CharField(widget=TextInput(attrs=old_attrs), + label=field.label, + required=field.required, + help_text=field.help_text, + initial=initial_name) + + # hidden fields + choices = getattr(field, 'choices', None) + hidden_field = MultiHiddenField(fields=[field], + required=field.required, + initial=field.initial, + choices=choices) + + # update fields in context + form.fields.update( + {fname: hidden_field, "multicomplete_" + fname: new_field}) + if fname in form.errors: + form.errors[ + "multicomplete_" + fname] = form.errors[fname] + del form.errors[fname] + + # todo: the code below was commented out because on django 1.7 it uses a OrderedDict, + # the SortedDict class was removed from django, this needs further investigation to check + # if everything is working as it should, as you can see the code was inserting in a + # specific index and I don't know how to do it on OrderedDict + + # restore original field order + # order = form.fields.keys() + # order.insert(order.index(fname), + # order.pop(order.index("multicomplete_" + fname))) + form.fields.pop("multicomplete_" + fname) + except: + raise + + return context diff --git a/data/treeio/treeio/treeio/core/api/doc.py b/data/treeio/treeio/treeio/core/api/doc.py new file mode 100644 index 0000000..a16ffa1 --- /dev/null +++ b/data/treeio/treeio/treeio/core/api/doc.py @@ -0,0 +1,280 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding: utf-8 -*- + +import re +import sys +import inspect +import piston3.handler as handler + +from types import ModuleType +from resource import CsrfExemptResource +from handlers import ObjectHandlerMetaClass + +from django.core.urlresolvers import get_resolver, get_callable, get_script_prefix +from django.utils.translation import ugettext_lazy as _ +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.db import models + + +def get_module(mdl): + if isinstance(mdl, basestring): + __import__(mdl) + return sys.modules[mdl] + elif isinstance(mdl, ModuleType): + return mdl + else: + raise ValueError("mdl must be string or module type.") + + +def generate_doc(handler_cls): + """ + Returns a `HandlerDocumentation` object + for the given handler. Use this to generate + documentation for your API. + """ + if not (type(handler_cls) is ObjectHandlerMetaClass + or type(handler_cls) is handler.HandlerMetaClass): + raise ValueError("Give me handler, not %s" % type(handler_cls)) + + return HandlerDocumentation(handler_cls) + + +def get_field_data_type(field): + """Returns the description for a given field type, if it exists, + Fields' descriptions can contain format strings, which will be interpolated + against the values of field.__dict__ before being output.""" + + return field.description % field.__dict__ + + +class HandlerMethod(object): + def __init__(self, method, stale=False): + self.method = method + self.stale = stale + + def iter_args(self): + args, _, _, defaults = inspect.getargspec(self.method) # NOQA + + for idx, arg in enumerate(args): + if arg in ('self', 'request', 'form'): + continue + + didx = len(args) - idx + + if defaults and len(defaults) >= didx: + yield (arg, str(defaults[-didx])) + else: + yield (arg, None) + + def get_signature(self, parse_optional=True): + spec = "" + + for argn, argdef in self.iter_args(): + spec += argn + + if argdef: + spec += '=%s' % argdef + + spec += ', ' + + spec = spec.rstrip(", ") + + if parse_optional: + return spec.replace("=None", "=") + + return spec + + signature = property(get_signature) + + def get_fields(self): + to_update = False + doc = inspect.getdoc(self.method) + if not doc and issubclass(self.method.im_class.model, models.Model): + fields = None + if self.method.__name__ == 'read': + fields = self.method.im_class.fields if self.method.im_class.fields else tuple( + attr.name for attr in self.method.im_class.model._meta.local_fields) + elif self.method.__name__ in ('create', 'update'): + to_update = True + if hasattr(self.method.im_class, 'form') and \ + hasattr(self.method.im_class.form, '_meta'): + fields = self.method.im_class.form._meta.fields + else: + fields = self.method.im_class.fields + if fields: + for field in fields: + for mfield in self.method.im_class.model._meta.fields: + if mfield.name == field: + yield {'name': mfield.name, + 'required': not mfield.blank if to_update else False, + 'type': get_field_data_type(mfield), + 'verbose': mfield.verbose_name, + 'help_text': mfield.help_text, } + break + + def get_doc(self): + doc = inspect.getdoc(self.method) + if not doc and issubclass(self.method.im_class.model, models.Model): + if self.method.__name__ == 'delete': + doc = _( + 'Function deletes object with object_ptr. If you declare "trash" parameter as true, object is marked as trash.') + elif self.method.__name__ == 'read': + doc = _( + 'Function gets info about object and returns following fields:') + elif hasattr(self.method.im_class, 'form'): + if self.method.__name__ == 'create': + doc = _('Function creates entry with following fields:') + elif self.method.__name__ == 'update': + doc = _('Function updates following fields:') + return doc + + doc = property(get_doc) + + def get_name(self): + return self.method.__name__ + + name = property(get_name) + + def __repr__(self): + return "" % self.name + + +def _convert(template, params=None): + """URI template converter""" + if params is None: + params = [] + paths = template % dict([p, "{%s}" % p] for p in params) + return u'/api%s%s' % (get_script_prefix(), paths) + + +class HandlerDocumentation(object): + def __init__(self, handler): + self.handler = handler + + def get_methods(self, include_default=False): + for method in self.handler.allowed_methods: + met = getattr(self.handler, CsrfExemptResource.callmap.get(method)) + stale = inspect.getmodule(met) is handler + + if not self.handler.is_anonymous: + if met and (not stale or include_default): + yield HandlerMethod(met, stale) + else: + if not stale or met.__name__ == "read" \ + and 'GET' in self.allowed_methods: + yield HandlerMethod(met, stale) + + def get_all_methods(self): + return self.get_methods(include_default=True) + + @property + def is_anonymous(self): + return handler.is_anonymous + + def get_model(self): + return getattr(self, 'model', None) + + def get_doc(self): + return self.handler.__doc__ + + doc = property(get_doc) + + @property + def name(self): + return self.handler.__name__ + + @property + def display_name(self): + name = self.handler.__name__.replace('Handler', '') + try: + pattern = re.compile('([A-Z][A-Z][a-z])|([a-z][A-Z])') + name = pattern.sub( + lambda m: m.group()[:1] + " " + m.group()[1:], name) + except: + pass + return name + + @property + def allowed_methods(self): + return self.handler.allowed_methods + + def get_resource_uri_template(self): + """ + URI template processor. + + See http://bitworking.org/projects/URI-Templates/ + """ + try: + resource_uri = self.handler.resource_uri() + + components = [None, [], {}] + + for i, value in enumerate(resource_uri): + components[i] = value + + lookup_view, args, kwargs = components + lookup_view = get_callable(lookup_view, True) + + possibilities = get_resolver( + 'treeio.core.api.urls').reverse_dict.getlist(lookup_view) + + for possibility, pattern in possibilities: + for result, params in possibility: + if args: + if len(args) != len(params): + continue + return _convert(result, params) + else: + if set(kwargs.keys()) != set(params): + continue + return _convert(result, params) + + except: + return None + + def get_resource_uri_index(self): + """ + INDEX URI template processor. + """ + try: + resource_uri = self.handler.resource_uri() + + components = [None, [], {}] + + for i, value in enumerate(resource_uri): + components[i] = value + + lookup_view, args, kwargs = components + # else this url will be in get_resource_uri_template + if args or kwargs: + lookup_view = get_callable(lookup_view, True) + + possibilities = get_resolver( + 'treeio.core.api.urls').reverse_dict.getlist(lookup_view) + + for possibility, pattern in possibilities: + for result, params in possibility: + if not params: + return _convert(result) + except: + return None + + resource_uri_template = property(get_resource_uri_template) + + def __repr__(self): + return u'' % self.name + + +def documentation_view(request, module): + docs = [] + + for name, clsmember in inspect.getmembers(get_module(module), inspect.isclass): + if issubclass(clsmember, handler.BaseHandler) and getattr(clsmember, 'model', None): + docs.append(generate_doc(clsmember)) + + return render_to_response('api/reference.html', {'docs': docs}, RequestContext(request)) diff --git a/data/treeio/treeio/treeio/core/api/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/core/api/south_migrations/0001_initial.py new file mode 100644 index 0000000..de30052 --- /dev/null +++ b/data/treeio/treeio/treeio/core/api/south_migrations/0001_initial.py @@ -0,0 +1,152 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Nonce' + db.create_table('api_nonce', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('token_key', self.gf( + 'django.db.models.fields.CharField')(max_length=18)), + ('consumer_key', self.gf( + 'django.db.models.fields.CharField')(max_length=18)), + ('key', self.gf('django.db.models.fields.CharField') + (max_length=255)), + )) + db.send_create_signal('api', ['Nonce']) + + # Adding model 'Consumer' + db.create_table('api_consumer', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('description', self.gf('django.db.models.fields.TextField')()), + ('key', self.gf('django.db.models.fields.CharField') + (unique=True, max_length=18)), + ('secret', self.gf('django.db.models.fields.CharField') + (max_length=32)), + ('status', self.gf('django.db.models.fields.CharField') + (default='pending', max_length=16)), + )) + db.send_create_signal('api', ['Consumer']) + + # Adding model 'Token' + db.create_table('api_token', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('key', self.gf('django.db.models.fields.CharField') + (max_length=18)), + ('secret', self.gf('django.db.models.fields.CharField') + (max_length=32)), + ('verifier', self.gf( + 'django.db.models.fields.CharField')(max_length=10)), + ('token_type', self.gf('django.db.models.fields.IntegerField')()), + ('timestamp', self.gf('django.db.models.fields.IntegerField') + (default=1303299544L)), + ('is_approved', self.gf( + 'django.db.models.fields.BooleanField')(default=False)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='tokens', null=True, to=orm['auth.User'])), + ('consumer_id', self.gf('django.db.models.fields.IntegerField') + (null=True, blank=True)), + ('callback', self.gf('django.db.models.fields.CharField') + (max_length=255, null=True, blank=True)), + ('callback_confirmed', self.gf( + 'django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('api', ['Token']) + + def backwards(self, orm): + + # Deleting model 'Nonce' + db.delete_table('api_nonce') + + # Deleting model 'Consumer' + db.delete_table('api_consumer') + + # Deleting model 'Token' + db.delete_table('api_token') + + models = { + 'api.consumer': { + 'Meta': {'object_name': 'Consumer'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}) + }, + 'api.nonce': { + 'Meta': {'object_name': 'Nonce'}, + 'consumer_key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'token_key': ('django.db.models.fields.CharField', [], {'max_length': '18'}) + }, + 'api.token': { + 'Meta': {'object_name': 'Token'}, + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'consumer_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1303299544L'}), + 'token_type': ('django.db.models.fields.IntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['api'] diff --git a/data/treeio/treeio/treeio/core/api/south_migrations/0002_auto__add_field_consumer_owner.py b/data/treeio/treeio/treeio/core/api/south_migrations/0002_auto__add_field_consumer_owner.py new file mode 100644 index 0000000..29d83d5 --- /dev/null +++ b/data/treeio/treeio/treeio/core/api/south_migrations/0002_auto__add_field_consumer_owner.py @@ -0,0 +1,96 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'Consumer.owner' + db.add_column('api_consumer', 'owner', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['auth.User'], null=True, blank=True), keep_default=False) + + def backwards(self, orm): + + # Deleting field 'Consumer.owner' + db.delete_column('api_consumer', 'owner_id') + + models = { + 'api.consumer': { + 'Meta': {'object_name': 'Consumer'}, + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}) + }, + 'api.nonce': { + 'Meta': {'object_name': 'Nonce'}, + 'consumer_key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'token_key': ('django.db.models.fields.CharField', [], {'max_length': '18'}) + }, + 'api.token': { + 'Meta': {'object_name': 'Token'}, + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'consumer_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1311780361L'}), + 'token_type': ('django.db.models.fields.IntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['api'] diff --git a/data/treeio/treeio/treeio/core/api/utils.py b/data/treeio/treeio/treeio/core/api/utils.py new file mode 100644 index 0000000..07df116 --- /dev/null +++ b/data/treeio/treeio/treeio/core/api/utils.py @@ -0,0 +1,346 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +import time +from django.http import HttpResponse +from django.core.cache import cache +from django import get_version as django_version +from django.core.mail import send_mail, mail_admins +from django.conf import settings +from django.utils.translation import ugettext as _ +from django.template import loader, TemplateDoesNotExist +from django.contrib.sites.models import Site +from piston3.decorator import decorator + +__version__ = '0.3dev' + + +def get_version(): + return __version__ + + +def format_error(error): + return u"Piston/%s (Django %s) crash report:\n\n%s" % \ + (get_version(), django_version(), error) + + +class rc_factory(object): + + """ + Status codes. + """ + CODES = dict(ALL_OK=('OK', 200), + CREATED = ('Created', 201), + DELETED = ('', 204), # 204 says "Don't send a body!" + BAD_REQUEST = ('Bad Request', 400), + FORBIDDEN = ('Forbidden', 401), + NOT_FOUND = ('Not Found', 404), + DUPLICATE_ENTRY = ('Conflict/Duplicate', 409), + NOT_HERE = ('Gone', 410), + INTERNAL_ERROR = ('Internal Error', 500), + NOT_IMPLEMENTED = ('Not Implemented', 501), + THROTTLED = ('Throttled', 503)) + + def __getattr__(self, attr): + """ + Returns a fresh `HttpResponse` when getting + an "attribute". This is backwards compatible + with 0.2, which is important. + """ + try: + (r, c) = self.CODES.get(attr) + except TypeError: + raise AttributeError(attr) + + return HttpResponse(r, content_type='text/plain', status=c) + +rc = rc_factory() + + +class FormValidationError(Exception): + + def __init__(self, form): + self.form = form + + +class HttpStatusCode(Exception): + + def __init__(self, response): + self.response = response + + +def validate(v_form, operation='POST'): + @decorator + def wrap(f, self, request, *a, **kwa): + form = v_form(getattr(request, operation)) + + if form.is_valid(): + setattr(request, 'form', form) + return f(self, request, *a, **kwa) + else: + raise FormValidationError(form) + return wrap + + +def throttle(max_requests, timeout=60 * 60, extra=''): + """ + Simple throttling decorator, caches + the amount of requests made in cache. + + If used on a view where users are required to + log in, the username is used, otherwise the + IP address of the originating request is used. + + Parameters:: + - `max_requests`: The maximum number of requests + - `timeout`: The timeout for the cache entry (default: 1 hour) + """ + @decorator + def wrap(f, self, request, *args, **kwargs): + if request.user.is_authenticated(): + ident = request.user.username + else: + ident = request.META.get('REMOTE_ADDR', None) + + if hasattr(request, 'throttle_extra'): + """ + Since we want to be able to throttle on a per- + application basis, it's important that we realize + that `throttle_extra` might be set on the request + object. If so, append the identifier name with it. + """ + ident += ':%s' % str(request.throttle_extra) + + if ident: + """ + Preferrably we'd use incr/decr here, since they're + atomic in memcached, but it's in django-trunk so we + can't use it yet. If someone sees this after it's in + stable, you can change it here. + """ + ident += ':%s' % extra + + now = time.time() + count, expiration = cache.get(ident, (1, None)) + + if expiration is None: + expiration = now + timeout + + if count >= max_requests and expiration > now: + t = rc.THROTTLED + wait = int(expiration - now) + t.content = 'Throttled, wait %d seconds.' % wait + t['Retry-After'] = wait + return t + + cache.set(ident, (count + 1, expiration), (expiration - now)) + + return f(self, request, *args, **kwargs) + return wrap + + +def coerce_put_post(request): + """ + Django doesn't particularly understand REST. + In case we send data over PUT, Django won't + actually look at the data and load it. We need + to twist its arm here. + + The try/except abominiation here is due to a bug + in mod_python. This should fix it. + """ + if request.method == "PUT": + # Bug fix: if _load_post_and_files has already been called, for + # example by middleware accessing request.POST, the below code to + # pretend the request is a POST instead of a PUT will be too late + # to make a difference. Also calling _load_post_and_files will result + # in the following exception: + # AttributeError: You cannot set the upload handlers after the upload has been processed. + # The fix is to check for the presence of the _post field which is set + # the first time _load_post_and_files is called (both by wsgi.py and + # modpython.py). If it's set, the request has to be 'reset' to redo + # the query value parsing in POST mode. + if hasattr(request, '_post'): + del request._post + del request._files + + try: + request.method = "POST" + request._load_post_and_files() + request.method = "PUT" + except AttributeError: + request.META['REQUEST_METHOD'] = 'POST' + request._load_post_and_files() + request.META['REQUEST_METHOD'] = 'PUT' + + request.PUT = request.POST + + +class MimerDataException(Exception): + + """ + Raised if the content_type and data don't match + """ + pass + + +class Mimer(object): + TYPES = dict() + + def __init__(self, request): + self.request = request + + def is_multipart(self): + content_type = self.content_type() + + if content_type is not None: + return content_type.lstrip().startswith('multipart') + + return False + + def loader_for_type(self, ctype): + """ + Gets a function ref to deserialize content + for a certain mimetype. + """ + for loadee, mimes in Mimer.TYPES.iteritems(): + for mime in mimes: + if ctype.startswith(mime): + return loadee + + def content_type(self): + """ + Returns the content type of the request in all cases where it is + different than a submitted form - application/x-www-form-urlencoded + """ + type_formencoded = "application/x-www-form-urlencoded" + + ctype = self.request.META.get('CONTENT_TYPE', type_formencoded) + + if type_formencoded in ctype: + return None + + return ctype + + def translate(self): + """ + Will look at the `Content-type` sent by the client, and maybe + deserialize the contents into the format they sent. This will + work for JSON, YAML, XML and Pickle. Since the data is not just + key-value (and maybe just a list), the data will be placed on + `request.data` instead, and the handler will have to read from + there. + + It will also set `request.content_type` so the handler has an easy + way to tell what's going on. `request.content_type` will always be + None for form-encoded and/or multipart form data (what your browser sends.) + """ + ctype = self.content_type() + self.request.content_type = ctype + + if not self.is_multipart() and ctype: + loadee = self.loader_for_type(ctype) + + if loadee: + try: + self.request.data = loadee(self.request.body) + + # Reset both POST and PUT from request, as its + # misleading having their presence around. + self.request.POST = self.request.PUT = dict() + except (TypeError, ValueError): + # This also catches if loadee is None. + raise MimerDataException + else: + self.request.data = None + + return self.request + + @classmethod + def register(cls, loadee, types): + cls.TYPES[loadee] = types + + @classmethod + def unregister(cls, loadee): + return cls.TYPES.pop(loadee) + + +def translate_mime(request): + request = Mimer(request).translate() + + +def require_mime(*mimes): + """ + Decorator requiring a certain mimetype. There's a nifty + helper called `require_extended` below which requires everything + we support except for post-data via form. + """ + @decorator + def wrap(f, self, request, *args, **kwargs): + m = Mimer(request) + realmimes = set() + + rewrite = {'json': 'application/json', + 'yaml': 'application/x-yaml', + 'xml': 'text/xml', + 'pickle': 'application/python-pickle'} + + for idx, mime in enumerate(mimes): + realmimes.add(rewrite.get(mime, mime)) + + if not m.content_type() in realmimes: + return rc.BAD_REQUEST + + return f(self, request, *args, **kwargs) + return wrap + +require_extended = require_mime('json', 'yaml', 'xml', 'pickle') + + +def send_consumer_mail(consumer): + """ + Send a consumer an email depending on what their status is. + """ + try: + subject = settings.PISTON_OAUTH_EMAIL_SUBJECTS[consumer.status] + except AttributeError: + subject = "Your API Consumer for %s " % Site.objects.get_current().name + if consumer.status == "accepted": + subject += "was accepted!" + elif consumer.status == "canceled": + subject += "has been canceled." + elif consumer.status == "rejected": + subject += "has been rejected." + else: + subject += "is awaiting approval." + + template = "piston/mails/consumer_%s.txt" % consumer.status + + try: + body = loader.render_to_string(template, + {'consumer': consumer, 'user': consumer.user}) + except TemplateDoesNotExist: + """ + They haven't set up the templates, which means they might not want + these emails sent. + """ + return + + try: + sender = settings.PISTON_FROM_EMAIL + except AttributeError: + sender = settings.DEFAULT_FROM_EMAIL + + if consumer.user: + send_mail( + _(subject), body, sender, [consumer.user.email], fail_silently=True) + + if consumer.status == 'pending' and len(settings.ADMINS): + mail_admins(_(subject), body, fail_silently=True) + + if settings.DEBUG and consumer.user: + print "Mail being sent, to=%s" % consumer.user.email + print "Subject: %s" % _(subject) + print body diff --git a/data/treeio/treeio/treeio/core/auth.py b/data/treeio/treeio/treeio/core/auth.py new file mode 100644 index 0000000..b688b6e --- /dev/null +++ b/data/treeio/treeio/treeio/core/auth.py @@ -0,0 +1,54 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Hardtree support authentication backend +""" +from django.contrib.auth.models import User +from treeio.identities.models import ContactValue + + +class EmailBackend(object): + + "Log a user in using email instead of their username" + + def authenticate(self, username, password): + # The user entered an email, so try to log them in by e-mail + emails = ContactValue.objects.filter(value=username, + field__field_type='email', + contact__trash=False, + contact__related_user__isnull=False) + for email in emails: + try: + user = email.contact.related_user.user.user + if user.check_password(password): + return user + except: + pass + return None + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None + + +class HashBackend(object): + + "Log a user in using their password as a hash" + + def authenticate(self, authkey): + try: + return User.objects.get(password=authkey) + except: + pass + return None + + def get_user(self, user_id): + try: + return User.objects.get(pk=user_id) + except User.DoesNotExist: + return None diff --git a/data/treeio/treeio/treeio/core/contrib/messages/storage/cache.py b/data/treeio/treeio/treeio/core/contrib/messages/storage/cache.py new file mode 100644 index 0000000..24c9561 --- /dev/null +++ b/data/treeio/treeio/treeio/core/contrib/messages/storage/cache.py @@ -0,0 +1,98 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding:utf-8 -*- + +from treeio.core.conf import settings +from django.contrib.messages.storage.base import BaseStorage +from django.core.cache import cache +import cPickle + + +def lock(key): + while True: + if cache.add(key + '_lock', '1', 10): # lifetime lock 10 seconds + break + + +def unlock(key): + cache.delete(key + '_lock') + + +class CacheStorage(BaseStorage): + + """ + Stores messages in a cache. + """ + + def __init__(self, request, *args, **kwargs): + super(CacheStorage, self).__init__(request, *args, **kwargs) + self.user = request.user.id + self.domain = getattr(settings, 'CURRENT_DOMAIN', 'default') + self.prefix = 'treeio_%s_storage_messages_%s' + self.key = self.prefix % (self.domain, self.user) + + def _get(self, *args, **kwargs): + """ + Retrieves a list of stored messages. Returns a tuple of the messages + and a flag indicating whether or not all the messages originally + intended to be stored in this storage were, in fact, stored and + retrieved; e.g., ``(messages, all_retrieved)``. + """ + lock(self.key) + try: + data = cache.get(self.key) + if not data: + data = cPickle.dumps([]) + messages = cPickle.loads(data) + except: + pass + unlock(self.key) + return messages, True + + def update(self, response, *args, **kwargs): + "Update flew by - don't pass response to avoid Exceptions being thrown by Django middleware" + super(CacheStorage, self).update(response, *args, **kwargs) + return [] + + def _store(self, messages, *args, **kwargs): + """ + Stores a list of messages, returning a list of any messages which could + not be stored. + + One type of object must be able to be stored, ``Message``. + """ + lock(self.key) + try: + if messages: + data = cache.get(self.key) + if not data: # if not data in cache + data = cPickle.dumps(([], True)) + data = cPickle.loads(data) + data.append(messages) + cache.set(self.key, cPickle.dumps(messages), 2592000) + else: + cache.set(self.key, cPickle.dumps([]), 2592000) + except: + pass + unlock(self.key) + return messages + + def _add(self, messages, *args, **kwargs): + """ + adds to the message store + """ + lock(self.key) + try: + if messages: + data = cache.get(self.key) + if not data: + data = cPickle.dumps([]) + data = cPickle.loads(data) + data.append(messages) + cache.set(self.key, cPickle.dumps(data), 2592000) + except: + pass + unlock(self.key) diff --git a/data/treeio/treeio/treeio/core/dashboard/views.py b/data/treeio/treeio/treeio/core/dashboard/views.py new file mode 100644 index 0000000..90a010d --- /dev/null +++ b/data/treeio/treeio/treeio/core/dashboard/views.py @@ -0,0 +1,279 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core module Dashboard views +""" + +from treeio.core.rendering import render_to_response +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.core.models import Object, Widget +from treeio.core.dashboard.forms import WidgetForm +from treeio.core.conf import settings +from jinja2 import Markup +import json +import re +import copy + + +def _preprocess_widget(widget, name): + "Populates widget with missing fields" + + module_name = widget['module_name'] + import_name = module_name + ".views" + module_views = __import__(import_name, fromlist=[str(module_name)]) + if hasattr(module_views, name): + if 'title' not in widget: + widget['title'] = getattr(module_views, name).__doc__ + widget = copy.deepcopy(widget) + + if 'view' not in widget: + widget['view'] = getattr(module_views, name) + + return widget + + +def _get_all_widgets(request): + "Retrieve widgets from all available modules" + + user = request.user.profile + perspective = user.get_perspective() + modules = perspective.get_modules() + + widgets = {} + + # For each Module in the Perspective get widgets + for module in modules: + try: + import_name = module.name + ".widgets" + module_widget_lib = __import__( + import_name, fromlist=[str(module.name)]) + module_widgets = module_widget_lib.get_widgets(request) + + # Preprocess widget, ensure it has all required fields + for name in module_widgets: + if 'module_name' not in module_widgets[name]: + module_widgets[name]['module_name'] = module.name + if 'module_title' not in module_widgets[name]: + module_widgets[name]['module_title'] = module.title + module_widgets[name] = _preprocess_widget( + module_widgets[name], name) + + widgets.update(module_widgets) + + except ImportError: + pass + except AttributeError: + pass + + return widgets + + +def _get_widget(request, module, widget_name): + "Gets a widget by name" + + import_name = module.name + ".widgets" + module_widget_lib = __import__(import_name, fromlist=[str(module.name)]) + module_widgets = module_widget_lib.get_widgets(request) + + widget = {} + # Preprocess widget, ensure it has all required fields + for name in module_widgets: + if name == widget_name: + widget = module_widgets[name] + if 'module_name' not in widget: + widget['module_name'] = module.name + if 'module_title' not in widget: + widget['module_title'] = module.title + widget = _preprocess_widget(widget, widget_name) + break + + return widget + + +def _create_widget_object(request, module_name, widget_name): + "Create a Widget object if one is available for the current user Perspective" + + user = request.user.profile + perspective = user.get_perspective() + modules = perspective.get_modules() + + obj = None + + current_module = modules.filter(name=module_name) + widget = None + if current_module: + current_module = current_module[0] + widget = _get_widget(request, current_module, widget_name) + if widget: + obj = Widget(user=user, perspective=perspective) + obj.module_name = widget['module_name'] + obj.widget_name = widget_name + obj.save() + + # except Exception: + # pass + + return obj + + +def _get_widget_content(content, response_format='html'): + "Extracts widget content from rendred HTML" + + widget_content = "" + regexp = r"(?P.*?)" + + if response_format == 'ajax': + try: + ajax_content = json.loads(content) + widget_content = ajax_content['response'][ + 'content']['module_content'] + except: + blocks = re.finditer(regexp, content, re.DOTALL) + for block in blocks: + widget_content = block.group('widget_content').strip() + else: + blocks = re.finditer(regexp, content, re.DOTALL) + for block in blocks: + widget_content = block.group('widget_content').strip() + + return Markup(widget_content) + + +@handle_response_format +@treeio_login_required +def index(request, response_format='html'): + "Homepage" + trash = Object.filter_by_request(request, manager=Object.objects.filter(trash=True), + mode='r', filter_trash=False).count() + user = request.user.profile + perspective = user.get_perspective() + widget_objects = Widget.objects.filter(user=user, perspective=perspective) + clean_widgets = [] + + for widget_object in widget_objects: + try: + module = perspective.get_modules().filter( + name=widget_object.module_name)[0] + widget = _get_widget(request, module, widget_object.widget_name) + if 'view' in widget: + try: + content = unicode( + widget['view'](request, response_format=response_format).content, 'utf_8') + widget_content = _get_widget_content( + content, response_format=response_format) + except Exception, e: + widget_content = "" + if settings.DEBUG: + widget_content = str(e) + + widget['content'] = widget_content + if widget: + widget_object.widget = widget + clean_widgets.append(widget_object) + except IndexError: + widget_object.delete() + + return render_to_response('core/dashboard/index', + {'trash': trash, + 'widgets': clean_widgets}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def dashboard_widget_add(request, module_name=None, widget_name=None, response_format='html'): + "Add a Widget to the Dashboard" + trash = Object.filter_by_request(request, manager=Object.objects.filter(trash=True), + mode='r', filter_trash=False).count() + + if module_name and widget_name: + widget = _create_widget_object(request, module_name, widget_name) + if widget: + return HttpResponseRedirect(reverse('core_dashboard_index')) + + widgets = _get_all_widgets(request) + + return render_to_response('core/dashboard/widget_add', + {'trash': trash, + 'widgets': widgets}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def dashboard_widget_edit(request, widget_id, response_format='html'): + "Edit an existing Widget on the Dashboard" + + user = request.user.profile + + widget_object = get_object_or_404(Widget, pk=widget_id) + if widget_object.user == user: + perspective = user.get_perspective() + module = perspective.get_modules().filter( + name=widget_object.module_name)[0] + widget = _get_widget(request, module, widget_object.widget_name) + widget_object.widget = widget + if 'view' in widget: + try: + content = unicode( + widget['view'](request, response_format=response_format).content, 'utf_8') + widget_content = _get_widget_content( + content, response_format=response_format) + except Exception: + widget_content = "" + widget['content'] = widget_content + if request.POST: + form = WidgetForm(user, request.POST, instance=widget_object) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('core_dashboard_index')) + else: + form = WidgetForm(user, instance=widget_object) + return render_to_response('core/dashboard/widget_edit', + {'widget': widget_object, + 'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + return HttpResponseRedirect(reverse('home')) + + +@handle_response_format +@treeio_login_required +def dashboard_widget_delete(request, widget_id, response_format='html'): + "Delete an existing Widget from the Dashboard" + + widget = get_object_or_404(Widget, pk=widget_id) + + if widget.user == request.user.profile: + widget.delete() + + return HttpResponseRedirect(reverse('core_dashboard_index')) + + +@handle_response_format +@treeio_login_required +def dashboard_widget_arrange(request, panel='left', response_format='html'): + "Arrange widgets with AJAX request" + user = request.user.profile + + if panel == 'left' or not panel: + shift = -100 + else: + shift = 100 + + if request.GET and 'id_widget[]' in request.GET: + widget_ids = request.GET.getlist('id_widget[]') + widgets = Widget.objects.filter(user=user, pk__in=widget_ids) + for widget in widgets: + if unicode(widget.id) in widget_ids: + widget.weight = shift + widget_ids.index(unicode(widget.id)) + widget.save() + + return HttpResponseRedirect(reverse('core_dashboard_index')) diff --git a/data/treeio/treeio/treeio/core/db/__init__.py b/data/treeio/treeio/treeio/core/db/__init__.py new file mode 100644 index 0000000..94667fc --- /dev/null +++ b/data/treeio/treeio/treeio/core/db/__init__.py @@ -0,0 +1,13 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- encoding: utf-8 -*- +""" +Database extension +""" +__author__ = 'Kirill Yakovenko, crystalnix' +__email__ = 'kirill.yakovenko@gmail.com' + +from db import * diff --git a/data/treeio/treeio/treeio/core/db/creation.py b/data/treeio/treeio/treeio/core/db/creation.py new file mode 100644 index 0000000..257bdf5 --- /dev/null +++ b/data/treeio/treeio/treeio/core/db/creation.py @@ -0,0 +1,77 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- encoding: utf-8 -*- +""" +Database creator +""" +__author__ = 'Kirill Yakovenko, crystalnix' +__email__ = 'kirill.yakovenko@gmail.com' + +from django.conf import settings +from django.db import connections +from django.utils.importlib import import_module + + +def DatabaseCreation(domain): + connection = connections[domain] + try: + BaseDatabaseCreation = import_module( + '.creation', connection.settings_dict['ENGINE']).DatabaseCreation + except: + BaseDatabaseCreation = import_module( + '.creation', 'django.db.backends').BaseDatabaseCreation + + class DBCreation(BaseDatabaseCreation): + + def __init__(self, connection): + super(DBCreation, self).__init__(connection) + self.database_name = self.connection.settings_dict.get( + 'NAME') or super(DBCreation, self)._get_test_db_name() + + def _get_test_db_name(self): + return self.database_name + + def create_db(self, load_initial): + from django.core.management import call_command + + # Deletes database name because if database doesn't exist, + # django orm isn't able to connect to it. + self.connection.settings_dict["NAME"] = None + self._create_test_db(0, True) + self.connection.settings_dict["NAME"] = self.database_name + + self.connection.close() + # Confirm the feature set of the database + self.connection.features.confirm() + + # Report syncdb messages at one level lower than that requested. + # This ensures we don't get flooded with messages during testing + # (unless you really ask to be flooded) + call_command('syncdb', + verbosity=0, + interactive=False, + database=domain, + load_initial_data=load_initial, + migrate_all=True) + + from django.core.cache import get_cache + from django.core.cache.backends.db import BaseDatabaseCache + + for cache_alias in settings.CACHES: + cache = get_cache(cache_alias) + if isinstance(cache, BaseDatabaseCache): + from django.db import router + + if router.allow_syncdb(self.connection.alias, cache.cache_model_class): + call_command( + 'createcachetable', cache._table, database=self.connection.alias) + + # Get a cursor (even though we don't need one yet). This has + # the side effect of initializing the test database. + cursor = self.connection.cursor() + return self.database_name + + return DBCreation(connection) diff --git a/data/treeio/treeio/treeio/core/db/db.py b/data/treeio/treeio/treeio/core/db/db.py new file mode 100644 index 0000000..f0e317c --- /dev/null +++ b/data/treeio/treeio/treeio/core/db/db.py @@ -0,0 +1,119 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Database manipulations + +Dynamically identifies the correct database to use for the current domain +""" +from pandora import box +from django.utils import simplejson as json +import UserDict + +from os import path + +FILE_ROOT = path.abspath(path.dirname(__file__)) + +HARDTREE_DB_SETTINGS_FILE = path.join(FILE_ROOT, 'dbsettings.json') +NO_DEFAULT = False + + +class DatabaseNotFound(Exception): + pass + + +class DatabaseDict(UserDict.DictMixin, dict): + + """A dictionary which applies an arbitrary key-altering function before accessing the keys""" + + def _ensure_defaults(self): + for db in self.values(): + db.setdefault('ENGINE', 'django.db.backends.dummy') + if db['ENGINE'] == 'django.db.backends.' or not db['ENGINE']: + db['ENGINE'] = 'django.db.backends.dummy' + db.setdefault('OPTIONS', {}) + db.setdefault('TEST_CHARSET', None) + db.setdefault('TEST_COLLATION', None) + db.setdefault('TEST_NAME', None) + db.setdefault('TEST_MIRROR', None) + db.setdefault('TIME_ZONE', 'UTC0') + for setting in ('NAME', 'USER', 'PASSWORD', 'HOST', 'PORT'): + db.setdefault(setting, '') + + def __init__(self, *args, **kwargs): + self._load_databases(*args, **kwargs) + + def _load_databases(self, *args, **kwargs): + dbfile = open(HARDTREE_DB_SETTINGS_FILE, 'r') + self.store = json.load(dbfile) + self.update(dict(*args, **kwargs)) # use the free update to set keys + self._ensure_defaults() + + def _save_databases(self): + f = open(HARDTREE_DB_SETTINGS_FILE, 'w') + json.dump(self.store, f) + f.close() + + def __getitem__(self, key): + try: + return self.store[key] + except KeyError: + self._load_databases() + try: + return self.store[key] + except KeyError: + try: + if NO_DEFAULT: + raise DatabaseNotFound( + 'No database found for %s' % key) + return self.store['default'] + except KeyError: + raise RuntimeError( + 'Default database is not specified in the config file and the current database is unavailable') + + def __setitem__(self, key, value): + self.store[key] = value + self._save_databases() + + def __delitem__(self, key): + del self.store[key] + self._save_databases() + + def __iter__(self): + return iter(self.store) + + def __len__(self): + return len(self.store) + + +class DBRouter(object): + + """A router to control all database operations and dynamically select the correct database""" + + def _get_current_database(self): + "Returns the database that should be used for the current request" + if 'request' in box and 'CURRENT_DATABASE_NAME' not in box: + current_db = box['request'].get_host().split('.')[0] + else: + current_db = box.get('CURRENT_DATABASE_NAME', 'default') + return current_db + + def db_for_read(self, model, **hints): + "Point all operations to the current database" + if 'instance' in hints: + return hints['instance']._state.db + return self._get_current_database() + + def db_for_write(self, model, **hints): + "Point all operations to the current database" + return self._get_current_database() + + def allow_relation(self, obj1, obj2, **hints): + "Allow any relation" + return True + + def allow_syncdb(self, db, model): + "Allow syncdb" + return True diff --git a/data/treeio/treeio/treeio/core/forms.py b/data/treeio/treeio/treeio/core/forms.py new file mode 100644 index 0000000..b820013 --- /dev/null +++ b/data/treeio/treeio/treeio/core/forms.py @@ -0,0 +1,383 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core module forms +""" + +import os +import sys +from django import forms +from django.db import router +from django.core.exceptions import ValidationError +from django.core.urlresolvers import reverse +from treeio.core.models import Object +from captcha.fields import CaptchaField +from django.utils.translation import ugettext as _ +from treeio.core.conf import settings +from django.db.models import Q +import django.contrib.auth.models as django_auth +from jinja2.filters import do_striptags, do_truncate +from treeio.core.models import Location, User, Widget, Tag, ConfigSetting +from treeio.core.mail import EmailPassword +from treeio.identities.models import Contact, ContactType, ContactValue + + +class PermissionForm(forms.ModelForm): + + "Permission Form" + + def __init__(self, *args, **kwargs): + "Prepare form" + super(PermissionForm, self).__init__(*args, **kwargs) + + self.fields['read_access'].help_text = "" + self.fields['read_access'].required = False + self.fields['read_access'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_access_lookup')}) + + self.fields['full_access'].help_text = "" + self.fields['full_access'].required = False + self.fields['full_access'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_access_lookup')}) + + class Meta: + + "Permission Form" + model = Object + fields = ('read_access', 'full_access') + + +class SubscribeForm(forms.Form): + + "Subscribe Form" + + subscriber = forms.ModelChoiceField(queryset=User.objects.all()) + + def __init__(self, instance, *args, **kwargs): + "Prepare form" + subscriptions = instance.subscribers.all() + + super(SubscribeForm, self).__init__(*args, **kwargs) + self.subscriptions = subscriptions + self.instance = instance + + self.fields['subscriber'].label = "" + self.fields['subscriber'].queryset = User.objects.exclude( + pk__in=subscriptions) + self.fields['subscriber'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_user_lookup')}) + + def save(self): + "Subscribe" + + user = self.cleaned_data['subscriber'] + object = self.instance + + if user not in self.subscriptions: + object.subscribers.add(user) + self.subscriptions = object.subscribers.all() + + return self.subscriptions + + +class ObjectLinksForm(forms.Form): + + """ Object Links Form """ + + links = forms.ModelChoiceField(queryset=[], empty_label=None, label='') + + def __init__(self, user, response_format, instance, *args, **kwargs): + + super(ObjectLinksForm, self).__init__(*args, **kwargs) + + queryset = Object.filter_permitted(user, Object.objects) + self.fields['links'].queryset = queryset + + if 'ajax' not in response_format: + if instance: + queryset = queryset.exclude(pk__in=instance.links.all()) + + choices = [] + for obj in queryset: + human_type = obj.get_human_type() + name = do_truncate( + do_striptags(unicode(obj.object_name)), 20, True) + if human_type: + name += u" (" + human_type + u")" + choices.append((obj.id, name)) + self.fields['links'].choices = choices + + self.fields['links'].label = "" + self.fields['links'].initial = "" + self.fields['links'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('core_ajax_object_lookup')}) + + +class TagsForm(forms.Form): + + tags = forms.ModelMultipleChoiceField(queryset=Tag.objects.all()) + + def __init__(self, tags, *args, **kwargs): + super(TagsForm, self).__init__(*args, **kwargs) + + self.fields['tags'].label = "" + self.fields['tags'].initial = [tag.id for tag in tags] + self.fields['tags'].required = False + self.fields['tags'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('core_ajax_tag_lookup')}) + + def save(self): + return self.cleaned_data['tags'] + + +class LoginForm(forms.Form): + + """ Login form""" + + captcha = CaptchaField(label=_("Enter text from the image")) + + def __init__(self, *args, **kwargs): + super(LoginForm, self).__init__(*args, **kwargs) + if settings.CAPTCHA_DISABLE: + del self.fields['captcha'] + + +class PasswordResetForm(forms.Form): + + "Password reset form" + + username = forms.CharField(label=("Username or E-mail"), max_length=75) + + def __init__(self, *args, **kwargs): + super(PasswordResetForm, self).__init__(*args, **kwargs) + self.fields['username'].label = _("Username or E-mail") + + def clean_username(self): + """ + Validates that a user exists with the given e-mail address. + """ + username = self.cleaned_data["username"] + if '@' in username: + # The user entered an email, so try to log them in by e-mail + emails = ContactValue.objects.filter(value=username, + field__field_type='email', + contact__trash=False, + contact__related_user__isnull=False) + users = [email.contact.related_user.user for email in emails] + else: + users = User.objects.filter(user__username=username) + if len(users) == 0: + raise forms.ValidationError( + _("Sorry, we don't know that user or e-mail.")) + else: + username = users[0] + return username + + def save(self): + "Send e-mail" + user = self.cleaned_data["username"] + if user: + toaddr = user.get_contact().get_email() + if toaddr: + password = user.generate_new_password() + email = EmailPassword(toaddr, user.user.username, password) + email.send_email() + + +class InvitationForm(forms.Form): + + """ Create account from Invitation form """ + + invitation = None + + def __init__(self, invitation=None, *args, **kwargs): + + super(InvitationForm, self).__init__(*args, **kwargs) + + self.fields['username'] = forms.CharField( + max_length=255, label=_("Username")) + self.fields['name'] = forms.CharField( + max_length=255, label=_("Your name")) + self.fields['password'] = forms.CharField(max_length=255, label=_("Password"), + widget=forms.PasswordInput(render_value=False)) + self.fields['password_again'] = forms.CharField(max_length=255, label=_("Confirm Password"), + widget=forms.PasswordInput(render_value=False)) + + self.invitation = invitation + + def clean_username(self): + "Clean Name" + data = self.cleaned_data['username'] + query = Q(name=data) + existing = User.objects.filter(query) + if existing: + raise forms.ValidationError( + _("User with username %s already exists.") % data) + # Check Hardtree Subscription user limit + user_limit = getattr(settings, 'HARDTREE_SUBSCRIPTION_USER_LIMIT', 0) + if user_limit > 0: + user_number = User.objects.filter(disabled=False).count() + if user_number >= user_limit: + raise forms.ValidationError( + _("Sorry, but your subscription does not allow more than %d users. You're currently at your limit.") % (user_limit)) + return data + + def clean_password_again(self): + "Clean password again" + password1 = self.cleaned_data['password'] + password2 = self.cleaned_data['password_again'] + if not password1 == password2: + raise forms.ValidationError(_("Passwords do not match")) + return password2 + + def save(self, *args, **kwargs): + "Form processor" + + profile = None + + if self.invitation: + # Create DjangoUser + django_user = django_auth.User( + username=self.cleaned_data['username'], password='') + django_user.set_password(self.cleaned_data['password']) + django_user.save() + + # Crate profile + try: + profile = django_user.profile + except: + profile = User() + profile.user = django_user + + profile.name = django_user.username + profile.default_group = self.invitation.default_group + profile.save() + + # Create contact + try: + contact_type = ContactType.objects.get( + Q(name='Person') | Q(slug='person')) + except: + contact_type = ContactType.objects.all()[0] + + try: + # Check if contact has already been created (e.g. by a signals + contact = profile.get_contact() + if not contact: + contact = Contact() + except: + contact = Contact() + + contact.name = self.cleaned_data['name'] + contact.contact_type = contact_type + contact.related_user = profile + contact.save() + + # Set email + try: + emailfield = contact_type.fields.filter(field_type='email')[0] + email = ContactValue( + value=self.invitation.email, field=emailfield, contact=contact) + email.save() + except: + pass + + # Add quick start widget + widget = Widget(user=profile, + perspective=profile.get_perspective(), + module_name='treeio.core', + widget_name='widget_welcome') + widget.save() + + return profile + + +class LocationForm(forms.ModelForm): + + """ Item location form """ + + def __init__(self, user, location_id, *args, **kwargs): + super(LocationForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + + self.fields['parent'].label = _("Parent") + self.fields['parent'].queryset = Object.filter_permitted( + user, Location.objects, mode='x') + if location_id: + self.fields['parent'].initial = location_id + + class Meta: + + "Location Form" + model = Location + fields = ('name', 'parent') + + +class SqlSettingsForm(forms.Form): + sql_engine = forms.ChoiceField(choices=(("postgresql", _('PostgreSQL'),), ("postgresql_psycopg2", _("Psycopg")), + ("sqlite3", _("SQLite")), ("mysql", _('MySql')), ("oracle", _("Oracle")))) + sql_database = forms.CharField(max_length=256) + sql_user = forms.CharField(max_length=30) + sql_password = forms.CharField( + max_length=128, required=False, widget=forms.PasswordInput) + + def clean_sql_engine(self): + engine = self.cleaned_data['sql_engine'] + return "django.db.backends.%s" % engine + + def create_database(self): + if not self._errors: + from django.db import connections + from django.core.exceptions import ImproperlyConfigured + from treeio.core.domains import setup_domain_database + database = { + 'ENGINE': self.cleaned_data['sql_engine'], + 'NAME': self.cleaned_data['sql_database'], + 'USER': self.cleaned_data['sql_user'], + 'PASSWORD': self.cleaned_data['sql_password'], + } + # creates database + settings.DATABASES['treeio_new_db'] = database + try: + setup_domain_database('treeio_new_db', True) + except ImproperlyConfigured as exc: + self._errors['sql_engine'] = self.error_class( + [_("Can't connect to engine. Error is ") + exc.message]) + del self.cleaned_data['sql_engine'] + except Exception as exc: + del connections._connections['treeio_new_db'] + raise ValidationError( + _("Can't create database. SQL error is") + ' %s' % exc) + finally: + del settings.DATABASES['treeio_new_db'] + + # save database settings + settings.DATABASES[router.db_for_read(ConfigSetting)] = database + connections._connections.clear() + if not getattr(settings, 'HARDTREE_MULTITENANCY', False): + settings_filepath = sys.modules[ + os.environ['DJANGO_SETTINGS_MODULE']].__file__ + if settings_filepath.endswith('.pyc'): + settings_filepath = settings_filepath[:-1] + with open(settings_filepath, 'r') as fl: + lines = fl.readlines() + with open(settings_filepath, 'w') as fl: + lines = iter(lines) + for line in lines: + if 'DATABASES' not in line: + fl.write(line) + else: + fl.write('DATABASES = ') + break + fl.write(repr(settings.DATABASES)) + fl.write('\n\n') + for line in lines: + if '=' in line: + fl.write(line) + break + for line in lines: + fl.write(line) diff --git a/data/treeio/treeio/treeio/core/mail.py b/data/treeio/treeio/treeio/core/mail.py new file mode 100644 index 0000000..5215655 --- /dev/null +++ b/data/treeio/treeio/treeio/core/mail.py @@ -0,0 +1,522 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core Mail Framework +""" +from treeio.core.conf import settings + +import re +import time +import base64 +import smtplib +import imaplib +import email +import poplib +from datetime import datetime + +from email.MIMEText import MIMEText +from email.header import decode_header +from email.MIMEMultipart import MIMEMultipart + +from threading import Thread +from django.utils.encoding import smart_unicode +from django.utils.translation import ugettext as _ +from django.template.defaultfilters import removetags + +EMAIL_SERVER = getattr(settings, 'EMAIL_SERVER', '127.0.0.1') +IMAP_SERVER = getattr(settings, 'IMAP_SERVER', '') +EMAIL_USERNAME = getattr(settings, 'EMAIL_USERNAME', None) +EMAIL_PASSWORD = getattr(settings, 'EMAIL_PASSWORD', None) +EMAIL_FROM = getattr(settings, 'EMAIL_FROM', 'noreply@tree.io') +DEFAULT_SIGNATURE = getattr(settings, 'DEFAULT_SIGNATURE', '') + + +class BaseEmail(Thread): + + "Generic e-mail class to send any emails" + + def __init__(self, server, username, password, fromaddr, + toaddr, subject, body, signature=None, html=None, + port=None, ssl=False): + Thread.__init__(self) + self.server = server + self.port = port + self.ssl = ssl + self.username = username + self.password = password + self.toaddr = toaddr + self.fromaddr = fromaddr + self.subject = subject + self.body = body + self.signature = signature + self.html = html + self.multipart = self.body and self.html + + def run(self): + "Run" + self.process_email() + + def send_email(self): + "Send email" + self.start() + + def get_smtp_port(self, server): + "Returns appropriate SMTP port number depending on incoming server name and boolean ssl" + # http://www.emailaddressmanager.com/tips/mail-settings.html + + port = 25 # default + ssl = False + + if "gmail.com" in server or "googlemail.com" in server: + port = 587 + ssl = False + elif server == "plus.smtp.mail.yahoo.com": + if hasattr(smtplib, 'SMTP_SSL'): + port = 465 + ssl = True + if server == "smtp.live.com" or server == "smtp.isp.netscape.com": + ssl = True + + return port, ssl + + def process_email(self): + "Create a message and send it" + try: + msg = MIMEMultipart('alternative') + + msg['From'] = self.fromaddr + msg['To'] = self.toaddr + msg['Subject'] = self.subject + + text = self.body + html = self.html + + # adding signature + if self.signature: + text += self.signature + + # Record the MIME types of both parts - text/plain and text/html. + part1 = MIMEText(text.encode('utf-8'), 'plain', 'utf-8') + msg.attach(part1) + + if html: + part2 = MIMEText(html.encode('utf-8'), 'html', 'utf-8') + msg.attach(part2) + + if not self.port: + self.port, self.ssl = self.get_smtp_port(self.server) + + if self.ssl and hasattr(smtplib, 'SMTP_SSL'): + s = smtplib.SMTP_SSL(self.server, self.port) + else: + s = smtplib.SMTP(self.server, self.port) + s.set_debuglevel(0) + s.ehlo() + try: + s.starttls() + except smtplib.SMTPException: + pass + s.ehlo() + if self.username is not None: + s.login(self.username, self.password) + s.sendmail(self.fromaddr, self.toaddr, msg.as_string()) + s.close() + return True + except: + if settings.DEBUG: + raise + else: + import traceback + import sys + from treeio import core + from django.core.mail import mail_admins + exc_type, exc_value, exc_traceback = sys.exc_info() + domain = getattr(settings, 'CURRENT_DOMAIN', 'default') + subject = "Exception for %s: %s %s" % ( + domain, unicode(exc_type), unicode(exc_value)) + body = subject + "\n\n" + body += unicode(core.__file__) + "\n\n" + body += u"Server: %s\n\n" % self.server + body += u"Port: %s\n\n" % unicode(self.port) + body += u"Username: %s\n\n" % self.username + body += u"From: %s\n\n" % self.fromaddr + body += u"To: %s\n\n" % self.toaddr + for s in traceback.format_tb(exc_traceback): + body += s + '\n' + mail_admins(subject, body) + + +class SystemEmail(BaseEmail): + + "E-mail class to send messages on behalf of Tree.io team" + + def __init__(self, toaddr, subject, body, signature=None, html=None): + + if not signature: + signature = _(DEFAULT_SIGNATURE) + + BaseEmail.__init__(self, EMAIL_SERVER, EMAIL_USERNAME, EMAIL_PASSWORD, EMAIL_FROM, + toaddr, subject, body, signature, html) + +# +# Specific email classes +# + + +class EmailInvitation(SystemEmail): + + "Email Invitation" + + invitation = None + sender = None + + def __init__(self, invitation, sender, domain): + self.invitation = invitation + self.sender = sender + self.domain = domain + + subject = '%s has invited you to Tree.io' % (self.sender) + + toaddr = self.invitation.email + + signature = """ +\r\n +- %s""" % unicode(self.sender) + + body = """ +Hi! +\r\n +I've invited you to Tree.io. +\r\n +Tree.io is a new online service that helps you manage your business online. +\r\n +Use this link to join me: +\r\n +http://%s/accounts/invitation/?email=%s&key=%s + """ % (unicode(self.domain), + unicode(self.invitation.email), + unicode(self.invitation.key)) + + super(EmailInvitation, self).__init__(toaddr, subject, body, signature) + + +class EmailPassword(SystemEmail): + + "Email Message" + + def __init__(self, toaddr, username, password): + + subject = "Password reset on Tree.io" + + body = """ +Hello! +\r\n +You have requested a password reset for your Tree.io account. +\r\n +New password for: %s\r\n\r\n Password: %s\r\n\r\n +""" % (username, password) + + super(EmailPassword, self).__init__(toaddr, subject, body) + + +# +# Abstract email receiver +# + +def intcmp(a, b): + try: + return cmp(int(a), int(b)) + except: + return cmp(a, b) + + +class EmailReceiver(Thread): + + """EmailReceiver fetches email from imap and pop email servers. + This class can be used only as parent. You should redefine + the process_msg method. + """ + + def __init__(self, server_type, server_name, username, password, folder_name=None): + Thread.__init__(self) + self.incoming_server_type = server_type + self.incoming_server_name = server_name + self.incoming_server_username = username + self.incoming_password = password + self.folder_name = folder_name or getattr( + settings, 'HARDTREE_MESSAGING_IMAP_DEFAULT_FOLDER_NAME', 'UNSEEN') + + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE', [ + (1, '(GMT-11:00) International Date Line West')]) + title = all_timezones[int(default_timezone)][1] + GMT = title[4:10] # with sign e.g. +06:00 + sign = GMT[0:1] # + or - + hours = int(GMT[1:3]) # e.g. 06 + mins = int(GMT[4:6]) + self.tzoffset = hours * 3600 + mins * 60 + if sign == "-": + self.tzoffset *= -1 + + def run(self): + "Run" + self.get_emails() + + def get_pop_port(self): + "Returns appropriate POP port number depending on incoming server name" + + port = 110 # default + ssl = False + + if self.incoming_server_type == 'POP3-SSL': + port = 995 + ssl = True + + return port, ssl + + def get_imap_port(self): + "Returns appropriate IMAP port number depending on incoming server name" + + port = 143 # default + ssl = False + + if self.incoming_server_type == 'IMAP-SSL': + port = 993 + ssl = True + + return port, ssl + + def get_emails(self): + "Fetches emails" + + if self.incoming_server_type == 'IMAP' or self.incoming_server_type == 'IMAP-SSL': + + HARDTREE_MESSAGING_IMAP_LIMIT = getattr( + settings, 'HARDTREE_MESSAGING_IMAP_LIMIT', 100) + # connect to the server + port, ssl = self.get_imap_port() + + if ssl: + M = imaplib.IMAP4_SSL(self.incoming_server_name, port) + else: + M = imaplib.IMAP4(self.incoming_server_name, port) + M.login(self.incoming_server_username, self.incoming_password) + M.select() + + msgnums = [] + try: + # fetch mail from ALL or UNSEEN] + typ, data = M.sort('REVERSE DATE', 'UTF-8', self.folder_name) + msgnums = data[0].split() if data[0] else [] + except: + # fetch mail from ALL or UNSEEN] + typ, data = M.search(None, self.folder_name) + msgnums = data[0].split() if data[0] else [] + msgnums = sorted(msgnums, cmp=intcmp, reverse=True) + + for num in msgnums[:HARDTREE_MESSAGING_IMAP_LIMIT]: + resp, msg = M.fetch(num, '(RFC822)') + mail = email.message_from_string(msg[0][1]) + self.process_mail(mail) + if self.folder_name == 'UNSEEN': + M.store(num, '+FLAGS', '\\Seen') + + M.close() + M.logout() + + if self.incoming_server_type == 'POP3' or self.incoming_server_type == 'POP3-SSL': + + HARDTREE_MESSAGING_POP3_LIMIT = getattr( + settings, 'HARDTREE_MESSAGING_POP3_LIMIT', 100) + # connect to the server + port, ssl = self.get_pop_port() + + if ssl: + M = poplib.POP3_SSL(self.incoming_server_name, port) + else: + M = poplib.POP3(self.incoming_server_name, port) + M.user(self.incoming_server_username) + M.pass_(self.incoming_password) + + numMessages = len(M.list()[1]) + + # Select correct limit for range(limit, numMessages) + if numMessages >= HARDTREE_MESSAGING_POP3_LIMIT: + limit = numMessages - HARDTREE_MESSAGING_POP3_LIMIT + else: + limit = 0 + + # select new emails + for i in range(limit, numMessages): + lines = M.retr(i)[1] + mail = email.message_from_string("\n".join(lines)) + self.process_mail(mail) + M.quit() + + def process_mail(self, mail): + # process message + body = None + attachments = [] + + if mail.is_multipart(): + text = None + html = None + for part in mail.walk(): + # multipart are just containers, so we skip them + if part.get_content_maintype() == 'multipart': + continue + + if part.get_filename() and part.get('Content-Transfer-Encoding', '') == 'base64': + attachments.append(part) + continue + + if part.get_content_type() == 'text/plain': + text = part.get_payload(decode=True) + + if part.get_content_type() == 'text/html': + html = part.get_payload(decode=True) + + body = html if html else text + else: + body = mail.get_payload(decode=True) + + class MailAttrs: + pass + attrs = MailAttrs() + attrs.subject, encoding = self.decode_subject(mail['subject']) + # replace annoying characters + attrs.body = self.decode(body, encoding) + if not mail.is_multipart() and not mail.get_content_type().endswith('html'): + attrs.body = attrs.body.replace('&', '&').replace('<', '<').replace( + '>', '>').replace('"', '"').replace("'", ''') + attrs.body = self.parse_email_body(attrs.body) + attrs.author_name, attrs.author_email = self.get_email_author(mail) + + attrs.email_date = None + try: + date_tuple = email.utils.parsedate_tz(mail['Date']) + email_ts = time.mktime( + date_tuple[:9]) - int(date_tuple[9]) + self.tzoffset + attrs.email_date = datetime.fromtimestamp(email_ts) + except: + pass + + self.process_msg(mail, attrs, attachments) + + def process_msg(self, msg, attrs, attachments): + raise NotImplementedError + + def decode(self, string, encoding): + if encoding: + return smart_unicode(string, encoding, errors='ignore') + else: + return self.make_unicode(string) + + def make_unicode(self, string): + "Detects string encoding and make it unicode" + + utf8_detector = re.compile(r"""^(?: + [\x09\x0A\x0D\x20-\x7E] # ASCII + | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )*$""", re.X) + + cp1252_detector = re.compile(r'^(?:[\x80-\xBF])*$', re.X) + xa4_detector = re.compile(r'^(?:\xA4)*$', re.X) + + try: + if re.match(utf8_detector, string): + return unicode(string, 'utf_8') + if re.match(cp1252_detector, string): + if re.match(xa4_detector, string): + return smart_unicode(string, 'iso8859_15') + else: + return smart_unicode(string, 'cp1252') + return smart_unicode(string, 'koi8-r', errors='ignore') + except: + return smart_unicode(string, 'utf-8', errors='ignore') + + def decode_subject(self, subject): + "Decodes email subjects" + if not subject: + subject = 'No subject' + encoding = None + else: + subject, encoding = decode_header(subject)[0] + subject = self.decode(subject, encoding) + return subject, encoding + + def decode_body(self, body): + "Decodes Base64-encoded string" + if body is None: + body = 'No message' + else: + body = str(body) + body = base64.b64decode(body) + + return body + + def parse_email_body(self, body): + "Removes all the dangerous and useless staff" + + # Replace annoying characters + body = body.replace('\r', '').replace('=\n', '').replace('=\n\r', '') + body = body.replace('=20\n', '\n\n') + + HARDTREE_MESSAGING_UNSAFE_BLOCKS = getattr(settings, 'HARDTREE_MESSAGING_UNSAFE_BLOCKS', + ('head', 'object', 'embed', 'applet', 'noframes', + 'noscript', 'noembed', 'iframe', 'frame', 'frameset')) + + # Strip unsafe tags + tags_str = ' '.join(HARDTREE_MESSAGING_UNSAFE_BLOCKS) + body = removetags(body, tags_str) + + # Remove multiple
    tags + rules = [ + {r'\s*\s*': u'\n'} + ] + + for rule in rules: + for (k, v) in rule.items(): + regex = re.compile(k) + body = regex.sub(v, body) + + # displaying messages correctly + if body.startswith('\n<', '><') + elif '\n') + + return body + + def get_email_author(self, msg): + "Returns author's name and email if any" + try: + header_from = msg['From'] + splits = header_from.split('<', 1) + name, email = splits if len(splits) == 2 else ('', header_from) + email = email.split('>', 1)[0] + if name: + name, encoding = decode_header(name.strip(' "\''))[0] + name = self.decode(name, encoding) + name = name.strip(' \'"') + except: + email = name = None + + if not email: + try: + email = msg['Return-path'] + email.strip(' \'"<>') + except Exception: + email = None + + return name, email diff --git a/data/treeio/treeio/treeio/core/management/commands/installdb.py b/data/treeio/treeio/treeio/core/management/commands/installdb.py new file mode 100644 index 0000000..26b8537 --- /dev/null +++ b/data/treeio/treeio/treeio/core/management/commands/installdb.py @@ -0,0 +1,92 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +from distutils.util import strtobool +from django.core.management.base import BaseCommand, CommandError +from treeio.core.conf import settings +import json +import subprocess +import sys + + +class Command(BaseCommand): + args = '' + help = 'Installs the database prompting the user for all details' + + def handle(self, *args, **options): + + initial_db = { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': './initial.db', + 'HOST': '', + 'USER': '', + 'PASSWORD': '' + } + + db = {} + + dbengine = raw_input( + 'Enter database engine (defaults to postgres): ') + if not dbengine: + dbengine = 'postgresql_psycopg2' + if dbengine in ('mysql', 'postgresql', 'postgresql_psycopg2', 'oracle', 'sqlite3'): + dbengine = 'django.db.backends.' + dbengine + else: + raise CommandError('Unknown database engine: %s' % dbengine) + + if dbengine.endswith('sqlite3'): + dbname = raw_input( + 'Enter database name (defaults to treeio.db): ') + if not dbname: + dbname = 'treeio.db' + else: + dbname = raw_input( + 'Enter database name (defaults to treeio): ') + if not dbname: + dbname = 'treeio' + + dbuser = raw_input('Database user (defaults to treeio): ') + if not dbuser: + dbuser = 'treeio' + + dbpassword = raw_input('Database password: ') + + dbhost = raw_input('Hostname (defaults to 127.0.0.1): ') + if not dbhost: + dbhost = '127.0.0.1' + dbport = raw_input('Port (empty for default): ') + + self.stdout.write('\n-- Saving database configuration...\n') + self.stdout.flush() + settings.CONF.set('db', 'ENGINE', dbengine) + settings.CONF.set('db', 'NAME', dbname) + if not dbengine.endswith('sqlite3'): + settings.CONF.set('db', 'USER', dbuser) + settings.CONF.set('db', 'PASSWORD', dbpassword) + settings.CONF.set('db', 'HOST', dbhost) + settings.CONF.set('db', 'PORT', dbport) + + with open(settings.USER_CONFIG_FILE, 'w') as f: + settings.CONF.write(f) + + answer = raw_input( + 'Would you like to create the tables (say no to use an existing database) [y/n] (defaults to yes): ') + if not len(answer): + answer = True + else: + answer = strtobool(answer) + + if answer: + exit_code = subprocess.call( + [sys.executable, 'manage.py', 'syncdb', '--all', '--noinput']) + if not exit_code == 0: + self.stdout.flush() + raise CommandError('Failed to install database.') + + exit_code = subprocess.call( + [sys.executable, 'manage.py', 'migrate', '--all', '--fake', '--noinput', '--no-initial-data']) + + self.stdout.write( + '\n-- Successfully installed database. \n-- You\'re ready to go!\n\n') diff --git a/data/treeio/treeio/treeio/core/management/commands/runcron.py b/data/treeio/treeio/treeio/core/management/commands/runcron.py new file mode 100644 index 0000000..3e7d37c --- /dev/null +++ b/data/treeio/treeio/treeio/core/management/commands/runcron.py @@ -0,0 +1,261 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Cron commands +""" +from django.core.management.base import BaseCommand +from django.conf import settings +from django.core.cache import cache +from core.domains import setup_domain +from optparse import make_option +import multiprocessing +import logging +import logging.handlers +import signal +import time +import sys + +LOG_LEVELS = {'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + 'critical': logging.CRITICAL} + +cronlogger = logging.getLogger('CronLogger') +cronlogger.setLevel(logging.DEBUG) + + +class CronJob(multiprocessing.Process): + + "Single Cron job" + + job = None + database = 'default' + priority = 5 + + def __init__(self, job, database='default', priority=5, *args, **kwargs): + super(CronJob, self).__init__(*args, **kwargs) + self.job = job + self.priority = priority + self.database = database + self._stopped = False + + def sigterm(self, *args, **kwargs): + cronlogger = logging.getLogger('CronLogger') + if self._stopped: + cronlogger.critical('JOB - Terminated ' + unicode(self)) + sys.exit(1) + else: + self._stopped = True + cronlogger.warning('JOB - Early shutdown ' + unicode(self)) + raise KeyboardInterrupt() + + def run(self): + signal.signal(signal.SIGTERM, self.sigterm) + setup_domain(self.database) + cronlogger = logging.getLogger('CronLogger') + cronlogger.debug('JOB - Starting ' + unicode(self)) + try: + self.job() + except KeyboardInterrupt: + self._stopped = True + except SystemExit: + self._stopped = True + except: + import traceback + from hardtree import core + from django.core.mail import mail_admins + exc_type, exc_value, exc_traceback = sys.exc_info() + subject = "CRON Exception for " + \ + unicode(self) + ": " + unicode(exc_type) + \ + " " + unicode(exc_value) + body = subject + "\n\n" + body += unicode(core.__file__) + "\n\n" + for s in traceback.format_tb(exc_traceback): + body += s + '\n' + cronlogger.error(subject + '\n' + body) + mail_admins(subject, body) + cronlogger.debug('JOB - Finished ' + unicode(self)) + + def __repr__(self): + return 'CronJob ' + unicode(self.database) + ': ' + unicode(self.job) + + def __unicode__(self): + return self.__repr__() + + +class CronRunner(): + + "Cron runner" + + pool = [] + queue = [] + jobs = [] + sleeptime = 60 + cycle = 1 + _stopped = False + + def __init__(self, databases=None, noloop=False, *args, **kwargs): + "Capture all cron jobs" + if databases is None: + databases = [] + + signal.signal(signal.SIGTERM, self.stop) + + self.databases = databases or [] + self.jobs = [] + self.sleeptime = getattr(settings, 'HARDTREE_CRON_PERIOD', 60) + self.priority_high = getattr( + settings, 'HARDTREE_CRON_HIGH_PRIORITY', 10) + self.priority_low = getattr(settings, 'HARDTREE_CRON_LOW_PRIORITY', 3) + self.qualify_high = getattr(settings, 'HARDTREE_CRON_QUALIFY_HIGH', 10) + self.qualify_run = getattr( + settings, 'HARDTREE_CRON_QUALIFY_RUN', 86400) + self.poolsize = getattr(settings, 'HARDTREE_CRON_POOL_SIZE', 10) + self.softkill = getattr(settings, 'HARDTREE_CRON_SOFT_KILL', 0) + self.hardkill = getattr(settings, 'HARDTREE_CRON_HARD_KILL', -1) + self.gracewait = getattr(settings, 'HARDTREE_CRON_GRACE_WAIT', 5) + self.noloop = noloop + + for module in settings.INSTALLED_APPS: + import_name = str( + module) + "." + settings.HARDTREE_MODULE_IDENTIFIER + try: + hmodule = __import__(import_name, fromlist=[str(module)]) + self.jobs.extend(hmodule.CRON) + except ImportError: + pass + except AttributeError: + pass + + if not self.databases: + self.databases = [db for db in settings.DATABASES] + + cronlogger.info('Starting cron...') + cronlogger.debug('DATABASES: ' + unicode(self.databases)) + cronlogger.debug('CRON_PERIOD: ' + unicode(self.sleeptime)) + cronlogger.debug('CRON_HIGH_PRIORITY: ' + unicode(self.priority_high)) + cronlogger.debug('CRON_LOW_PRIORITY: ' + unicode(self.priority_low)) + cronlogger.debug('CRON_QUALIFY_HIGH: ' + unicode(self.qualify_high)) + cronlogger.debug('CRON_POOL_SIZE: ' + unicode(self.poolsize)) + cronlogger.debug('CRON_SOFT_KILL: ' + unicode(self.softkill)) + cronlogger.debug('CRON_HARD_KILL: ' + unicode(self.hardkill)) + cronlogger.debug('CRON_GRACE_WAIT: ' + unicode(self.gracewait)) + cronlogger.debug('CRON_NO_LOOP: ' + unicode(self.noloop)) + + def add_jobs(self): + "Adds all jobs to the queue" + cronlogger.info( + 'Adding ' + unicode(len(self.jobs)) + ' jobs to the queue.') + for db in self.databases: + cronlogger.debug('ADDING JOBS FOR ' + unicode(db)) + cache_key = 'hardtree_' + db + '_last' + last_accessed = cache.get(cache_key) + if last_accessed: + if last_accessed >= time.time() - int(self.qualify_run): + for job in self.jobs: + cron = CronJob(job, db, self.priority_low) + self.queue.append(cron) + cronlogger.debug('JOB ADDED ' + unicode(cron)) + else: + cronlogger.debug( + 'JOB DOES NOT QUALIFY ' + unicode(db) + ': NOT USED in last ' + unicode(self.qualify_run)) + else: + cronlogger.debug( + 'JOB DOES NOT QUALIFY ' + unicode(db) + ': NO KEY IN cache') + cronlogger.debug('Queue: ' + unicode(self.jobs)) + + def start(self): + "Start cron runner" + self.add_jobs() + try: + while not self._stopped: + if len(self.pool) < self.poolsize: + while len(self.queue) > 0 and len(self.pool) < self.poolsize: + cron = self.queue.pop() + self.pool.append(cron) + if 'hardtree_%s_last' % (cron.database) in cache: + last_accessed = cache.get( + 'hardtree_%s_last' % (cron.database)) + if last_accessed > (time.time() - int(self.qualify_high)): + cron.priority = self.priority_high + cronlogger.debug( + 'HIGH PRIORITY set to ' + unicode(cron)) + cron.start() + if len(self.queue) == 0: + cronlogger.info( + 'Cron cycle ' + unicode(self.cycle) + ' completed.') + self.cycle += 1 + if self.noloop: + self._stopped = True + else: + self.add_jobs() + time.sleep(self.sleeptime) + cronlogger.debug("POOL SIZE: " + unicode(len(self.pool))) + for cron in self.pool: + cronlogger.debug("POOL JOB: " + unicode(cron) + ', PRIORITY ' + + unicode(cron.priority) + ', ACTIVE: ' + unicode(cron.is_alive())) + if not cron.is_alive(): + self.pool.remove(cron) + continue + if cron.priority == self.softkill: + cron.terminate() + elif cron.priority <= self.hardkill: + cron.terminate() + cron.priority -= 1 + time.sleep(self.sleeptime) + except KeyboardInterrupt: + self.stop() + + def stop(self, *args, **kwargs): + cronlogger.info('Stopping...') + self._stopped = True + for cron in self.pool: + while cron.is_alive(): + cron.terminate() + time.sleep(self.gracewait) + cronlogger.debug('Stopped.') + sys.exit(0) + + +class Command(BaseCommand): + args = '[database database ...]' + help = 'Starts cron runner' + option_list = BaseCommand.option_list + ( + make_option('-l', '--logfile', + action='store', + dest='logfile', + default='/tmp/hardtree-cron.log', + help='Cron log file' + ), + make_option('-d', '--loglevel', + type='choice', + action='store', + dest='loglevel', + default='info', + choices=[i for i in LOG_LEVELS], + help='Logging level' + ), + make_option('-n', '--noloop', + action='store_true', + dest='noloop', + default=False, + help='Exit after all jobs are finished' + ) + + ) + + def handle(self, *args, **options): + loghandler = logging.handlers.RotatingFileHandler( + options.get('logfile'), maxBytes=100 * 1024 * 1024, backupCount=5) + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s") + loghandler.setFormatter(formatter) + loghandler.setLevel(LOG_LEVELS[options.get('loglevel')]) + cronlogger.setLevel(LOG_LEVELS[options.get('loglevel')]) + cronlogger.addHandler(loghandler) + self.runner = CronRunner(args, noloop=options.get('noloop')) + self.runner.start() diff --git a/data/treeio/treeio/treeio/core/middleware/chat.py b/data/treeio/treeio/treeio/core/middleware/chat.py new file mode 100644 index 0000000..f0330b4 --- /dev/null +++ b/data/treeio/treeio/treeio/core/middleware/chat.py @@ -0,0 +1,519 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding:utf-8 -*- + +import json +from time import sleep +from django.http import HttpResponse, HttpRequest +from treeio.core.conf import settings +import threading +from datetime import datetime +from time import strftime +from hashlib import md5 +import sys +from django.core.cache import cache +import cPickle +from django.contrib.messages.storage import default_storage + + +def get_key(postfix=""): + """ + Return key for memcached + :param postfix: postfix key + :type postfix: basestring + """ + domain = getattr(settings, 'CURRENT_DOMAIN', 'default') + key = "treeio_%s_chat_%s" % (domain, postfix) + return key + + +def create_lock(key): + try: + while True: + if cache.add(key + '_lock', '1', 10): # lifetime lock 10 seconds + break + return True + except: + print "Error: ", sys.exc_info() + return False + + +def delete_lock(key): + cache.delete(key + '_lock') + + +def set_memcached(key, obj, lock=True): + """ + Serialization object and add his in memcached + """ + if lock: + if create_lock(key): + # 60 sec * 60 min * 24 hour * 30 day = 2 592 000 sec + cache.set(key, cPickle.dumps(obj), 2592000) + delete_lock(key) + else: + cache.set(key, cPickle.dumps(obj), 2592000) + + +def get_memcached(key): + """ + Return deserialize object from memcached + """ + data = cache.get(key) + if not data: + set_memcached(key, {}) + obj = cPickle.loads(cache.get(key)) + return obj + + +def get_notifications(user): + """ + Return list notifications + :param user: object user + """ + notifications = [] + try: + user.id + except: + return [] + try: + if not getattr(settings, 'HARDTREE_ALLOW_GRITTER_NOTIFICATIONS', False): + return notifications + request = HttpRequest() + request.user = user + storage = default_storage(request) + for msg in storage._get()[0]: + notifications.append({'message': msg.message, + 'tags': msg._get_tags()}) + storage._store(None) + except: + pass + return notifications + + +def get_user_profile(user): + """ + return user profile + """ + try: + listeners = get_memcached(get_key("listeners")) + return listeners[user]["profile"] + except: + return user + + +def update_user(user, location): + """ + update location and the last time request Users + :param user: object user + :param location: location user on site + :type location: basestring + """ + try: + listeners = get_memcached(get_key("listeners")) + _user_profile = str(user.profile) + _user = str(user) + listeners[_user] = { + "datetime": datetime.now(), "locations": location, "profile": _user_profile} + set_memcached(get_key("listeners"), listeners) + except: + print "Error: ", sys.exc_info() + + +def remove_user(id, user): + """ + Remove user from conference + :param id: ID conference + :type id: basestring + :type user: basestring + """ + conferences = get_memcached(get_key("conferences")) + if verification_user(id, user): + del conferences[id]["users"][user] + set_memcached(get_key("conferences"), conferences) + return get_new_message_for_user(user) + + +def verification_user(id, user): + """ + Verification user in conference + return True if the user is present in conference, else return False + :param id: ID conference + :type id: basestring + :type user: basestring + """ + conferences = get_memcached(get_key("conferences")) + if user not in conferences[id]["users"].keys(): + return False + return True + + +def checking_conference(id_conference): + """ + Checking for the existence of the conference + :param id_conference: ID conference + :type id_conference: basestring + """ + conferences = get_memcached(get_key("conferences")) + if id_conference in conferences.keys(): + return True + return False + + +def is_owner_user(id, user): + """ + Checks whether user is owner of conferences + :param id: ID conference + :type id: basestring + :type user: basestring + """ + conferences = get_memcached(get_key("conferences")) + if conferences[id]["info"]["creator"] == user: + return True + return False + + +def exit_from_conference(id, user): + """ + Remove user from conference if user exited from conference + :param id: ID conference + :type id: basestring + :type user: basestring + """ + if checking_conference(id): + if verification_user(id, user): + conferences = get_memcached(get_key("conferences")) + if is_owner_user(id, user): + delete_conference(id, user) + del conferences[id]["users"][user] + set_memcached(get_key("conferences"), conferences) + return get_new_message_for_user(user) + + +def delete_conference(id, user): + """ + Delete conference (if user is owner conference) + :param id: ID conference + :type id: basestring + :type user: basestring + """ + if is_owner_user(id, user): + conferences = get_memcached(get_key("conferences")) + del conferences[id] + set_memcached(get_key("conferences"), conferences) + return get_new_message_for_user(user) + + +def remove_users_in_conference(id, user, users): + """ + Remove users from conference (if user is owner conference) + :param id: ID conference + :type id: basestring + :type user: basestring + :type users: baselist + :param users: List of users to remove from conferences + """ + if checking_conference(id) and is_owner_user(id, user): + conferences = get_memcached(get_key("conferences")) + for val in users: + del conferences[id]["users"][val] + set_memcached(get_key("conferences"), conferences) + return get_new_message_for_user(user) + + +def add_users_in_conference(id, user, users): + """ + Add users in conference (if user is owner conference) + :param id: ID conference + :type id: basestring + :type user: basestring + :type users: baselist + :param users: List of users to add in conference + """ + if checking_conference(id): + conferences = get_memcached(get_key("conferences")) + for val in users: + conferences[id]["users"][val] = {"messages": [], "locations": []} + set_memcached(get_key("conferences"), conferences) + return get_new_message_for_user(user) + + +def create_conference(user, users, title): + """ + Create conference + :type user: basestring + :type users: baselist + :param users: List of users to add in conference + :type title: basestring + """ + id = md5() + id.update(str(datetime.now())) + id = user + "_" + id.hexdigest() + users.append(user) + conferences = get_memcached(get_key("conferences")) + if id in conferences.keys(): + return get_new_message_for_user(user) + conferences[id] = {} + conferences[id]["users"] = {} + conferences[id]["info"] = { + "creator": user, + "title": title, + "creation_date": datetime.now() + } + set_memcached(get_key("conferences"), conferences) + add_users_in_conference(id, user, users) + return get_new_message_for_user(user) + + +def get_active_conferences(user): + """ + get_active_conferences(user) -> list active conferences + Return active conferences + :type user: basestring + :return: list + """ + conferences = get_memcached(get_key("conferences")) + list_conferences = [] + for key in conferences.keys(): + if user in conferences[key]["users"].keys(): + list_conferences.append( + dict(id=key, + title=conferences[key]["info"]["title"], + creator=conferences[key]["info"]["creator"], + creation_date=str( + conferences[key]["info"]["creation_date"]), + users=[dict(username=username, profile=get_user_profile(username)) for username in conferences[key]["users"].keys() if not username == user]) + ) + return list_conferences + + +def get_new_message_for_user(user, **kwargs): + """ + get_new_message_for_user(user, **kwargs) -> HTTPResponse(json(new_data)) + Return HTTP response to new data + :type user: basestring + """ + def __update_data(_data): + conferences = get_memcached(get_key("conferences")) + for key in conferences.keys(): + if user in conferences[key]["users"].keys(): + try: + msg = conferences[key]["users"][user]["messages"] + except: + msg = [] + if msg: + _data["new_data"].append({key: {"messages": msg}}) + conferences[key]["users"][user]["messages"] = [] + set_memcached(get_key("conferences"), conferences) + notifications = get_notifications(kwargs["user_obj"]) + if notifications: + _data["notifications"] = _data["notifications"] + notifications + return _data + + def __get_new_data(): + listeners = get_memcached(get_key("listeners")) + _new_data = { + "users": [dict(name=key, + location=listeners[key]["locations"], + profile=listeners[key]["profile"]) for key in listeners.keys() if not key == str(user)], + "new_data": [], + "conferences": get_active_conferences(user), + "notifications": get_notifications(kwargs["user_obj"]) + } + return _new_data.copy() + + if "flag" not in kwargs.keys(): + kwargs["flag"] = None + if "long_polling" not in kwargs.keys(): + kwargs["long_polling"] = False + if "user_obj" in kwargs.keys() and "location" in kwargs.keys(): + _location = kwargs['location'] + else: + kwargs["user_obj"] = None + + data = __get_new_data() + + if settings.CHAT_LONG_POLLING and kwargs["long_polling"]: + + out_time = 0 + while not data["new_data"] and out_time < settings.CHAT_TIMEOUT: + + if kwargs["user_obj"]: + if _location: + update_user(kwargs["user_obj"], _location) + + out_time += 1 + + _temp_data = __get_new_data() + _temp_data = __update_data(_temp_data) + + if not _temp_data == data: + data = _temp_data.copy() + break + + if kwargs["flag"] == "connect": + break + + if not _temp_data["new_data"]: + sleep(settings.CHAT_TIME_SLEEP_NEWDATA) + else: + data = _temp_data.copy() + + data = json.dumps(data) + return HttpResponse(data, content_type='application/json', status=200) + + +def add_new_message(id, user, user_profile, text): + """ + Add new message + :param id: ID conference + :type id: basestring + :type user: basestring + :type user_profile: basestring + :type text: basestring + """ + try: + if not verification_user(id, user): + return get_new_message_for_user(user) + if checking_conference(id): + conferences = get_memcached(get_key("conferences")) + for key in conferences[id]["users"].keys(): + conferences[id]["users"][key]["messages"].append( + dict(user=user, + text=text, + time=strftime("%H:%M:%S"), + date=strftime("%Y-%m-%d"), + profile=user_profile) + ) + set_memcached(get_key("conferences"), conferences) + except: + data = json.dumps( + {"cmd": "Error", "data": {"msg": str(sys.exc_info())}}) + return HttpResponse(data, content_type='application/json', status=200) + + return get_new_message_for_user(user) + + +def connect(user, location): + update_user(user, location) + # if first response, then long_polling = False + return get_new_message_for_user(str(user).lower(), location=location, long_polling=False, user_obj=user) + + +def disconnect(user): + listeners = get_memcached(get_key("listeners")) + del listeners[user] + set_memcached(get_key("listeners"), listeners) + return HttpResponse(json.dumps({"cmd": "Disconnect"}), content_type='application/json', status=200) + + +def cmd(message, user): + """ + Handler AJAX query + :param user: object user + :param message: content POST + :type message: basedict + """ + try: + user_obj = user + user_profile = str(user.profile) + user = str(user).lower() + data = json.loads(message["json"]) + except: + print "error: ", sys.exc_info() + data = json.dumps( + {"cmd": "Error", "data": {"msg": str(sys.exc_info())}}) + return HttpResponse(data, content_type='application/json', status=200) + + try: + + update_user(user_obj, data['location']) + + if data['cmd'] == 'Connect': + return connect(user_obj, data['location']) + + if data['cmd'] == 'Disconnect': + return disconnect(user) + + if data['cmd'] == 'Get': + return get_new_message_for_user(user, user_obj=user_obj, location=data['location'], long_polling=True) + + if data['cmd'] == 'Message': + return add_new_message(data["data"]["id"], user, user_profile, data["data"]["text"]) + + if data['cmd'] == 'Exit': + return exit_from_conference(data["data"]["id"], user) + + if data['cmd'] == 'Delete': + return delete_conference(data['data']['id'], user) + + if data['cmd'] == 'Remove': + return remove_users_in_conference(data['data']['id'], user, data['data']['users']) + + if data['cmd'] == 'Add': + return add_users_in_conference(data['data']['id'], user, data['data']['users']) + + if data['cmd'] == 'Create': + return create_conference(user, data['data']['users'], data['data']['title']) + + except: + print "Error: ", sys.exc_info() + data = json.dumps( + {"cmd": "Error", "data": {"msg": str(sys.exc_info())}}) + return HttpResponse(data, content_type='application/json', status=200) + + return HttpResponse(json.dumps({"cmd": "Error", "data": {"msg": "unknown command"}}), + content_type='application/json', status=200) + + +class Search_Inactive_Users(threading.Thread): + + """ + Delete inactive users + """ + + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + while True: + try: + sleep(settings.CHAT_TIME_SLEEP_THREAD) + listeners = get_memcached(get_key("listeners")) + for user in listeners.keys(): + time = datetime.now() - listeners[user]['datetime'] + if time.seconds > settings.CHAT_TIMEOUT: + del listeners[user] + set_memcached(get_key("listeners"), listeners) + except: + print "error: ", sys.exc_info() + + +class ChatAjaxMiddleware(object): + + def __init__(self, *args, **kwargs): + if not settings.HARDTREE_CRON_DISABLED: + Search_Inactive_Users().start() + pass + # noinspection PyArgumentList + super(ChatAjaxMiddleware, self).__init__(*args, **kwargs) + + def process_request(self, request): + + if not request.META['PATH_INFO'] == '/chat': + return + + if not request.user.is_authenticated(): + data = json.dumps( + {"cmd": "Error", "data": "User is not authenticated"}) + response = HttpResponse(content_type='application/json') + response.write(data) + return response + + if request.method == "POST": + return cmd(request.POST, request.user) + + return diff --git a/data/treeio/treeio/treeio/core/middleware/user.py b/data/treeio/treeio/treeio/core/middleware/user.py new file mode 100644 index 0000000..1298c08 --- /dev/null +++ b/data/treeio/treeio/treeio/core/middleware/user.py @@ -0,0 +1,354 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +User middleware: performs user-specific request pre-processing +""" +import urllib +import urlparse + +from django.db.models import signals +from django.utils.functional import curry +from django.core.urlresolvers import resolve +from django.contrib.auth import logout, login, authenticate +from django.http import HttpResponse, HttpResponseRedirect +from django.utils import translation +from treeio.core.models import Object, ModuleSetting, UpdateRecord +from treeio.core.views import ajax_popup +from treeio.core.conf import settings +from django.db import models +from django.core.cache import cache +import json +import time + + +class CommonMiddleware(object): + "Set up Object notifications" + + objects = {} + + def process_request(self, request): + "Process request" + + # check mobile: + if getattr(request, 'mobile', False) and \ + not request.POST and \ + '/m' not in request.path[:2] and \ + '/static' not in request.path[:7]: + + if request.GET.get('nomobile', False): + request.session['nomobile'] = True + elif 'nomobile' not in request.session: + return HttpResponseRedirect('/m' + request.path) + + if hasattr(request, 'user') and request.user.is_authenticated(): + + domain = getattr(settings, 'CURRENT_DOMAIN', 'default') + cache.set('treeio_%s_last' % (domain), time.time()) + if getattr(settings, 'HARDTREE_SUBSCRIPTION_BLOCKED', False) and '/accounts' not in request.path: + return HttpResponseRedirect('/accounts/logout') + + user = None + try: + user = request.user.profile + self.objects[unicode(user.id)] = {} + except Exception: + pass + + if not user: + logout(request) + return HttpResponseRedirect('/') + + do_fresh_subscribers = curry( + self.do_fresh_subscribers, user, request) + send_notifications_on_save = curry( + self.send_notifications_on_save, user, request) + send_notifications_on_delete = curry( + self.send_notifications_on_delete, user, request) + send_notifications_on_m2m = curry( + self.send_notifcations_on_m2m, user, request) + + signals.pre_save.connect( + send_notifications_on_save, dispatch_uid=request, weak=False) + signals.post_save.connect( + do_fresh_subscribers, dispatch_uid=request, weak=False) + signals.pre_delete.connect( + send_notifications_on_delete, dispatch_uid=request, weak=False) + signals.m2m_changed.connect( + send_notifications_on_m2m, dispatch_uid=request, weak=False) + + def do_fresh_subscribers(self, user, request, sender, instance, created, **kwargs): + "Adds current user to Subscribers of an Object on creation" + auto_notify = getattr(instance, 'auto_notify', True) + if auto_notify and created: + if isinstance(instance, Object) and instance.is_searchable(): + instance.subscribers.add(user) + try: + instance.create_notification('create', user) + except: + pass + + def send_notifications_on_save(self, user, request, sender, instance, **kwargs): + "Send notifications to subscribers of an Object on Object change" + auto_notify = getattr(instance, 'auto_notify', True) + if auto_notify and isinstance(instance, Object) and instance.id: + try: + instance.create_notification('update', user) + except: + pass + if isinstance(instance, Object): + process_timezone_field(user, instance) + + def send_notifications_on_delete(self, user, request, sender, instance, **kwargs): + "Send notifications to subscribers of an Object on Object delete" + auto_notify = getattr(instance, 'auto_notify', True) + if auto_notify and isinstance(instance, Object) and instance.get_related_object(): + try: + instance.create_notification('delete', user) + except: + pass + instance.subscribers.clear() + + def send_notifcations_on_m2m(self, user, request, sender, instance, action, reverse, model, pk_set, **kwargs): + "Send notification on changes ManyToMany field (needs to be handled separately due to Django design)" + + if isinstance(instance, Object): + attr = sender._meta.object_name.split('_', 1)[1] + if attr in settings.HARDTREE_OBJECT_BLACKLIST: + return + + if action == "pre_clear" or action == "pre_remove": + original = list(getattr(instance, attr).all()) + self.objects[unicode(user.id)].update( + {unicode(instance.id): original}) + elif action == "post_add" or action == "post_remove": + updated = list(getattr(instance, attr).all()) + if unicode(user.id) in self.objects and unicode(instance.id) in self.objects[unicode(user.id)]: + original = self.objects[ + unicode(user.id)][unicode(instance.id)] + if not original == updated: + try: + instance.create_notification( + 'm2m', user, field=attr, original=original, updated=updated) + except: + pass + del self.objects[unicode(user.id)][unicode(instance.id)] + elif isinstance(instance, UpdateRecord): + attr = sender._meta.object_name.split('_', 1)[1] + if action == "post_add" and attr == 'about': + obj_query = model.objects.filter(pk__in=pk_set) + subscribers = set() + # Send notifications to author Contact's subscribers too + if instance.author: + contact = instance.author.get_contact() + if contact: + subscribers.update(contact.subscribers.all()) + subscribers.add(instance.author) + for obj in obj_query: + # add object's subscribes to update record + subscribers.update(obj.subscribers.all()) + for subscriber in subscribers: + instance.recipients.add(subscriber) + for obj in obj_query: + instance.notify_subscribers(obj, request=request) + + def process_response(self, request, response): + "Process response" + signals.pre_save.disconnect(dispatch_uid=request) + signals.post_save.disconnect(dispatch_uid=request) + signals.m2m_changed.disconnect(dispatch_uid=request) + signals.pre_delete.disconnect(dispatch_uid=request) + + try: + user = request.user.profile + self.objects[unicode[user.id]] = {} + except: + pass + + return response + + +class PopupMiddleware(): + "Tracks Object creation for popups" + + objects = {} + + def __init__(self): + "Initialize objects" + self.objects = {} + + def process_request(self, request): + "Process request" + + view = None + try: + view, args, kwargs = resolve(request.path) + except Exception: + pass + + if view == ajax_popup: + process_created_object = curry( + self.process_created_object, request) + signals.post_save.connect( + process_created_object, dispatch_uid=request.user, weak=False) + + def process_created_object(self, request, sender, instance, created, **kwargs): + "Store a newly created object and request during which it was created" + if isinstance(instance, Object) and created and not instance.is_attached(): + self.objects.update( + {unicode(instance.id): {'object': instance, 'request': request}}) + + def process_response(self, request, response): + "Process response" + if not getattr(request, 'user', None) or not request.user.username: + return response + + try: + signals.post_save.disconnect(dispatch_uid=request.user) + except AttributeError: + pass + + view = None + try: + view, args, kwargs = resolve(request.path) + except Exception: + pass + + if view == ajax_popup and response.status_code == 302: + for obj in self.objects: + if self.objects[obj]['request'] == request: + hobject = self.objects[obj]['object'] + content = json.loads(response.content) + content['popup'].update({'object': {'name': unicode(hobject), + 'id': obj}}) + response = HttpResponse(json.dumps(content), + content_type=settings.HARDTREE_RESPONSE_FORMATS['json']) + break + + content = json.loads(response.content) + content['popup'].update({'redirect': True}) + response = HttpResponse(json.dumps(content), + content_type=settings.HARDTREE_RESPONSE_FORMATS['json']) + + return response + + +from django.utils.translation.trans_real import to_language + + +class LanguageMiddleware(object): + "Automatically set chosen language" + + def process_request(self, request): + "Set language for the current user" + + lang = getattr(settings, 'HARDTREE_LANGUAGES_DEFAULT', 'en') + + if request.user.username: + try: + user = request.user.profile + conf = ModuleSetting.get('language', user=user)[0] + lang = conf.value + except IndexError: + pass + except AttributeError: + pass + else: + try: + conf = ModuleSetting.get( + 'language', user__isnull=True, strict=True)[0] + lang = conf.value + except IndexError: + pass + lang = to_language(lang) + request.session['django_language'] = lang + + +def process_timezone_field(user, instance): + "Processes date and datetime fields according to the selected time zone" + from datetime import date, datetime, timedelta + + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get('default_timezone')[0] + default_timezone = conf.value + except Exception: + pass + + try: + conf = ModuleSetting.get('default_timezone', user=user)[0] + default_timezone = conf.value + except Exception: + default_timezone = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE')[default_timezone][0] + + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE', [ + (1, '(GMT-11:00) International Date Line West')]) + title = all_timezones[int(default_timezone)][1] + GMT = title[4:10] # with sign e.g. +06:00 + sign = GMT[0:1] # + or - + hours = int(GMT[1:3]) # e.g. 06 + mins = int(GMT[4:6]) + + for field in instance.get_fields(): + if field.name not in getattr(settings, 'HARDTREE_TIMEZONE_BLACKLIST', []): + if isinstance(field, models.DateTimeField) or \ + isinstance(field, models.DateField): + if getattr(instance, field.name): + cur_date = getattr(instance, field.name) + if sign == "-": + new_date = cur_date + \ + timedelta(hours=hours, minutes=mins) + else: + new_date = cur_date - \ + timedelta(hours=hours, minutes=mins) + setattr(instance, field.name, new_date) + elif isinstance(field, models.TimeField): + if getattr(instance, field.name): + datetime.combine(date.today(), getattr( + instance, field.name)) + timedelta(hours=hours, minutes=mins) + + +class SSLMiddleware(object): + """ Keep protocol the same on redirects """ + + def process_request(self, request): + """ Revert to SSL/no SSL depending on settings """ + if getattr(settings, 'HARDTREE_SUBSCRIPTION_SSL_ENABLED', True): + if getattr(settings, 'HARDTREE_SUBSCRIPTION_SSL_ENFORCE', False) and not request.is_secure(): + redirect_url = request.build_absolute_uri() + return HttpResponseRedirect(redirect_url.replace('https://', 'http://')) + else: + if request.is_secure(): + redirect_url = request.build_absolute_uri() + return HttpResponseRedirect(redirect_url.replace('https://', 'http://')) + + def process_response(self, request, response): + """ Keep protocol """ + if getattr(settings, 'HARDTREE_SUBSCRIPTION_SSL_ENABLED', True): + if response.status_code == 302: + redirect_url = request.build_absolute_uri(response['Location']) + if request.is_secure() or getattr(settings, 'HARDTREE_SUBSCRIPTION_SSL_ENFORCE', False): + response['Location'] = redirect_url.replace( + 'http://', 'https://') + return response + + +class AuthMiddleware(object): + """ Log in by hash """ + + def process_request(self, request): + authkey = request.GET.get('authkey', '') + user = authenticate(authkey=authkey) + + if user: + login(request, user) + url = request.build_absolute_uri() + p = urlparse.urlparse(url) + params = urlparse.parse_qs(p.query) + del params['authkey'] + url = urlparse.urlunparse( + (p.scheme, p.netloc, p.path, p.params, urllib.urlencode(params, doseq=True), p.fragment)) + return HttpResponseRedirect(url) diff --git a/data/treeio/treeio/treeio/core/models.py b/data/treeio/treeio/treeio/core/models.py new file mode 100644 index 0000000..31d5f15 --- /dev/null +++ b/data/treeio/treeio/treeio/core/models.py @@ -0,0 +1,1544 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Treeio Core system objects +""" + +from django.contrib import messages +from django.http import HttpRequest +from django.contrib.messages.storage import default_storage +from django.contrib.messages.storage.base import Message + +from django.db import models +from django.core.exceptions import MultipleObjectsReturned +from django.contrib.sites.models import RequestSite +from django.core.urlresolvers import reverse, NoReverseMatch +from django.utils.translation import ugettext as _ +from django.utils.html import strip_tags +from django.shortcuts import get_object_or_404 +from django.template.defaultfilters import date as djangodate +import django.contrib.auth.models as django_auth + +from treeio.core.conf import settings +from treeio.core.mail import SystemEmail + +from datetime import datetime +import re +import os +import pickle +import base64 +import random +import hashlib +import string + + +class AccessEntity(models.Model): + """Generic model for both User and Group""" + last_updated = models.DateTimeField(auto_now=True) + + def get_entity(self): + try: + return self.group + except Group.DoesNotExist: + try: + return self.user + except User.DoesNotExist: + return None + + def is_user(self): + try: + return self.user is not None + except User.DoesNotExist: + return False + + def __unicode__(self): + try: + return self.get_entity().__unicode__() + except AttributeError: + return unicode(self.id) + + def get_absolute_url(self): + try: + return self.get_entity().get_absolute_url() + except AttributeError: + return '' + + +class Group(AccessEntity): + """Group record""" + name = models.CharField(max_length=256) + parent = models.ForeignKey( + 'self', blank=True, null=True, related_name='child_set') + details = models.TextField(blank=True, null=True) + + def __unicode__(self): + return self.name + + def get_absolute_url(self, module='identities'): + """Returns absolute URL of the Group""" + if not module or module == 'identities': + try: + return reverse('identities_group_view', args=[self.id]) + except NoReverseMatch: + return "" + else: + try: + return reverse('core_administration_group_view', args=[self.id]) + except NoReverseMatch: + return "" + + def get_root(self): + """Get the root Group""" + + root = self + # keep track of items we've looked at to avoid infinite looping + stack = [self] + while getattr(root, 'parent', None): + root = getattr(root, 'parent') + if root in stack: + break + stack.append(root) + return root + + def get_tree_path(self, skipself=False): + """Get tree path as a list() starting with root Group""" + + if skipself: + path = [] + else: + path = [self] + + current = self + while getattr(current, 'parent', None): + parent = getattr(current, 'parent') + if parent in path: + # To avoid infinite looping, check that we don't add existing + # parent on the path again + break + else: + path.insert(0, parent) + current = parent + + return path + + def get_contact(self): + """Returns first available Contact""" + try: + return self.contact_set.all()[0] + except IndexError: + return None + + def has_contact(self): + """Returns true if any Contacts exist for this Group""" + return self.contact_set.exists() + + def get_fullname(self, save=True): + """Returns the full name with parent(s) separated by slashes""" + current = self + fullname = self.name + while current.parent: + current = current.parent + fullname = current.name + " / " + fullname + return fullname + + def get_perspective(self): + """Returns currently set Perspective for the Group""" + ids = [] + try: + for setting in ModuleSetting.get_for_module('treeio.core', name='default_perspective', group=self): + ids.append(long(setting.value)) + _id = ids[0] + perspective = get_object_or_404(Perspective, pk=_id) + except: + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_perspective')[0] + perspective = Perspective.objects.get(pk=long(conf.value)) + except: + try: + perspective = Perspective.objects.all()[0] + ModuleSetting.set_for_module( + 'default_perspective', perspective.id, 'treeio.core', group=self) + except: + raise Perspective.DoesNotExist('No Perspective exists') + return perspective + + def set_perspective(self, perspective): + """Sets the Perspective for the Group""" + ModuleSetting.set_for_module( + 'default_perspective', perspective.id, 'treeio.core', group=self) + + # Ensure the Group has access to the modules in the Perspective + modules = perspective.modules.all() or Module.objects.all() + try: + for module in modules: + full_access = not module.full_access.exists() or module.full_access.filter( + pk=self.id).exists() + read_access = not module.read_access.exists() or module.read_access.filter( + pk=self.id).exists() + if not (read_access or full_access): + module.read_access.add(self) + except: + pass + + class Meta: + """Group""" + ordering = ['name'] + + +class User(AccessEntity): + """A record about a user registered within the system""" + name = models.CharField(max_length=256) + user = models.OneToOneField(django_auth.User, related_name='profile') + default_group = models.ForeignKey( + Group, related_name='default_user_set', blank=True, null=True) + other_groups = models.ManyToManyField(Group, blank=True, null=True) + disabled = models.BooleanField(default=False) + last_access = models.DateTimeField(default=datetime.now) + + class Meta: + """User""" + ordering = ['name'] + + def __unicode__(self): + """Returns Contact name if available, username otherwise""" + contact = self.get_contact() + if contact: + return unicode(contact) + return unicode(self.name) + + def save(self, *args, **kwargs): + """Override to automatically set User.name from attached Django User""" + if not self.name and self.user: + self.name = self.user.username + if not self.default_group: + try: + self.default_group = Group.objects.all()[0] + except Exception: + pass + + super(User, self).save(*args, **kwargs) + # Check Hardtree Subscription user limit + if not self.id: + user_limit = getattr( + settings, 'HARDTREE_SUBSCRIPTION_USER_LIMIT', 0) + if user_limit > 0: + user_number = User.objects.all().count() + if user_number >= user_limit: + self.delete() + + def delete(self, *args, **kwargs): + user = self.user + super(User, self).delete(*args, **kwargs) + try: + user.delete() + except: + pass + + def get_absolute_url(self, module='identities'): + """Returns absolute URL of the User""" + if not module or module == 'identities': + try: + return reverse('identities_user_view', args=[self.id]) + except Exception: + return "" + else: + try: + return reverse('core_administration_user_view', args=[self.id]) + except Exception: + return "" + + def generate_new_password(self, size=8): + """Generates a new password and sets it to the user""" + # todo: use XKCD technique + password = ''.join( + [random.choice(string.letters + string.digits) for i in range(size)]) + + self.user.set_password(password) + self.user.save() + + return password + + def get_groups(self): + """Returns the list of all groups the user belongs to""" + groups = list(self.other_groups.all()) + groups.append(self.default_group) + + return groups + + def _check_permission(self, obj, mode='r'): + """Helper for User.has_permissions(), accepts only one character for mode""" + + query = models.Q(pk=self.id) + for group in self.get_groups(): + if group: + query = query | models.Q(pk=group.id) + + if obj.full_access.filter(query).exists(): + return True + + if mode == 'r' or mode == 'x': + if obj.read_access.filter(query).exists(): + return True + + if not obj.full_access.exists(): + # if no one can have full access, then allow everyone + return True + + return False + + def has_permission(self, obj, mode="r"): + """Checks permissions on a given object for a given mode""" + if self.is_admin() or not obj: + return True + + for imode in mode: + if not self._check_permission(obj, imode): + return False + + return True + + def is_admin(self, module_name=''): + """True if the user has write permissions on the given module""" + access = False + if not module_name: + module_name = 'treeio.core' + + try: + module = Module.objects.get(name=module_name) + access = self._check_permission(module, mode='w') + except Module.DoesNotExist: + pass + if access or module_name == 'treeio.core': + return access + return self.is_admin(module_name='treeio.core') + + def get_username(self): + """String username, picked up from attached Django User or self.name string otherwise""" + if self.user: + return self.user.username + return self.name + + def get_perspective(self): + """Returns currently set Perspective for the User""" + ids = [] + try: + for setting in ModuleSetting.get_for_module('treeio.core', name='default_perspective', user=self): + ids.append(long(setting.value)) + perspective_id = ids[0] + perspective = get_object_or_404(Perspective, pk=perspective_id) + except: + try: + perspective = self.default_group.get_perspective() + except: + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_perspective')[0] + perspective = Perspective.objects.get(pk=long(conf.value)) + except Exception: + try: + perspective = Perspective.objects.all()[0] + except: + perspective = Perspective(name='Default') + perspective.save() + ModuleSetting.set_for_module( + 'default_perspective', perspective.id, 'treeio.core', user=self) + return perspective + + def set_perspective(self, perspective): + """Sets the Perspective for the User""" + ModuleSetting.set_for_module( + 'default_perspective', perspective.id, 'treeio.core', user=self) + + # Ensure the User has access to the modules in the Perspective + modules = perspective.modules.all() or Module.objects.all() + for module in modules: + if not self.has_permission(module): + module.read_access.add(self) + + def get_contact(self): + """Returns first available Contact""" + try: + return self.contact_set.all()[0] + except Exception: + return None + + def has_contact(self): + """Returns true if any Contacts exist for this User""" + try: + return self.contact_set.exists() + except Exception: + return False + +# User signals + + +def user_autocreate_handler(sender, instance, created, **kwargs): + """When a Django User is created, automatically create a treeio User""" + if created: + try: + profile = instance.profile + except: + profile = User(user=instance) + profile.save() + +# Autocreate a treeio user when Django user is created +if getattr(settings, 'HARDTREE_SIGNALS_AUTOCREATE_USER', False): + models.signals.post_save.connect( + user_autocreate_handler, sender=django_auth.User) + + +class Invitation(models.Model): + """Invitation to register on Hardtree""" + email = models.EmailField() + key = models.CharField(max_length=256) + sender = models.ForeignKey(User, blank=True, null=True) + default_group = models.ForeignKey(Group, blank=True, null=True) + date_created = models.DateTimeField(default=datetime.now) + + def __init__(self, *args, **kwargs): + """Create a hash automatically""" + super(Invitation, self).__init__(*args, **kwargs) + if self.email and not self.key: + hasher = hashlib.sha256() + hasher.update(str(random.random()) + str(self.email)) + self.key = hasher.hexdigest() + + +class Tag(models.Model): + """Model for Global Tagging""" + name = models.CharField(max_length=512) + date_created = models.DateTimeField(default=datetime.now) + + def __unicode__(self): + return self.name + + class Meta: + ordering = ['name'] + + +class Comment(models.Model): + """Comment on any Object""" + author = models.ForeignKey(User, blank=True, null=True) + body = models.TextField(blank=True, null=True) + likes = models.ManyToManyField( + User, blank=True, null=True, related_name='comments_liked') + dislikes = models.ManyToManyField( + User, blank=True, null=True, related_name='comments_disliked') + date_created = models.DateTimeField(default=datetime.now) + + def __unicode__(self): + return self.body + + +class Object(models.Model): + """Generic Treeio object""" + creator = models.ForeignKey( + User, blank=True, null=True, related_name='objects_created', on_delete=models.SET_NULL) + read_access = models.ManyToManyField( + AccessEntity, blank=True, null=True, related_name='objects_read_access') + full_access = models.ManyToManyField( + AccessEntity, blank=True, null=True, related_name='objects_full_access') + + object_name = models.CharField(max_length=512, blank=True, null=True) + object_type = models.CharField(max_length=512, blank=True, null=True) + trash = models.BooleanField(default=False) + + links = models.ManyToManyField('self', blank=True, null=True) + subscribers = models.ManyToManyField( + User, blank=True, null=True, related_name='subscriptions') + # subscribers_outside = models.ManyToManyField(Contact, blank=True, null=True, related_name='subscriptions_outside') + tags = models.ManyToManyField(Tag, blank=True, null=True) + + comments = models.ManyToManyField( + Comment, blank=True, null=True, related_name='comments') + likes = models.ManyToManyField( + User, blank=True, null=True, related_name='objects_liked') + dislikes = models.ManyToManyField( + User, blank=True, null=True, related_name='objects_disliked') + + last_updated = models.DateTimeField(auto_now=True) + date_created = models.DateTimeField(default=datetime.now) + + nuvius_resource = models.TextField(blank=True, null=True) + + # What to inherit permissions from: + # default: Object's module first, then the user who created it. + access_inherit = ('*module', '*user') + + def _get_query_filter_permitted(user, mode='r', filter_trash=True): + """Helper for filter_permitted(), accepts one character per mode""" + + query = models.Q() + if not user.is_admin(): + query = models.Q(full_access=user) | models.Q( + full_access__isnull=True) + query = query | models.Q(full_access=user.default_group) | models.Q( + full_access__in=user.other_groups.all()) + + if mode == 'r' or mode == 'x': + query = query | models.Q(read_access=user) + query = query | models.Q(read_access=user.default_group) | models.Q( + read_access__in=user.other_groups.all()) + + if filter_trash: + query = query & models.Q(trash=False) + + return query + _get_query_filter_permitted = staticmethod(_get_query_filter_permitted) + + def filter_permitted(user, manager, mode="r", filter_trash=True): + """Returns Objects the given user is allowed to access, depending on mode - read(r), write(w) or execute(x)""" + if not user: + return [] + + query = models.Q() + for imode in mode: + query = query & Object._get_query_filter_permitted( + user, imode, filter_trash) + + objects = manager.filter(query).distinct() + return objects + filter_permitted = staticmethod(filter_permitted) + + def filter_by_request(request, manager, mode="r", filter_trash=True): + """Returns Objects the current user is allowed to access, depending on mode - read(r), write(w) or execute(x)""" + user = None + if request.user.username: + try: + user = request.user.profile + except MultipleObjectsReturned: + user = User.objects.filter(user__id=request.user.id)[0] + if user: + return Object.filter_permitted(user, manager, mode, filter_trash) + return [] + filter_by_request = staticmethod(filter_by_request) + + def __unicode__(self): + """String representation""" + try: + return unicode(self.get_related_object()) + except Exception: + return unicode(self.object_type) + " [" + unicode(self.id) + "]" + + def save(self, *args, **kwargs): + """Override to auto-detect object type and set default user if unset""" + + try: + name = self.__unicode__() + if not name == self.object_name: + self.object_name = name[0:510] + except: + pass + + types = re.findall('\'(.+)\'', str(self.__class__)) + if types: + self.object_type = types[0] + + return super(Object, self).save(*args, **kwargs) + + def get_object_module(self): + """Returns the module for this object, e.g. 'projects'""" + + return getattr(self._meta, 'app_label', None) + + def get_nuvius_resources(self): + """Returns a list of items generated from self.nuvius_resource + + The convention used is: + Each item is specified as '#id.key#' separated by commas + Returns items as tuples (id, key) from self.nuvius_resource + """ + resources = [] + if self.nuvius_resource: + text = unicode(self.nuvius_resource) + splits = text.split(',') + for bit in splits: + res = bit.strip('#').split('.', 1) + resources.append(res) + return resources + + def add_nuvius_resource(self, resource_id, key=None): + """Add a Nuvius resource to self.nuvius_resource""" + existing = [res[0] for res in self.get_nuvius_resources()] + if not unicode(resource_id) in existing: + new = "#" + unicode(resource_id) + if key: + new += "." + unicode(key) + new += "#" + if self.nuvius_resource: + self.nuvius_resource += "," + new + else: + self.nuvius_resource = new + return self + + def get_root(self): + """Get the root element, for objects implementing a tree-like structure via .parent""" + + root = self + # keep track of items we've looked at to avoid infinite looping + stack = [self] + while getattr(root, 'parent', None): + root = getattr(root, 'parent') + if root in stack: + break + stack.append(root) + return root + + def get_tree_path(self, skipself=False): + """Get tree path as a list() starting with root element, + for objects implementing a tree-like structure via .parent""" + + if skipself: + path = [] + else: + path = [self] + + current = self + while getattr(current, 'parent', None): + parent = getattr(current, 'parent') + if parent in path: + # To avoid infinite looping, check that we don't add existing + # parent on the path again + break + else: + path.insert(0, parent) + current = parent + + return path + + def get_related_object(self): + """Returns a child object which inherits from self, if exists""" + try: + obj_name = re.match( + ".*\.(?P\w+)$", self.object_type).group('name') + return getattr(self, obj_name.lower()) + except Exception: + return None + + def get_human_type(self, translate=True): + """Returns prettified name of the object type""" + try: + obj_name = re.match( + ".*\.(?P\w+)$", self.object_type).group('name') + pattern = re.compile('([A-Z][A-Z][a-z])|([a-z][A-Z])') + human_type = pattern.sub( + lambda m: m.group()[:1] + " " + m.group()[1:], obj_name) + if translate: + human_type = _(human_type) + return human_type + except Exception: + return self.object_type + + def get_absolute_url(self): + """Returns a URL to the child object, if available""" + try: + return self.get_related_object().get_absolute_url() + except Exception: + return "" + + def is_searchable(self): + """Returns True if the item should be included in Search index""" + return getattr(self, 'searchable', True) + + def is_attached(self): + """Returns True if item is attached to some other Object and serves as a part of it""" + return getattr(self, 'attached', False) + + def get_search_item(self): + """Constructs a search item as a dictionary with title, content and URL""" + + obj = self.get_related_object() + if not obj: + obj = self + + item = { + 'id': u'', + 'name': u'', + 'type': unicode(obj.get_human_type()), + 'content': u'', + 'url': unicode(obj.get_absolute_url()) + } + + if obj.id: + item['id'] = unicode(obj.id) + + if hasattr(obj, 'title'): + item['name'] = unicode(obj.title) + elif hasattr(obj, 'name'): + item['name'] = unicode(obj.name) + else: + item['name'] = unicode(obj) + + if hasattr(self, 'body'): + item['content'] = unicode(self.body) + elif hasattr(self, 'details'): + item['content'] = unicode(self.details) + else: + for field in self.get_fields(): + try: + value = self.get_field_value(field.name) + item['content'] += ' ' + unicode(value) + except: + pass + + if hasattr(self, 'tags'): + for tag in self.tags.all(): + item['content'] += ' ' + unicode(tag) + + return item + + def create_notification(self, action='update', author=None, *args, **kwargs): + """Creates an UpdateRecord to be submitted to all subscribers of an Object""" + + if not (self.subscribers.all().count() or author): + return None + + notification = UpdateRecord() + updated = False + + if action == 'delete': + notification.format_message = "%s \"%s\" deleted." + notification.set_format_strings( + [unicode(self.get_human_type(translate=False)), unicode(self)]) + notification.record_type = 'delete' + updated = True + + elif action == 'm2m': + notification.record_type = 'update' + if 'field' in kwargs and 'original' in kwargs and 'updated' in kwargs: + if kwargs['updated']: + updated_text = "" + for obj in kwargs['updated']: + updated_text += '%s' % ( + obj.get_absolute_url(), unicode(obj)) + if kwargs['updated'].index(obj) < len(kwargs['updated']) - 1: + updated_text += ', ' + else: + updated_text = "None" + + field = unicode(kwargs['field']) + + if field == 'assigned': + notification.format_message = "%s assigned to %s.
    " + notification.set_format_strings( + [self.get_human_type(translate=False), updated_text]) + notification.save() + for obj in kwargs['updated']: + if isinstance(obj, AccessEntity) or isinstance(obj, User): + notification.recipients.add(obj) + self.subscribers.add(obj) + try: + if not obj.has_permission(self, mode='w'): + self.full_access.add(obj) + except: + pass + else: + try: + notification.recipients.add(obj.related_user) + self.subscribers.add(obj.related_user) + except: + pass + try: + if not obj.related_user.has_permission(self, mode='w'): + self.full_access.add(obj.related_user) + except: + pass + else: + notification.format_message = "%s changed to \"%s\".
    " + notification.set_format_strings( + [field.replace('_', ' ').capitalize(), updated_text]) + + updated = True + + elif action == 'create': + notification.format_message = "%s created." + notification.set_format_strings( + [unicode(self.get_human_type(translate=False))]) + notification.record_type = 'create' + updated = True + + else: + notification.record_type = 'update' + + fields = self.get_field_names() + original = self.get_related_object() + + notification.format_message = "" + strings = [] + + for field in fields: + if hasattr(getattr(self, field), 'all'): + # Skip ManyToMany fields - they handled differently + continue + + if field in getattr(settings, 'HARDTREE_UPDATE_BLACKLIST', []): + # Skip blacklisted items + continue + + if 'password' in field: + # Skip password fields - it's not nice to give them away in + # plain-text + continue + + current = self.get_field_value(field) + before = original.get_field_value(field) + if current != before: + # cut out '_display' from some fields + if '_display' in field: + field = field.replace('_display', '') + if isinstance(self._meta.get_field(field), models.TextField): + notification.format_message += "%s changed.
    " + strings.extend( + [unicode(field).replace('_', ' ').capitalize()]) + elif isinstance(self._meta.get_field(field), models.DateTimeField): + notification.format_message += "%s changed from \"%s\" to \"%s\".
    " + # this needs to be done properly, via getting current + # locale's format + localeformat = "j F Y, H:i" + strings.extend([unicode(field).replace('_', ' ').capitalize(), + unicode( + djangodate(before, localeformat)), + unicode(djangodate(current, localeformat))]) + else: + notification.format_message += "%s changed from \"%s\" to \"%s\".
    " + strings.extend([unicode(field).replace('_', ' ').capitalize(), + unicode(before), unicode(current)]) + updated = True + + notification.set_format_strings(strings) + + if self.trash: + try: + old_object = Object.objects.get(pk=self.id) + if not old_object.trash: + notification.format_message += " %s moved to %s" + strings = notification.get_format_strings() + strings.extend([unicode(self.get_human_type(translate=False)), + reverse('core_trash'), "Trash"]) + notification.set_format_strings(strings) + notification.record_type = 'delete' + updated = True + except Exception: + pass + + if 'body' in kwargs: + notification.body = kwargs['body'] + updated = True + + if not updated: + return None + + notification.url = self.get_absolute_url() + notification.author = author + + if action != 'delete': + notification.sender = self + + notification.save() + notification.about.add(self) + return notification + + def set_user(self, user): + """Sets owner of the Object to the given user + :param User: + """ + # TODO: check every call that it send only treeio.core.User model + if user is None: + return self + if isinstance(user, django_auth.User): + user = user.profile + elif not isinstance(user, User): + raise ValueError('user argument should be either a django_auth.User or treeio.core.User object got %s' % type(user)) + + # get default permissions from settings + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_permissions')[0] + default_permissions = conf.value + except: + default_permissions = settings.HARDTREE_DEFAULT_PERMISSIONS + + if not self.creator: + self.creator = user + self.save() + + if default_permissions == 'everyone': + return self + + if 'force' in default_permissions: + # Don't do automatic inheritance + do_user = True + + else: + # try to inherit permissions first from any parent objects as + # specified in models.py + cascade = getattr(self, 'access_inherit', []) + do_user = False + if cascade: + for attr in cascade: + if attr == '*user': + do_user = True + break + elif attr == '*module': + if 'nomodule' not in default_permissions: + modules = Module.objects.filter( + name__contains=self.get_object_module()) + if modules: + for module in modules: + self.copy_permissions(module) + if not user.has_permission(self, 'w'): + do_user = True + break + else: + do_user = True + break + else: + try: + obj = getattr(self, attr) + if obj: + self.copy_permissions(obj) + if not user.has_permission(self, 'w'): + do_user = True + break + except: + pass + if do_user: + # if we can't inherit set permissions to the user as specified in the settings + + if 'readonly' in default_permissions: + access_container = self.read_access + else: + access_container = self.full_access + + if 'user' in default_permissions: + access_container.add(user) + + if 'usergroup' in default_permissions and user.default_group: + access_container.add(user.default_group) + + if 'userallgroups' in default_permissions: + if user.default_group: + access_container.add(user.default_group) + for group in user.other_groups.all(): + access_container.add(group) + + # process assigned fields to give auto-permissions to assignees + if hasattr(self, 'assigned'): + for obj in self.assigned.all(): + if isinstance(obj, AccessEntity) or isinstance(obj, User): + try: + if not obj.has_permission(self, mode='w'): + self.full_access.add(obj) + except: + pass + else: + try: + if not obj.related_user.has_permission(self, mode='w'): + self.full_access.add(obj.related_user) + except: + pass + + return self + + def set_default_user(self): + """Sets the user defined in settings.HARDTREE_DEFAULT_USER_ID and default mode""" + try: + user = User.objects.get(pk=settings.HARDTREE_DEFAULT_USER_ID) + except: + try: + user = User.objects.all()[0] + except IndexError: + user = None + self.set_user(user) + return self + + def set_user_from_request(self, request, mode=None): + """Sets the user to the current in request and default mode for the user""" + user = request.user.profile + self.set_user(user) + return self + + def copy_permissions(self, object): + """Copies all permissions from object. Existing permissions will NOT be removed or dropped""" + read_access = object.read_access.all() + for entity in read_access: + self.read_access.add(entity) + + full_access = object.full_access.all() + for entity in full_access: + self.full_access.add(entity) + + return self + + def get_fields(self): + """Returns list of fields for given object""" + return filter(lambda f: f.name not in settings.HARDTREE_OBJECT_BLACKLIST, self._meta.fields) + + def get_field_names(self): + """Returns list of field names for given object""" + x = [] + for f in self._meta.fields: + if f.name not in settings.HARDTREE_OBJECT_BLACKLIST: + x.append(f.name) + for f in self._meta.many_to_many: + if f.name not in settings.HARDTREE_OBJECT_BLACKLIST: + x.append(f.name) + return x + + def get_field_value(self, field_name, default=None): + """Returns the value of a given field""" + value = getattr(self, field_name, default) + if hasattr(value, 'all'): + # Returns value for ManyToMany fields + value = value.all() + if hasattr(self, 'get_%s_display' % field_name): + # Handle choices to ChoiceFields + try: + value = getattr(self, 'get_%s_display' % field_name)() + except: + pass + return value + + def set_field_value(self, field_name, value): + """Sets the value of a given field""" + return setattr(self, field_name) + + def set_last_updated(self, last_updated=datetime.now()): + self.last_updated = last_updated + self.save() + + +class Revision(models.Model): + previous = models.OneToOneField( + 'self', blank=True, null=True, related_name='next') + object = models.ForeignKey(Object) + change_type = models.CharField(max_length=512, null=True, blank=True) + date_created = models.DateTimeField(default=datetime.now) + + +class RevisionField(models.Model): + revision = models.ForeignKey(Revision) + field_type = models.CharField(max_length=512, null=True, blank=True) + field = models.CharField(max_length=512, null=True, blank=True) + value = models.TextField(null=True, blank=True) + value_key = models.ForeignKey( + Object, null=True, blank=True, related_name='revisionfield_key', on_delete=models.SET_NULL) + value_m2m = models.ManyToManyField( + Object, related_name='revisionfield_m2m') + value_key_acc = models.ForeignKey( + AccessEntity, null=True, blank=True, related_name='revisionfield_key_acc', on_delete=models.SET_NULL) + value_m2m_acc = models.ManyToManyField( + AccessEntity, related_name='revisionfield_m2m_acc') + + +class UpdateRecord(models.Model): + """Update of an Object""" + author = models.ForeignKey( + User, blank=True, null=True, related_name="sent_updates") + sender = models.ForeignKey( + Object, blank=True, null=True, related_name="sent_updates", on_delete=models.SET_NULL) + about = models.ManyToManyField( + Object, blank=True, null=True, related_name="updates") + recipients = models.ManyToManyField( + AccessEntity, blank=True, null=True, related_name="received_updates") + # recipients_outside = models.ManyToManyField(Contact, blank=True, null=True, related_name="outside_received_updates") + record_type = models.CharField(max_length=32, + choices=(('create', 'create'), ('update', 'update'), + ('delete', 'delete'), ( + 'trash', 'trash'), + ('message', 'message'), + ('manual', 'manual'), ('share', 'share'))) + url = models.CharField(max_length=512, blank=True, null=True) + body = models.TextField(default='', blank=True, null=True) + score = models.IntegerField(default=0) + format_message = models.TextField(blank=True, null=True) + format_strings = models.TextField(blank=True, null=True) + comments = models.ManyToManyField( + Comment, blank=True, null=True, related_name='comments_on_updates') + likes = models.ManyToManyField( + User, blank=True, null=True, related_name='updates_liked') + dislikes = models.ManyToManyField( + User, blank=True, null=True, related_name='updates_disliked') + date_created = models.DateTimeField(default=datetime.now) + + class Meta: + """UpdateRecord""" + ordering = ['-date_created'] + + def __unicode__(self): + return self.body + + def set_user_from_request(self, request): + """Sets .author to current user and .sender to user's Contact (if available)""" + user = request.user.profile + self.author = user + self.recipients.add(user) + contact = user.get_contact() + if contact: + self.sender = contact + self.save() + + def set_format_strings(self, strings): + """Sets format_strings to the list of strings""" + self.format_strings = base64.b64encode( + pickle.dumps(strings, pickle.HIGHEST_PROTOCOL)) + return self + + def extend_format_strings(self, strings): + """Extends existing format strings""" + existing = self.get_format_strings() + if existing: + existing.extend(strings) + else: + existing = strings + self.set_format_strings(existing) + return self + + def get_format_strings(self): + """Gets format_strings as a list of strings""" + result = None + if self.format_strings: + try: + result = pickle.loads(base64.b64decode(self.format_strings)) + except pickle.PickleError: + pass + return result + + def get_format_message(self): + """Returns translatable message in the current language with all attributes applied""" + strings = self.get_format_strings() + result = '' + if self.format_message: + result = self.format_message + if result and strings: + try: + # first, try to translate + translated = [] + for item in strings: + if item: + translated.append(_(item)) + else: + translated.append(_("None")) + result = _(self.format_message) % tuple(translated) + except TypeError: + # then try untranslated + try: + result = self.format_message % tuple(strings) + except TypeError: + # give up + pass + return result + + def get_full_message(self): + """Return full message""" + result = '' + format_message = self.get_format_message() + if format_message: + result += format_message + if self.body: + result += '

    ' + self.body + '

    ' + if result.endswith('
    '): + result = result[:len(result) - 6] + return result + full_message = property(get_full_message) + + def notify_subscribers(self, obj, *args, **kwargs): + if obj not in self.about.all(): + return + author = self.author + if author: + # E-mail contents for e-mail notifications + full_message = self.get_full_message() + html = '%s:

    \n\n%s (%s):

    \n\n%s

    \n\n' % \ + (unicode(author), obj.get_absolute_url(), unicode(obj), + unicode(obj.get_human_type()), full_message) + grittertext = '%s:
    \n\n%s (%s):
    \n\n%s
    \n\n' % \ + (unicode(author), obj.get_absolute_url(), unicode(obj), + unicode(obj.get_human_type()), full_message) + if 'request' in kwargs: + domain = RequestSite(kwargs['request']).domain + html = html.replace('href="', 'href="http://' + domain) + body = strip_tags(html) + signature = "This is an automated message from Tree.io service (http://tree.io). Please do not reply to this e-mail." + subject = "[Tree.io%s] %s: %s - %s" % (' #%d' % obj.id if self.record_type != 'delete' else '', unicode(author), + unicode(obj.get_human_type()), unicode(strip_tags(full_message)[:100])) + + for recipient in self.recipients.all(): + if author and author.id == recipient.id: + continue + email_notifications = getattr( + settings, 'HARDTREE_ALLOW_EMAIL_NOTIFICATIONS', False) + gritter_notifications = getattr( + settings, 'HARDTREE_ALLOW_GRITTER_NOTIFICATIONS', False) + try: + conf = ModuleSetting.get( + 'email_notifications', user=recipient)[0] + email_notifications = conf.value + except: + pass + + if email_notifications == 'True': + try: + toaddr = recipient.get_entity( + ).get_contact().get_email() + except: + toaddr = None + if toaddr: + SystemEmail( + toaddr, subject, body, signature, html + signature).send_email() + + if gritter_notifications: + try: + request = HttpRequest() + request.user = recipient.user.user + storage = default_storage(request) + storage._add( + Message(messages.constants.INFO, "%s" % grittertext)) + except: + pass + + +class Module(Object): + """Record of a module (application) existing within the system""" + name = models.CharField(max_length=256) + title = models.CharField(max_length=256) + details = models.TextField(blank=True) + url = models.CharField(max_length=512) + display = models.BooleanField(default=True) + system = models.BooleanField(default=True) + + searcheable = False + + class Meta: + """Module""" + ordering = ['name'] + + def get_absolute_url(self): + """Returns absolute URL""" + try: + return reverse('core_admin_module_view', args=[self.id]) + except Exception: + pass + + def __unicode__(self): + return self.title + + +class Perspective(Object): + """Defines a set of modules enabled for a given user""" + name = models.CharField(max_length=256) + details = models.TextField(blank=True) + modules = models.ManyToManyField(Module, blank=True, null=True) + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + """Returns absolute URL""" + try: + return reverse('core_admin_perspective_view', args=[self.id]) + except Exception: + pass + + def get_modules(self): + """Get Modules""" + modules = self.modules.all() + if not modules: + modules = Module.objects.all() + return modules + + +class ModuleSetting(models.Model): + """Free-type Module setting""" + name = models.CharField(max_length=512) + label = models.CharField(max_length=512) + perspective = models.ForeignKey(Perspective, blank=True, null=True) + module = models.ForeignKey(Module, blank=True, null=True) + user = models.ForeignKey(User, blank=True, null=True) + group = models.ForeignKey(Group, blank=True, null=True) + value = models.TextField() + + def loads(self): + """Unpickle a ModuleSetting value""" + result = None + if self.value: + try: + result = pickle.loads(base64.b64decode((self.value))) + except pickle.PickleError: + pass + return result + + def dumps(self, value): + """Pickle a ModuleSetting value""" + self.value = base64.b64encode( + pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) + return self + + def get(name='', strict=False, **kwargs): + """Setting getter""" + if name: + settings_qs = ModuleSetting.objects.filter(name=name) + else: + settings_qs = ModuleSetting.objects.all() + if strict: + settings_qs = settings_qs.filter(**kwargs) + elif settings_qs: + new_settings = settings_qs + for arg in kwargs: + new_settings = new_settings.filter(**{arg: kwargs[arg]}) + if new_settings: + settings_qs = new_settings + + return settings_qs + get = staticmethod(get) + + def get_for_module(module_name, name='', strict=False, **kwargs): + """Get a setting per module""" + try: + module = Module.objects.get(name=module_name) + return ModuleSetting.get(name=name, module=module, strict=strict, **kwargs) + except Exception: + return None + get_for_module = staticmethod(get_for_module) + + def set(name, value, **kwargs): + """Define a ModuleSetting""" + existing = ModuleSetting.objects.filter(name=name, **kwargs) + if existing: + for setting in existing: + setting.value = value + setting.save() + else: + setting = ModuleSetting(name=name, value=value, **kwargs) + setting.save() + return setting + set = staticmethod(set) + + def add(name, value, **kwargs): + """Add a ModuleSetting""" + setting = ModuleSetting(name=name, value=value, **kwargs) + setting.save() + return setting + add = staticmethod(add) + + def set_for_module(name, value, module_name, **kwargs): + """Define a ModuleSetting per module""" + try: + module = Module.objects.get(name=module_name) + return ModuleSetting.set(name=name, value=value, module=module, **kwargs) + except Exception: + return None + set_for_module = staticmethod(set_for_module) + + def add_for_module(name, value, module_name, **kwargs): + """Add a ModuleSetting per module""" + try: + module = Module.objects.get(name=module_name) + return ModuleSetting.add(name=name, value=value, module=module, **kwargs) + except Exception: + return None + add_for_module = staticmethod(add_for_module) + + def __unicode__(self): + return unicode(self.name) + ": " + unicode(self.value) + + def save(self, *args, **kwargs): + """Override to set label from name if undefined""" + if not self.label: + self.label = self.name + super(ModuleSetting, self).save(*args, **kwargs) + + +class ConfigSetting(models.Model): + """Config setting to be activated dynamically from the database on request""" + name = models.CharField(max_length=255, unique=True) + value = models.TextField(blank=True, null=True) + last_updated = models.DateTimeField(auto_now=True) + + def __unicode__(self): + return unicode(self.loads()) + + def loads(self): + """Unpickle a ModuleSetting value""" + result = None + try: + result = pickle.loads(base64.b64decode((self.value))) + except: + result = self.value + return result + + def dumps(self, value): + """Pickle a ModuleSetting value""" + self.value = base64.b64encode( + pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) + return self + + def save(self, *args, **kwargs): + self.dumps(self.value) + super(ConfigSetting, self).save(*args, **kwargs) + + +class IntegrationResource: + + """ Resource set for integration via Nuvius + + nuvius_id = + resource_id = + resource_name = + services = + role = + + """ + + nuvius_id = 0 + resource_id = 0 + resource_name = '' + service = '' + role = 'slave' + + def __init__(self, nuvius_id, resource_id, resource_name, services='', role='slave'): + """Initialize an Integration Resource""" + + self.nuvius_id = nuvius_id + self.resource_id = resource_id + self.resource_name = resource_name + self.services = services + if not self.service: + self.service = getattr(settings, 'NUVIUS_SERVICES', '') + self.role = role + + +class Location(Object): + """Location for users, assets, etc.""" + name = models.CharField(max_length=512) + parent = models.ForeignKey( + 'self', blank=True, null=True, related_name='child_set') + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('identities_location_view', args=[self.id]) + except Exception: + pass + + +class PageFolder(Object): + """Folder for static Pages""" + name = models.CharField(max_length=256) + details = models.TextField(blank=True) + + searchable = False + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + """Returns absolute URL""" + try: + return reverse('core_admin_pagefolder_view', args=[self.id]) + except Exception: + pass + + +class Page(Object): + """Static page""" + name = models.CharField(max_length=256) + title = models.CharField(max_length=256) + folder = models.ForeignKey(PageFolder) + body = models.TextField(blank=True) + published = models.BooleanField(default=True) + + searchable = False + + class Meta: + """Page""" + ordering = ['name'] + + def __unicode__(self): + return self.title + + def get_absolute_url(self): + """Returns absolute URL""" + try: + return reverse('core_admin_page_view', args=[self.id]) + except Exception: + pass + + +class Widget(models.Model): + """Widget object to remember the set and order of Widget for a user""" + user = models.ForeignKey(User) + perspective = models.ForeignKey(Perspective) + module_name = models.CharField(max_length=256) + widget_name = models.CharField(max_length=256) + weight = models.IntegerField(default=0) + + def __unicode__(self): + return self.widget_name + + class Meta: + """Widget""" + ordering = ['weight'] + + +class Attachment(models.Model): + """Attachment object to upload and reference a file""" + filename = models.CharField(max_length=64) + attached_object = models.ForeignKey(Object, blank=True, null=True) + attached_record = models.ForeignKey(UpdateRecord, blank=True, null=True) + attached_file = models.FileField(upload_to='attachments') + mimetype = models.CharField(max_length=64, editable=False) + created = models.DateTimeField(auto_now_add=True, editable=False) + uploaded_by = models.ForeignKey(User) + + class Meta: + ordering = ['-id'] + + def __unicode__(self): + return unicode(self.filename) + + def get_file_type(self): + match = re.match( + '.*\.(?P[a-zA-Z0-9]+)$', unicode(self.filename)) + if match: + return unicode(match.group('extension')).upper() + else: + return '' + + def can_preview(self): + filetype = self.get_file_type() + exts = ('PNG', 'JPG', 'JPEG', 'BMP', 'GIF', 'SVG') + if filetype.upper() in exts: + return True + return False + + def get_icon(self): + if self.get_file_type() == 'PDF': + # PDF + return 'pdf' + elif self.get_file_type() in ['DOCX', 'DOC', 'TXT']: + # Documents + return 'blue-document-word' + elif self.get_file_type() in ['JPG', 'JPEG', 'GIF', 'PNG', 'TIFF', 'PSD', 'BMP']: + # Images + return 'image' + else: + # Other + return 'blue-document' + + def delete(self, *args, **kwargs): + filepath = os.path.join( + getattr(settings, 'MEDIA_ROOT'), 'attachments', self.attached_file.name) + try: + self.attached_file.delete() + os.remove(filepath) + except: + pass + super(Attachment, self).delete(*args, **kwargs) diff --git a/data/treeio/treeio/treeio/core/rendering.py b/data/treeio/treeio/treeio/core/rendering.py new file mode 100644 index 0000000..9c53433 --- /dev/null +++ b/data/treeio/treeio/treeio/core/rendering.py @@ -0,0 +1,246 @@ +""" +Rendering routines for Hardtree +""" + +from django.http import HttpResponse +from django.contrib.sites.models import RequestSite +from django.utils.translation import ugettext as _ +from django.forms import BaseForm +from django.contrib import messages +from jinja2 import Template +from coffin.template import loader +from treeio.core.conf import settings +from treeio.core.models import UpdateRecord +from treeio.core.ajax.converter import preprocess_context as preprocess_context_ajax, convert_to_ajax +import hashlib +import random +import os +import codecs +import re +import subprocess +import json + + +def _preprocess_context_html(context): + "Prepares context to be rendered for HTML" + + # Process popuplink fields + for key in context: + if isinstance(context[key], BaseForm): + form = context[key] + for fname in form.fields: + field = form.fields[fname] + try: + # find popuplink fields + if field.widget.attrs and 'popuplink' in field.widget.attrs: + field.help_text += '%s' % \ + (field.widget.attrs['popuplink'], fname, fname, _("New")) + except Exception: + pass + + return context + + +def render_to_string(template_name, context=None, context_instance=None, response_format='html'): + "Picks up the appropriate template to render to string" + if context is None: + context = {} + if not response_format or 'pdf' in response_format or response_format not in settings.HARDTREE_RESPONSE_FORMATS: + response_format = 'html' + + if not ("." + response_format) in template_name: + template_name += "." + response_format + + template_name = response_format + "/" + template_name + + context['response_format'] = response_format + if context_instance: + context['site_domain'] = RequestSite( + context_instance['request']).domain + + context = _preprocess_context_html(context) + + rendered_string = loader.render_to_string( + template_name, context, context_instance) + return rendered_string + + +def render_to_ajax(template_name, context=None, context_instance=None): + "Render request into JSON object to be handled by AJAX on the server-side" + if context is None: + context = {} + response_format = 'html' + if 'response_format_tags' not in context: + context['response_format_tags'] = 'ajax' + + context = preprocess_context_ajax(context) + content = render_to_string( + template_name, context, context_instance, response_format) + content = convert_to_ajax(content, context_instance) + context['content'] = json.dumps(content) + + notifications = [] + if context_instance and 'request' in context_instance: + request = context_instance['request'] + maxmsgs = 5 + try: + for message in list(messages.get_messages(request))[:maxmsgs]: + msgtext = unicode(message) + if 'updaterecord:' in msgtext[:13]: + try: + update_id = int(msgtext.split(':', 1)[1]) + update = UpdateRecord.objects.get(pk=update_id) + message = {'message': update.get_full_message(), + 'tags': message.tags} + if update.author: + if update.record_type == 'manual' or update.record_type == 'share': + try: + message[ + 'image'] = update.author.get_contact().get_picture() + except: + pass + message['title'] = unicode(update.author) + for obj in update.about.all(): + message['message'] = "(%s) %s:
    %s" % ( + obj.get_human_type(), unicode(obj), message['message']) + notifications.append(message) + except: + pass + else: + notifications.append({'message': unicode(message), + 'tags': message.tags}) + except: + pass + context['notifications'] = json.dumps(notifications) + + rendered_string = render_to_string( + 'ajax_base', context, context_instance, response_format='json') + + return rendered_string + + +def render_to_response(template_name, context=None, context_instance=None, response_format='html'): + "Extended render_to_response to support different formats" + if context is None: + context = {} + if not response_format: + response_format = 'html' + + if response_format not in settings.HARDTREE_RESPONSE_FORMATS: + response_format = 'html' + + content_type = settings.HARDTREE_RESPONSE_FORMATS[response_format] + + if 'pdf' in response_format: + while True: + hasher = hashlib.md5() + hasher.update(str(random.random())) + filepath = u"pdfs/" + hasher.hexdigest() + output = settings.MEDIA_ROOT + filepath + if not os.path.exists(output + ".pdf"): + break + + while True: + hasher = hashlib.md5() + hasher.update(str(random.random())) + filepath = hasher.hexdigest() + ".html" + source = getattr(settings, 'WKCWD', './') + filepath + if not os.path.exists(source): + break + + page_size = "A4" + orientation = "portrait" + + rendered_string = render_to_string( + template_name, context, context_instance, response_format) + + f = codecs.open(source, encoding='utf-8', mode='w') + pdf_string = unicode(rendered_string) + + if context_instance and context_instance['request']: + pdf_string = pdf_string.replace( + "a href=\"/", "a href=\"http://" + RequestSite(context_instance['request']).domain + "/") + + pdf_string.replace("href=\"/", "href=\"") + + pattern = """Content-Type: text/html|\n\W*
    \n\W.*\n.*
    |Select""" + pdf_string = re.sub(pattern, "", pdf_string).replace( + '/static/', 'static/') + + f.write(pdf_string) + f.close() + + wkpath = getattr(settings, 'WKPATH', './bin/wkhtmltopdf-i386') + x = subprocess.Popen("%s --print-media-type --orientation %s --page-size %s %s %s" % + (wkpath, + orientation, + page_size, + source, + output), + shell=True, + cwd=getattr(settings, 'WKCWD', './')) + x.wait() + + f = open(output) + response = HttpResponse(f.read(), content_type='application/pdf') + f.close() + + os.remove(output) + os.remove(source) + + # response['Content-Disposition'] = 'attachment; filename=%s'%(pdf_name) + + return response + + if 'ajax' in response_format: + rendered_string = render_to_ajax( + template_name, context, context_instance) + + else: + + if response_format == 'html' and context_instance and context_instance['request'].path[:3] == '/m/': + context['response_format'] = response_format = 'mobile' + + if getattr(settings, 'HARDTREE_FORCE_AJAX_RENDERING', False): + context = preprocess_context_ajax(context) + + rendered_string = render_to_string( + template_name, context, context_instance, response_format) + + response = HttpResponse(rendered_string, content_type=content_type) + + return response + + +def render_string_template(template_string, context=None, context_instance=None): + """ + Performs rendering using template_string instead of a file, and context. + context_instance is only used to feed user into context (unless already defined) + + Returns string. + """ + if context is None: + context = {} + template = Template(template_string) + if 'user' not in context and context_instance: + if 'request' in context_instance: + context.update({'user': context_instance['request']}) + + return template.render(context) + + +def get_template_source(template_name, response_format='html'): + "Returns source of the template file" + + if not response_format or 'pdf' in response_format or response_format not in settings.HARDTREE_RESPONSE_FORMATS: + response_format = 'html' + + if not ("." + response_format) in template_name: + template_name += "." + response_format + + template_name = response_format + "/" + template_name + + t = loader.get_template(template_name) + f = open(t.filename, 'r') + + return f.read() diff --git a/data/treeio/treeio/treeio/core/rss.py b/data/treeio/treeio/treeio/core/rss.py new file mode 100644 index 0000000..5d097b9 --- /dev/null +++ b/data/treeio/treeio/treeio/core/rss.py @@ -0,0 +1,117 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Global RSS Framework +""" +from django.contrib.syndication.views import Feed +from django.contrib.sites.models import RequestSite +from treeio.core.models import Object, UpdateRecord, User +import hashlib +import random + + +class ObjectFeed(Feed): + + "Generic RSS class" + + def __init__(self, title, link, description, objects, *args, **kwargs): + self.title = title + self.link = link + self.description = description + self.key = '' + self.objects = objects + super(ObjectFeed, self).__init__(*args, **kwargs) + + def __call__(self, request, *args, **kwargs): + "Generates response" + self.site_url = 'http://' + RequestSite(request).domain + self.link = self.site_url + self.link + response = super(ObjectFeed, self).__call__(request, *args, **kwargs) + # Dirty hack for "example.com" - I hate it too but it works (contrast to all other solutions) + # TODO: proper workaround for "example.com" in URLs + # P.S. worship Ctulhu before you attempt this + response.content = response.content.replace( + 'http://example.com', self.site_url) + return response + + def get_object(self, request, *args, **kwargs): + "Returns feed objects" + return self.objects[:50] + + def items(self, obj): + "Returns a single object" + return obj + + def item_title(self, obj): + "Returns object title" + if isinstance(obj, Object): + return obj.creator + elif isinstance(obj, UpdateRecord): + return obj.author + + def item_pubdate(self, obj): + "Returns object's date_created" + return obj.date_created + + def item_description(self, obj): + "Returns object's body, details or full message" + if isinstance(obj, Object): + if obj.body: + return obj.body + else: + return obj.details + elif isinstance(obj, UpdateRecord): + body = '' + for object in obj.about.all(): + body += '' + unicode(object) + ' (' + object.get_human_type() + ')
    ' + body += obj.get_full_message() + return body + + def item_link(self, obj): + "Returns object's full url" + if isinstance(obj, Object): + return self.site_url + obj.get_absolute_url() + elif isinstance(obj, UpdateRecord): + # link must be unique + return self.link + '?' + str(random.random()) + + +def verify_secret_key(request): + "Verifies secret key for a request" + if request.user.username: + # always allow authenticated users + return True + else: + key = request.GET['secret'] + user_id, secret = key.split('.', 1) + try: + profile = User.objects.get(pk=user_id) + except: + return False + if key == get_secret_key(request, profile): + request.user = profile.user + return True + return False + + +def get_secret_key(request, profile=None): + "Generates secret key for a request in RSS format" + if not profile: + if request.user.username: + profile = request.user.profile + if profile: + params = request.GET.copy() + if 'secret' in params: + del params['secret'] + hash = hashlib.sha224() + hash.update(unicode(params)) + hash.update(unicode(profile.id)) + hash.update(unicode(profile.user.date_joined)) + key = unicode(profile.id) + '.' + hash.hexdigest() + return key + return '' diff --git a/data/treeio/treeio/treeio/core/sanitizer.py b/data/treeio/treeio/treeio/core/sanitizer.py new file mode 100644 index 0000000..485658f --- /dev/null +++ b/data/treeio/treeio/treeio/core/sanitizer.py @@ -0,0 +1,318 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +import re +from xml.sax.saxutils import escape, unescape +import html5lib +from html5lib import treebuilders, treewalkers, serializer +from html5lib.tokenizer import HTMLTokenizer +from html5lib.constants import tokenTypes + + +class HTMLSanitizerMixin(object): + + """ sanitization of XHTML+MathML+SVG and of inline style attributes.""" + + acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area', + 'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button', + 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', + 'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn', + 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset', + 'figcaption', 'figure', 'footer', 'font', 'form', 'header', 'h1', + 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'ins', + 'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter', + 'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option', + 'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select', + 'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong', + 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot', + 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video'] + + mathml_elements = ['maction', 'math', 'merror', 'mfrac', 'mi', + 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', + 'mprescripts', 'mroot', 'mrow', 'mspace', 'msqrt', 'mstyle', 'msub', + 'msubsup', 'msup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', + 'munderover', 'none'] + + svg_elements = ['a', 'animate', 'animateColor', 'animateMotion', + 'animateTransform', 'clipPath', 'circle', 'defs', 'desc', 'ellipse', + 'font-face', 'font-face-name', 'font-face-src', 'g', 'glyph', 'hkern', + 'linearGradient', 'line', 'marker', 'metadata', 'missing-glyph', + 'mpath', 'path', 'polygon', 'polyline', 'radialGradient', 'rect', + 'set', 'stop', 'svg', 'switch', 'text', 'tspan', 'use'] + + acceptable_attributes = ['abbr', 'accept', 'accept-charset', 'accesskey', + 'action', 'align', 'alt', 'autocomplete', 'autofocus', 'axis', + 'background', 'balance', 'bgcolor', 'bgproperties', 'border', + 'bordercolor', 'bordercolordark', 'bordercolorlight', 'bottompadding', + 'cellpadding', 'cellspacing', 'ch', 'challenge', 'char', 'charoff', + 'choff', 'charset', 'checked', 'cite', 'class', 'clear', 'color', 'callback', + 'cols', 'colspan', 'compact', 'contenteditable', 'controls', 'coords', + 'data', 'datafld', 'datapagesize', 'datasrc', 'datetime', 'default', + 'delay', 'dir', 'disabled', 'draggable', 'dynsrc', 'enctype', 'end', + 'face', 'for', 'form', 'field', 'frame', 'galleryimg', 'gutter', 'headers', + 'height', 'hidefocus', 'hidden', 'high', 'href', 'hreflang', 'hspace', + 'icon', 'id', 'inputmode', 'ismap', 'keytype', 'label', 'leftspacing', + 'lang', 'list', 'longdesc', 'loop', 'loopcount', 'loopend', + 'loopstart', 'low', 'lowsrc', 'max', 'maxlength', 'media', 'method', + 'min', 'multiple', 'name', 'nohref', 'noshade', 'nowrap', 'open', + 'optimum', 'pattern', 'ping', 'point-size', 'prompt', 'pqg', + 'radiogroup', 'readonly', 'rel', 'repeat-max', 'repeat-min', + 'replace', 'required', 'rev', 'rightspacing', 'rows', 'rowspan', + 'rules', 'scope', 'selected', 'shape', 'size', 'span', 'src', 'start', + 'step', 'style', 'summary', 'suppress', 'tabindex', 'target', + 'template', 'title', 'toppadding', 'type', 'unselectable', 'usemap', + 'urn', 'valign', 'value', 'variable', 'volume', 'vspace', 'vrml', + 'width', 'wrap', 'xml:lang'] + + mathml_attributes = ['actiontype', 'align', 'columnalign', 'columnalign', + 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'depth', + 'display', 'displaystyle', 'equalcolumns', 'equalrows', 'fence', + 'fontstyle', 'fontweight', 'frame', 'height', 'linethickness', 'lspace', + 'mathbackground', 'mathcolor', 'mathvariant', 'mathvariant', 'maxsize', + 'minsize', 'other', 'rowalign', 'rowalign', 'rowalign', 'rowlines', + 'rowspacing', 'rowspan', 'rspace', 'scriptlevel', 'selection', + 'separator', 'stretchy', 'width', 'width', 'xlink:href', 'xlink:show', + 'xlink:type', 'xmlns', 'xmlns:xlink'] + + svg_attributes = ['accent-height', 'accumulate', 'additive', 'alphabetic', + 'arabic-form', 'ascent', 'attributeName', 'attributeType', + 'baseProfile', 'bbox', 'begin', 'by', 'calcMode', 'cap-height', + 'class', 'clip-path', 'color', 'color-rendering', 'content', 'cx', + 'cy', 'd', 'dx', 'dy', 'descent', 'display', 'dur', 'end', 'fill', + 'fill-opacity', 'fill-rule', 'font-family', 'font-size', + 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'from', + 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'gradientUnits', 'hanging', + 'height', 'horiz-adv-x', 'horiz-origin-x', 'id', 'ideographic', 'k', + 'keyPoints', 'keySplines', 'keyTimes', 'lang', 'marker-end', + 'marker-mid', 'marker-start', 'markerHeight', 'markerUnits', + 'markerWidth', 'mathematical', 'max', 'min', 'name', 'offset', + 'opacity', 'orient', 'origin', 'overline-position', + 'overline-thickness', 'panose-1', 'path', 'pathLength', 'points', + 'preserveAspectRatio', 'r', 'refX', 'refY', 'repeatCount', + 'repeatDur', 'requiredExtensions', 'requiredFeatures', 'restart', + 'rotate', 'rx', 'ry', 'slope', 'stemh', 'stemv', 'stop-color', + 'stop-opacity', 'strikethrough-position', 'strikethrough-thickness', + 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', + 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', + 'stroke-width', 'systemLanguage', 'target', 'text-anchor', 'to', + 'transform', 'type', 'u1', 'u2', 'underline-position', + 'underline-thickness', 'unicode', 'unicode-range', 'units-per-em', + 'values', 'version', 'viewBox', 'visibility', 'width', 'widths', 'x', + 'x-height', 'x1', 'x2', 'xlink:actuate', 'xlink:arcrole', + 'xlink:href', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type', + 'xml:base', 'xml:lang', 'xml:space', 'xmlns', 'xmlns:xlink', 'y', + 'y1', 'y2', 'zoomAndPan'] + + attr_val_is_uri = ['href', 'src', 'cite', 'action', 'longdesc', + 'xlink:href', 'xml:base'] + + svg_attr_val_allows_ref = ['clip-path', 'color-profile', 'cursor', 'fill', + 'filter', 'marker', 'marker-start', 'marker-mid', 'marker-end', + 'mask', 'stroke'] + + svg_allow_local_href = ['altGlyph', 'animate', 'animateColor', + 'animateMotion', 'animateTransform', 'cursor', 'feImage', 'filter', + 'linearGradient', 'pattern', 'radialGradient', 'textpath', 'tref', + 'set', 'use'] + + acceptable_css_properties = ['azimuth', 'background-color', + 'border-bottom-color', 'border-collapse', 'border-color', + 'border-left-color', 'border-right-color', 'border-top-color', 'clear', + 'color', 'cursor', 'direction', 'display', 'elevation', 'float', 'font', + 'font-family', 'font-size', 'font-style', 'font-variant', 'font-weight', + 'height', 'letter-spacing', 'line-height', 'overflow', 'pause', + 'pause-after', 'pause-before', 'pitch', 'pitch-range', 'richness', + 'speak', 'speak-header', 'speak-numeral', 'speak-punctuation', + 'speech-rate', 'stress', 'text-align', 'text-decoration', 'text-indent', + 'unicode-bidi', 'vertical-align', 'voice-family', 'volume', + 'white-space', 'width'] + + acceptable_css_keywords = ['auto', 'aqua', 'black', 'block', 'blue', + 'bold', 'both', 'bottom', 'brown', 'center', 'collapse', 'dashed', + 'dotted', 'fuchsia', 'gray', 'green', '!important', 'italic', 'left', + 'lime', 'maroon', 'medium', 'none', 'navy', 'normal', 'nowrap', 'olive', + 'pointer', 'purple', 'red', 'right', 'solid', 'silver', 'teal', 'top', + 'transparent', 'underline', 'white', 'yellow'] + + acceptable_svg_properties = ['fill', 'fill-opacity', 'fill-rule', + 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', + 'stroke-opacity'] + + acceptable_protocols = ['ed2k', 'ftp', 'http', 'https', 'irc', + 'mailto', 'news', 'gopher', 'nntp', 'telnet', 'webcal', + 'xmpp', 'callto', 'feed', 'urn', 'aim', 'rsync', 'tag', + 'ssh', 'sftp', 'rtsp', 'afs'] + + remove_tags = ['script', 'style'] + + # subclasses may define their own versions of these constants + allowed_elements = acceptable_elements + mathml_elements + svg_elements + allowed_attributes = acceptable_attributes + \ + mathml_attributes + svg_attributes + allowed_css_properties = acceptable_css_properties + allowed_css_keywords = acceptable_css_keywords + allowed_svg_properties = acceptable_svg_properties + allowed_protocols = acceptable_protocols + + # Sanitize the +html+, escaping all elements not in ALLOWED_ELEMENTS, and + # stripping out all # attributes not in ALLOWED_ATTRIBUTES. Style + # attributes are parsed, and a restricted set, # specified by + # ALLOWED_CSS_PROPERTIES and ALLOWED_CSS_KEYWORDS, are allowed through. + # attributes in ATTR_VAL_IS_URI are scanned, and only URI schemes specified + # in ALLOWED_PROTOCOLS are allowed. + # + # sanitize_html('') + # => <script> do_nasty_stuff() </script> + # sanitize_html('Click here for $100') + # => Click here for $100 + def sanitize_token(self, token): + + # accommodate filters which use token_type differently + token_type = token["type"] + if token_type in tokenTypes.keys(): + token_type = tokenTypes[token_type] + + if token_type in (tokenTypes["StartTag"], tokenTypes["EndTag"], + tokenTypes["EmptyTag"]): + token["name"] = token["name"].lower() + if token["name"] in self.allowed_elements: + if "data" in token: + attrs = dict([(name, val) for name, val in + token["data"][::-1] + if name in self.allowed_attributes]) + for attr in self.attr_val_is_uri: + if not attrs.has_key(attr): + continue + val_unescaped = re.sub("[`\000-\040\177-\240\s]+", '', + unescape(attrs[attr])).lower() + # remove replacement characters from unescaped + # characters + val_unescaped = val_unescaped.replace(u"\ufffd", "") + if (re.match("^[a-z0-9][-+.a-z0-9]*:", val_unescaped) and + (val_unescaped.split(':')[0] not in + self.allowed_protocols)): + del attrs[attr] + for attr in self.svg_attr_val_allows_ref: + if attr in attrs: + attrs[attr] = re.sub(r'url\s*\(\s*[^#\s][^)]+?\)', + ' ', + unescape(attrs[attr])) + if (token["name"] in self.svg_allow_local_href and + 'xlink:href' in attrs and re.search('^\s*[^#\s].*', + attrs['xlink:href'])): + del attrs['xlink:href'] + if 'style' in attrs: + attrs['style'] = self.sanitize_css(attrs['style']) + token["data"] = [[name, val] + for name, val in attrs.items()] + return token + else: + if token["name"] in self.remove_tags: + token["name"] = "toberemoved" + + if token_type == tokenTypes["EndTag"]: + token["data"] = "" % token["name"] + elif token["data"]: + attrs = ''.join([' %s="%s"' % (k, escape(v)) + for k, v in token["data"]]) + token["data"] = "<%s%s>" % (token["name"], attrs) + else: + token["data"] = "<%s>" % token["name"] + if token.get("selfClosing"): + token["data"] = token["data"][:-1] + "/>" + + if token["type"] in tokenTypes.keys(): + token["type"] = "Characters" + else: + token["type"] = tokenTypes["Characters"] + + if "name" in token and token["name"] == "style": + print "style", token["data"], dir(token) + + return token + elif token_type == tokenTypes["Comment"]: + pass + else: + return token + + def sanitize_css(self, style): + # disallow urls + style = re.compile('url\s*\(\s*[^\s)]+?\s*\)\s*').sub(' ', style) + + # gauntlet + if not re.match("""^([:,;#%.\sa-zA-Z0-9!]|\w-\w|'[\s\w]+'|"[\s\w]+"|\([\d,\s]+\))*$""", style): + return '' + if not re.match("^\s*([-\w]+\s*:[^:;]*(;\s*|$))*$", style): + return '' + + clean = [] + for prop, value in re.findall("([-\w]+)\s*:\s*([^:;]*)", style): + if not value: + continue + if prop.lower() in self.allowed_css_properties: + clean.append(prop + ': ' + value + ';') + elif prop.split('-')[0].lower() in ['background', 'border', 'margin', + 'padding']: + for keyword in value.split(): + if keyword not in self.acceptable_css_keywords and \ + not re.match("^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$", keyword): + break + else: + clean.append(prop + ': ' + value + ';') + elif prop.lower() in self.allowed_svg_properties: + clean.append(prop + ': ' + value + ';') + + return ' '.join(clean) + + +class HTMLSanitizer(HTMLTokenizer, HTMLSanitizerMixin): + + def __init__(self, stream, encoding=None, parseMeta=True, useChardet=True, + lowercaseElementName=False, lowercaseAttrName=False): + # Change case matching defaults as we only output lowercase html anyway + # This solution doesn't seem ideal... + HTMLTokenizer.__init__(self, stream, encoding, parseMeta, useChardet, + lowercaseElementName, lowercaseAttrName) + + def __iter__(self): + for token in HTMLTokenizer.__iter__(self): + token = self.sanitize_token(token) + if token: + yield token + + +def clean_html(buf): + """Cleans HTML of dangerous tags and content.""" + buf = buf.strip() + if not buf: + return buf + + html_parser = html5lib.HTMLParser( + tree=treebuilders.getTreeBuilder("dom"), tokenizer=HTMLSanitizer) + dom_tree = html_parser.parseFragment(buf) + + walker = treewalkers.getTreeWalker("dom") + stream = walker(dom_tree) + + s = serializer.htmlserializer.HTMLSerializer( + omit_optional_tags=False, quote_attr_values=True) + output = s.render(stream, 'utf-8') + + while 'toberemoved' in output: + oldoutput = output + matches = re.findall( + r'<toberemoved.*?>.*?</toberemoved>', output, re.DOTALL) + for s in matches: + output = output.replace(s, '') + matches = re.findall(r'</toberemoved>', output, re.DOTALL) + for s in matches: + output = output.replace(s, '') + matches = re.findall(r'<toberemoved.*?>', output, re.DOTALL) + for s in matches: + output = output.replace(s, '') + if output == oldoutput: + break + + return output diff --git a/data/treeio/treeio/treeio/core/search/models.py b/data/treeio/treeio/treeio/core/search/models.py new file mode 100644 index 0000000..c5be6af --- /dev/null +++ b/data/treeio/treeio/treeio/core/search/models.py @@ -0,0 +1,74 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core Search module model hooks +""" + +import os +from django.db.models import signals +from treeio.core.conf import settings +from treeio.core.models import Object +from whoosh import index + + +def create_index(sender=None, **kwargs): + "Create initial (empty) search index" + if not os.path.exists(settings.WHOOSH_INDEX): + os.mkdir(settings.WHOOSH_INDEX) + index.create_in(settings.WHOOSH_INDEX, schema=settings.WHOOSH_SCHEMA) + +if not settings.SEARCH_DISABLED and getattr(settings, 'SEARCH_ENGINE', 'whoosh') == 'whoosh': + signals.post_syncdb.connect(create_index) + + +def update_index(sender, instance, created, **kwargs): + "Add Object to search index" + if isinstance(instance, Object) and instance.is_searchable(): + search_item = instance.get_search_item() + ix = index.open_dir(settings.WHOOSH_INDEX) + try: + writer = ix.writer() + try: + if created: + writer.add_document(id=search_item['id'], + name=search_item['name'], + type=search_item['type'], + content=search_item['content'], + url=unicode(search_item['url'])) + writer.commit() + else: + writer.update_document(id=search_item['id'], + name=search_item['name'], + type=search_item['type'], + content=search_item['content'], + url=search_item['url']) + writer.commit() + except: + writer.cancel() + except: + pass + +if not settings.SEARCH_DISABLED and getattr(settings, 'SEARCH_ENGINE', 'whoosh') == 'whoosh': + signals.post_save.connect(update_index) + + +def delete_index(sender, instance, **kwargs): + "Delete Object from search index" + + if isinstance(instance, Object) and instance.is_searchable(): + ix = index.open_dir(settings.WHOOSH_INDEX) + try: + writer = ix.writer() + try: + writer.delete_by_term(u'id', unicode(instance.id)) + writer.commit() + except: + writer.cancel() + except: + pass + +if not settings.SEARCH_DISABLED and getattr(settings, 'SEARCH_ENGINE', 'whoosh') == 'whoosh': + signals.post_delete.connect(delete_index) diff --git a/data/treeio/treeio/treeio/core/search/views.py b/data/treeio/treeio/treeio/core/search/views.py new file mode 100644 index 0000000..7d79bac --- /dev/null +++ b/data/treeio/treeio/treeio/core/search/views.py @@ -0,0 +1,67 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core Search module views +""" + +from treeio.core.rendering import render_to_response +from django.template import RequestContext +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.core.conf import settings +from treeio.core.models import Object, Tag +from treeio.core.search import dbsearch +from whoosh import index, qparser + + +@treeio_login_required +@handle_response_format +def search_query(request, response_format='html'): + "Account view" + + objects = [] + query = request.GET.get('q', '') + if query: + if query[:5] == 'tags:': + tag_names = query[5:].strip().split(',') + tags = Tag.objects.filter(name__in=tag_names) + objects = Object.objects.filter(tags__in=tags) + else: + search_engine = getattr(settings, 'SEARCH_ENGINE', 'whoosh') + if search_engine == 'whoosh': + ix = index.open_dir(settings.WHOOSH_INDEX) + # Whoosh doesn't understand '+' or '-' but we can replace + # them with 'AND' and 'NOT'. + squery = query.replace( + '+', ' AND ').replace('|', ' OR ').replace(' ', ' OR ') + parser = qparser.MultifieldParser( + ["name", "url", "type", "content"], schema=ix.schema) + qry = parser.parse(squery) + try: + qry = parser.parse(squery) + except: + # don't show the user weird errors only because we don't + # understand the query. + # parser.parse("") would return None + qry = None + if qry: + searcher = ix.searcher() + try: + hits = searcher.search(qry, limit=100) + except: + hits = [] + + hit_ids = [hit['id'] for hit in hits] + + objects = Object.objects.filter(pk__in=hit_ids) + elif search_engine == 'db': + objects = dbsearch.search(query) + else: + raise RuntimeError('Unknown Search engine: %s' % search_engine) + + return render_to_response('core/search/query_view', + {'query': query, 'objects': objects}, + context_instance=RequestContext(request), + response_format=response_format) diff --git a/data/treeio/treeio/treeio/core/south_migrations/0002_auto__del_notification__add_comment__add_tag__add_revisionfield__add_r.py b/data/treeio/treeio/treeio/core/south_migrations/0002_auto__del_notification__add_comment__add_tag__add_revisionfield__add_r.py new file mode 100644 index 0000000..358fd08 --- /dev/null +++ b/data/treeio/treeio/treeio/core/south_migrations/0002_auto__del_notification__add_comment__add_tag__add_revisionfield__add_r.py @@ -0,0 +1,719 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting model 'Notification' + db.delete_table('core_notification') + + # Adding model 'Comment' + db.create_table('core_comment', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('author', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['core.User'], null=True, blank=True)), + ('body', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('date_created', self.gf('django.db.models.fields.DateTimeField') + (default=datetime.datetime.now)), + )) + db.send_create_signal('core', ['Comment']) + + # Adding M2M table for field likes on 'Comment' + db.create_table('core_comment_likes', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('comment', models.ForeignKey(orm['core.comment'], null=False)), + ('user', models.ForeignKey(orm['core.user'], null=False)) + )) + db.create_unique('core_comment_likes', ['comment_id', 'user_id']) + + # Adding M2M table for field dislikes on 'Comment' + db.create_table('core_comment_dislikes', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('comment', models.ForeignKey(orm['core.comment'], null=False)), + ('user', models.ForeignKey(orm['core.user'], null=False)) + )) + db.create_unique('core_comment_dislikes', ['comment_id', 'user_id']) + + # Adding model 'Tag' + db.create_table('core_tag', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('date_created', self.gf('django.db.models.fields.DateTimeField') + (default=datetime.datetime.now)), + )) + db.send_create_signal('core', ['Tag']) + + # Adding model 'RevisionField' + db.create_table('core_revisionfield', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('revision', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['core.Revision'])), + ('field_type', self.gf('django.db.models.fields.CharField') + (max_length=512, null=True, blank=True)), + ('field', self.gf('django.db.models.fields.CharField') + (max_length=512, null=True, blank=True)), + ('value', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('value_key', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='revisionfield_key', null=True, to=orm['core.Object'])), + ('value_key_acc', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='revisionfield_key_acc', null=True, to=orm['core.AccessEntity'])), + )) + db.send_create_signal('core', ['RevisionField']) + + # Adding M2M table for field value_m2m on 'RevisionField' + db.create_table('core_revisionfield_value_m2m', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('revisionfield', models.ForeignKey( + orm['core.revisionfield'], null=False)), + ('object', models.ForeignKey(orm['core.object'], null=False)) + )) + db.create_unique( + 'core_revisionfield_value_m2m', ['revisionfield_id', 'object_id']) + + # Adding M2M table for field value_m2m_acc on 'RevisionField' + db.create_table('core_revisionfield_value_m2m_acc', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('revisionfield', models.ForeignKey( + orm['core.revisionfield'], null=False)), + ('accessentity', models.ForeignKey( + orm['core.accessentity'], null=False)) + )) + db.create_unique( + 'core_revisionfield_value_m2m_acc', ['revisionfield_id', 'accessentity_id']) + + # Adding model 'Revision' + db.create_table('core_revision', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('previous', self.gf('django.db.models.fields.related.OneToOneField')( + blank=True, related_name='next_set', unique=True, null=True, to=orm['core.Revision'])), + ('object', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['core.Object'])), + ('change_type', self.gf('django.db.models.fields.CharField') + (max_length=512, null=True, blank=True)), + ('date_created', self.gf('django.db.models.fields.DateTimeField') + (default=datetime.datetime.now)), + )) + db.send_create_signal('core', ['Revision']) + + # Adding model 'Invitation' + db.create_table('core_invitation', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('email', self.gf('django.db.models.fields.EmailField') + (max_length=75)), + ('key', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('sender', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['core.User'], null=True, blank=True)), + ('default_group', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.Group'], null=True, blank=True)), + ('date_created', self.gf('django.db.models.fields.DateTimeField') + (default=datetime.datetime.now)), + )) + db.send_create_signal('core', ['Invitation']) + + # Adding model 'AccessEntity' + db.create_table('core_accessentity', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('last_updated', self.gf('django.db.models.fields.DateTimeField') + (auto_now=True, blank=True)), + )) + db.send_create_signal('core', ['AccessEntity']) + + # Adding model 'UpdateRecord' + db.create_table('core_updaterecord', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('author', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='sent_updates', null=True, to=orm['core.User'])), + ('sender', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='sent_updates', null=True, to=orm['core.Object'])), + ('record_type', self.gf( + 'django.db.models.fields.CharField')(max_length=32)), + ('url', self.gf('django.db.models.fields.CharField') + (max_length=512, null=True, blank=True)), + ('body', self.gf('django.db.models.fields.TextField') + (default='', null=True, blank=True)), + ('score', self.gf( + 'django.db.models.fields.IntegerField')(default=0)), + ('format_message', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('format_strings', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('date_created', self.gf('django.db.models.fields.DateTimeField') + (default=datetime.datetime.now)), + )) + db.send_create_signal('core', ['UpdateRecord']) + + # Adding M2M table for field about on 'UpdateRecord' + db.create_table('core_updaterecord_about', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('updaterecord', models.ForeignKey( + orm['core.updaterecord'], null=False)), + ('object', models.ForeignKey(orm['core.object'], null=False)) + )) + db.create_unique( + 'core_updaterecord_about', ['updaterecord_id', 'object_id']) + + # Adding M2M table for field recipients on 'UpdateRecord' + db.create_table('core_updaterecord_recipients', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('updaterecord', models.ForeignKey( + orm['core.updaterecord'], null=False)), + ('accessentity', models.ForeignKey( + orm['core.accessentity'], null=False)) + )) + db.create_unique( + 'core_updaterecord_recipients', ['updaterecord_id', 'accessentity_id']) + + # Adding M2M table for field comments on 'UpdateRecord' + db.create_table('core_updaterecord_comments', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('updaterecord', models.ForeignKey( + orm['core.updaterecord'], null=False)), + ('comment', models.ForeignKey(orm['core.comment'], null=False)) + )) + db.create_unique( + 'core_updaterecord_comments', ['updaterecord_id', 'comment_id']) + + # Adding M2M table for field likes on 'UpdateRecord' + db.create_table('core_updaterecord_likes', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('updaterecord', models.ForeignKey( + orm['core.updaterecord'], null=False)), + ('user', models.ForeignKey(orm['core.user'], null=False)) + )) + db.create_unique( + 'core_updaterecord_likes', ['updaterecord_id', 'user_id']) + + # Adding M2M table for field dislikes on 'UpdateRecord' + db.create_table('core_updaterecord_dislikes', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('updaterecord', models.ForeignKey( + orm['core.updaterecord'], null=False)), + ('user', models.ForeignKey(orm['core.user'], null=False)) + )) + db.create_unique( + 'core_updaterecord_dislikes', ['updaterecord_id', 'user_id']) + + # Deleting field 'Group.last_updated' + db.delete_column('core_group', 'last_updated') + + # Adding field 'Group.accessentity_ptr' + db.add_column('core_group', 'accessentity_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.AccessEntity'], unique=True, null=True, blank=True), keep_default=False) + + # Deleting field 'Object.group_read' + db.delete_column('core_object', 'group_read') + + # Deleting field 'Object.user_write' + db.delete_column('core_object', 'user_write') + + # Deleting field 'Object.group' + db.delete_column('core_object', 'group_id') + + # Deleting field 'Object.everybody_execute' + db.delete_column('core_object', 'everybody_execute') + + # Deleting field 'Object.user_execute' + db.delete_column('core_object', 'user_execute') + + # Deleting field 'Object.user_read' + db.delete_column('core_object', 'user_read') + + # Deleting field 'Object.everybody_write' + db.delete_column('core_object', 'everybody_write') + + # Deleting field 'Object.group_write' + db.delete_column('core_object', 'group_write') + + # Deleting field 'Object.group_execute' + db.delete_column('core_object', 'group_execute') + + # Deleting field 'Object.everybody_read' + db.delete_column('core_object', 'everybody_read') + + # Adding field 'Object.creator' + db.add_column('core_object', 'creator', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='objects_created', null=True, to=orm['core.User']), keep_default=False) + + # Adding M2M table for field read_access on 'Object' + db.create_table('core_object_read_access', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('object', models.ForeignKey(orm['core.object'], null=False)), + ('accessentity', models.ForeignKey( + orm['core.accessentity'], null=False)) + )) + db.create_unique( + 'core_object_read_access', ['object_id', 'accessentity_id']) + + # Adding M2M table for field full_access on 'Object' + db.create_table('core_object_full_access', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('object', models.ForeignKey(orm['core.object'], null=False)), + ('accessentity', models.ForeignKey( + orm['core.accessentity'], null=False)) + )) + db.create_unique( + 'core_object_full_access', ['object_id', 'accessentity_id']) + + # Adding M2M table for field tags on 'Object' + db.create_table('core_object_tags', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('object', models.ForeignKey(orm['core.object'], null=False)), + ('tag', models.ForeignKey(orm['core.tag'], null=False)) + )) + db.create_unique('core_object_tags', ['object_id', 'tag_id']) + + # Adding M2M table for field comments on 'Object' + db.create_table('core_object_comments', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('object', models.ForeignKey(orm['core.object'], null=False)), + ('comment', models.ForeignKey(orm['core.comment'], null=False)) + )) + db.create_unique('core_object_comments', ['object_id', 'comment_id']) + + # Adding M2M table for field likes on 'Object' + db.create_table('core_object_likes', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('object', models.ForeignKey(orm['core.object'], null=False)), + ('user', models.ForeignKey(orm['core.user'], null=False)) + )) + db.create_unique('core_object_likes', ['object_id', 'user_id']) + + # Adding M2M table for field dislikes on 'Object' + db.create_table('core_object_dislikes', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('object', models.ForeignKey(orm['core.object'], null=False)), + ('user', models.ForeignKey(orm['core.user'], null=False)) + )) + db.create_unique('core_object_dislikes', ['object_id', 'user_id']) + + # Changing field 'Object.user' + db.alter_column('core_object', 'user_id', self.gf( + 'django.db.models.fields.related.ForeignKey')(to=orm['core.User'], null=True)) + + # Deleting field 'User.last_updated' + db.delete_column('core_user', 'last_updated') + + # Adding field 'User.accessentity_ptr' + db.add_column('core_user', 'accessentity_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.AccessEntity'], unique=True, null=True, blank=True), keep_default=False) + + # Adding field 'User.disabled' + db.add_column('core_user', 'disabled', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'User.last_access' + db.add_column('core_user', 'last_access', self.gf('django.db.models.fields.DateTimeField')( + default=datetime.datetime.now), keep_default=False) + + # Changing field 'User.default_group' + db.alter_column('core_user', 'default_group_id', self.gf( + 'django.db.models.fields.related.ForeignKey')(null=True, to=orm['core.AccessEntity'])) + + def backwards(self, orm): + + # Adding model 'Notification' + db.create_table('core_notification', ( + ('object', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['core.Object'], null=True, blank=True)), + ('format_strings', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('object_type', self.gf('django.db.models.fields.CharField') + (max_length=512, null=True, blank=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['core.User'])), + ('message', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('format_message', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('sender', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='notification_sent_set', null=True, to=orm['core.User'], blank=True)), + ('url', self.gf('django.db.models.fields.CharField') + (max_length=512, null=True, blank=True)), + ('date_created', self.gf('django.db.models.fields.DateTimeField') + (auto_now=True, blank=True)), + ('type', self.gf('django.db.models.fields.CharField') + (max_length=32)), + )) + db.send_create_signal('core', ['Notification']) + + # Deleting model 'Comment' + db.delete_table('core_comment') + + # Removing M2M table for field likes on 'Comment' + db.delete_table('core_comment_likes') + + # Removing M2M table for field dislikes on 'Comment' + db.delete_table('core_comment_dislikes') + + # Deleting model 'Tag' + db.delete_table('core_tag') + + # Deleting model 'RevisionField' + db.delete_table('core_revisionfield') + + # Removing M2M table for field value_m2m on 'RevisionField' + db.delete_table('core_revisionfield_value_m2m') + + # Removing M2M table for field value_m2m_acc on 'RevisionField' + db.delete_table('core_revisionfield_value_m2m_acc') + + # Deleting model 'Revision' + db.delete_table('core_revision') + + # Deleting model 'Invitation' + db.delete_table('core_invitation') + + # Deleting model 'AccessEntity' + db.delete_table('core_accessentity') + + # Deleting model 'UpdateRecord' + db.delete_table('core_updaterecord') + + # Removing M2M table for field about on 'UpdateRecord' + db.delete_table('core_updaterecord_about') + + # Removing M2M table for field recipients on 'UpdateRecord' + db.delete_table('core_updaterecord_recipients') + + # Removing M2M table for field comments on 'UpdateRecord' + db.delete_table('core_updaterecord_comments') + + # Removing M2M table for field likes on 'UpdateRecord' + db.delete_table('core_updaterecord_likes') + + # Removing M2M table for field dislikes on 'UpdateRecord' + db.delete_table('core_updaterecord_dislikes') + + # User chose to not deal with backwards NULL issues for + # 'Group.last_updated' + raise RuntimeError( + "Cannot reverse this migration. 'Group.last_updated' and its values cannot be restored.") + + # Deleting field 'Group.accessentity_ptr' + db.delete_column('core_group', 'accessentity_ptr_id') + + # Adding field 'Object.group_read' + db.add_column('core_object', 'group_read', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'Object.user_write' + db.add_column('core_object', 'user_write', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # User chose to not deal with backwards NULL issues for 'Object.group' + raise RuntimeError( + "Cannot reverse this migration. 'Object.group' and its values cannot be restored.") + + # Adding field 'Object.everybody_execute' + db.add_column('core_object', 'everybody_execute', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'Object.user_execute' + db.add_column('core_object', 'user_execute', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'Object.user_read' + db.add_column('core_object', 'user_read', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'Object.everybody_write' + db.add_column('core_object', 'everybody_write', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'Object.group_write' + db.add_column('core_object', 'group_write', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'Object.group_execute' + db.add_column('core_object', 'group_execute', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'Object.everybody_read' + db.add_column('core_object', 'everybody_read', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Deleting field 'Object.creator' + db.delete_column('core_object', 'creator_id') + + # Removing M2M table for field read_access on 'Object' + db.delete_table('core_object_read_access') + + # Removing M2M table for field full_access on 'Object' + db.delete_table('core_object_full_access') + + # Removing M2M table for field tags on 'Object' + db.delete_table('core_object_tags') + + # Removing M2M table for field comments on 'Object' + db.delete_table('core_object_comments') + + # Removing M2M table for field likes on 'Object' + db.delete_table('core_object_likes') + + # Removing M2M table for field dislikes on 'Object' + db.delete_table('core_object_dislikes') + + # User chose to not deal with backwards NULL issues for 'Object.user' + raise RuntimeError( + "Cannot reverse this migration. 'Object.user' and its values cannot be restored.") + + # User chose to not deal with backwards NULL issues for + # 'User.last_updated' + raise RuntimeError( + "Cannot reverse this migration. 'User.last_updated' and its values cannot be restored.") + + # Deleting field 'User.accessentity_ptr' + db.delete_column('core_user', 'accessentity_ptr_id') + + # Deleting field 'User.disabled' + db.delete_column('core_user', 'disabled') + + # Deleting field 'User.last_access' + db.delete_column('core_user', 'last_access') + + # Changing field 'User.default_group' + db.alter_column('core_user', 'default_group_id', self.gf( + 'django.db.models.fields.related.ForeignKey')(null=True, to=orm['core.Group'])) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.module': { + 'Meta': {'ordering': "['name']", 'object_name': 'Module', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'system': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.modulesetting': { + 'Meta': {'object_name': 'ModuleSetting'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.page': { + 'Meta': {'ordering': "['name']", 'object_name': 'Page', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.PageFolder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'core.pagefolder': { + 'Meta': {'object_name': 'PageFolder', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.perspective': { + 'Meta': {'object_name': 'Perspective', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.revision': { + 'Meta': {'object_name': 'Revision'}, + 'change_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']"}), + 'previous': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'next_set'", 'unique': 'True', 'null': 'True', 'to': "orm['core.Revision']"}) + }, + 'core.revisionfield': { + 'Meta': {'object_name': 'RevisionField'}, + 'field': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Revision']"}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'value_key': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key'", 'null': 'True', 'to': "orm['core.Object']"}), + 'value_key_acc': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key_acc'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'value_m2m': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m'", 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'value_m2m_acc': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m_acc'", 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'core.widget': { + 'Meta': {'ordering': "['weight']", 'object_name': 'Widget'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'module_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'widget_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['core'] diff --git a/data/treeio/treeio/treeio/core/south_migrations/0003_treeiocore.py b/data/treeio/treeio/treeio/core/south_migrations/0003_treeiocore.py new file mode 100644 index 0000000..b83b7f7 --- /dev/null +++ b/data/treeio/treeio/treeio/core/south_migrations/0003_treeiocore.py @@ -0,0 +1,245 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models +from treeio.core.models import Object + + +class Migration(DataMigration): + + def forwards(self, orm): + "Migrate the Users and Groups so they extend AccessEntity" + for user in orm['core.User'].objects.all(): + entity = orm['core.AccessEntity'].objects.create() + entity.id = user.id + entity.save() + user.accessentity_ptr = entity + user.save() + for group in orm['core.Group'].objects.all(): + group.accessentity_ptr = orm['core.AccessEntity'].objects.create() + group.accessentity_ptr.save() + if group.parent: + parent = orm['core.Group'].objects.get(id=group.parent_id) + group.parent_id = parent.accessentity_ptr_id + group.save() + for user in orm['core.User'].objects.all(): + group = orm['core.Group'].objects.get(pk=user.default_group_id) + user.default_group = group.accessentity_ptr + user.save() + for obj in orm['core.Object'].objects.all(): + obj.creator = obj.user + obj.save() + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.module': { + 'Meta': {'ordering': "['name']", 'object_name': 'Module', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'system': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.modulesetting': { + 'Meta': {'object_name': 'ModuleSetting'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.page': { + 'Meta': {'ordering': "['name']", 'object_name': 'Page', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.PageFolder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'core.pagefolder': { + 'Meta': {'object_name': 'PageFolder', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.perspective': { + 'Meta': {'object_name': 'Perspective', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.revision': { + 'Meta': {'object_name': 'Revision'}, + 'change_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']"}), + 'previous': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'next_set'", 'unique': 'True', 'null': 'True', 'to': "orm['core.Revision']"}) + }, + 'core.revisionfield': { + 'Meta': {'object_name': 'RevisionField'}, + 'field': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Revision']"}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'value_key': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key'", 'null': 'True', 'to': "orm['core.Object']"}), + 'value_key_acc': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key_acc'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'value_m2m': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m'", 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'value_m2m_acc': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m_acc'", 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'core.widget': { + 'Meta': {'ordering': "['weight']", 'object_name': 'Widget'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'module_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'widget_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['core'] diff --git a/data/treeio/treeio/treeio/core/south_migrations/0004_auto__del_field_object_user.py b/data/treeio/treeio/treeio/core/south_migrations/0004_auto__del_field_object_user.py new file mode 100644 index 0000000..581dcfb --- /dev/null +++ b/data/treeio/treeio/treeio/core/south_migrations/0004_auto__del_field_object_user.py @@ -0,0 +1,228 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting field 'Object.user' + db.delete_column('core_object', 'user_id') + + def backwards(self, orm): + + # Adding field 'Object.user' + db.add_column('core_object', 'user', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='objects_created', null=True, to=orm['core.User'], blank=True), keep_default=False) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.module': { + 'Meta': {'ordering': "['name']", 'object_name': 'Module', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'system': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.modulesetting': { + 'Meta': {'object_name': 'ModuleSetting'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.page': { + 'Meta': {'ordering': "['name']", 'object_name': 'Page', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.PageFolder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'core.pagefolder': { + 'Meta': {'object_name': 'PageFolder', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.perspective': { + 'Meta': {'object_name': 'Perspective', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.revision': { + 'Meta': {'object_name': 'Revision'}, + 'change_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']"}), + 'previous': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'next_set'", 'unique': 'True', 'null': 'True', 'to': "orm['core.Revision']"}) + }, + 'core.revisionfield': { + 'Meta': {'object_name': 'RevisionField'}, + 'field': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Revision']"}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'value_key': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key'", 'null': 'True', 'to': "orm['core.Object']"}), + 'value_key_acc': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key_acc'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'value_m2m': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m'", 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'value_m2m_acc': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m_acc'", 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'core.widget': { + 'Meta': {'ordering': "['weight']", 'object_name': 'Widget'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'module_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'widget_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['core'] diff --git a/data/treeio/treeio/treeio/core/south_migrations/0005_auto__del_field_group_id__chg_field_group_accessentity_ptr__del_field_.py b/data/treeio/treeio/treeio/core/south_migrations/0005_auto__del_field_group_id__chg_field_group_accessentity_ptr__del_field_.py new file mode 100644 index 0000000..e7b5023 --- /dev/null +++ b/data/treeio/treeio/treeio/core/south_migrations/0005_auto__del_field_group_id__chg_field_group_accessentity_ptr__del_field_.py @@ -0,0 +1,257 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting field 'Group.id' + db.delete_column('core_group', 'id') + + # Changing field 'Group.accessentity_ptr' + db.alter_column('core_group', 'accessentity_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')( + default=1, to=orm['core.AccessEntity'], unique=True, primary_key=True)) + + # Deleting field 'User.id' + db.delete_column('core_user', 'id') + + # Changing field 'User.default_group' + db.alter_column('core_user', 'default_group_id', self.gf( + 'django.db.models.fields.related.ForeignKey')(null=True, to=orm['core.Group'])) + + # Changing field 'User.accessentity_ptr' + db.alter_column('core_user', 'accessentity_ptr_id', self.gf('django.db.models.fields.related.OneToOneField')( + default=1, to=orm['core.AccessEntity'], unique=True, primary_key=True)) + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'Group.id' + raise RuntimeError( + "Cannot reverse this migration. 'Group.id' and its values cannot be restored.") + + # Changing field 'Group.accessentity_ptr' + db.alter_column('core_group', 'accessentity_ptr_id', self.gf( + 'django.db.models.fields.related.OneToOneField')(to=orm['core.AccessEntity'], unique=True, null=True)) + + # User chose to not deal with backwards NULL issues for 'User.id' + raise RuntimeError( + "Cannot reverse this migration. 'User.id' and its values cannot be restored.") + + # Changing field 'User.default_group' + db.alter_column('core_user', 'default_group_id', self.gf( + 'django.db.models.fields.related.ForeignKey')(null=True, to=orm['core.AccessEntity'])) + + # Changing field 'User.accessentity_ptr' + db.alter_column('core_user', 'accessentity_ptr_id', self.gf( + 'django.db.models.fields.related.OneToOneField')(to=orm['core.AccessEntity'], unique=True, null=True)) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.module': { + 'Meta': {'ordering': "['name']", 'object_name': 'Module', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'system': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.modulesetting': { + 'Meta': {'object_name': 'ModuleSetting'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.page': { + 'Meta': {'ordering': "['name']", 'object_name': 'Page', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.PageFolder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'core.pagefolder': { + 'Meta': {'object_name': 'PageFolder', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.perspective': { + 'Meta': {'object_name': 'Perspective', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.revision': { + 'Meta': {'object_name': 'Revision'}, + 'change_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']"}), + 'previous': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'next_set'", 'unique': 'True', 'null': 'True', 'to': "orm['core.Revision']"}) + }, + 'core.revisionfield': { + 'Meta': {'object_name': 'RevisionField'}, + 'field': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Revision']"}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'value_key': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key'", 'null': 'True', 'to': "orm['core.Object']"}), + 'value_key_acc': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key_acc'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'value_m2m': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m'", 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'value_m2m_acc': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m_acc'", 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'core.widget': { + 'Meta': {'ordering': "['weight']", 'object_name': 'Widget'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'module_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'widget_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['core'] diff --git a/data/treeio/treeio/treeio/core/south_migrations/0006_auto__add_configsetting.py b/data/treeio/treeio/treeio/core/south_migrations/0006_auto__add_configsetting.py new file mode 100644 index 0000000..143bf94 --- /dev/null +++ b/data/treeio/treeio/treeio/core/south_migrations/0006_auto__add_configsetting.py @@ -0,0 +1,242 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'ConfigSetting' + db.create_table('core_configsetting', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (unique=True, max_length=255)), + ('value', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('last_updated', self.gf('django.db.models.fields.DateTimeField') + (auto_now=True, blank=True)), + )) + db.send_create_signal('core', ['ConfigSetting']) + + def backwards(self, orm): + + # Deleting model 'ConfigSetting' + db.delete_table('core_configsetting') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.configsetting': { + 'Meta': {'object_name': 'ConfigSetting'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.module': { + 'Meta': {'ordering': "['name']", 'object_name': 'Module', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'system': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.modulesetting': { + 'Meta': {'object_name': 'ModuleSetting'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.page': { + 'Meta': {'ordering': "['name']", 'object_name': 'Page', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.PageFolder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'core.pagefolder': { + 'Meta': {'object_name': 'PageFolder', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.perspective': { + 'Meta': {'object_name': 'Perspective', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.revision': { + 'Meta': {'object_name': 'Revision'}, + 'change_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']"}), + 'previous': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'next_set'", 'unique': 'True', 'null': 'True', 'to': "orm['core.Revision']"}) + }, + 'core.revisionfield': { + 'Meta': {'object_name': 'RevisionField'}, + 'field': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Revision']"}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'value_key': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key'", 'null': 'True', 'to': "orm['core.Object']"}), + 'value_key_acc': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key_acc'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'value_m2m': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m'", 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'value_m2m_acc': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m_acc'", 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'core.widget': { + 'Meta': {'ordering': "['weight']", 'object_name': 'Widget'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'module_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'widget_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['core'] diff --git a/data/treeio/treeio/treeio/core/south_migrations/0007_auto__add_attachment.py b/data/treeio/treeio/treeio/core/south_migrations/0007_auto__add_attachment.py new file mode 100644 index 0000000..21dbec2 --- /dev/null +++ b/data/treeio/treeio/treeio/core/south_migrations/0007_auto__add_attachment.py @@ -0,0 +1,258 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Attachment' + db.create_table('core_attachment', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('attached_object', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.Object'], null=True, blank=True)), + ('attached_record', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.UpdateRecord'], null=True, blank=True)), + ('attached_file', self.gf( + 'django.db.models.fields.files.FileField')(max_length=100)), + ('mimetype', self.gf( + 'django.db.models.fields.CharField')(max_length=64)), + ('created', self.gf('django.db.models.fields.DateTimeField') + (auto_now_add=True, blank=True)), + ('uploaded_by', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.User'])), + )) + db.send_create_signal('core', ['Attachment']) + + def backwards(self, orm): + + # Deleting model 'Attachment' + db.delete_table('core_attachment') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'attached_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'attached_object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']", 'null': 'True', 'blank': 'True'}), + 'attached_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.UpdateRecord']", 'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.configsetting': { + 'Meta': {'object_name': 'ConfigSetting'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.module': { + 'Meta': {'ordering': "['name']", 'object_name': 'Module', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'system': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.modulesetting': { + 'Meta': {'object_name': 'ModuleSetting'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.page': { + 'Meta': {'ordering': "['name']", 'object_name': 'Page', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.PageFolder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'core.pagefolder': { + 'Meta': {'object_name': 'PageFolder', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.perspective': { + 'Meta': {'object_name': 'Perspective', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.revision': { + 'Meta': {'object_name': 'Revision'}, + 'change_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']"}), + 'previous': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'next'", 'unique': 'True', 'null': 'True', 'to': "orm['core.Revision']"}) + }, + 'core.revisionfield': { + 'Meta': {'object_name': 'RevisionField'}, + 'field': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Revision']"}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'value_key': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key'", 'null': 'True', 'to': "orm['core.Object']"}), + 'value_key_acc': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key_acc'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'value_m2m': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m'", 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'value_m2m_acc': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m_acc'", 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'core.widget': { + 'Meta': {'ordering': "['weight']", 'object_name': 'Widget'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'module_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'widget_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['core'] diff --git a/data/treeio/treeio/treeio/core/south_migrations/0008_auto__add_field_attachment_filename.py b/data/treeio/treeio/treeio/core/south_migrations/0008_auto__add_field_attachment_filename.py new file mode 100644 index 0000000..b9dbf72 --- /dev/null +++ b/data/treeio/treeio/treeio/core/south_migrations/0008_auto__add_field_attachment_filename.py @@ -0,0 +1,244 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'Attachment.filename' + db.add_column('core_attachment', 'filename', self.gf( + 'django.db.models.fields.CharField')(default='', max_length=64), keep_default=False) + + def backwards(self, orm): + + # Deleting field 'Attachment.filename' + db.delete_column('core_attachment', 'filename') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.attachment': { + 'Meta': {'object_name': 'Attachment'}, + 'attached_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'attached_object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']", 'null': 'True', 'blank': 'True'}), + 'attached_record': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.UpdateRecord']", 'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.configsetting': { + 'Meta': {'object_name': 'ConfigSetting'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.module': { + 'Meta': {'ordering': "['name']", 'object_name': 'Module', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'system': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.modulesetting': { + 'Meta': {'object_name': 'ModuleSetting'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'module': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'value': ('django.db.models.fields.TextField', [], {}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.page': { + 'Meta': {'ordering': "['name']", 'object_name': 'Page', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.PageFolder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'core.pagefolder': { + 'Meta': {'object_name': 'PageFolder', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.perspective': { + 'Meta': {'object_name': 'Perspective', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Module']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'core.revision': { + 'Meta': {'object_name': 'Revision'}, + 'change_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Object']"}), + 'previous': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'next'", 'unique': 'True', 'null': 'True', 'to': "orm['core.Revision']"}) + }, + 'core.revisionfield': { + 'Meta': {'object_name': 'RevisionField'}, + 'field': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'revision': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Revision']"}), + 'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'value_key': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key'", 'null': 'True', 'to': "orm['core.Object']"}), + 'value_key_acc': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisionfield_key_acc'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'value_m2m': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m'", 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'value_m2m_acc': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'revisionfield_m2m_acc'", 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'core.widget': { + 'Meta': {'ordering': "['weight']", 'object_name': 'Widget'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'module_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'perspective': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Perspective']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'widget_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['core'] diff --git a/data/treeio/treeio/treeio/core/templatetags/modules.py b/data/treeio/treeio/treeio/core/templatetags/modules.py new file mode 100644 index 0000000..6a3e2db --- /dev/null +++ b/data/treeio/treeio/treeio/core/templatetags/modules.py @@ -0,0 +1,894 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core templatetags +""" +from coffin import template +from jinja2 import contextfunction, contextfilter, Markup +from treeio.core.conf import settings +from django.utils.translation import ugettext as _ +from django.utils.encoding import smart_unicode +from django.utils import translation +from django.template.defaultfilters import date as djangodate, time as djangotime +from django.db import models +from treeio.core.rendering import render_to_string +from treeio.core.models import Module, ModuleSetting +from treeio.core.rss import get_secret_key +from treeio.core import sanitizer +from treeio.finance.models import Currency +from datetime import datetime, timedelta +from dajaxice.templatetags.dajaxice_templatetags import dajaxice_js_import as dajaxice_orig_tag +import re +import base64 +import urllib + + +register = template.Library() + + +def _get_modules(request): + """Returns set of current modules and active module + :param WSGIRequest request: + """ + + perspective = request.user.profile.get_perspective() + + modules = perspective.modules.filter(display=True).order_by('title') + if not modules: + modules = Module.objects.filter(display=True).order_by('title') + active = None + + for module in modules: + module.type = 'minor' + try: + import_name = module.name + "." + \ + settings.HARDTREE_MODULE_IDENTIFIER + hmodule = __import__(import_name, fromlist=[str(module.name)]) + urls = hmodule.URL_PATTERNS + for regexp in urls: + if re.match(regexp, request.path): + active = module + module.type = hmodule.PROPERTIES['type'] + except ImportError: + pass + except AttributeError: + pass + except KeyError: + pass + + return modules, active + + +@contextfunction +def modules_header_block(context): + "Modules header block" + request = context['request'] + modules, active = _get_modules(request) + + for module in modules: + module.title = _(module.title) + + response_format = 'html' + if 'response_format' in context: + response_format = context['response_format'] + + return Markup(render_to_string('core/tags/modules_header_block', + {'modules': modules, + 'active': active, + 'request': request}, + response_format=response_format)) + +register.object(modules_header_block) + + +@contextfunction +def dajaxice_js_import(context): + "Thin wrapper for dajaxice" + + return Markup(dajaxice_orig_tag(context)) + +register.object(dajaxice_js_import) + + +@contextfunction +def modules_active(context): + "Active modules" + request = context['request'] + modules, active = _get_modules(request) + + if active: + return active.name.replace(".", "-") + + return "treeio-home" + +register.object(modules_active) + + +@contextfunction +def paginate(context, items, plength=None): + "Pagination" + request = context['request'] + + skip = 0 + if 'page_skip' in request.GET: + try: + skip = long(request.GET['page_skip']) + except Exception: + pass + + if not plength: + plength = getattr(settings, 'HARDTREE_PAGINATOR_LENGTH', 10) + length = skip + plength + + return items[skip:length] + +register.object(paginate) + + +@contextfunction +def pager(context, items, plength=None): + "Pager" + request = context['request'] + + response_format = 'html' + if 'response_format' in context: + response_format = context['response_format'] + + skip = 0 + if 'page_skip' in request.GET: + try: + skip = long(request.GET['page_skip']) + except Exception: + pass + + if not plength: + plength = getattr(settings, 'HARDTREE_PAGINATOR_LENGTH', 10) + if hasattr(items, 'count'): + try: + items_length = items.count() + except: + items_length = len(items) + else: + items_length = len(items) + pagenum = items_length // plength + + if items_length % plength: + pagenum += 1 + + maxpages = getattr(settings, 'HARDTREE_PAGINATOR_PAGES', 20) + + current = skip // plength + start = current - (maxpages // 2) + if pagenum - start < maxpages: + start = pagenum - maxpages + if start <= 0: + start = 1 + + pages = [] + if pagenum > 1: + if current >= 1: + pages.append( + {'page': '←', 'skip': (current - 1) * plength, 'mover': True}) + else: + pages.append({'page': '←', 'skip': 0, 'mover': True}) + if start > 1: + pages.append( + {'page': '...', 'skip': (start - 1) * plength, 'mover': False}) + maxpages -= 1 + for i in range(start, pagenum + 1): + if i > (maxpages + start + 1): + pages.append( + {'page': '...', 'skip': (i) * plength, 'mover': False}) + break + else: + pages.append({'page': i, 'skip': (i - 1) * plength}) + if current + 1 == pagenum: + pages.append( + {'page': '→', 'skip': current * plength, 'mover': True}) + else: + pages.append( + {'page': '→', 'skip': (current + 1) * plength, 'mover': True}) + + url = request.path + "?" + if request.GET: + for arg in request.GET: + if arg != 'page_skip': + values = request.GET.getlist(arg) + for value in values: + url += unicode(arg) + "=" + value + "&" + + return Markup(render_to_string('core/tags/pager', + {'url': url, 'pages': pages, 'skip': skip}, + response_format=response_format)) + +register.object(pager) + + +@contextfunction +def htsort(context, objects): + "Sort objects based on request" + if not objects or 'request' not in context: + # Don't bother trying sorting if we can't do it + return objects + + request = context['request'] + + if 'sorting' not in request.GET or not hasattr(objects, 'order_by') or not hasattr(objects, 'model'): + # Dont bother if there's nothing to sort on + return objects + + args = request.GET.getlist('sorting') + fields = objects.model._meta.get_all_field_names() + for arg in args: + field_name = arg.lstrip('-') + if field_name in fields: + field = objects.model._meta.get_field(field_name) + if isinstance(field, models.ManyToManyField): + agg_field = agg_arg = str('sorting_%s' % field_name) + if arg[0] == '-': + agg_arg = '-' + agg_arg + kwargs = {agg_field: models.Count(field_name)} + objects = objects.annotate(**kwargs).order_by(agg_arg) + else: + objects = objects.order_by(arg) + + return objects.distinct() + +register.object(htsort) + + +@contextfunction +def htsortlink(context, field_name): + "Return URL of the sorting field" + + if 'request' not in context: + return '' + request = context['request'] + + sort_value = field_name + url = u"%s?" % (request.path) + if request.GET: + for arg in request.GET: + values = request.GET.getlist(arg) + for value in values: + svalue = value.lstrip('-') + if arg == 'sorting' and svalue == field_name: + if value[0] != '-': + sort_value = u'-%s' % sort_value + else: + url += unicode(arg) + "=" + value + "&" + url += "sorting=%s" % sort_value + + return url + +register.object(htsortlink) + + +@contextfunction +def object_tree_path(context, object, skipself=False): + "Object tree path" + response_format = 'html' + if 'response_format' in context: + response_format = context['response_format'] + + path = object.get_tree_path(skipself) + + return Markup(render_to_string('core/tags/object_tree_path', + {'path': path, 'skipself': skipself}, + response_format=response_format)) + +register.object(object_tree_path) + + +def htsafe(text): + """ + Strip all unsafe tags + + 1. Replace unsafe tags + 2. Return text with 'safe' filter applied --- Alex: Thanks, Cap! + """ + text = smart_unicode(text) + + safe_string = smart_unicode(sanitizer.clean_html(text)) + + return Markup(safe_string) + +register.filter('htsafe', htsafe) + + +def httranslate(text): + "Translates given string into chosen language" + return Markup(_(text)) + +register.object(httranslate) + + +@contextfunction +def htform(context, form): + "Set time zone" + + request = context['request'] + + user = None + if request.user.username: + try: + user = request.user.profile + except Exception: + pass + + # timezone + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get('default_timezone')[0] + default_timezone = conf.value + except: + pass + + try: + conf = ModuleSetting.get('default_timezone', user=user)[0] + default_timezone = conf.value + except Exception: + default_timezone = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE')[default_timezone][0] + + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE', [ + (1, '(GMT-11:00) International Date Line West')]) + title = all_timezones[int(default_timezone)][1] + GMT = title[4:10] # with sign e.g. +06:00 + sign = GMT[0:1] # + or - + hours = int(GMT[1:3]) # e.g. 06 + mins = int(GMT[4:6]) + + if not form.errors: + for field in form: + try: + date = datetime.strptime( + str(field.form.initial[field.name]), "%Y-%m-%d %H:%M:%S") + if date: + if sign == "-": + field.form.initial[ + field.name] = date - timedelta(hours=hours, minutes=mins) + else: + field.form.initial[ + field.name] = date + timedelta(hours=hours, minutes=mins) + except: + pass + + return form + +register.object(htform) + + +@contextfilter +def htdate(context, date, dateformat='DATE_FORMAT'): + """ Render date in the current locale + + To render date in a custom format use Django format, details: + http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date + + """ + + if not date: + return '' + + lang = translation.get_language() + + localeformat = dateformat + formatspath = getattr(settings, 'FORMAT_MODULE_PATH', 'treeio.formats') + try: + modulepath = formatspath + "." + lang + ".formats" + module = __import__(modulepath, fromlist=[str(modulepath)]) + localeformat = getattr(module, dateformat, dateformat) + except ImportError: + pass + + request = context['request'] + + user = None + if request.user.username: + try: + user = request.user.profile + except Exception: + pass + + # timezone + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get('default_timezone')[0] + default_timezone = conf.value + except: + pass + + try: + conf = ModuleSetting.get('default_timezone', user=user)[0] + default_timezone = conf.value + except Exception: + default_timezone = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE')[default_timezone][0] + + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE', [ + (1, '(GMT-11:00) International Date Line West')]) + title = all_timezones[int(default_timezone)][1] + GMT = title[4:10] # with sign e.g. +06:00 + sign = GMT[0:1] # + or - + hours = int(GMT[1:3]) # e.g. 06 + mins = int(GMT[4:6]) + + if sign == "-": + date = date - timedelta(hours=hours, minutes=mins) + else: + date = date + timedelta(hours=hours, minutes=mins) + + result = djangodate(date, localeformat) + return Markup(result) + +register.filter('htdate', htdate) + + +@contextfilter +def htdatetime(context, date, dateformat='DATETIME_FORMAT'): + """ Shortcut: render datetime in the current locale """ + + if not date: + return '' + + lang = translation.get_language() + + localeformat = dateformat + formatspath = getattr(settings, 'FORMAT_MODULE_PATH', 'treeio.formats') + try: + modulepath = formatspath + "." + lang + ".formats" + module = __import__(modulepath, fromlist=[str(modulepath)]) + localeformat = getattr(module, dateformat, dateformat) + except ImportError: + pass + + request = context['request'] + + user = None + if request.user.username: + try: + user = request.user.profile + except Exception: + pass + + # timezone + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get('default_timezone')[0] + default_timezone = conf.value + except: + pass + + try: + conf = ModuleSetting.get('default_timezone', user=user)[0] + default_timezone = conf.value + except Exception: + default_timezone = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE')[default_timezone][0] + + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE', [ + (1, '(GMT-11:00) International Date Line West')]) + title = all_timezones[int(default_timezone)][1] + GMT = title[4:10] # with sign e.g. +06:00 + sign = GMT[0:1] # + or - + hours = int(GMT[1:3]) # e.g. 06 + mins = int(GMT[4:6]) + + if sign == "-": + date = date - timedelta(hours=hours, minutes=mins) + else: + date = date + timedelta(hours=hours, minutes=mins) + + result = djangodate(date, localeformat) + + return Markup(result) + +register.filter('htdatetime', htdatetime) + + +@contextfilter +def httime(context, time, timeformat='TIME_FORMAT'): + """ Render time in the current locale """ + + if not time: + return '' + + lang = translation.get_language() + + localeformat = timeformat + formatspath = getattr(settings, 'FORMAT_MODULE_PATH', 'treeio.formats') + try: + modulepath = formatspath + "." + lang + ".formats" + module = __import__(modulepath, fromlist=[str(modulepath)]) + localeformat = getattr(module, timeformat, timeformat) + except ImportError: + pass + + request = context['request'] + + user = None + if request.user.username: + try: + user = request.user.profile + except Exception: + pass + + # timezone + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get('default_timezone')[0] + default_timezone = conf.value + except: + pass + + try: + conf = ModuleSetting.get('default_timezone', user=user)[0] + default_timezone = conf.value + except Exception: + default_timezone = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE')[default_timezone][0] + + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE', [ + (1, '(GMT-11:00) International Date Line West')]) + title = all_timezones[int(default_timezone)][1] + GMT = title[4:10] # with sign e.g. +06:00 + sign = GMT[0:1] # + or - + hours = int(GMT[1:3]) # e.g. 06 + mins = int(GMT[4:6]) + + if sign == "-": + time = time - timedelta(hours=hours, minutes=mins) + else: + time = time + timedelta(hours=hours, minutes=mins) + + result = djangotime(time, localeformat) + + return Markup(result) + +register.filter('httime', httime) + + +@contextfunction +def core_logo_content(context, gif=False): + "Return current logo encoded as base64" + + staticpath = getattr(settings, 'STATIC_DOC_ROOT', './static') + logopath = staticpath + '/logo' + if gif: + logopath += '.gif' + mimetype = 'image/gif' + else: + logopath += '.png' + mimetype = 'image/png' + + customlogo = '' + try: + conf = ModuleSetting.get_for_module('treeio.core', 'logopath')[0] + customlogo = getattr( + settings, 'MEDIA_ROOT', './static/media') + conf.value + except: + pass + + logofile = '' + if customlogo: + try: + logofile = open(customlogo, 'r') + except: + pass + + if not logofile: + try: + logofile = open(logopath, 'r') + except: + pass + + result = "data:" + mimetype + ";base64," + \ + base64.b64encode(logofile.read()) + + return Markup(result) + +register.object(core_logo_content) + + +MOMENT = 120 # duration in seconds within which the time difference + # will be rendered as 'a moment ago' + + +@contextfilter +def humanize_datetime(context, value): + """ + Finds the difference between the datetime value given and now() + and returns appropriate humanize form + """ + + request = context['request'] + + user = None + if request.user.username: + try: + user = request.user.profile + except: + pass + + # timezone + default_timezone = settings.HARDTREE_SERVER_DEFAULT_TIMEZONE + try: + conf = ModuleSetting.get('default_timezone')[0] + default_timezone = conf.value + except: + pass + + try: + conf = ModuleSetting.get('default_timezone', user=user)[0] + default_timezone = conf.value + except Exception: + default_timezone = getattr( + settings, 'HARDTREE_SERVER_TIMEZONE')[default_timezone][0] + + all_timezones = getattr(settings, 'HARDTREE_SERVER_TIMEZONE', [ + (1, '(GMT-11:00) International Date Line West')]) + title = all_timezones[int(default_timezone)][1] + GMT = title[4:10] # with sign e.g. +06:00 + sign = GMT[0:1] # + or - + hours = int(GMT[1:3]) # e.g. 06 + mins = int(GMT[4:6]) + + now = datetime.now() + + if value: + if sign == "-": + value = value - timedelta(hours=hours, minutes=mins) + now = now - timedelta(hours=hours, minutes=mins) + else: + value = value + timedelta(hours=hours, minutes=mins) + now = now + timedelta(hours=hours, minutes=mins) + + if isinstance(value, timedelta): + delta = value + elif isinstance(value, datetime): + delta = now - value + else: + delta = None + + if delta: + if delta.days > 6: # May 15, 17:55 + month = None + if value.strftime("%b") == 'Jan': + month = _("Jan") + elif value.strftime("%b") == 'Feb': + month = _("Feb") + elif value.strftime("%b") == 'Mar': + month = _("Mar") + elif value.strftime("%b") == 'Apr': + month = _("Apr") + elif value.strftime("%b") == 'May': + month = _("May") + elif value.strftime("%b") == 'Jun': + month = _("Jun") + elif value.strftime("%b") == 'Jul': + month = _("Jul") + elif value.strftime("%b") == 'Aug': + month = _("Aug") + elif value.strftime("%b") == 'Sep': + month = _("Sep") + elif value.strftime("%b") == 'Oct': + month = _("Oct") + elif value.strftime("%b") == 'Nov': + month = _("Nov") + elif value.strftime("%b") == 'Dec': + month = _("Dec") + return month + value.strftime(" %d, %H:%M") + + if delta.days > 1: # Wednesday + if value.strftime("%A") == 'Monday': + return _("Monday") + elif value.strftime("%A") == 'Tuesday': + return _("Tuesday") + elif value.strftime("%A") == 'Wednesday': + return _("Wednesday") + elif value.strftime("%A") == 'Thursday': + return _("Thursday") + elif value.strftime("%A") == 'Friday': + return _("Friday") + elif value.strftime("%A") == 'Saturday': + return _("Saturday") + elif value.strftime("%A") == 'Sunday': + return _("Sunday") + + elif delta.days == 1: + return _("yesterday") # yesterday + elif delta.seconds >= 7200: + return str(delta.seconds / 3600) + _(" hours ago") # 3 hours ago + elif delta.seconds >= 3600: + return _("1 hour ago") # 1 hour ago + elif delta.seconds > MOMENT: + # 29 minutes ago + return str(delta.seconds / 60) + _(" minutes ago") + else: + return _("a moment ago") # a moment ago + return djangodate(value) + else: + return str(value) + +register.filter('humanize_datetime', humanize_datetime) + + +@contextfilter +def currency_format(context, value, currency=None): + """ + Adds the currency symbol as set in Sales module settings to a given string + If the currency has no symbol it adds a three letter code to the end e.g. USD + """ + + # get default currency + if not currency: + currency = Currency.objects.get(is_default=True) + if not currency.symbol: + return unicode(value) + " " + currency.code + else: + return currency.symbol + unicode(value) + +register.filter('currency_format', currency_format) + + +@contextfunction +def currency_print(context, currency=None): + """ + Just returns the currency symbol as set in Sales module settings to a given string. + """ + if not currency: + currency = Currency.objects.get(is_default=True) + # if currency.symbol: + # return unicode(currency.symbol) + # else: + return unicode(currency.code) + +register.object(currency_print) + + +@contextfilter +def number_format(context, value): + """ + Enforces 2 decimal places after a number if only one is given (adds a zero) + also formats comma separators every 3rd digit before decimal place. + """ + value = str(value) + + negative = False + addzero = None + + if value[0] == '-': + value = value[1:] + negative = True + + if '.' in value: + point = value.rindex('.') + if point == len(value) - 2: + addzero = True + else: + point = len(value) + + # Ensure we don't get lots of zero's after '.' + # Cut trailing zeros and only leave two + while point < len(value) - 3: + if value[len(value) - 1] == '0': + value = value[:len(value) - 1] + else: + break + + while point > 3: + value = value[:point - 3] + "," + value[point - 3:] + point = value.index(',') + + if addzero: + value += "0" + + if negative: + value = "-" + value + + return value + +register.filter('number_format', number_format) + + +@contextfunction +def show_hint(context, hint=None, object=None): + "Generic hint framework" + + request = context['request'] + response_format = 'html' + + user = None + if request.user.username: + try: + user = request.user.profile + except Exception: + pass + + return Markup(render_to_string('core/hints/' + hint, {'user': user, 'object': object}, + response_format=response_format)) + +register.object(show_hint) + + +@contextfilter +def group_by_letter(context, object_list): + "Group contacts by letter" + res = {} + + for x in object_list: + # case 1 + r = re.search('^[a-zA-Z]', x.name) + if r: + key = r.group().lower() + if key not in res: + # print "reg", key + res[key] = [x] + else: + res[key].append(x) + + # case 2 + n = re.search('^[0-9_]', x.name) + if n: + if '#' not in res: + res['#'] = [x] + else: + res['#'].append(x) + + # case 3 + if not n and not r: + if '#' not in res: + res['#'] = [x] + else: + res['#'].append(x) + + # converting dictionary to list of tuples, since template support only + # List. + l = [] + for k, v in res.items(): + l.append((k, v)) + l.sort(cmp=lambda x, y: cmp(x, y)) + return l + +register.filter('group_by_letter', group_by_letter) + + +@contextfunction +def rss_link(context, url=None): + "Generic rss link for this URL" + + request = context['request'] + + params = request.GET.copy() + params.update({'secret': get_secret_key(request)}) + + if not url: + url = request.path + for ext in getattr(settings, 'HARDTREE_RESPONSE_FORMATS', {'html': 'text/html'}): + url = url.replace('.' + ext, '') + url += '.rss' + try: + url += '?' + urllib.urlencode(params) + except: + pass + + return Markup(url) + +register.object(rss_link) + + +@contextfunction +def logo_block_container(context): + "Returns logo_block_container" + + # request = context['request'] + response_format = 'html' + + return Markup(render_to_string('core/tags/logo_block_container', + response_format=response_format)) + +register.object(logo_block_container) diff --git a/data/treeio/treeio/treeio/core/templatetags/user.py b/data/treeio/treeio/treeio/core/templatetags/user.py new file mode 100644 index 0000000..37840d7 --- /dev/null +++ b/data/treeio/treeio/treeio/core/templatetags/user.py @@ -0,0 +1,90 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +User-related Core templatetags +""" +from coffin import template +from django.core.context_processors import csrf +from treeio.core.rendering import render_to_string +from jinja2 import contextfunction, Markup +from treeio.core.models import Object, Perspective +from treeio.core.conf import settings + +register = template.Library() + + +@contextfunction +def user_block(context): + """User block + :param Context context: + """ + request = context['request'] + + user = request.user.profile + modules = user.get_perspective().get_modules() + account = modules.filter(name='treeio.account') + admin = modules.filter(name='treeio.core') + if admin: + admin = admin[0] + + response_format = 'html' + if 'response_format' in context: + response_format = context['response_format'] + + trial = False + if getattr(settings, 'HARDTREE_SUBSCRIPTION_USER_LIMIT') == 3: + trial = True + + active = context.get('active', None) + + return Markup(render_to_string('core/tags/user_block', + {'user': user, + 'account': account, + 'admin': admin, + 'active': active, + 'trial': trial}, + response_format=response_format)) + +register.object(user_block) + + +@contextfunction +def demo_user(context): + "Print demo block if demo" + + response_format = 'html' + + demo = getattr(settings, 'HARDTREE_DEMO_MODE', False) + + return Markup(render_to_string('core/tags/demo_user', + {'demo': demo}, + response_format=response_format)) + +register.object(demo_user) + + +@contextfunction +def core_perspective_switch(context): + "Quick perspective switcher" + + response_format = 'html' + request = context['request'] + try: + user = request.user.profile + + current = user.get_perspective() + perspectives = Object.filter_by_request(request, Perspective.objects) + except: + current = None + perspectives = [] + + context = {'current': current, 'perspectives': perspectives} + context.update(csrf(request)) + + return Markup(render_to_string('core/tags/perspective_switch', context, + response_format=response_format)) + +register.object(core_perspective_switch) diff --git a/data/treeio/treeio/treeio/core/trash/views.py b/data/treeio/treeio/treeio/core/trash/views.py new file mode 100644 index 0000000..b637bd2 --- /dev/null +++ b/data/treeio/treeio/treeio/core/trash/views.py @@ -0,0 +1,109 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core module: Trash views +""" +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.core.urlresolvers import reverse +from treeio.core.models import Object +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.core.rendering import render_to_response +from treeio.core.views import user_denied +from treeio.core.trash.forms import MassActionForm + + +def _process_mass_form(f): + "Pre-process request to handle mass action form for Tasks and Milestones" + + def wrap(request, *args, **kwargs): + "Wrap" + if 'massform' in request.POST: + if 'delete_all' in request.POST.values(): + try: + object = Object.filter_by_request(request, manager=Object.objects.filter(trash=True), + mode='r', filter_trash=False) + form = MassActionForm(request.POST, instance=object) + if form.is_valid() and request.user.profile.has_permission(object, mode='w'): + form.save() + except: + pass + else: + for key in request.POST: + if 'mass-object' in key: + try: + object = Object.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.POST, instance=object) + if form.is_valid() and request.user.profile.has_permission(object, mode='w'): + form.save() + except: + pass + + return f(request, *args, **kwargs) + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + + return wrap + + +@treeio_login_required +@handle_response_format +@_process_mass_form +def index(request, response_format='html'): + "List of items in Trash" + + trash = Object.filter_by_request(request, manager=Object.objects.filter(trash=True), + mode='r', filter_trash=False) + massform = MassActionForm() + + return render_to_response('core/trash/index', + {'trash': trash, + 'massform': massform}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def object_delete(request, object_id, response_format='html'): + "Completely delete item" + + object = get_object_or_404(Object, pk=object_id) + if not request.user.profile.has_permission(object, mode='w'): + return user_denied(request, message="You don't have access to this Object") + + if request.POST: + if 'delete' in request.POST: + object.delete() + return HttpResponseRedirect(reverse('core_trash')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('core_trash')) + + return render_to_response('core/trash/object_delete', + {'object': object}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def object_untrash(request, object_id, response_format='html'): + "Untrash item" + + object = get_object_or_404(Object, pk=object_id) + if not request.user.profile.has_permission(object, mode='w'): + return user_denied(request, message="You don't have access to this Object") + + related = object.get_related_object() + if related: + related.trash = False + related.save() + else: + object.trash = False + object.save() + + return HttpResponseRedirect(reverse('core_trash')) diff --git a/data/treeio/treeio/treeio/core/views.py b/data/treeio/treeio/treeio/core/views.py new file mode 100644 index 0000000..cde6d2f --- /dev/null +++ b/data/treeio/treeio/treeio/core/views.py @@ -0,0 +1,604 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Core module views +""" + +from django.contrib.auth import authenticate, login, logout +from django.contrib.sessions.models import Session +from django.contrib.sites.models import RequestSite +# from django.contrib.csrf.middleware import CsrfMiddleware as csrf +from django.utils.encoding import smart_unicode +from django.views.decorators.csrf import csrf_protect +from django.views.decorators.cache import cache_control +from django.template import RequestContext +from django.http import HttpResponseRedirect, Http404, HttpResponse, HttpResponseBadRequest +from django.core.urlresolvers import resolve, reverse +from django.shortcuts import get_object_or_404 +from treeio.core.conf import settings +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.core.forms import LoginForm, PasswordResetForm, InvitationForm, SqlSettingsForm +from treeio.core.models import Object, Module, ModuleSetting, Perspective, User, Attachment, Invitation, Tag, \ + UpdateRecord +from treeio.core.rendering import render_to_response +from jinja2 import Markup +from os.path import join +import re +import json +import urllib2 +import random + + +@handle_response_format +@treeio_login_required +def user_logout(request, response_format='html'): + """User logout""" + logout(request) + return HttpResponseRedirect(reverse('user_login')) + + +@handle_response_format +def user_login(request, response_format='html'): + "User login" + if request.user.username: + return HttpResponseRedirect(reverse('user_denied')) + next = request.GET.get('next', '/') + form = LoginForm(request.POST) + if request.POST: + username = request.POST['username'] + password = request.POST['password'] + + user = authenticate(username=username, password=password) + if user and getattr(settings, 'HARDTREE_DISABLE_EVERGREEN_USERS', False) and 'evergreen_' in user.username[:10]: + user = None + if form.is_valid(): + if user is not None: + + try: + profile = user.profile + except: + profile = None + + if not profile: + return render_to_response('core/user_login', { + 'error_message': 'Username or password you entered is not valid', 'form': Markup(form)}, + context_instance=RequestContext(request), response_format=response_format) + + if profile.disabled: + return render_to_response('core/user_login', { + 'error_message': 'Your account is disabled.', + 'form': Markup(form)}, + context_instance=RequestContext(request), + response_format=response_format) + + if user.is_active and profile: + + # Disable account with overdue payment + if getattr(settings, "HARDTREE_SUBSCRIPTION_BLOCKED", False): + return render_to_response('core/user_login', { + 'error_message': 'We are sorry to inform you but your account has been deactivated. Please login to your control panel to see details.', + 'form': Markup(form)}, + context_instance=RequestContext(request), + response_format=response_format) + + login(request, user) + + # Prevent same user from logging in at 2 different machines + if getattr(settings, "HARDTREE_MULTIPLE_LOGINS_DISABLED", False): + for ses in Session.objects.all(): + if ses != request.session: + try: + data = ses.get_decoded() + if '_auth_user_id' in data and data['_auth_user_id'] == request.user.id: + ses.delete() + except Exception: + pass + + if 'next' in request.POST: + return HttpResponseRedirect(request.POST['next']) + else: + return HttpResponseRedirect(next) + else: + return render_to_response('core/user_login_disabled', + context_instance=RequestContext( + request), + response_format=response_format) + else: + return render_to_response('core/user_login', { + 'error_message': 'Username or password you entered is not valid', 'form': Markup(form)}, + context_instance=RequestContext(request), response_format=response_format) + elif not form.is_valid() and user is None: + return render_to_response('core/user_login', + {'error_message': 'Username or password you entered is not valid', 'form': Markup( + form)}, + context_instance=RequestContext(request), response_format=response_format) + else: + return render_to_response('core/user_login', + {'error_message': 'Please re-enter the text from the image', + 'form': Markup(form)}, + context_instance=RequestContext(request), response_format=response_format) + else: + return render_to_response('core/user_login', {'form': Markup(form)}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +def user_denied(request, message='', response_format='html'): + "User denied page" + response = render_to_response('core/user_denied', + {'message': message}, + context_instance=RequestContext(request), response_format=response_format) + # response.status_code = 403 + return response + + +@treeio_login_required +@handle_response_format +def user_perspective(request, response_format='html'): + "Change user perspective" + + user = request.user.profile + if request.POST and 'core_perspective' in request.POST: + id = request.POST['core_perspective'] + perspective = get_object_or_404(Perspective, pk=id) + if user.has_permission(perspective): + user.set_perspective(perspective) + + return HttpResponseRedirect(reverse('home')) + + +@cache_control(private=True, must_revalidate=True, max_age=60) +def logo_image(request, gif=False, response_format='html'): + "Return current logo image" + + staticpath = getattr(settings, 'STATIC_DOC_ROOT', './static') + logopath = staticpath + '/logo' + if gif: + logopath += '.gif' + mimetype = 'image/gif' + else: + logopath += '.png' + mimetype = 'image/png' + + customlogo = '' + try: + conf = ModuleSetting.get_for_module('treeio.core', 'logopath')[0] + customlogo = getattr( + settings, 'MEDIA_ROOT', './static/media') + conf.value + except: + pass + + logofile = '' + if customlogo: + try: + logofile = open(customlogo, 'rb') + except: + pass + + if not logofile: + try: + logofile = open(logopath, 'rb') + except: + pass + + return HttpResponse(logofile.read(), content_type=mimetype) + + +def ajax_popup(request, popup_id='', url='/'): + "Handles pop up forms and requests, by extracting only the required content from response content" + + view, args, kwargs = resolve(url) + + if not request.user.username: + return HttpResponseRedirect('/accounts/login') + + modules = Module.objects.all() + active = None + for module in modules: + try: + import_name = module.name + "." + \ + settings.HARDTREE_MODULE_IDENTIFIER + hmodule = __import__(import_name, fromlist=[str(module.name)]) + urls = hmodule.URL_PATTERNS + for regexp in urls: + if re.match(regexp, url): + active = module + except ImportError: + pass + except AttributeError: + pass + + response = None + if active: + if not request.user.profile.has_permission(active): + response = user_denied(request, "You do not have access to the %s module" % unicode(active), + response_format='ajax') + + if not response: + if view == ajax_popup: + raise Http404("OMG, I see myself!") + + kwargs['request'] = request + kwargs['response_format'] = 'ajax' + response = view(*args, **kwargs) + + # response = csrf().process_response(request, response) + + module_inner = "" + regexp = r"(?P.*?)" + blocks = re.finditer(regexp, response.content, re.DOTALL) + for block in blocks: + module_inner += block.group('module_inner').strip() + + title = "" + regexp = r"
    (?P.*?)</div>" + blocks = re.finditer(regexp, response.content, re.DOTALL) + for block in blocks: + title += block.group('title').replace('\\n', '').strip() + if not title: + blocks = re.finditer( + r"<title>(?P<title>.*?)", response.content, re.DOTALL) + for block in blocks: + title += block.group('title').replace('\\n', '').strip() + + subtitle = "" + regexp = r"
    (?P.*?)
    " + blocks = re.finditer(regexp, response.content, re.DOTALL) + for block in blocks: + subtitle += block.group('subtitle').replace('\\n', '').strip() + + context = {'content': module_inner, 'title': title, 'subtitle': subtitle, 'popup_id': popup_id, 'url': request.path} + + if settings.HARDTREE_RESPONSE_FORMATS['json'] in response.get('Content-Type', 'text/html'): + new_response = render_to_response('core/ajax_popup', context, + context_instance=RequestContext(request), response_format='json') + else: + new_response = HttpResponse(json.dumps({'popup': context})) + + new_response.mimetype = settings.HARDTREE_RESPONSE_FORMATS['json'] + try: + jsonresponse = json.loads(response.content) + if 'redirect' in jsonresponse: + new_response.status_code = 302 + except Exception: + new_response.status_code = response.status_code + + return new_response + + +def mobile_view(request, url='/'): + "Returns the same page in mobile view" + + if not url: + url = '/' + + view, args, kwargs = resolve(url) + + if view == mobile_view: + raise Http404("OMG, I see myself!") + + kwargs['request'] = request + kwargs['response_format'] = 'html' + response = view(*args, **kwargs) + + # response = csrf().process_response(request, response) + + if response.status_code == 302 and not response['Location'][:2] == '/m': + response['Location'] = '/m' + response['Location'] + + return response + + +def iframe_close(request, response_format='html'): + "For third-party resources, when returned back to Hardtree, close iframe" + + return render_to_response('core/iframe_close', {}, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +def database_setup(request, response_format='html'): + if not User.objects.all().count(): + if request.POST: + sql_form = SqlSettingsForm(data=request.POST) + if sql_form.is_valid(): + sql_form.create_database() + if sql_form.is_valid(): + return HttpResponseRedirect('/') + else: + sql_form = SqlSettingsForm() + return render_to_response('core/database_setup', {'sql_form': sql_form}, + context_instance=RequestContext(request), response_format=response_format) + return HttpResponseRedirect('/') + + +@treeio_login_required +def help_page(request, url='/', response_format='html'): + "Returns a Help page from Evergreen" + + source = getattr( + settings, 'HARDTREE_HELP_SOURCE', 'http://127.0.0.1:7000/help') + + if not url: + url = '/' + + body = '' + try: + body = urllib2.urlopen( + source + url + '?domain=' + RequestSite(request).domain).read() + except: + pass + + regexp = r"(?P.*?)" + blocks = re.finditer(regexp, body, re.DOTALL) + for block in blocks: + body = smart_unicode(block.group('module_inner').strip()) + + return render_to_response('core/help_page', {'body': body}, + context_instance=RequestContext(request), + response_format=response_format) + + +# +# AJAX lookups +# +@treeio_login_required +def ajax_object_lookup(request, response_format='html'): + "Returns a list of matching objects" + + objects = [] + if request.GET and 'term' in request.GET: + objects = Object.filter_permitted(request.user.profile, + Object.objects.filter( + object_name__icontains=request.GET['term']), + mode='x')[:10] + + return render_to_response('core/ajax_object_lookup', + {'objects': objects}, + context_instance=RequestContext(request), + response_format=response_format) + + +@treeio_login_required +def ajax_tag_lookup(request, response_format='html'): + "Returns a list of matching tags" + + tags = [] + if request.GET and 'term' in request.GET: + tags = Tag.objects.filter(name__icontains=request.GET['term']) + + return render_to_response('core/ajax_tag_lookup', + {'tags': tags}, + context_instance=RequestContext(request), + response_format=response_format) + + +# +# Widgets +# + + +@treeio_login_required +def widget_welcome(request, response_format='html'): + "Quick start widget, which users see when they first log in" + + trial = False + if getattr(settings, 'HARDTREE_SUBSCRIPTION_USER_LIMIT') == 3: + trial = True + + customization = getattr( + settings, 'HARDTREE_SUBSCRIPTION_CUSTOMIZATION', True) + + return render_to_response('core/widgets/welcome', {'trial': trial, 'customization': customization}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Passwords +# +@csrf_protect +def password_reset(request, response_format='html'): + "Password_reset sends the email with the new password" + + if request.POST: + form = PasswordResetForm(request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('password_reset_done')) + else: + form = PasswordResetForm() + + return render_to_response('core/password_reset_form', + {'form': form}, + context_instance=RequestContext(request), + response_format=response_format) + + +def password_reset_done(request, response_format='html'): + "Shows success message" + + return render_to_response('core/password_reset_done', + context_instance=RequestContext(request), + response_format=response_format) + + +def invitation_retrieve(request, response_format='html'): + "Retrieve invitation and create account" + + if request.user.username: + return HttpResponseRedirect('/') + + email = request.REQUEST.get('email', None) + key = request.REQUEST.get('key', None) + if email and key: + try: + invitation = Invitation.objects.get(email=email, key=key) + except: + raise Http404 + else: + raise Http404 + + if request.POST: + form = InvitationForm(invitation, request.POST) + if form.is_valid(): + profile = form.save() + username = profile.user.username + password = form.cleaned_data['password'] + user = authenticate(username=username, password=password) + if user: + invitation.delete() + login(request, user) + return HttpResponseRedirect('/') + else: + form = InvitationForm(invitation) + + return render_to_response('core/invitation_retrieve', + {'invitation': invitation, + 'form': form}, + context_instance=RequestContext(request), + response_format=response_format) + + +def save_upload(uploaded, filename, raw_data): + ''' + raw_data: if True, uploaded is an HttpRequest object with the file being + the raw post data + if False, uploaded has been submitted via the basic form + submission and is a regular Django UploadedFile in request.FILES + ''' + try: + from io import FileIO, BufferedWriter + + with BufferedWriter(FileIO(filename, "wb")) as dest: + # if the "advanced" upload, read directly from the HTTP request + # with the Django 1.3 functionality + if raw_data: + if isinstance(uploaded, basestring): + dest.write(uploaded) + else: + foo = uploaded.read(1024) + while foo: + dest.write(foo) + foo = uploaded.read(1024) + # if not raw, it was a form upload so read in the normal Django + # chunks fashion + else: + for c in uploaded.chunks(): + dest.write(c) + # got through saving the upload, report success + return True + except IOError: + # could not open the file most likely + pass + return False + + +@treeio_login_required +def ajax_upload(request, object_id=None, record=None): + try: + object = None + if request.method == "POST": + if request.is_ajax(): + # the file is stored raw in the request + upload = request + is_raw = True + # AJAX Upload will pass the filename in the querystring if it + # is the "advanced" ajax upload + try: + filename = request.GET['qqfile'] + content_type = "application/octet-stream" + except KeyError: + return HttpResponseBadRequest("AJAX request not valid") + # not an ajax upload, so it was the "basic" iframe version with + # submission via form + else: + is_raw = False + if len(request.FILES) == 1: + # FILES is a dictionary in Django but Ajax Upload gives the uploaded file an + # ID based on a random number, so it cannot be guessed here in the code. + # Rather than editing Ajax Upload to pass the ID in the querystring, + # observer that each upload is a separate request, + # so FILES should only have one entry. + # Thus, we can just grab the first (and only) value in the + # dict. + upload = request.FILES.values()[0] + content_type = upload.content_type + else: + raise Http404("Bad Upload") + filename = upload.name + + random.seed() + filehash = str(random.getrandbits(128)) + + savefile = join( + getattr(settings, 'MEDIA_ROOT'), 'attachments', filehash) + + # save the file + success = save_upload(upload, savefile, is_raw) + + attachment = Attachment(filename=filename, + content_type=content_type, + uploaded_by=request.user.profile, + attached_file=filehash) + + if record: + attachment.attached_record = record + about = record.about.all() + if about.count(): + attachment.attached_object = about[0] + object = attachment.attached_object + else: + object = Object.objects.get(id=object_id) + attachment.attached_object = object + + attachment.save() + + if object: + object.set_last_updated() + + # TODO: smart markup and return as string, and object id, different + # classnames,id or attribute for update records and objects + + if success: + ret_json = {'success': success, + 'object_id': object.id if object else None, + 'update_id': record.id if record else None} + + else: + ret_json = {'success': False, + 'object_id': None, + 'update_id': None} + + return HttpResponse(json.dumps(ret_json)) + except: + pass + + +@treeio_login_required +def ajax_upload_record(request, record_id=None): + record = UpdateRecord.objects.get(id=record_id) + return ajax_upload(request, None, record) + + +@treeio_login_required +def attachment_download(request, attachment_id): + try: + attachment = Attachment.objects.get(pk=attachment_id) + except Attachment.DoesNotExist: + raise Http404() + + filepath = join( + getattr(settings, 'MEDIA_ROOT'), 'attachments', attachment.attached_file.name) + try: + data = open(filepath).read() + except IOError: + raise Http404() + + response = HttpResponse(data, content_type=attachment.mimetype) + response[ + 'Content-Disposition'] = 'filename="%s"' % smart_unicode(attachment.filename) + return response diff --git a/data/treeio/treeio/treeio/documents/forms.py b/data/treeio/treeio/treeio/documents/forms.py new file mode 100644 index 0000000..680e906 --- /dev/null +++ b/data/treeio/treeio/treeio/documents/forms.py @@ -0,0 +1,200 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Documents module forms +""" +from django.forms import ModelForm, CharField, TextInput, ChoiceField, Form +from treeio.documents.models import Folder, Document, File, WebLink +from treeio.core.models import Object +from django.utils.translation import ugettext as _ +from treeio.core.decorators import preprocess_form +from django.core.urlresolvers import reverse + +preprocess_form() + + +class MassActionForm(Form): + """ Mass action form for Reports """ + + delete = ChoiceField(label=_("With selected"), choices=(('', '-----'), ('delete', 'Delete Completely'), + ('trash', 'Move to Trash')), required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(MassActionForm, self).__init__(*args, **kwargs) + self.fields['delete'] = ChoiceField(label=_("With selected"), choices=(('', '-----'), + ('delete', _( + 'Delete Completely')), + ('trash', _('Move to Trash'))), + required=False) + + def save(self, *args, **kwargs): + "Process form" + + if self.instance: + if self.is_valid(): + if self.cleaned_data['delete']: + if self.cleaned_data['delete'] == 'delete': + self.instance.delete() + if self.cleaned_data['delete'] == 'trash': + self.instance.trash = True + self.instance.save() + + +class FolderForm(ModelForm): + """ Folder form """ + + def __init__(self, user, folder_id, *args, **kwargs): + super(FolderForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + + self.fields['parent'].label = _("Parent") + self.fields['parent'].queryset = Object.filter_permitted( + user, Folder.objects, mode='x') + if folder_id: + self.fields['parent'].initial = folder_id + + class Meta: + "Folder" + model = Folder + fields = ('name', 'parent') + + +class DocumentForm(ModelForm): + """ Document form """ + title = CharField(widget=TextInput(attrs={'size': '50'})) + + def __init__(self, user, folder_id, *args, **kwargs): + super(DocumentForm, self).__init__(*args, **kwargs) + + self.fields['title'].label = _("Title") + + self.fields['folder'].label = _("Folder") + self.fields['folder'].queryset = Object.filter_permitted( + user, Folder.objects, mode='x') + self.fields['folder'].widget.attrs.update( + {'popuplink': reverse('documents_folder_add')}) + if folder_id: + self.fields['folder'].initial = folder_id + else: + try: + self.fields['folder'].initial = self.fields[ + 'folder'].queryset[0].id + except: + pass + + self.fields['body'].label = _("Body") + self.fields['body'].widget.attrs.update({'class': 'full-editor'}) + + class Meta: + + "Document" + model = Document + fields = ('title', 'folder', 'body') + + +class FileForm(ModelForm): + """ File form """ + name = CharField(widget=TextInput(attrs={'size': '25'})) + + def __init__(self, user, folder_id, *args, **kwargs): + super(FileForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + + self.fields['folder'].label = _("Folder") + self.fields['folder'].queryset = Object.filter_permitted( + user, Folder.objects, mode='x') + self.fields['folder'].widget.attrs.update( + {'popuplink': reverse('documents_folder_add')}) + if folder_id: + self.fields['folder'].initial = folder_id + else: + try: + self.fields['folder'].initial = self.fields[ + 'folder'].queryset[0].id + except: + pass + + self.fields['content'].label = _("Content") + + class Meta: + + "File" + model = File + fields = ('name', 'folder', 'content') + + +class WebLinkForm(ModelForm): + """ WebLink form """ + + def __init__(self, user, folder_id, *args, **kwargs): + super(WebLinkForm, self).__init__(*args, **kwargs) + + self.fields['title'].label = _("Title") + self.fields['title'].widget = TextInput(attrs={'size': '35'}) + + self.fields['url'].label = _("URL") + self.fields['url'].initial = 'http://' + self.fields['url'].widget = TextInput(attrs={'size': '50'}) + + self.fields['folder'].label = _("Folder") + self.fields['folder'].queryset = Object.filter_permitted( + user, Folder.objects, mode='x') + self.fields['folder'].widget.attrs.update( + {'popuplink': reverse('documents_folder_add')}) + + if folder_id: + self.fields['folder'].initial = folder_id + else: + try: + self.fields['folder'].initial = self.fields[ + 'folder'].queryset[0].id + except: + pass + + class Meta: + + "WebLink" + model = WebLink + fields = ('title', 'folder', 'url') + + +class FilterForm(ModelForm): + """ Filter form definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(FilterForm, self).__init__(*args, **kwargs) + + self.fields['title'].label = _("Title") + self.fields['folder'].label = _("Folder") + + if 'title' in skip: + del self.fields['title'] + else: + self.fields['title'].required = False + + if 'folder' in skip: + del self.fields['folder'] + else: + self.fields['folder'].queryset = Object.filter_permitted( + user, Folder.objects, mode='x') + # self.fields['folder'].required = False + self.fields['folder'].null = True + + class Meta: + + "Filter" + model = Document + fields = ('title', 'folder') diff --git a/data/treeio/treeio/treeio/documents/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/documents/south_migrations/0001_initial.py new file mode 100644 index 0000000..8942287 --- /dev/null +++ b/data/treeio/treeio/treeio/documents/south_migrations/0001_initial.py @@ -0,0 +1,187 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Folder' + db.create_table('documents_folder', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['documents.Folder'])), + )) + db.send_create_signal('documents', ['Folder']) + + # Adding model 'File' + db.create_table('documents_file', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('folder', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['documents.Folder'])), + ('content', self.gf('django.db.models.fields.files.FileField') + (max_length=100)), + )) + db.send_create_signal('documents', ['File']) + + # Adding model 'Document' + db.create_table('documents_document', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('folder', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['documents.Folder'])), + ('body', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('documents', ['Document']) + + # Adding model 'WebLink' + db.create_table('documents_weblink', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('folder', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['documents.Folder'])), + ('url', self.gf('django.db.models.fields.CharField') + (max_length=255)), + )) + db.send_create_signal('documents', ['WebLink']) + + def backwards(self, orm): + + # Deleting model 'Folder' + db.delete_table('documents_folder') + + # Deleting model 'File' + db.delete_table('documents_file') + + # Deleting model 'Document' + db.delete_table('documents_document') + + # Deleting model 'WebLink' + db.delete_table('documents_weblink') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'documents.document': { + 'Meta': {'ordering': "['-last_updated']", 'object_name': 'Document', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Folder']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'documents.file': { + 'Meta': {'ordering': "['-last_updated']", 'object_name': 'File', '_ormbases': ['core.Object']}, + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Folder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'documents.folder': { + 'Meta': {'object_name': 'Folder', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['documents.Folder']"}) + }, + 'documents.weblink': { + 'Meta': {'ordering': "['-last_updated']", 'object_name': 'WebLink', '_ormbases': ['core.Object']}, + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Folder']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['documents'] diff --git a/data/treeio/treeio/treeio/events/forms.py b/data/treeio/treeio/treeio/events/forms.py new file mode 100644 index 0000000..73cc4f9 --- /dev/null +++ b/data/treeio/treeio/treeio/events/forms.py @@ -0,0 +1,159 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Events module forms +""" +from django import forms +from django.utils.translation import ugettext as _ +from django.core.urlresolvers import reverse +from treeio.events.models import Event +from treeio.core.models import Object, Location +from treeio.core.decorators import preprocess_form +import datetime +preprocess_form() + + +class MassActionForm(forms.Form): + + """ Mass action form for Reports """ + + delete = forms.ChoiceField(label=_("Delete"), choices=(('', '-----'), ('delete', _('Delete Completely')), + ('trash', _('Move to Trash'))), required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(MassActionForm, self).__init__(*args, **kwargs) + + def save(self, *args, **kwargs): + "Process form" + + if self.instance: + if self.is_valid(): + if self.cleaned_data['delete']: + if self.cleaned_data['delete'] == 'delete': + self.instance.delete() + if self.cleaned_data['delete'] == 'trash': + self.instance.trash = True + self.instance.save() + + +class EventForm(forms.ModelForm): + + """ Event form """ + + def _set_initial(self, field, value): + "Sets initial value" + + def __init__(self, user=None, date=None, hour=None, *args, **kwargs): + super(EventForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _('Title') + self.fields['name'].widget = forms.TextInput(attrs={'size': '30'}) + self.fields['location'].queryset = Object.filter_permitted( + user, Location.objects, mode='x') + self.fields['location'].widget.attrs.update( + {'popuplink': reverse('identities_location_add')}) + self.fields['location'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_location_lookup')}) + + self.fields['location'].label = _("Location") + self.fields['start'].label = _("Start") + self.fields['end'].label = _("End") + self.fields['details'].label = _("Details") + self.fields['details'].widget.attrs.update({'class': 'full-editor'}) + + if date: + rdate = None + try: + rdate = datetime.datetime.strptime(date, "%Y-%m-%d") + if hour: + hour = int(hour) + else: + hour = 12 + rdate = datetime.datetime(year=rdate.year, + month=rdate.month, + day=rdate.day, + hour=hour) + self.fields['end'].initial = rdate + except ValueError: + pass + + # Set datepicker + self.fields['start'].widget.attrs.update({'class': 'datetimepicker'}) + self.fields['end'].widget.attrs.update({'class': 'datetimepicker'}) + + if self.fields['start'].initial: + self.fields['start'].widget.attrs.update( + {'initial': (self.fields['start'].initial - datetime.datetime(1970, 1, 1)).total_seconds()}) + + if self.fields['end'].initial: + self.fields['end'].widget.attrs.update( + {'initial': (self.fields['end'].initial - datetime.datetime(1970, 1, 1)).total_seconds()}) + + if 'instance' in kwargs: + instance = kwargs['instance'] + if instance.start: + self.fields['start'].widget.attrs.update( + {'initial': (instance.start - datetime.datetime(1970, 1, 1)).total_seconds()}) + if instance.end: + self.fields['end'].widget.attrs.update( + {'initial': (instance.end - datetime.datetime(1970, 1, 1)).total_seconds()}) + + def clean_end(self): + "Make sure end date is greater than start date, when specified" + try: + start = self.cleaned_data['start'] + if start: + end = self.cleaned_data['end'] + if end < start: + raise forms.ValidationError( + _("End date can not be before the start date")) + except: + pass + return self.cleaned_data['end'] + + class Meta: + + "Event" + model = Event + fields = ('name', 'location', 'start', 'end', 'details') + + +class GoToDateForm(forms.Form): + + """ Go to date form definition """ + + def __init__(self, date, *args, **kwargs): + super(GoToDateForm, self).__init__(*args, **kwargs) + + self.fields['goto'] = forms.DateField( + label=_("Go to date"), required=False) + self.fields['goto'].widget.attrs.update({'class': 'datepicker'}) + + +class FilterForm(forms.Form): + + """ Filters for Events """ + + def __init__(self, *args, **kwargs): + super(FilterForm, self).__init__(*args, **kwargs) + + self.fields['datefrom'] = forms.DateField(label=_("Date From")) + self.fields['datefrom'].widget.attrs.update({'class': 'datepicker'}) + + self.fields['dateto'] = forms.DateField(label=_("Date To")) + self.fields['dateto'].widget.attrs.update({'class': 'datepicker'}) + + def clean_dateto(self): + "Clean date_to" + if not self.cleaned_data['dateto'] >= self.cleaned_data['datefrom']: + raise forms.ValidationError( + "From date can not be greater than To date.") diff --git a/data/treeio/treeio/treeio/events/rendering.py b/data/treeio/treeio/treeio/events/rendering.py new file mode 100644 index 0000000..b5653a1 --- /dev/null +++ b/data/treeio/treeio/treeio/events/rendering.py @@ -0,0 +1,253 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Events module wrappers to be used for rendering +""" +from jinja2 import Markup +from datetime import datetime +from django.core.urlresolvers import reverse + +# Percentage of calendar cell used by events in week and day views +CELL_WIDTH = 90 + + +def _smart_truncate(content, length=100, suffix='...'): + "Smart truncate" + if len(content) <= length: + return content + return content[:length].rsplit(' ', 1)[0] + suffix + + +class EventRenderer: + + "Basic EventRenderer, used for rendering events, independently of the underlying model" + + name = "" + start = None + end = None + url = "" + markup = '

    %s

    ' + rendered = [] + margin_step = {} + + def __init__(self, name, start, end, url, markup=None): + self.name = name + self.start = start + self.end = end + self.url = url + + if markup: + self.markup = markup + + self.rendered = [] + self.css_class = 'calendar-event' + self.style = '' + + self.margin_step = {} + + def __unicode__(self): + return self.name + + def is_full_day(self, date): + "True if the event takes place throughout the specified date" + if self.start and self.end and self.start.date() < date and self.end.date() > date: + return True + return False + + def covers(self, date, hour): + "True if the event covers the given date and/or hour" + if self.start: + if self.start.date() < date and self.end.date() > date: + return True + elif self.start.date() == date and self.start.hour <= hour: + if self.end.date() > date: + return True + elif self.end.date() == date and self.end.hour >= hour: + if self.end.hour > hour: + return True + elif self.end.hour == hour and self.end.minute > 0: + return True + elif self.start.date() < date and self.end.date() == date: + if self.end.hour > hour: + return True + elif self.end.hour == hour and self.end.minute > 0: + return True + elif self.end.date() == date and self.end.hour == hour: + return True + return False + + def is_renderable(self, date, hour=None): + "True if the event should be rendered for the given date and hour" + if hour: + if date in self.rendered: + return False + if self.start and self.start.date() == date and self.start.hour == hour: + return True + elif self.start and self.start.date() < date and self.end.date() == date: + return True + elif self.start and self.start.date() < date and self.end.date() > date: + return True + elif not self.start and self.end.date() == date and self.end.hour == hour: + return True + else: + if self.start and self.start.date() == date: + return True + elif self.end and self.end.date() == date: + return True + elif self.start and self.end and self.start.date() < date < self.end.date(): + return True + return False + + def get_duration(self, date=None, start_hour=8, end_hour=23): + "Returns duration as hours (float)" + if date: + if self.start: + if self.start.date() < date and self.end.date() > date: + return start_hour - end_hour + 1 + else: + if self.start.date() == date and self.end.date() > date: + start = datetime( + date.year, date.month, date.day, start_hour, 0, 0) + delta = start - self.start + elif self.start.date() < date and self.end.date() == date: + start = datetime( + date.year, date.month, date.day, start_hour, 0, 0) + delta = self.end - start + else: + delta = self.end - self.start + return float(delta.seconds // 3600) + (float(delta.seconds % 3600) / 3600) + else: + return 1 + else: + if self.start and self.end: + return self.end - self.start + else: + return 1 + + def render(self, css_class='', style=''): + "Render event into HTML" + if not css_class: + css_class = self.css_class + else: + css_class = self.css_class + " " + css_class + if not style: + style = self.style + return Markup(self.markup % (css_class, self.url, style, self.name)) + + def render_for_date(self, date, css_class='', style=''): + "Render event for a certain date" + output = "" + if self.is_renderable(date): + output = self.render(css_class, style) + return output + + def render_for_datehour(self, date, hour, css_class='', style=''): + "Render event for a certain date and hour" + output = "" + if self.is_renderable(date, hour): + output = self.render(css_class, style) + self.rendered.append(date) + return output + + +class EventCollection: + + "Set of EventWrappers available for rendering" + events = [] + + def __init__(self, raw_events=None, start_hour=8, end_hour=22): + "Initialize with raw_events as a list or QuerySet of Events" + if raw_events is None: + raw_events = [] + self.events = [] + for event in raw_events: + wrapper = EventRenderer(event.name, event.start, event.end, + reverse("events_event_view", args=[event.id])) + self.events.append(wrapper) + + self.start_hour = start_hour + self.end_hour = end_hour + + def collect_events(self, request): + "Gathers Events from all user modules where .get_events() callable is available" + modules = request.user.profile.get_perspective().get_modules() + + for module in modules: + if request.user.profile.has_permission(module): + try: + import_name = str(module.name) + ".events" + imodule = __import__( + import_name, fromlist=[str(module.name)]) + if hasattr(imodule, 'get_events'): + collected = imodule.get_events(request) + self.events.extend(collected) + except: + pass + + def renderable_events(self, date, hour): + "Returns the number of renderable events" + renderable_events = [] + + for event in self.events: + if event.covers(date, hour): + renderable_events.append(event) + + if hour: + for current in renderable_events: + for event in self.events: + if event not in renderable_events: + for hour in range(self.start_hour, self.end_hour): + if current.covers(date, hour) and event.covers(date, hour): + renderable_events.append(event) + break + + return renderable_events + + def render_for_date(self, date): + "Render all events for a certain date" + output = "" + + for event in self.events: + if event.is_renderable(date): + output += event.render_for_date(date) + + return output + + def render_for_datehour(self, date, hour): + "Render all events for a certain date and hour" + output = "" + + renderable_events = self.renderable_events(date, hour) + css_class = "calendar-event-hour" + + if renderable_events: + width = (90.0 / len(renderable_events)) + current_margin = 0 + for event in renderable_events: + if str(date) in event.margin_step and event.margin_step[str(date)] > current_margin: + current_margin = event.margin_step[str(date)] + for event in renderable_events: + if event.is_renderable(date, hour): + margin = width * current_margin + event.margin_step[ + str(date)] = current_margin = current_margin + 1 + if event.is_full_day(date): + height = (self.end_hour - self.start_hour + 1) * 40 - 5 + else: + duration = event.get_duration( + date, self.start_hour, self.end_hour) + if hour + duration > self.end_hour + 1: + if event.start.date() == date: + duration = self.end_hour - hour + 1 + else: + duration = duration - hour + height = duration * float(40) - 5 + style = "width: %.2f%%; height: %dpx; margin-left: %.2f%%" % ( + width, height, margin) + output += event.render_for_datehour(date, + hour, css_class, style) + + return output diff --git a/data/treeio/treeio/treeio/events/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/events/south_migrations/0001_initial.py new file mode 100644 index 0000000..564eb9b --- /dev/null +++ b/data/treeio/treeio/treeio/events/south_migrations/0001_initial.py @@ -0,0 +1,182 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Event' + db.create_table('events_event', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('location', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['core.Location'], null=True, blank=True)), + ('details', self.gf('django.db.models.fields.TextField') + (max_length=255, null=True, blank=True)), + ('start', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + ('end', self.gf('django.db.models.fields.DateTimeField')()), + )) + db.send_create_signal('events', ['Event']) + + # Adding model 'Invitation' + db.create_table('events_invitation', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('contact', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['identities.Contact'])), + ('event', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['events.Event'])), + ('status', self.gf('django.db.models.fields.CharField') + (max_length=255)), + )) + db.send_create_signal('events', ['Invitation']) + + def backwards(self, orm): + + # Deleting model 'Event' + db.delete_table('events_event') + + # Deleting model 'Invitation' + db.delete_table('events_invitation') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'events.event': { + 'Meta': {'ordering': "['-end']", 'object_name': 'Event', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end': ('django.db.models.fields.DateTimeField', [], {}), + 'location': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Location']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'start': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'events.invitation': { + 'Meta': {'object_name': 'Invitation'}, + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'event': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['events.Event']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['events'] diff --git a/data/treeio/treeio/treeio/finance/api/handlers.py b/data/treeio/treeio/treeio/finance/api/handlers.py new file mode 100644 index 0000000..a346f27 --- /dev/null +++ b/data/treeio/treeio/treeio/finance/api/handlers.py @@ -0,0 +1,240 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, with_statement + +__all__ = ['CurrencyHandler', 'TaxHandler', 'CategoryHandler', 'AssetHandler', + 'AccountHandler', 'EquityHandler', 'LiabilityHandler', + 'TransactionHandler'] + +from django.core.exceptions import ObjectDoesNotExist + +from treeio.core.api.utils import rc +from treeio.sales.models import SaleOrder +from treeio.finance.helpers import convert +from treeio.sales.forms import dict_currencies +from treeio.core.api.handlers import ObjectHandler +from treeio.finance.models import Currency, Tax, Category, Asset, Account, Equity, Liability, Transaction +from treeio.finance.forms import TransactionForm, LiabilityForm, AccountForm, EquityForm, AssetForm, \ + CategoryForm, CurrencyForm, TaxForm + + +class FinanceCommonHandler(ObjectHandler): + def check_create_permission(self, request, mode): + return True # request.user.profile.is_admin('treeio.finance') + + def check_instance_permission(self, request, inst, mode): + return request.user.profile.has_permission(inst, mode=mode) \ + or request.user.profile.is_admin('treeio.finance') + + +class CurrencyHandler(ObjectHandler): + """ Process Currency objects""" + + model = Currency + form = CurrencyForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_finance_currencies', [object_id]) + + def create(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + if not self.check_create_permission(request, "x"): + return rc.FORBIDDEN + + currency = Currency() + form = CurrencyForm( + request.user.profile, request.data, instance=currency) + if form.is_valid(): + currency = form.save(commit=False) + cname = dict_currencies[currency.code] + currency.name = cname[cname.index(' ') + 2:] + # currency.factor = 1.0 #Get currency conversion here + currency.save() + currency.set_user_from_request(request) + return currency + else: + self.status = 400 + return form.errors + + +class TaxHandler(FinanceCommonHandler): + model = Tax + form = TaxForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_finance_taxes', [object_id]) + + +class CategoryHandler(FinanceCommonHandler): + model = Category + form = CategoryForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_finance_categories', [object_id]) + + +class AssetHandler(FinanceCommonHandler): + model = Asset + form = AssetForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_finance_assets', [object_id]) + + +class AccountHandler(FinanceCommonHandler): + model = Account + form = AccountForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_finance_accounts', [object_id]) + + def create(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + if not self.check_create_permission(request, "x"): + return rc.FORBIDDEN + + account = Account() + form = AccountForm( + request.user.profile, request.data, instance=account) + if form.is_valid(): + account = form.save(commit=False) + convert(account, 'balance') + account.set_user_from_request(request) + return account + else: + self.status = 400 + return form.errors + + +class EquityHandler(FinanceCommonHandler): + model = Equity + form = EquityForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_finance_equities', [object_id]) + + +class LiabilityHandler(FinanceCommonHandler): + model = Liability + form = LiabilityForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_finance_liabilities', [object_id]) + + def create(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + # if not self.check_create_permission(request, "x"): + # return rc.FORBIDDEN + + liability = self.model() + form = self.form( + request.user.profile, request.data, instance=liability) + if form.is_valid(): + liability = form.save(commit=False) + liability.source = liability.account.owner + convert(liability, 'value') + liability.set_user_from_request(request) + return liability + else: + self.status = 400 + return form.errors + + +class TransactionHandler(FinanceCommonHandler): + model = Transaction + form = TransactionForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_finance_transactions', [object_id]) + + def create(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + # if not self.check_create_permission(request, "x"): + # return rc.FORBIDDEN + + transaction = self.model() + form = self.form( + request.user.profile, None, None, request.POST, instance=transaction) + if form.is_valid(): + transaction = form.save(commit=False) + convert(transaction, 'value') + transaction.set_user_from_request(request) + if "order" in request.data: + try: + order = SaleOrder.objects.get(pk=request.data['order']) + order.payment.add(transaction) + order.save() + except: + pass + return transaction + else: + self.status = 400 + return form.errors + + def update(self, request, *args, **kwargs): + + pkfield = kwargs.get(self.model._meta.pk.name) or request.data.get( + self.model._meta.pk.name) + + if not pkfield: + return rc.BAD_REQUEST + + try: + obj = self.model.objects.get(pk=pkfield) + except ObjectDoesNotExist: + return rc.NOT_FOUND + + form = self.form( + request.user.profile, None, None, request.data, instance=obj) + if form.is_valid(): + transaction = form.save(commit=False) + convert(transaction, 'value') + return transaction + else: + self.status = 400 + return form.errors diff --git a/data/treeio/treeio/treeio/finance/forms.py b/data/treeio/treeio/treeio/finance/forms.py new file mode 100644 index 0000000..f815385 --- /dev/null +++ b/data/treeio/treeio/treeio/finance/forms.py @@ -0,0 +1,782 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Finance module forms +""" +from django.shortcuts import get_object_or_404 +from django import forms +from treeio.identities.models import Contact +from treeio.finance.models import Transaction, Liability, Category, Account, Asset, Equity, Currency, Tax +from treeio.sales.models import SaleOrder +from treeio.core.models import Object, ModuleSetting +from django.core.urlresolvers import reverse +from treeio.core.decorators import preprocess_form +from django.utils.translation import ugettext as _ +from treeio.sales.forms import standard_currencies + +preprocess_form() + + +class MassActionForm(forms.Form): + """ Mass action form for Transactions & Liabilities """ + + category = forms.ModelChoiceField(queryset=[], required=False) + delete = forms.ChoiceField(label=_("Delete"), choices=(('', '-----'), ('delete', _('Delete Completely')), + ('trash', _('Move to Trash'))), required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(MassActionForm, self).__init__(*args, **kwargs) + self.fields['delete'] = forms.ChoiceField(label=_("Delete"), choices=(('', '-----'), + ('delete', _( + 'Delete Completely')), + ('trash', _('Move to Trash'))), + required=False) + + self.fields['category'].label = _("Category") + self.fields['category'].queryset = Object.filter_permitted( + user, Category.objects, mode='x') + self.fields['category'].label = _("Add to Category:") + + def save(self, *args, **kwargs): + "Process form" + + if self.instance: + if self.is_valid(): + if self.cleaned_data['category']: + self.instance.category = self.cleaned_data['category'] + self.instance.save() + if self.cleaned_data['delete']: + if self.cleaned_data['delete'] == 'delete': + self.instance.delete() + if self.cleaned_data['delete'] == 'trash': + self.instance.trash = True + self.instance.save() + + +class CategoryForm(forms.ModelForm): + """ Category form """ + + def __init__(self, user, *args, **kwargs): + super(CategoryForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + self.fields['details'].label = _("Details") + + class Meta: + "Category Form" + model = Category + fields = ('name', 'details') + + +class AccountForm(forms.ModelForm): + """ Account form """ + + def __init__(self, user, *args, **kwargs): + super(AccountForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + + self.fields['owner'].label = _("Owner") + self.fields['owner'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['owner'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['owner'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + + self.fields['balance_currency'].label = _("Currency") + self.fields['balance_currency'].widget.attrs.update( + {'popuplink': reverse('finance_currency_add')}) + try: + self.fields['balance_currency'].initial = Currency.objects.get( + is_default=True) + except: + pass + self.fields['balance_display'].label = _("Initial Balance") + + self.fields['details'].label = _("Details") + + class Meta: + + "Account Form" + model = Account + fields = ( + 'name', 'owner', 'balance_currency', 'balance_display', 'details') + + +class AccountFilterForm(forms.ModelForm): + """ Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(AccountFilterForm, self).__init__(*args, **kwargs) + + if 'owner' in skip: + del self.fields['owner'] + else: + self.fields['owner'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['owner'].required = False + self.fields['owner'].label = _("Owner") + self.fields['owner'].help_text = "" + self.fields['owner'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + class Meta: + + "Account Filter Form" + model = Account + fields = ['owner'] + + +class AssetForm(forms.ModelForm): + """ Asset form """ + + def __init__(self, user, *args, **kwargs): + super(AssetForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + self.fields['asset_type'].label = _("Asset type") + self.fields['initial_value'].label = _("Initial value") + self.fields['lifetime'].label = _("Lifetime (years)") + self.fields['endlife_value'].label = _("Endlife value") + self.fields['depreciation_rate'].label = _("Depreciation rate") + self.fields['purchase_date'].label = _("Purchase date") + self.fields['purchase_date'].widget.attrs.update( + {'class': 'datepicker'}) + self.fields['current_value'].label = _("Current value") + self.fields['owner'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['owner'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['owner'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + + class Meta: + "Asset Form" + model = Asset + fields = ('name', 'asset_type', 'initial_value', 'lifetime', 'endlife_value', + 'depreciation_rate', 'depreciation_type', 'purchase_date', 'current_value', 'owner') + + +class AssetFilterForm(forms.ModelForm): + """ Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(AssetFilterForm, self).__init__(*args, **kwargs) + + if 'purchase_date_from' in skip: + del self.fields['purchase_date_from'] + else: + self.fields['purchase_date_from'] = forms.DateField(label="Purchase Date From:", + required=False) + self.fields['purchase_date_from'].widget.attrs.update( + {'class': 'datepicker'}) + self.fields['purchase_date_from'].label = _("Purchase Date From") + + if 'purchase_date_to' in skip: + del self.fields['purchase_date_to'] + else: + self.fields['purchase_date_to'] = forms.DateField( + label="Purchase Date To:", required=False) + self.fields['purchase_date_to'].widget.attrs.update( + {'class': 'datepicker'}) + self.fields['purchase_date_to'].label = _("Purchase Date To") + + if 'asset_type' in skip: + del self.fields['asset_type'] + else: + self.fields['asset_type'].label = _("Asset Type") + self.fields['asset_type'].help_text = "" + self.fields['asset_type'].required = False + + if 'owner' in skip: + del self.fields['owner'] + else: + self.fields['owner'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['owner'].required = False + self.fields['owner'].label = _("Owner") + self.fields['owner'].help_text = "" + self.fields['owner'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + class Meta: + + "Asset Filter Form" + model = Asset + fields = ('owner', 'asset_type') + + +class EquityForm(forms.ModelForm): + """ Equity form """ + + def __init__(self, user, *args, **kwargs): + super(EquityForm, self).__init__(*args, **kwargs) + + self.fields['equity_type'].label = _("Equity type") + self.fields['issue_price'].label = _("Issue price") + self.fields['sell_price'].label = _("Sell price") + self.fields['issuer'].label = _("Issuer") + self.fields['owner'].label = _("Owner") + self.fields['amount'].label = _("Quantity") + self.fields['purchase_date'].label = _("Purchase date") + self.fields['details'].label = _("Details") + + self.fields['owner'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['owner'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['owner'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['issuer'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['issuer'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['issuer'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + try: + conf = ModuleSetting.get_for_module( + 'treeio.finance', 'my_company')[0] + self.fields['issuer'].initial = long(conf.value) + except Exception: + pass + + self.fields['purchase_date'].widget.attrs.update( + {'class': 'datepicker'}) + + class Meta: + + "Equity Form" + model = Equity + fields = ('equity_type', 'issue_price', 'sell_price', 'issuer', + 'owner', 'amount', 'purchase_date', 'details') + + +class EquityFilterForm(forms.ModelForm): + """ Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(EquityFilterForm, self).__init__(*args, **kwargs) + + if 'purchase_date_from' in skip: + del self.fields['purchase_date_from'] + else: + self.fields['purchase_date_from'] = forms.DateField(label="Purchase Date From:", + required=False) + self.fields['purchase_date_from'].widget.attrs.update( + {'class': 'datepicker'}) + self.fields['purchase_date_from'].label = _("Purchase Date From") + + if 'purchase_date_to' in skip: + del self.fields['purchase_date_to'] + else: + self.fields['purchase_date_to'] = forms.DateField( + label="Purchase Date To:", required=False) + self.fields['purchase_date_to'].widget.attrs.update( + {'class': 'datepicker'}) + self.fields['purchase_date_to'].label = _("Purchase Date To") + + if 'equity_type' in skip: + del self.fields['equity_type'] + else: + self.fields['equity_type'].label = _("Equity Type") + self.fields['equity_type'].help_text = "" + self.fields['equity_type'].required = False + + if 'issuer' in skip: + del self.fields['issuer'] + else: + self.fields['issuer'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['issuer'].label = _("Issuer") + self.fields['issuer'].help_text = "" + self.fields['issuer'].required = False + self.fields['issuer'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + if 'owner' in skip: + del self.fields['owner'] + else: + self.fields['owner'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['owner'].required = False + self.fields['owner'].label = _("Owner") + self.fields['owner'].help_text = "" + self.fields['owner'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + class Meta: + + "Equity Filter Form" + model = Equity + fields = ('issuer', 'owner', 'equity_type') + + +class ReceivableForm(forms.ModelForm): + """ Receivable form """ + + def __init__(self, user, *args, **kwargs): + super(ReceivableForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + self.fields['category'].label = _("Category") + self.fields['source'].label = _("Source") + self.fields['target'].label = _("Target") + self.fields['account'].label = _("Bank Account") + self.fields['due_date'].label = _("Due date") + self.fields['value_currency'].label = _("Currency") + self.fields['value_currency'].widget.attrs.update( + {'popuplink': reverse('finance_currency_add')}) + self.fields['value_currency'].initial = Currency.objects.get( + is_default=True) + self.fields['value_display'].label = _("Value") + self.fields['details'].label = _("Details") + + self.fields['source'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['source'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['source'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + + self.fields['account'].queryset = Object.filter_permitted( + user, Account.objects) + + try: + conf = ModuleSetting.get_for_module( + 'treeio.finance', 'default_account')[0] + self.fields['account'].initial = long(conf.value) + except Exception: + pass + + self.fields['due_date'].widget.attrs.update({'class': 'datepicker'}) + + del self.fields['target'] + + class Meta: + + "Receivable Form" + model = Liability + fields = ('name', 'category', 'source', 'target', 'account', + 'due_date', 'value_currency', 'value_display', 'details') + + +class TransactionForm(forms.ModelForm): + """ Transaction form """ + + def __init__(self, user, liability_id=None, order_id=None, *args, **kwargs): + super(TransactionForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Description") + self.fields['category'].label = _("Category") + self.fields['source'].label = _("Source") + self.fields['target'].label = _("Target") + self.fields['account'].label = _("Bank Account") + self.fields['datetime'].label = _("Date & Time") + self.fields['value_currency'].label = _("Currency") + self.fields['value_currency'].widget.attrs.update( + {'popuplink': reverse('finance_currency_add')}) + self.fields['value_currency'].initial = Currency.objects.get( + is_default=True) + self.fields['value_display'].label = _("Value") + self.fields['details'].label = _("Details") + + self.fields['source'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['target'].queryset = Object.filter_permitted( + user, Contact.objects) + + self.fields['source'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['target'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + self.fields['source'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['target'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + + self.fields['datetime'].widget.attrs.update( + {'class': 'datetimepicker'}) + + self.fields['account'].queryset = Object.filter_permitted( + user, Account.objects) + + try: + conf = ModuleSetting.get_for_module( + 'treeio.finance', 'default_account')[0] + self.fields['account'].initial = long(conf.value) + except Exception: + pass + + self.fields['liability'].queryset = Object.filter_permitted( + user, Liability.objects) + self.fields['liability'].label = _("Liability / Receivable") + + if order_id: + order = get_object_or_404(SaleOrder, pk=order_id) + self.fields['name'].initial = order.reference + if order.client: + self.fields['source'].initial = order.client + + # default company + try: + conf = ModuleSetting.get_for_module( + 'treeio.finance', 'my_company')[0] + self.fields['target'].initial = Contact.objects.get( + pk=long(conf.value)) + + except Exception: + pass + self.fields['details'].initial = order.details + self.fields['value_display'].initial = order.balance_due() + self.fields['value_currency'].initial = order.currency + + if liability_id: + self.fields['liability'].initial = liability_id + liability = get_object_or_404(Liability, pk=liability_id) + self.fields['name'].initial = liability.name + self.fields['source'].initial = liability.source + self.fields['target'].initial = liability.target + self.fields['details'].initial = liability.details + self.fields['category'].initial = liability.category + self.fields['account'].initial = liability.account + self.fields['value_display'].initial = liability.value_display + self.fields['value_currency'].initial = liability.value_currency + + class Meta: + + "Transaction Form" + model = Transaction + fields = ('name', 'category', 'source', 'target', 'account', + 'datetime', 'liability', 'value_currency', 'value_display', 'details') + + +class TransactionFilterForm(forms.ModelForm): + """ Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(TransactionFilterForm, self).__init__(*args, **kwargs) + + if 'datefrom' in skip: + del self.fields['datefrom'] + del self.fields['dateto'] + else: + self.fields['datefrom'] = forms.DateField( + label=_("Date From"), required=False) + self.fields['datefrom'].widget.attrs.update( + {'class': 'datepicker'}) + + if 'dateto' in skip: + del self.fields['dateto'] + del self.fields['datefrom'] + else: + self.fields['dateto'] = forms.DateField( + label=_("Date To"), required=False) + self.fields['dateto'].widget.attrs.update({'class': 'datepicker'}) + + if 'category' in skip: + del self.fields['category'] + else: + self.fields['category'].queryset = Object.filter_permitted( + user, Category.objects) + self.fields['category'].label = _("Category") + self.fields['category'].help_text = "" + self.fields['category'].required = False + + if 'source' in skip: + del self.fields['source'] + else: + self.fields['source'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['source'].label = _("Source") + self.fields['source'].help_text = "" + self.fields['source'].required = False + self.fields['source'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + if 'target' in skip: + del self.fields['target'] + else: + self.fields['target'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['target'].required = False + self.fields['target'].label = _("Target") + self.fields['target'].help_text = "" + self.fields['target'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + class Meta: + + "Transaction Filter Form" + model = Transaction + fields = ('category', 'source', 'target') + + +class LiabilityForm(forms.ModelForm): + """ Folder form """ + + def __init__(self, user, *args, **kwargs): + super(LiabilityForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + self.fields['category'].label = _("Category") + self.fields['source'].label = _("Source") + self.fields['target'].label = _("Target") + self.fields['account'].label = _("Bank Account") + self.fields['due_date'].label = _("Due date") + self.fields['value_currency'].label = _("Currency") + self.fields['value_currency'].widget.attrs.update( + {'popuplink': reverse('finance_currency_add')}) + self.fields['value_currency'].initial = Currency.objects.get( + is_default=True) + self.fields['value_display'].label = _("Value") + self.fields['details'].label = _("Details") + + self.fields['target'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['target'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['target'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + + self.fields['account'].queryset = Object.filter_permitted( + user, Account.objects) + + try: + conf = ModuleSetting.get_for_module( + 'treeio.finance', 'default_account')[0] + self.fields['account'].initial = long(conf.value) + except Exception: + pass + + self.fields['due_date'].widget.attrs.update({'class': 'datepicker'}) + + del self.fields['source'] + + class Meta: + + "Liability Form" + model = Liability + fields = ('name', 'category', 'source', 'target', 'account', + 'due_date', 'value_currency', 'value_display', 'details') + + +class LiabilityFilterForm(forms.ModelForm): + """ Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(LiabilityFilterForm, self).__init__(*args, **kwargs) + + if 'due_date_from' in skip: + del self.fields['due_date_from'] + else: + self.fields['due_date_from'] = forms.DateField( + label=_("Due Date From:"), required=False) + self.fields['due_date_from'].widget.attrs.update( + {'class': 'datepicker'}) + + if 'due_date_to' in skip: + del self.fields['due_date_to'] + else: + self.fields['due_date_to'] = forms.DateField( + label=_("Due Date To:"), required=False) + self.fields['due_date_to'].widget.attrs.update( + {'class': 'datepicker'}) + + if 'category' in skip: + del self.fields['category'] + else: + self.fields['category'].queryset = Object.filter_permitted( + user, Category.objects) + self.fields['category'].label = _("Category") + self.fields['category'].help_text = "" + self.fields['category'].required = False + + if 'source' in skip: + del self.fields['source'] + else: + self.fields['source'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['source'].label = _("Source") + self.fields['source'].help_text = "" + self.fields['source'].required = False + self.fields['source'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + if 'target' in skip: + del self.fields['target'] + else: + self.fields['target'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['target'].required = False + self.fields['target'].label = _("Target") + self.fields['target'].help_text = "" + self.fields['target'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + if 'account' in skip: + del self.fields['account'] + else: + self.fields['account'].queryset = Object.filter_permitted( + user, Account.objects) + self.fields['account'].required = False + self.fields['account'].label = _("Account") + self.fields['account'].help_text = "" + + class Meta: + + "Liability Filter Form" + model = Liability + fields = ('category', 'source', 'target', 'account') + + +class SettingsForm(forms.Form): + """ Administration settings form """ + + default_currency = forms.ModelChoiceField( + label='Base Currency', queryset=[]) + my_company = forms.ModelChoiceField(label='My Company', queryset=[]) + default_account = forms.ModelChoiceField( + label='Default Account', queryset=[]) + + def __init__(self, user, *args, **kwargs): + "Sets choices and initial value" + super(SettingsForm, self).__init__(*args, **kwargs) + + self.fields['my_company'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['my_company'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['default_account'].queryset = Object.filter_permitted( + user, Account.objects) + + # Translation + self.fields['default_currency'].label = _('Base Currency') + self.fields['my_company'].label = _('My Company') + self.fields['default_account'].label = _('Default Account') + + try: + self.fields['default_currency'].widget.attrs.update( + {'popuplink': reverse('finance_currency_add')}) + self.fields['default_currency'].queryset = Currency.objects.all() + self.fields['default_currency'].initial = Currency.objects.get( + is_default=True) + except Exception: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.finance', 'my_company')[0] + my_company = Contact.objects.get(pk=long(conf.value)) + self.fields['my_company'].initial = my_company.id + except Exception: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.finance', 'default_account')[0] + default_account = Account.objects.get(pk=long(conf.value)) + self.fields['default_account'].initial = default_account.id + except Exception: + pass + + def clean_my_company(self, *args, **kwargs): + "Check that my company has an account" + my_company = self.cleaned_data['my_company'] + + if not my_company.account_set.count(): + raise forms.ValidationError( + _("Your company has to have at least one Financial Account")) + + return my_company + + def clean_default_account(self): + "Check that account owner is the same as my company" + account = self.cleaned_data['default_account'] + try: + company = self.cleaned_data['my_company'] + + if not account.owner_id == company.id: + raise forms.ValidationError( + _("Default Account has to belong to your company")) + + except KeyError: + pass + + return account + + def save(self): + "Form processor" + try: + ModuleSetting.set_for_module('my_company', + self.cleaned_data['my_company'].id, + 'treeio.finance') + ModuleSetting.set_for_module('default_account', + self.cleaned_data[ + 'default_account'].id, + 'treeio.finance') + currency = Currency.objects.get( + pk=self.cleaned_data['default_currency']) + currency.is_default = True + currency.save() + return True + + except Exception: + return False + + +# +# Currency +# + + +class CurrencyForm(forms.ModelForm): + "Currency Form" + + code = forms.ChoiceField( + label=_("Currency Code"), choices=standard_currencies) + + def __init__(self, user, *args, **kwargs): + super(CurrencyForm, self).__init__(*args, **kwargs) + + class Meta: + "Currency Form" + model = Currency + fields = ('name', 'code', 'symbol', 'factor') # ,'is_active') + + +# +# Tax +# + + +class TaxForm(forms.ModelForm): + "Tax Form" + + def __init__(self, user, *args, **kwargs): + super(TaxForm, self).__init__(*args, **kwargs) + + class Meta: + "Tax Form" + model = Tax + fields = ('name', 'rate', 'compound') diff --git a/data/treeio/treeio/treeio/finance/models.py b/data/treeio/treeio/treeio/finance/models.py new file mode 100644 index 0000000..14cef2a --- /dev/null +++ b/data/treeio/treeio/treeio/finance/models.py @@ -0,0 +1,349 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Finance module objects +""" + +from django.db import models +from treeio.core.models import Object +from treeio.identities.models import Contact +from datetime import datetime +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ +import math +from decimal import Decimal, ROUND_UP + + +class Currency(Object): + + "Currency Object" + + code = models.CharField(_('code'), max_length=3) + name = models.CharField(_('name'), max_length=255) + symbol = models.CharField(_('symbol'), max_length=1, blank=True, null=True, + help_text=_('If no symbol is entered, the 3 letter code will be used.')) + factor = models.DecimalField(_('factor'), max_digits=10, decimal_places=4, + help_text=_( + 'Specifies the ratio to the base currency, e.g. 1.324'), + default=1) + is_active = models.BooleanField(_('active'), default=True, + help_text=_('The currency will be updated with daily exchange rates.')) + is_default = models.BooleanField(_('default'), default=False, + help_text=_('Make this the default currency.')) + + class Meta: + verbose_name = _('currency') + verbose_name_plural = _('currencies') + + def __unicode__(self): + # return self.code + if self.symbol: + return "%s %s" % (self.symbol, self.name) + else: + return "%s %s" % (self.code, self.name) + + def save(self, **kwargs): + try: + default_currency = Currency.objects.get(is_default=True) + if self.is_default and not self == default_currency: + default_currency.is_default = False + default_currency.save() + except: + pass + super(Currency, self).save(**kwargs) + + +class Tax(Object): + + """ Tax model """ + name = models.CharField(max_length=512) + rate = models.DecimalField(max_digits=4, decimal_places=2) + compound = models.BooleanField(default=False) + + def __unicode__(self): + # return self in unicode + return unicode(self.name) + + +class Category(Object): + + """ Category model """ + name = models.CharField(max_length=512) + details = models.TextField(blank=True, null=True) + + revenue = 0 + expense = 0 + + def __unicode__(self): + return unicode(self.name) + + +class Asset(Object): + + """ Asset model """ + name = models.CharField(max_length=512) + asset_type = models.CharField(max_length=32, + choices=( + ('fixed', 'Fixed'), ('intangible', 'Intangible')), + default='fixed') + initial_value = models.DecimalField( + max_digits=20, decimal_places=2, default=0) + lifetime = models.DecimalField( + max_digits=20, decimal_places=0, blank=True, null=True) + endlife_value = models.DecimalField( + max_digits=20, decimal_places=2, blank=True, null=True) + depreciation_rate = models.DecimalField( + max_digits=20, decimal_places=5, blank=True, null=True) + depreciation_type = models.CharField(max_length=32, blank=True, null=True, + choices=(('straight', 'Straight Line'), + ('reducing', 'Reducing balance')), + default='straight') + purchase_date = models.DateField( + blank=True, null=True, default=datetime.now) + current_value = models.DecimalField( + max_digits=20, decimal_places=2, default=0) + owner = models.ForeignKey(Contact) + details = models.TextField(blank=True, null=True) + + class Meta: + + "Event" + ordering = ['-purchase_date'] + + def check_depreciate(self): + "Check Depreciate" + if (self.purchase_date and self.endlife_value is not None and self.initial_value and self.lifetime): + return True + else: + return False + + def get_depreciation(self): + "Get Depreciation" + if self.check_depreciate(): # Edited by Renat + + self.set_rate() + + from_purchase = datetime.date(datetime.now()) - self.purchase_date + days_from_purchase = from_purchase.days + + if days_from_purchase >= (self.lifetime * 365): + return (self.initial_value - self.endlife_value).quantize(Decimal('.01'), rounding=ROUND_UP) + + straight = Decimal(((self.initial_value - + self.endlife_value) / ((self.lifetime * 365)) * + days_from_purchase)).quantize(Decimal('.01'), rounding=ROUND_UP) + + if self.depreciation_type == 'reducing': + i = self.initial_value + daily_depreciation_rate = Decimal(str(1 - math.pow((self.endlife_value / self.initial_value), + (1 / (self.lifetime * 365))))) + for g in range(1, days_from_purchase): + i -= (i * daily_depreciation_rate) + reducing = round(self.initial_value - i, 2) + if reducing > straight: + return reducing.quantize(Decimal('.01'), rounding=ROUND_UP) + else: + return straight.quantize(Decimal('.01'), rounding=ROUND_UP) + + elif self.depreciation_type == 'straight': + return straight.quantize(Decimal('.01'), rounding=ROUND_UP) + + else: + return Decimal('0.00') + else: + return Decimal('0.00') + + def set_rate(self): + "Set Rate" + if self.depreciation_type == 'straight': + if self.lifetime: + self.depreciation_rate = (100 / + self.lifetime).quantize(Decimal('00.01'), rounding=ROUND_UP) + + elif self.depreciation_type == 'reducing': + if not self.check_depreciate(): + return Decimal('0.00') + self.depreciation_rate = Decimal(str(100 / (1 - + math.pow((self.endlife_value / self.initial_value), + (1 / self.lifetime))))).quantize(Decimal('00.01'), rounding=ROUND_UP) + else: + return Decimal('0.00') + if self.depreciation_rate == Decimal('100.00'): + self.depreciation_rate = Decimal('99.99') + + self.save() + return self + + def set_current_value(self): + "Set current value" + if not self.check_depreciate(): + return self.initial_value + + self.current_value = self.initial_value - self.get_depreciation() + self.save() + + return self.current_value + + def __unicode__(self): + return unicode(self.name) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('finance_asset_view', args=[self.id]) + except Exception: + return "" + + +class Account(Object): + + """ Account model """ + name = models.CharField(max_length=512) + owner = models.ForeignKey(Contact) + balance = models.DecimalField(max_digits=20, decimal_places=2, default=0) + balance_currency = models.ForeignKey(Currency) + balance_display = models.DecimalField( + max_digits=20, decimal_places=2, default=0) + details = models.TextField(blank=True, null=True) + + class Meta: + + "Event" + ordering = ['name'] + + def get_balance(self): + "Returns balance" + bal = self.balance + transactions = Transaction.objects.filter(account=self.id) + for transaction in transactions: + bal += transaction.get_relative_value() + return bal + + def __unicode__(self): + return unicode(self.name) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('finance_account_view', args=[self.id]) + except Exception: + return "" + + +class Equity(Object): + + """ Equity model """ + equity_type = models.CharField(max_length=32, + choices=(('share', 'Ordinary Share'), ('preferred', 'Preferred'), + ('warrant', 'Warrant')), + default='share') + issue_price = models.DecimalField(max_digits=20, decimal_places=2) + sell_price = models.DecimalField(max_digits=20, decimal_places=2) + issuer = models.ForeignKey(Contact, related_name='finance_equity_issued') + owner = models.ForeignKey(Contact, related_name='finance_equity_owned') + amount = models.PositiveIntegerField(default=1) + purchase_date = models.DateField(default=datetime.now) + details = models.TextField(blank=True, null=True) + + class Meta: + + "Event" + ordering = ['-purchase_date'] + + def __unicode__(self): + return unicode(unicode(self.issuer) + " (" + unicode(self.equity_type) + ")") + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('finance_equity_view', args=[self.id]) + except Exception: + return "" + + +class Liability(Object): + + """ Liability model, used for both Liabilities and Receivables """ + name = models.CharField(max_length=512) + source = models.ForeignKey( + Contact, related_name='finance_liability_source') + target = models.ForeignKey( + Contact, related_name='finance_liability_target') + category = models.ForeignKey( + Category, blank=True, null=True, on_delete=models.SET_NULL) + account = models.ForeignKey(Account) + due_date = models.DateField(blank=True, null=True) + value = models.DecimalField(default=0, max_digits=20, decimal_places=2) + value_currency = models.ForeignKey(Currency) + value_display = models.DecimalField( + default=0, max_digits=20, decimal_places=2) + details = models.TextField(blank=True) + + class Meta: + + "Event" + ordering = ['-due_date'] + + def __unicode__(self): + return unicode(self.name) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('finance_liability_view', args=[self.id]) + except Exception: + return "" + + def is_receivable(self): + "Returns True if self is receivable relative to self.account" + return self.account.owner == self.target + + +class Transaction(Object): + + """ Transaction model """ + name = models.CharField(max_length=512) + source = models.ForeignKey( + Contact, related_name='finance_transaction_source') + target = models.ForeignKey( + Contact, related_name='finance_transaction_target') + liability = models.ForeignKey( + Liability, blank=True, null=True, on_delete=models.SET_NULL) + category = models.ForeignKey( + Category, blank=True, null=True, on_delete=models.SET_NULL) + account = models.ForeignKey(Account) + datetime = models.DateTimeField(default=datetime.now) + value = models.DecimalField(default=0, max_digits=20, decimal_places=2) + value_currency = models.ForeignKey(Currency) + value_display = models.DecimalField( + default=0, max_digits=20, decimal_places=2) + details = models.TextField(blank=True) + + class Meta: + + "Event" + ordering = ['-datetime'] + + def __unicode__(self): + return unicode(self.name) + + def get_relative_value(self): + "Get Relative Value" + if self.trash: + return 0 + elif self.account.owner_id == self.source_id: + return -abs(self.value) + elif self.account.owner_id == self.target_id: + return abs(self.value) + else: + return 0 + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('finance_transaction_view', args=[self.id]) + except Exception: + return "" diff --git a/data/treeio/treeio/treeio/finance/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/finance/south_migrations/0001_initial.py new file mode 100644 index 0000000..a64c958 --- /dev/null +++ b/data/treeio/treeio/treeio/finance/south_migrations/0001_initial.py @@ -0,0 +1,331 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Category' + db.create_table('finance_category', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('finance', ['Category']) + + # Adding model 'Asset' + db.create_table('finance_asset', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('asset_type', self.gf('django.db.models.fields.CharField') + (default='fixed', max_length=32)), + ('initial_value', self.gf( + 'django.db.models.fields.FloatField')(default=0)), + ('lifetime', self.gf('django.db.models.fields.FloatField') + (null=True, blank=True)), + ('endlife_value', self.gf('django.db.models.fields.FloatField') + (null=True, blank=True)), + ('depreciation_rate', self.gf( + 'django.db.models.fields.FloatField')(null=True, blank=True)), + ('depreciation_type', self.gf('django.db.models.fields.CharField') + (default='straight', max_length=32, null=True, blank=True)), + ('purchase_date', self.gf('django.db.models.fields.DateField') + (default=datetime.datetime.now, null=True, blank=True)), + ('current_value', self.gf( + 'django.db.models.fields.FloatField')(default=0)), + ('owner', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['identities.Contact'])), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('finance', ['Asset']) + + # Adding model 'Account' + db.create_table('finance_account', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('owner', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['identities.Contact'])), + ('balance', self.gf( + 'django.db.models.fields.FloatField')(default=0)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('finance', ['Account']) + + # Adding model 'Equity' + db.create_table('finance_equity', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('equity_type', self.gf('django.db.models.fields.CharField') + (default='share', max_length=32)), + ('issue_price', self.gf('django.db.models.fields.FloatField')()), + ('sell_price', self.gf('django.db.models.fields.FloatField')()), + ('issuer', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='finance_equity_issued', to=orm['identities.Contact'])), + ('owner', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='finance_equity_owned', to=orm['identities.Contact'])), + ('amount', self.gf( + 'django.db.models.fields.PositiveIntegerField')(default=1)), + ('purchase_date', self.gf('django.db.models.fields.DateField') + (default=datetime.datetime.now)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('finance', ['Equity']) + + # Adding model 'Liability' + db.create_table('finance_liability', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('source', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='finance_liability_source', to=orm['identities.Contact'])), + ('target', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='finance_liability_target', to=orm['identities.Contact'])), + ('category', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['finance.Category'], null=True, blank=True)), + ('account', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['finance.Account'])), + ('due_date', self.gf('django.db.models.fields.DateField') + (null=True, blank=True)), + ('value', self.gf('django.db.models.fields.FloatField')()), + ('details', self.gf( + 'django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('finance', ['Liability']) + + # Adding model 'Transaction' + db.create_table('finance_transaction', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('source', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='finance_transaction_source', to=orm['identities.Contact'])), + ('target', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='finance_transaction_target', to=orm['identities.Contact'])), + ('liability', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['finance.Liability'], null=True, blank=True)), + ('category', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['finance.Category'], null=True, blank=True)), + ('account', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['finance.Account'])), + ('datetime', self.gf('django.db.models.fields.DateTimeField') + (default=datetime.datetime.now)), + ('value', self.gf('django.db.models.fields.FloatField')()), + ('details', self.gf( + 'django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('finance', ['Transaction']) + + def backwards(self, orm): + + # Deleting model 'Category' + db.delete_table('finance_category') + + # Deleting model 'Asset' + db.delete_table('finance_asset') + + # Deleting model 'Account' + db.delete_table('finance_account') + + # Deleting model 'Equity' + db.delete_table('finance_equity') + + # Deleting model 'Liability' + db.delete_table('finance_liability') + + # Deleting model 'Transaction' + db.delete_table('finance_transaction') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'finance.account': { + 'Meta': {'ordering': "['name']", 'object_name': 'Account', '_ormbases': ['core.Object']}, + 'balance': ('django.db.models.fields.FloatField', [], {'default': '0'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}) + }, + 'finance.asset': { + 'Meta': {'ordering': "['-purchase_date']", 'object_name': 'Asset', '_ormbases': ['core.Object']}, + 'asset_type': ('django.db.models.fields.CharField', [], {'default': "'fixed'", 'max_length': '32'}), + 'current_value': ('django.db.models.fields.FloatField', [], {'default': '0'}), + 'depreciation_rate': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'depreciation_type': ('django.db.models.fields.CharField', [], {'default': "'straight'", 'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'endlife_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'initial_value': ('django.db.models.fields.FloatField', [], {'default': '0'}), + 'lifetime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'purchase_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}) + }, + 'finance.category': { + 'Meta': {'object_name': 'Category', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'finance.equity': { + 'Meta': {'ordering': "['-purchase_date']", 'object_name': 'Equity', '_ormbases': ['core.Object']}, + 'amount': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'equity_type': ('django.db.models.fields.CharField', [], {'default': "'share'", 'max_length': '32'}), + 'issue_price': ('django.db.models.fields.FloatField', [], {}), + 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_equity_issued'", 'to': "orm['identities.Contact']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_equity_owned'", 'to': "orm['identities.Contact']"}), + 'purchase_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now'}), + 'sell_price': ('django.db.models.fields.FloatField', [], {}) + }, + 'finance.liability': { + 'Meta': {'ordering': "['-due_date']", 'object_name': 'Liability', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'due_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.FloatField', [], {}) + }, + 'finance.transaction': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'Transaction', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'liability': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Liability']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.FloatField', [], {}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['finance'] diff --git a/data/treeio/treeio/treeio/finance/south_migrations/0002_auto__add_currency__add_tax__add_field_liability_value_currency__add_f.py b/data/treeio/treeio/treeio/finance/south_migrations/0002_auto__add_currency__add_tax__add_field_liability_value_currency__add_f.py new file mode 100644 index 0000000..c4c40e6 --- /dev/null +++ b/data/treeio/treeio/treeio/finance/south_migrations/0002_auto__add_currency__add_tax__add_field_liability_value_currency__add_f.py @@ -0,0 +1,393 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Currency' + db.create_table('finance_currency', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('code', self.gf('django.db.models.fields.CharField') + (max_length=3)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('symbol', self.gf('django.db.models.fields.CharField') + (max_length=1, null=True, blank=True)), + ('factor', self.gf('django.db.models.fields.DecimalField') + (default=1, max_digits=10, decimal_places=4)), + ('is_active', self.gf( + 'django.db.models.fields.BooleanField')(default=True)), + ('is_default', self.gf( + 'django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('finance', ['Currency']) + + # Adding model 'Tax' + db.create_table('finance_tax', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('rate', self.gf('django.db.models.fields.DecimalField') + (max_digits=4, decimal_places=2)), + ('compound', self.gf( + 'django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('finance', ['Tax']) + + # Adding field 'Liability.value_currency' + db.add_column('finance_liability', 'value_currency', self.gf( + 'django.db.models.fields.related.ForeignKey')(default=1, to=orm['finance.Currency']), keep_default=False) + + # Adding field 'Liability.value_display' + db.add_column('finance_liability', 'value_display', self.gf('django.db.models.fields.DecimalField')( + default=0, max_digits=20, decimal_places=2), keep_default=False) + + # Changing field 'Liability.value' + db.alter_column('finance_liability', 'value', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Changing field 'Asset.initial_value' + db.alter_column('finance_asset', 'initial_value', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Changing field 'Asset.current_value' + db.alter_column('finance_asset', 'current_value', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Changing field 'Asset.endlife_value' + db.alter_column('finance_asset', 'endlife_value', self.gf( + 'django.db.models.fields.DecimalField')(null=True, max_digits=20, decimal_places=2)) + + # Changing field 'Asset.lifetime' + db.alter_column('finance_asset', 'lifetime', self.gf( + 'django.db.models.fields.DecimalField')(null=True, max_digits=20, decimal_places=0)) + + # Changing field 'Asset.depreciation_rate' + db.alter_column('finance_asset', 'depreciation_rate', self.gf( + 'django.db.models.fields.DecimalField')(null=True, max_digits=4, decimal_places=2)) + + # Adding field 'Account.balance_currency' + db.add_column('finance_account', 'balance_currency', self.gf( + 'django.db.models.fields.related.ForeignKey')(default=1, to=orm['finance.Currency']), keep_default=False) + + # Adding field 'Account.balance_display' + db.add_column('finance_account', 'balance_display', self.gf('django.db.models.fields.DecimalField')( + default=0, max_digits=20, decimal_places=2), keep_default=False) + + # Changing field 'Account.balance' + db.alter_column('finance_account', 'balance', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Adding field 'Transaction.value_currency' + db.add_column('finance_transaction', 'value_currency', self.gf( + 'django.db.models.fields.related.ForeignKey')(default=1, to=orm['finance.Currency']), keep_default=False) + + # Adding field 'Transaction.value_display' + db.add_column('finance_transaction', 'value_display', self.gf('django.db.models.fields.DecimalField')( + default=0, max_digits=20, decimal_places=2), keep_default=False) + + # Changing field 'Transaction.value' + db.alter_column('finance_transaction', 'value', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Changing field 'Equity.issue_price' + db.alter_column('finance_equity', 'issue_price', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Changing field 'Equity.sell_price' + db.alter_column('finance_equity', 'sell_price', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + def backwards(self, orm): + + # Deleting model 'Currency' + db.delete_table('finance_currency') + + # Deleting model 'Tax' + db.delete_table('finance_tax') + + # Deleting field 'Liability.value_currency' + db.delete_column('finance_liability', 'value_currency_id') + + # Deleting field 'Liability.value_display' + db.delete_column('finance_liability', 'value_display') + + # Changing field 'Liability.value' + db.alter_column('finance_liability', 'value', self.gf( + 'django.db.models.fields.FloatField')()) + + # Changing field 'Asset.initial_value' + db.alter_column('finance_asset', 'initial_value', self.gf( + 'django.db.models.fields.FloatField')()) + + # Changing field 'Asset.current_value' + db.alter_column('finance_asset', 'current_value', self.gf( + 'django.db.models.fields.FloatField')()) + + # Changing field 'Asset.endlife_value' + db.alter_column('finance_asset', 'endlife_value', self.gf( + 'django.db.models.fields.FloatField')(null=True)) + + # Changing field 'Asset.lifetime' + db.alter_column('finance_asset', 'lifetime', self.gf( + 'django.db.models.fields.FloatField')(null=True)) + + # Changing field 'Asset.depreciation_rate' + db.alter_column('finance_asset', 'depreciation_rate', self.gf( + 'django.db.models.fields.FloatField')(null=True)) + + # Deleting field 'Account.balance_currency' + db.delete_column('finance_account', 'balance_currency_id') + + # Deleting field 'Account.balance_display' + db.delete_column('finance_account', 'balance_display') + + # Changing field 'Account.balance' + db.alter_column('finance_account', 'balance', self.gf( + 'django.db.models.fields.FloatField')()) + + # Deleting field 'Transaction.value_currency' + db.delete_column('finance_transaction', 'value_currency_id') + + # Deleting field 'Transaction.value_display' + db.delete_column('finance_transaction', 'value_display') + + # Changing field 'Transaction.value' + db.alter_column('finance_transaction', 'value', self.gf( + 'django.db.models.fields.FloatField')()) + + # Changing field 'Equity.issue_price' + db.alter_column('finance_equity', 'issue_price', self.gf( + 'django.db.models.fields.FloatField')()) + + # Changing field 'Equity.sell_price' + db.alter_column('finance_equity', 'sell_price', self.gf( + 'django.db.models.fields.FloatField')()) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'finance.account': { + 'Meta': {'ordering': "['name']", 'object_name': 'Account', '_ormbases': ['core.Object']}, + 'balance': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'balance_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'balance_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}) + }, + 'finance.asset': { + 'Meta': {'ordering': "['-purchase_date']", 'object_name': 'Asset', '_ormbases': ['core.Object']}, + 'asset_type': ('django.db.models.fields.CharField', [], {'default': "'fixed'", 'max_length': '32'}), + 'current_value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'depreciation_rate': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '4', 'decimal_places': '2', 'blank': 'True'}), + 'depreciation_type': ('django.db.models.fields.CharField', [], {'default': "'straight'", 'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'endlife_value': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '2', 'blank': 'True'}), + 'initial_value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'lifetime': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '0', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'purchase_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}) + }, + 'finance.category': { + 'Meta': {'object_name': 'Category', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'finance.currency': { + 'Meta': {'object_name': 'Currency', '_ormbases': ['core.Object']}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + 'factor': ('django.db.models.fields.DecimalField', [], {'default': '1', 'max_digits': '10', 'decimal_places': '4'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'symbol': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True', 'blank': 'True'}) + }, + 'finance.equity': { + 'Meta': {'ordering': "['-purchase_date']", 'object_name': 'Equity', '_ormbases': ['core.Object']}, + 'amount': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'equity_type': ('django.db.models.fields.CharField', [], {'default': "'share'", 'max_length': '32'}), + 'issue_price': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_equity_issued'", 'to': "orm['identities.Contact']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_equity_owned'", 'to': "orm['identities.Contact']"}), + 'purchase_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now'}), + 'sell_price': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}) + }, + 'finance.liability': { + 'Meta': {'ordering': "['-due_date']", 'object_name': 'Liability', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'due_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'finance.tax': { + 'Meta': {'object_name': 'Tax', '_ormbases': ['core.Object']}, + 'compound': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '2'}) + }, + 'finance.transaction': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'Transaction', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'liability': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Liability']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['finance'] diff --git a/data/treeio/treeio/treeio/finance/south_migrations/0003_treeiocurrency.py b/data/treeio/treeio/treeio/finance/south_migrations/0003_treeiocurrency.py new file mode 100644 index 0000000..837325d --- /dev/null +++ b/data/treeio/treeio/treeio/finance/south_migrations/0003_treeiocurrency.py @@ -0,0 +1,255 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + +class Migration(DataMigration): + + def forwards(self, orm): + "Add currencies to financial items" + try: + currency = orm['finance.Currency'].objects.get(is_default=True) + except: + currency = orm['finance.Currency'].objects.create() + currency.code = "USD" + currency.name = "USD United States of America, Dollars" + currency.symbol = u"$" + currency.is_default = True + currency.save() + for obj in orm['finance.Liability'].objects.all(): + obj.value_currency = currency + obj.value_display = obj.value + obj.save() + for obj in orm['finance.Transaction'].objects.all(): + obj.value_currency = currency + obj.value_display = obj.value + obj.save() + for obj in orm['finance.Account'].objects.all(): + obj.balance_currency = currency + obj.balance_display = obj.balance + obj.save() + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'finance.account': { + 'Meta': {'ordering': "['name']", 'object_name': 'Account', '_ormbases': ['core.Object']}, + 'balance': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'balance_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'balance_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}) + }, + 'finance.asset': { + 'Meta': {'ordering': "['-purchase_date']", 'object_name': 'Asset', '_ormbases': ['core.Object']}, + 'asset_type': ('django.db.models.fields.CharField', [], {'default': "'fixed'", 'max_length': '32'}), + 'current_value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'depreciation_rate': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '4', 'decimal_places': '2', 'blank': 'True'}), + 'depreciation_type': ('django.db.models.fields.CharField', [], {'default': "'straight'", 'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'endlife_value': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '2', 'blank': 'True'}), + 'initial_value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'lifetime': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '20', 'decimal_places': '0', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'purchase_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}) + }, + 'finance.category': { + 'Meta': {'object_name': 'Category', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'finance.currency': { + 'Meta': {'object_name': 'Currency', '_ormbases': ['core.Object']}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + 'factor': ('django.db.models.fields.DecimalField', [], {'default': '1', 'max_digits': '10', 'decimal_places': '4'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'symbol': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True', 'blank': 'True'}) + }, + 'finance.equity': { + 'Meta': {'ordering': "['-purchase_date']", 'object_name': 'Equity', '_ormbases': ['core.Object']}, + 'amount': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'equity_type': ('django.db.models.fields.CharField', [], {'default': "'share'", 'max_length': '32'}), + 'issue_price': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'issuer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_equity_issued'", 'to': "orm['identities.Contact']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_equity_owned'", 'to': "orm['identities.Contact']"}), + 'purchase_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now'}), + 'sell_price': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}) + }, + 'finance.liability': { + 'Meta': {'ordering': "['-due_date']", 'object_name': 'Liability', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'due_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'finance.tax': { + 'Meta': {'object_name': 'Tax', '_ormbases': ['core.Object']}, + 'compound': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '2'}) + }, + 'finance.transaction': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'Transaction', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'liability': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Liability']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + } + } + + complete_apps = ['finance'] diff --git a/data/treeio/treeio/treeio/finance/views.py b/data/treeio/treeio/treeio/finance/views.py new file mode 100644 index 0000000..e216900 --- /dev/null +++ b/data/treeio/treeio/treeio/finance/views.py @@ -0,0 +1,1441 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Finance Management module: views +""" +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse +from django.db.models import Q, Sum +from treeio.core.rendering import render_to_response +from treeio.core.models import Object, ModuleSetting +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.core.views import user_denied +from treeio.identities.models import Contact +from treeio.finance.forms import TransactionForm, LiabilityForm, AccountForm, EquityForm, AssetForm, \ + CategoryForm, MassActionForm, TransactionFilterForm, LiabilityFilterForm, \ + EquityFilterForm, AssetFilterForm, AccountFilterForm, ReceivableForm, \ + SettingsForm, CurrencyForm, TaxForm +from treeio.finance.models import Transaction, Liability, Category, Account, Equity, Asset, Currency, Tax +from treeio.finance.csvapi import ProcessTransactions +from treeio.sales.models import Product, SaleOrder +from treeio.sales.forms import dict_currencies +from treeio.finance.helpers import convert +from datetime import datetime, timedelta +from decimal import Decimal + + +def _get_filter_query(model, args): + "Creates a query to filter Transactions based on FilterForm arguments" + query = Q() + + for arg in args: + if args[arg]: + if hasattr(model, arg): + kwargs = {str(arg + '__id'): long(args[arg])} + query = query & Q(**kwargs) + + if 'datefrom' in args and args['datefrom']: + datefrom = datetime.date( + datetime.strptime(args['datefrom'], '%m/%d/%Y')) + query = query & Q(date_created__gte=datefrom) + + if 'dateto' in args and args['dateto']: + dateto = datetime.date(datetime.strptime(args['dateto'], '%m/%d/%Y')) + query = query & Q(date_created__lte=dateto) + + ## Assets, Equities + + if 'purchase_date_from' in args and args['purchase_date_from']: + purchase_date_from = datetime.date( + datetime.strptime(args['purchase_date_from'], '%m/%d/%Y')) + query = query & Q(purchase_date__gte=purchase_date_from) + + if 'purchase_date_to' in args and args['purchase_date_to']: + purchase_date_to = datetime.date( + datetime.strptime(args['purchase_date_to'], '%m/%d/%Y')) + query = query & Q(purchase_date__lte=purchase_date_to) + + ## Liabilities, Receivables + + # Assets + + if 'due_date_from' in args and args['due_date_from']: + due_date_from = datetime.date( + datetime.strptime(args['due_date_from'], '%m/%d/%Y')) + query = query & Q(due_date__gte=due_date_from) + + if 'due_date_to' in args and args['due_date_to']: + due_date_to = datetime.date( + datetime.strptime(args['due_date_to'], '%m/%d/%Y')) + query = query & Q(due_date__lte=due_date_to) + + if 'equity_type' in args and args['equity_type']: + equity_type = args['equity_type'] + query = query & Q(equity_type=equity_type) + + if 'asset_type' in args and args['asset_type']: + asset_type = args['asset_type'] + query = query & Q(asset_type=asset_type) + + return query + + +# +# Categories +# + +@handle_response_format +@treeio_login_required +def index_categories(request, response_format='html'): + "Finance categories page" + + transactions = Object.filter_by_request(request, Transaction.objects) + liabilities = Object.filter_by_request(request, Liability.objects) + + categories = Object.filter_by_request(request, Category.objects) + + return render_to_response('finance/index_categories', + {'categories': categories, + 'transactions': transactions, + 'liabilities': liabilities}, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def category_edit(request, category_id, response_format='html'): + "category edit page" + category = get_object_or_404(Category, pk=category_id) + if request.POST: + if not 'cancel' in request.POST: + form = CategoryForm( + request.user.profile, request.POST, instance=category) + if form.is_valid(): + category = form.save() + return HttpResponseRedirect(reverse('finance_category_view', args=[category.id])) + else: + return HttpResponseRedirect(reverse('finance_category_view', args=[category.id])) + else: + form = CategoryForm(request.user.profile, instance=category) + return render_to_response('finance/category_edit', + {'form': form, 'category': category}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def category_add(request, response_format='html'): + "new category form" + categories = Object.filter_by_request(request, Category.objects, mode="r") + if request.POST: + if 'cancel' not in request.POST: + category = Category() + form = CategoryForm( + request.user.profile, request.POST, instance=category) + if form.is_valid(): + category = form.save() + category.set_user_from_request(request) + return HttpResponseRedirect(reverse('finance_category_view', args=[category.id])) + else: + return HttpResponseRedirect(reverse('finance_categories')) + else: + form = CategoryForm(request.user.profile) + return render_to_response('finance/category_add', {'form': form, 'categories': categories}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def category_view(request, category_id, response_format='html'): + "Single category view page" + category = get_object_or_404(Category, pk=category_id) + + if not request.user.profile.has_permission(category): + return user_denied(request, message="You don't have access to this Category") + + if 'massform1' in request.POST: + for key in request.POST: + if 'mass-transaction' in key: + try: + transaction = Transaction.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=transaction) + if form.is_valid() and request.user.profile.has_permission(transaction, mode='w'): + form.save() + except Exception: + pass + + massform_transaction = MassActionForm(request.user.profile) + + if 'massform2' in request.POST: + for key in request.POST: + if 'mass-liability' in key: + try: + liability = Liability.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=liability) + if form.is_valid() and request.user.profile.has_permission(liability, mode='w'): + form.save() + except Exception: + pass + + massform_liability = MassActionForm(request.user.profile) + + transactions = Object.filter_by_request(request, Transaction.objects) + liabilities = Object.filter_by_request(request, Liability.objects) + + return render_to_response('finance/category_view', + {'category': category, + 'transactions': transactions, + 'liabilities': liabilities, + 'massform_transaction': massform_transaction, + 'massform_liability': massform_liability}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def category_delete(request, category_id, response_format='html'): + "Category delete" + + category = get_object_or_404(Category, pk=category_id) + if not request.user.profile.has_permission(category, mode='w'): + return user_denied(request, "You don't have access to this Category", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + category.trash = True + category.save() + else: + category.delete() + return HttpResponseRedirect(reverse('finance_settings_view')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_category_view', args=[category.id])) + + return render_to_response('finance/category_delete', + {'category': category}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Accounts +# + + +@handle_response_format +@treeio_login_required +def account_edit(request, account_id, response_format='html'): + "account edit page" + account = get_object_or_404(Account, pk=account_id) + if request.POST: + if 'cancel' not in request.POST: + form = AccountForm( + request.user.profile, request.POST, instance=account) + if form.is_valid(): + account = form.save(commit=False) + convert(account, 'balance') + return HttpResponseRedirect(reverse('finance_account_view', args=[account.id])) + else: + return HttpResponseRedirect(reverse('finance_account_view', args=[account.id])) + + else: + form = AccountForm(request.user.profile, instance=account) + return render_to_response('finance/account_edit', + {'form': form, 'account': account}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def account_add(request, response_format='html'): + "new account form" + if request.POST: + if 'cancel' not in request.POST: + account = Account() + form = AccountForm( + request.user.profile, request.POST, instance=account) + if form.is_valid(): + account = form.save(commit=False) + convert(account, 'balance') + account.set_user_from_request(request) + return HttpResponseRedirect(reverse('finance_account_view', args=[account.id])) + else: + return HttpResponseRedirect(reverse('finance_index_accounts')) + else: + form = AccountForm(request.user.profile) + return render_to_response('finance/account_add', {'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def account_view(request, account_id, response_format='html'): + "Single transaction view page" + account = get_object_or_404(Account, pk=account_id) + return render_to_response('finance/account_view', + {'account': account}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def account_delete(request, account_id, response_format='html'): + "Account delete" + + account = get_object_or_404(Account, pk=account_id) + if not request.user.profile.has_permission(account, mode='w'): + return user_denied(request, "You don't have access to this Account", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + account.trash = True + account.save() + else: + account.delete() + return HttpResponseRedirect(reverse('finance_index_accounts')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_account_view', args=[account.id])) + + return render_to_response('finance/account_delete', + {'account': account}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Assets +# + + +@handle_response_format +@treeio_login_required +def index_assets(request, response_format='html'): + "Index_assets page: displays all Assets" + + if request.GET: + query = _get_filter_query(Asset, request.GET) + else: + query = Q() + + filters = AssetFilterForm(request.user.profile, 'title', request.GET) + + assets = Object.filter_by_request( + request, Asset.objects.filter(query), mode="r") + + return render_to_response('finance/index_assets', + {'assets': assets, + 'filters': filters + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def asset_edit(request, asset_id, response_format='html'): + "asset edit page" + asset = get_object_or_404(Asset, pk=asset_id) + if request.POST: + if 'cancel' not in request.POST: + form = AssetForm( + request.user.profile, request.POST, instance=asset) + if form.is_valid(): + asset = form.save() + return HttpResponseRedirect(reverse('finance_asset_view', args=[asset.id])) + else: + return HttpResponseRedirect(reverse('finance_asset_view', args=[asset.id])) + else: + form = AssetForm(request.user.profile, instance=asset) + return render_to_response('finance/asset_edit', + {'form': form, 'asset': asset}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def asset_add(request, response_format='html'): + "new asset form" + assets = Object.filter_by_request(request, Asset.objects, mode="r") + if request.POST: + if 'cancel' not in request.POST: + asset = Asset() + form = AssetForm( + request.user.profile, request.POST, instance=asset) + if form.is_valid(): + asset = form.save() + asset.set_user_from_request(request) + return HttpResponseRedirect(reverse('finance_asset_view', args=[asset.id])) + else: + return HttpResponseRedirect(reverse('finance_index_assets')) + else: + form = AssetForm(request.user.profile) + return render_to_response('finance/asset_add', {'form': form, 'assets': assets}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def asset_view(request, asset_id, response_format='html'): + "Single transaction view page" + asset = get_object_or_404(Asset, pk=asset_id) + return render_to_response('finance/asset_view', + {'asset': asset}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def asset_delete(request, asset_id, response_format='html'): + "Asset delete" + + asset = get_object_or_404(Asset, pk=asset_id) + if not request.user.profile.has_permission(asset, mode='w'): + return user_denied(request, "You don't have access to this Asset", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + asset.trash = True + asset.save() + else: + asset.delete() + return HttpResponseRedirect(reverse('finance_index_assets')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_asset_view', args=[asset.id])) + + return render_to_response('finance/asset_delete', + {'asset': asset}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Equities +# + + +@handle_response_format +@treeio_login_required +def index_equities(request, response_format='html'): + "Index_equities page: displays all Equities" + if request.GET: + query = _get_filter_query(Equity, request.GET) + else: + query = Q() + + filters = EquityFilterForm( + request.user.profile, 'title', request.GET) + + equities = Object.filter_by_request( + request, Equity.objects.filter(query), mode="r") + + return render_to_response('finance/index_equities', + {'equities': equities, + 'filters': filters + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def equity_edit(request, equity_id, response_format='html'): + "equity edit page" + equity = get_object_or_404(Equity, pk=equity_id) + if request.POST: + if 'cancel' not in request.POST: + form = EquityForm( + request.user.profile, request.POST, instance=equity) + if form.is_valid(): + equity = form.save() + return HttpResponseRedirect(reverse('finance_equity_view', args=[equity.id])) + else: + return HttpResponseRedirect(reverse('finance_equity_view', args=[equity.id])) + else: + form = EquityForm(request.user.profile, instance=equity) + return render_to_response('finance/equity_edit', + {'form': form, 'equity': equity}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def equity_add(request, response_format='html'): + "new equity form" + equities = Object.filter_by_request(request, Equity.objects, mode="r") + if request.POST: + if 'cancel' not in request.POST: + equity = Equity() + form = EquityForm( + request.user.profile, request.POST, instance=equity) + if form.is_valid(): + equity = form.save() + equity.set_user_from_request(request) + return HttpResponseRedirect(reverse('finance_equity_view', args=[equity.id])) + else: + return HttpResponseRedirect(reverse('finance_index_equities')) + else: + form = EquityForm(request.user.profile) + return render_to_response('finance/equity_add', {'form': form, 'equities': equities}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def equity_view(request, equity_id, response_format='html'): + "Single transaction view page" + equity = get_object_or_404(Equity, pk=equity_id) + return render_to_response('finance/equity_view', + {'equity': equity}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def equity_delete(request, equity_id, response_format='html'): + "Equity delete" + + equity = get_object_or_404(Equity, pk=equity_id) + if not request.user.profile.has_permission(equity, mode='w'): + return user_denied(request, "You don't have access to this Equity", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + equity.trash = True + equity.save() + else: + equity.delete() + return HttpResponseRedirect(reverse('finance_index_equities')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_equity_view', args=[equity.id])) + + return render_to_response('finance/equity_delete', + {'equity': equity}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Transactions +# + + +@handle_response_format +@treeio_login_required +def index_transactions(request, response_format='html'): + "Index_transactions page: displays all Transactions" + if request.GET: + query = _get_filter_query(Transaction, request.GET) + else: + query = Q() + + if 'massform' in request.POST: + for key in request.POST: + if 'mass-transaction' in key: + try: + transaction = Transaction.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=transaction) + if form.is_valid() and request.user.profile.has_permission(transaction, mode='w'): + form.save() + except: + pass + + massform = MassActionForm(request.user.profile) + + transactions = Object.filter_by_request( + request, Transaction.objects.filter(query), mode="r") + + filters = TransactionFilterForm( + request.user.profile, 'title', request.GET) + + return render_to_response('finance/index_transactions', + {'transactions': transactions, + 'massform': massform, + 'filters': filters + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def transaction_add(request, liability_id=None, order_id=None, response_format='html'): + "new transaction form" + transactions = Object.filter_by_request( + request, Transaction.objects, mode="r") + if request.POST: + if 'cancel' not in request.POST: + transaction = Transaction() + form = TransactionForm( + request.user.profile, None, None, request.POST, instance=transaction) + if form.is_valid(): + transaction = form.save(commit=False) + convert(transaction, 'value') + transaction.set_user_from_request(request) + if order_id: + try: + order = SaleOrder.objects.get(pk=order_id) + order.payment.add(transaction) + order.save() + except: + pass + return HttpResponseRedirect(reverse('finance_transaction_view', args=[transaction.id])) + else: + return HttpResponseRedirect(reverse('finance_index_transactions')) + else: + form = TransactionForm( + request.user.profile, liability_id=liability_id, order_id=order_id) + return render_to_response('finance/transaction_add', {'form': form, 'transactions': transactions}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def transaction_edit(request, transaction_id, response_format='html'): + "Transaction edit page" + transaction = get_object_or_404(Transaction, pk=transaction_id) + if request.POST: + if 'cancel' not in request.POST: + form = TransactionForm( + request.user.profile, None, None, request.POST, instance=transaction) + if form.is_valid(): + transaction = form.save(commit=False) + convert(transaction, 'value') + return HttpResponseRedirect(reverse('finance_transaction_view', args=[transaction.id])) + else: + return HttpResponseRedirect(reverse('finance_transaction_view', args=[transaction.id])) + else: + form = TransactionForm( + request.user.profile, None, None, instance=transaction) + return render_to_response('finance/transaction_edit', + {'form': form, 'transaction': transaction}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def transaction_view(request, transaction_id, response_format='html'): + "Single transaction view page" + transaction = get_object_or_404(Transaction, pk=transaction_id) + return render_to_response('finance/transaction_view', + {'transaction': transaction}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def transaction_delete(request, transaction_id, response_format='html'): + "Transaction delete" + + transaction = get_object_or_404(Transaction, pk=transaction_id) + if not request.user.profile.has_permission(transaction, mode='w'): + return user_denied(request, "You don't have access to this Transaction", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + transaction.trash = True + transaction.save() + else: + transaction.delete() + return HttpResponseRedirect(reverse('finance_index_transactions')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_transaction_view', args=[transaction.id])) + + return render_to_response('finance/transaction_delete', + {'transaction': transaction}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Liabilities +# + + +@handle_response_format +@treeio_login_required +def index_liabilities(request, response_format='html'): + "Index_liabilities page: displays all Liabilities" + if request.GET: + query = _get_filter_query(Liability, request.GET) + else: + query = Q() + + if 'massform' in request.POST: + for key in request.POST: + if 'mass-liability' in key: + try: + liability = Liability.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=liability) + if form.is_valid() and request.user.profile.has_permission(liability, mode='w'): + form.save() + except Exception: + pass + + massform = MassActionForm(request.user.profile) + + filters = LiabilityFilterForm( + request.user.profile, 'title', request.GET) + + liabilities = Object.filter_by_request( + request, Liability.objects.filter(query), mode="r") + + template_liabilities = [] + for liability in liabilities: + if liability.account.owner_id == liability.source_id: + template_liabilities.append(liability) + + return render_to_response('finance/index_liabilities', + {'liabilities': template_liabilities, + 'massform': massform, + 'filters': filters + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def liability_edit(request, liability_id, response_format='html'): + "liability edit page" + liability = get_object_or_404(Liability, pk=liability_id) + if request.POST: + if 'cancel' not in request.POST: + form = LiabilityForm( + request.user.profile, request.POST, instance=liability) + if form.is_valid(): + liability = form.save(commit=False) + convert(liability, 'value') + return HttpResponseRedirect(reverse('finance_liability_view', args=[liability.id])) + else: + return HttpResponseRedirect(reverse('finance_liability_view', args=[liability.id])) + else: + form = LiabilityForm(request.user.profile, instance=liability) + return render_to_response('finance/liability_edit', + {'form': form, 'liability': liability}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def liability_add(request, response_format='html'): + "new liability form" + liabilities = Object.filter_by_request( + request, Liability.objects, mode="r") + if request.POST: + if 'cancel' not in request.POST: + liability = Liability() + form = LiabilityForm( + request.user.profile, request.POST, instance=liability) + if form.is_valid(): + liability = form.save(commit=False) + liability.source = liability.account.owner + convert(liability, 'value') + liability.set_user_from_request(request) + return HttpResponseRedirect(reverse('finance_liability_view', args=[liability.id])) + else: + return HttpResponseRedirect(reverse('finance_index_liabilities')) + else: + form = LiabilityForm(request.user.profile) + return render_to_response('finance/liability_add', {'form': form, 'liabilities': liabilities}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def liability_view(request, liability_id, response_format='html'): + "Single transaction view page" + liability = get_object_or_404(Liability, pk=liability_id) + + transactions = liability.transaction_set.all() + + return render_to_response('finance/liability_view', + {'liability': liability, + 'transactions': transactions}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def liability_delete(request, liability_id, response_format='html'): + "Liability delete" + + liability = get_object_or_404(Liability, pk=liability_id) + if not request.user.profile.has_permission(liability, mode='w'): + return user_denied(request, "You don't have access to this Liability", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + liability.trash = True + liability.save() + else: + liability.delete() + return HttpResponseRedirect(reverse('finance_index_liabilities')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_liability_view', args=[liability.id])) + + return render_to_response('finance/liability_delete', + {'liability': liability}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Receivables +# + +@handle_response_format +@treeio_login_required +def index_receivables(request, response_format='html'): + "Index_receivables page: displays all Liabilities" + if request.GET: + query = _get_filter_query(Liability, request.GET) + else: + query = Q() + + if 'massform' in request.POST: + for key in request.POST: + if 'mass-liability' in key: + try: + liability = Liability.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=liability) + if form.is_valid() and request.user.profile.has_permission(liability, mode='w'): + form.save() + except Exception: + pass + + massform = MassActionForm(request.user.profile) + + filters = LiabilityFilterForm( + request.user.profile, 'title', request.GET) + + receivables = Object.filter_by_request( + request, Liability.objects.filter(query), mode="r") + + template_receivables = [] + for receivable in receivables: + if receivable.account.owner_id == receivable.target_id: + template_receivables.append(receivable) + + return render_to_response('finance/index_receivables', + {'liabilities': template_receivables, + 'filters': filters, + 'massform': massform}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def receivable_add(request, response_format='html'): + "new receivable form" + if request.POST: + if 'cancel' not in request.POST: + receivable = Liability() + form = ReceivableForm( + request.user.profile, request.POST, instance=receivable) + if form.is_valid(): + receivable = form.save(commit=False) + receivable.target = receivable.account.owner + convert(receivable, 'value') + receivable.set_user_from_request(request) + return HttpResponseRedirect(reverse('finance_receivable_view', args=[receivable.id])) + else: + return HttpResponseRedirect(reverse('finance_index_receivables')) + else: + form = ReceivableForm(request.user.profile) + return render_to_response('finance/receivable_add', {'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def receivable_edit(request, receivable_id, response_format='html'): + "Liability edit page" + receivable = get_object_or_404(Liability, pk=receivable_id) + if request.POST: + if 'cancel' not in request.POST: + form = ReceivableForm( + request.user.profile, request.POST, instance=receivable) + if form.is_valid(): + receivable = form.save(commit=False) + convert(receivable, 'value') + return HttpResponseRedirect(reverse('finance_receivable_view', args=[receivable.id])) + else: + return HttpResponseRedirect(reverse('finance_receivable_view', args=[receivable.id])) + else: + form = ReceivableForm(request.user.profile, instance=receivable) + return render_to_response('finance/receivable_edit', + {'form': form, 'liability': receivable}, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def receivable_view(request, receivable_id, response_format='html'): + "Single receivable view page" + receivable = get_object_or_404(Liability, pk=receivable_id) + transactions = receivable.transaction_set.all() + + return render_to_response('finance/receivable_view', + {'liability': receivable, + 'transactions': transactions}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def receivable_delete(request, receivable_id, response_format='html'): + "Receivable delete" + + receivable = get_object_or_404(Liability, pk=receivable_id) + if not request.user.profile.has_permission(receivable, mode='w'): + return user_denied(request, "You don't have access to this Receivable", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + receivable.trash = True + receivable.save() + else: + receivable.delete() + return HttpResponseRedirect(reverse('finance_index_receivables')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_receivable_view', args=[receivable.id])) + + return render_to_response('finance/receivable_delete', + {'liability': receivable}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Reports +# + + +@handle_response_format +@treeio_login_required +def income_view(request, response_format='html'): + "Income Statement view page" + + try: + conf = ModuleSetting.get_for_module('treeio.finance', 'my_company')[0] + my_company = Contact.objects.get(pk=long(conf.value), trash=False) + + except Exception: + my_company = None + + if my_company: + categories = Category.objects.filter(trash=False) + transactions = Transaction.objects.filter( + account__owner=my_company, trash=False) + else: + categories = Object.filter_by_request(request, Category.objects) + transactions = Object.filter_by_request(request, Transaction.objects) + + total = 0 + revenues = 0 + expenses = 0 + + if my_company: + # Receivables + for receivable in Liability.objects.filter(target=my_company, trash=False): + value = receivable.value + paid = receivable.transaction_set.filter( + source=my_company, trash=False).aggregate(Sum('value')) + if paid['value__sum']: + value = receivable.value - paid['value__sum'] + if value > 0: + revenues += value + total += value + + for category in categories: + if receivable.category == category: + category.revenue += value + + # Actual Transactions + for transaction in transactions: + val = transaction.get_relative_value() + total += val + if val > 0: + revenues += val + else: + expenses += abs(val) + + for category in categories: + for transaction in transactions: + if transaction.category == category: + val = transaction.get_relative_value() + if val > 0: + category.revenue += val + else: + category.expense += abs(val) + + return render_to_response('finance/income_view', + {'transactions': transactions, + 'categories': categories, + 'total': total, + 'revenues': revenues, + 'expenses': expenses + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def balance_sheet(request, response_format='html'): + "Balance Sheet view page" + + balance = {'assets_fixed': Decimal('0.0'), + 'assets_intangible': Decimal('0.0'), + 'assets_inventories': Decimal('0.0'), + 'assets_receivables': Decimal('0.0'), + 'assets_prepayments': Decimal('0.0'), + 'assets_cash': Decimal('0.0'), + 'assets_total': Decimal('0.0'), + 'liabilities_borrowings': Decimal('0.0'), + 'liabilities_payables': Decimal('0.0'), + 'liabilities_total': Decimal('0.0'), + 'equity_share_capital': Decimal('0.0'), + 'equity_share_premium': Decimal('0.0'), + 'equity_retained': Decimal('0.0'), + 'equity_total': Decimal('0.0'), + 'equity_liabilities_total': Decimal('0.0')} + + try: + conf = ModuleSetting.get_for_module('treeio.finance', 'my_company')[0] + my_company = Contact.objects.get(pk=long(conf.value), trash=False) + except: + my_company = None + + if my_company: + + # Assets Fixed and Intangible + for asset in Asset.objects.filter(owner=my_company, current_value__gt=0, trash=False): + if asset.asset_type == 'fixed': + balance['assets_fixed'] += asset.current_value + elif asset.asset_type == 'intangible': + balance['assets_intangible'] += asset.current_value + + # Inventories + for product in Product.objects.filter(active=True, buy_price__isnull=False, stock_quantity__gt=0, + trash=False): + balance['assets_inventories'] += product.buy_price * \ + product.stock_quantity + + # Receivables + for receivable in Liability.objects.filter(target=my_company, trash=False): + value = receivable.value + paid = receivable.transaction_set.filter( + source=my_company, trash=False).aggregate(Sum('value')) + if paid['value__sum']: + value = receivable.value - paid['value__sum'] + balance['assets_receivables'] += value + balance['equity_retained'] += value + + # Assets Cash + for account in Account.objects.filter(owner=my_company, trash=False): + account_balance = account.get_balance() + if account_balance > 0: + balance['assets_cash'] += account_balance + + # Prepayments (and Liabilities, since retrieving Liabilities anyway) + for liability in Liability.objects.filter(source=my_company, trash=False): + value = liability.value + paid = liability.transaction_set.filter( + source=my_company, trash=False).aggregate(Sum('value')) + if paid['value__sum']: + value = liability.value - paid['value__sum'] + if value > 0: + # If Due Date is more than a year from now, that's a borrowing + if liability.due_date and liability.due_date >= (datetime.today() + timedelta(days=365)).date(): + balance['liabilities_borrowings'] += value + else: + balance['liabilities_payables'] += value + else: + balance['assets_prepayments'] += -value + + # Assets Total + balance['assets_total'] = balance['assets_fixed'] + balance['assets_total'] += balance['assets_intangible'] + balance['assets_total'] += balance['assets_inventories'] + balance['assets_total'] += balance['assets_receivables'] + balance['assets_total'] += balance['assets_prepayments'] + balance['assets_total'] += balance['assets_cash'] + + # Liabilities Total + balance['liabilities_total'] += balance['liabilities_borrowings'] + balance['liabilities_total'] += balance['liabilities_payables'] + + # Shares + for share in Equity.objects.filter(issuer=my_company, trash=False): + capital = share.issue_price * share.amount + premium = (share.sell_price - share.issue_price) * share.amount + balance['equity_share_capital'] += capital + balance['equity_share_premium'] += premium + + # Retained Revenues calculated out of Transactions + for transaction in Transaction.objects.filter(account__owner=my_company, trash=False): + balance['equity_retained'] += transaction.get_relative_value() + + # Total Equities + balance['equity_total'] = balance['equity_share_capital'] + balance['equity_total'] += balance['equity_share_premium'] + balance['equity_total'] += balance['equity_retained'] + + # Total Equities + Liabilities + balance['equity_liabilities_total'] = balance[ + 'equity_total'] + balance['liabilities_total'] + + context = {'company': my_company, 'today': datetime.today(), 'red': False} + if balance['equity_liabilities_total'] != balance['assets_total']: + context['red'] = True + + context.update(balance) + + return render_to_response('finance/balance_sheet', context, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Settings +# +@handle_response_format +@treeio_login_required +def index_accounts(request, response_format='html'): + "Settings" + + if not request.user.profile.is_admin('treeio.finance'): + return user_denied(request, message="You don't have administrator access to the Finance module") + + if request.GET: + query = _get_filter_query(Account, request.GET) + else: + query = Q() + + filters = AccountFilterForm( + request.user.profile, 'title', request.GET) + + all_accounts = Object.filter_by_request( + request, Account.objects.filter(query)) + + return render_to_response('finance/index_accounts', + { + 'accounts': all_accounts, + 'filters': filters + }, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Settings +# + + +@treeio_login_required +@handle_response_format +def settings_view(request, response_format='html'): + "Settings" + + # default currency + if not request.user.profile.is_admin('treeio.finance'): + return user_denied(request, message="You don't have administrator access to the Finance module") + + try: + default_currency = Currency.objects.get(is_default=True) + except Exception: + default_currency = "$" + + # default company + try: + conf = ModuleSetting.get_for_module('treeio.finance', 'my_company')[0] + my_company = Contact.objects.get(pk=long(conf.value)) + + except Exception: + my_company = None + + # default account + try: + conf = ModuleSetting.get_for_module( + 'treeio.finance', 'default_account')[0] + default_account = Account.objects.get(pk=long(conf.value)) + except Exception: + default_account = None + + # check not trashed + if my_company: + if my_company.trash: + my_company = None + if default_account: + if default_account.trash: + default_account = None + + categories = Object.filter_by_request( + request, Category.objects.filter(trash=False)) + + # all currencies + currencies = Object.filter_by_request( + request, Currency.objects.filter(trash=False)) + + if request.GET and 'export' in request.GET: + transactions = Transaction.objects.filter(trash=False) + # Export all contacts into a CSV file + export = ProcessTransactions() + return export.export_transactions(transactions) + + if request.POST: + if 'file' in request.FILES: + csv_file = request.FILES['file'] + + # TODO: check file extension + content = csv_file.read() + Import = ProcessTransactions() + Import.import_transactions(content) + + return render_to_response('finance/settings_view', + { + 'default_account': default_account, + 'default_currency': default_currency, + 'my_company': my_company, + 'categories': categories, + 'currencies': currencies, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def settings_edit(request, response_format='html'): + "Settings" + + if not request.user.profile.is_admin('treeio.finance'): + return user_denied(request, message="You don't have administrator access to the Finance module") + + if request.POST: + if 'cancel' not in request.POST: + form = SettingsForm(request.user.profile, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('finance_settings_view')) + else: + return HttpResponseRedirect(reverse('finance_settings_view')) + else: + form = SettingsForm(request.user.profile) + + return render_to_response('finance/settings_edit', + { + 'form': form, + }, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Currency +# + +@treeio_login_required +@handle_response_format +def currency_add(request, response_format='html'): + "Currency add" + + if not request.user.profile.is_admin('treeio.finance'): + return user_denied(request, message="You don't have administrator access to the Finance module") + + if request.POST: + if 'cancel' not in request.POST: + currency = Currency() + form = CurrencyForm( + request.user.profile, request.POST, instance=currency) + if form.is_valid(): + currency = form.save(commit=False) + cname = dict_currencies[currency.code] + currency.name = cname[cname.index(' ') + 2:] + # currency.factor = 1.0 #Get currency conversion here + currency.save() + currency.set_user_from_request(request) + return HttpResponseRedirect(reverse('finance_currency_view', args=[currency.id])) + else: + return HttpResponseRedirect(reverse('finance_settings_view')) + else: + form = CurrencyForm(request.user.profile) + + return render_to_response('finance/currency_add', + {'form': form, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def currency_edit(request, currency_id, response_format='html'): + "Currency edit" + + currency = get_object_or_404(Currency, pk=currency_id) + if not request.user.profile.has_permission(currency, mode='w') \ + and not request.user.profile.is_admin('treeio_finance'): + return user_denied(request, "You don't have access to this Currency", response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = CurrencyForm( + request.user.profile, request.POST, instance=currency) + if form.is_valid(): + currency = form.save() + return HttpResponseRedirect(reverse('finance_currency_view', args=[currency.id])) + else: + return HttpResponseRedirect(reverse('finance_currency_view', args=[currency.id])) + else: + form = CurrencyForm(request.user.profile, instance=currency) + + return render_to_response('finance/currency_edit', + {'form': form, + 'currency': currency}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def currency_view(request, currency_id, response_format='html'): + "View a currency" + + currency = get_object_or_404(Currency, pk=currency_id) + if not request.user.profile.has_permission(currency, mode='r') \ + and not request.user.profile.is_admin('treeio_finance'): + return user_denied(request, "You don't have access to this Currency", response_format) + + return render_to_response('finance/currency_view', + {'currency': currency}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def currency_delete(request, currency_id, response_format='html'): + "Currency delete" + + currency = get_object_or_404(Currency, pk=currency_id) + if not request.user.profile.has_permission(currency, mode='w'): + return user_denied(request, "You don't have access to this Currency", response_format) + + if currency.is_default: + return user_denied(request, "You cannot delete the Base Currency", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + currency.trash = True + currency.save() + else: + currency.delete() + return HttpResponseRedirect(reverse('finance_settings_view')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_currency_view', args=[currency.id])) + + return render_to_response('finance/currency_delete', + {'currency': currency}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Tax +# + + +@treeio_login_required +@handle_response_format +def tax_add(request, response_format='html'): + "Tax add" + + if not request.user.profile.is_admin('treeio.finance'): + return user_denied(request, message="You don't have administrator access to the Finance module") + + if request.POST: + if 'cancel' not in request.POST: + tax = Tax() + form = TaxForm( + request.user.profile, request.POST, instance=tax) + if form.is_valid(): + tax = form.save() + tax.set_user_from_request(request) + return HttpResponseRedirect(reverse('finance_tax_view', args=[tax.id])) + else: + return HttpResponseRedirect(reverse('finance_settings_view')) + else: + form = TaxForm(request.user.profile) + + return render_to_response('finance/tax_add', + {'form': form, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def tax_edit(request, tax_id, response_format='html'): + "Tax edit" + + tax = get_object_or_404(Tax, pk=tax_id) + if not request.user.profile.has_permission(tax, mode='w') \ + and not request.user.profile.is_admin('treeio_finance'): + return user_denied(request, "You don't have access to this Tax", response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = TaxForm( + request.user.profile, request.POST, instance=tax) + if form.is_valid(): + tax = form.save() + return HttpResponseRedirect(reverse('finance_tax_view', args=[tax.id])) + else: + return HttpResponseRedirect(reverse('finance_tax_view', args=[tax.id])) + else: + form = TaxForm(request.user.profile, instance=tax) + + return render_to_response('finance/tax_edit', + {'form': form, + 'tax': tax}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def tax_view(request, tax_id, response_format='html'): + "View a tax" + + tax = get_object_or_404(Tax, pk=tax_id) + if not request.user.profile.has_permission(tax, mode='r') \ + and not request.user.profile.is_admin('treeio_finance'): + return user_denied(request, "You don't have access to this Tax", response_format) + + return render_to_response('finance/tax_view', + {'tax': tax}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def tax_delete(request, tax_id, response_format='html'): + "Tax delete" + + tax = get_object_or_404(Tax, pk=tax_id) + if not request.user.profile.has_permission(tax, mode='w'): + return user_denied(request, "You don't have access to this Tax", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + tax.trash = True + tax.save() + else: + tax.delete() + return HttpResponseRedirect(reverse('finance_settings_view')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('finance_tax_view', args=[tax.id])) + + return render_to_response('finance/tax_delete', + {'tax': tax}, + context_instance=RequestContext(request), response_format=response_format) diff --git a/data/treeio/treeio/treeio/identities/api/handlers.py b/data/treeio/treeio/treeio/identities/api/handlers.py new file mode 100644 index 0000000..5fd4c2d --- /dev/null +++ b/data/treeio/treeio/treeio/identities/api/handlers.py @@ -0,0 +1,124 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding:utf-8 -*- + +from __future__ import absolute_import, with_statement + +__all__ = ['ContactFieldHandler', 'ContactTypeHandler', 'ContactHandler'] + +from treeio.core.api.utils import rc +from treeio.identities.models import ContactField, ContactType, Contact +from treeio.identities.forms import ContactForm, ContactTypeForm, ContactFieldForm +from treeio.core.api.handlers import ObjectHandler, getOrNone + + +class ContactFieldHandler(ObjectHandler): + "Entrypoint for ContactField model." + model = ContactField + form = ContactFieldForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_identities_fields', [object_id]) + + def flatten_dict(self, request): + return {'data': super(ObjectHandler, self).flatten_dict(request.data)} + + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.identities') + + +class ContactTypeHandler(ObjectHandler): + "Entrypoint for ContactType model." + model = ContactType + form = ContactTypeForm + fields = ('id',) + ContactTypeForm._meta.fields + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_identities_types', [object_id]) + + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.identities') + + +class ContactHandler(ObjectHandler): + "Entrypoint for Contact model." + model = Contact + form = ContactForm + fields = ['id', ('contactvalue_set', ('name', 'value'))] + \ + [i.name for i in model._meta.local_fields if i.name != 'object_ptr'] + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_identities_contacts', [object_id]) + + def create_instance(self, request, *args, **kwargs): + return None + + def check_create_permission(self, request, mode): + type_pk = request.REQUEST.get('type') + try: + request.contact_type = ContactType.objects.get(pk=type_pk) + return request.user.profile.has_permission(request.contact_type, mode='x') + except ContactType.DoesNotExist: + return True + + def create(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + contact_type = getOrNone(ContactType, request.data.get('contact_type')) + if not contact_type or not request.user.profile.has_permission(contact_type, mode='x'): + return rc.FORBIDDEN + + attrs = self.flatten_dict(request) + + form = ContactForm(contact_type=contact_type, **attrs) + if form.is_valid(): + contact = form.save(request, contact_type) + contact.set_user_from_request(request) + return contact + else: + self.status = 400 + return form.errors + + def update(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + pkfield = kwargs.get(self.model._meta.pk.name) or request.data.get( + self.model._meta.pk.name) + + if not pkfield: + return rc.BAD_REQUEST + + item = getOrNone(self.model, pkfield) + if not item: + return rc.NOT_FOUND + + if not request.user.profile.has_permission(item, mode="w"): + return rc.FORBIDDEN + + attrs = self.flatten_dict(request) + + form = ContactForm( + contact_type=item.contact_type, instance=item, **attrs) + if form.is_valid(): + item = form.save(request) + return item + else: + self.status = 400 + return form.errors diff --git a/data/treeio/treeio/treeio/identities/integration.py b/data/treeio/treeio/treeio/identities/integration.py new file mode 100644 index 0000000..0d047ee --- /dev/null +++ b/data/treeio/treeio/treeio/identities/integration.py @@ -0,0 +1,248 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Identities Integration library +""" +from treeio.core.models import Object, ModuleSetting +from treeio.identities.models import Contact, ContactType, ContactValue +from nuconnector import Connector, DataBlock +from django.db.models import Q +from django.db import transaction +from treeio.core.conf import settings + + +def _clean_missing(resource_id, items, user): + "Clean items missing from data of their original resource" + key = '#' + unicode(resource_id) + '.' + contacts = Object.filter_permitted( + user, Contact.objects).filter(nuvius_resource__contains=key) + if not len(contacts) == len(items): + candidates = [] + for contact in contacts: + found = False + for item in items: + itemkey = key + unicode(item.id.raw) + if itemkey in contact.nuvius_resource: + found = True + if not found: + candidates.append(contact) + for victim in candidates: + victim.subscribers.clear() + victim.delete() + + +def _find_duplicates(resource_id, item, user): + "Finds matching items" + + dups = [] + item_id = None + if 'id' in item.raw: + item_id = item.id.raw + + # Finding previously syncd items + if item_id: + key = '#' + unicode(resource_id) + '.' + unicode(item_id) + '#' + dups = Object.filter_permitted(user, Contact.objects).filter( + nuvius_resource__contains=key) + if dups: + return dups + + # Finding equivalent items + # If name and (email or phone) are the same - it's same person + if item.name: + candidates = Object.filter_permitted( + user, Contact.objects).filter(name=item.name.raw).distinct() + dups = [] + if candidates and (item.email or item.phone): + for candidate in candidates: + matching_emails = [] + emails = candidate.contactvalue_set.filter( + field__field_type='email') + if item.email.raw and emails: + matching_emails = emails.filter(value__in=item.email.raw) + phones = candidate.contactvalue_set.filter( + field__field_type='phone') + matching_phones = [] + if item.phone.raw and phones: + matching_phones = phones.filter(value__in=item.phone.raw) + # If anything matches or if we have no emails or no phones at + # all - add to duplicates + if matching_emails or matching_phones or (not emails and not phones): + dups.append(candidate) + elif not candidates and (item.email or item.phone): + query = Q() + if item.email: + query = query & Q(contactvalue__value__in=item.email.raw) + if item.phone: + query = query | Q(contactvalue__value__in=item.phone.raw) + dups = Object.filter_permitted( + user, Contact.objects).filter(query).distinct() + else: + dups = candidates + elif item.email or item.phone: + query = Q() + if item.email: + query = query & Q(contactvalue__value__in=item.email.raw) + if item.phone: + query = query & Q(contactvalue__value__in=item.phone.raw) + dups = Object.filter_permitted(user, Contact.objects).filter(query) + + return dups + + +def _get_contact_type(user): + "Returns default contact_type for integration" + contact_type_name = getattr( + settings, 'HARDTREE_IDENTITIES_DEFAULT_TYPE', 'person') + contact_type = Object.filter_permitted( + user, ContactType.objects).filter(name__iexact=contact_type_name) + try: + contact_type = contact_type[0] + except IndexError: + contact_type = None + return contact_type + + +@transaction.commit_manually +def _do_sync(data, user): + "Run updates" + + resource_id = data.info.application.id.raw + + contact_type = _get_contact_type(user) + + for item in data.result: + item_id = None + if 'id' in item.raw: + item_id = item.id.raw + dups = _find_duplicates(resource_id, item, user) + if dups: + for contact in dups: + transaction.commit() + try: + fields = contact.contact_type.fields + contact.add_nuvius_resource(resource_id, item_id) + if item.name.raw: + contact.name = item.name.raw + if item.email: + fs = fields.filter(field_type='email') + if fs: + for iemail in item.email: + values = contact.contactvalue_set.filter( + field__in=fs, value=iemail.raw) + if not values: + value = ContactValue( + contact=contact, field=fs[0], value=iemail.raw) + value.save() + if item.phone: + fs = fields.filter(field_type='phone') + if fs: + for iphone in item.phone: + values = contact.contactvalue_set.filter( + field__in=fs, value=iphone.raw) + if not values: + value = ContactValue( + contact=contact, field=fs[0], value=iphone.raw) + value.save() + + if item.address: + fs = fields.filter(name='address') + if fs: + for iaddress in item.address: + values = contact.contactvalue_set.filter( + field__in=fs, value__icontains=iaddress.raw) + if not values: + value = ContactValue( + contact=contact, field=fs[0], value=iaddress.raw) + value.save() + if item.website: + fs = fields.filter(name='website') + if fs: + for iwebsite in item.website: + values = contact.contactvalue_set.filter( + field__in=fs, value__icontains=iwebsite.raw) + if not values: + value = ContactValue( + contact=contact, field=fs[0], value=iwebsite.raw) + value.save() + contact.auto_notify = False + contact.save() + transaction.commit() + except KeyboardInterrupt: + transaction.rollback() + break + except: + transaction.rollback() + else: + if contact_type and item.name.raw: + transaction.commit() + try: + contact = Contact(contact_type=contact_type) + contact.add_nuvius_resource(resource_id, item_id) + contact.name = item.name.raw + contact.auto_notify = False + contact.set_user(user) + contact.save() + fields = contact_type.fields + if item.email: + fs = fields.filter(field_type='email') + if fs: + for iemail in item.email: + value = ContactValue( + contact=contact, field=fs[0], value=iemail.raw) + value.save() + if item.phone: + fs = fields.filter(field_type='phone') + if fs: + for iphone in item.phone: + value = ContactValue( + contact=contact, field=fs[0], value=iphone.raw) + value.save() + if item.address: + fs = fields.filter(name='address') + if fs: + for iaddress in item.address: + value = ContactValue( + contact=contact, field=fs[0], value=iaddress.raw) + value.save() + if item.website: + fs = fields.filter(name='website') + if fs: + for iwebsite in item.website: + value = ContactValue( + contact=contact, field=fs[0], value=iwebsite.raw) + value.save() + transaction.commit() + except KeyboardInterrupt: + transaction.rollback() + break + except: + transaction.rollback() + + _clean_missing(resource_id, data.result, user) + + +def sync(user=None): + + if user: + conf = ModuleSetting.get('nuvius_profile', user=user, strict=True) + else: + conf = ModuleSetting.get('nuvius_profile') + + for item in conf: + profile = item.loads() + user = item.user + if user: + connector = Connector(profile_id=profile['id']) + active_resources = ModuleSetting.get_for_module( + 'treeio.identities', 'integration_resource', user=user, strict=True) + for resource in active_resources: + res = resource.loads() + response = connector.get( + '/service/contact-book/contact/data.json/id' + profile['id'] + '/app' + str(res.resource_id)) + data = DataBlock(response['data']) + if data.result_name == 'success': + _do_sync(data, user) diff --git a/data/treeio/treeio/treeio/identities/models.py b/data/treeio/treeio/treeio/identities/models.py new file mode 100644 index 0000000..074199d --- /dev/null +++ b/data/treeio/treeio/treeio/identities/models.py @@ -0,0 +1,185 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Identities module objects +""" + +from django.db import models +from treeio.core.models import AccessEntity, User, Object +from django.core.urlresolvers import reverse +from django.db.models.signals import post_save +from treeio.core.conf import settings +from django.template import defaultfilters +from unidecode import unidecode + + +class ContactField(Object): + "Represents a field within a ContentType" + FIELD_TYPES = ( + ('text', 'Text'), + ('textarea', 'Multiline Text'), + ('details', 'Details'), + ('url', 'URL'), + ('email', 'E-mail'), + ('phone', 'Phone'), + ('picture', 'Picture'), + ('date', 'Date') + ) + + name = models.CharField(max_length=256) + label = models.CharField(max_length=256) + field_type = models.CharField(max_length=64, choices=FIELD_TYPES) + required = models.BooleanField(default=False) + allowed_values = models.TextField(blank=True, null=True) + details = models.TextField(blank=True, null=True) + + searchable = False + + class Meta: + "ContactField" + ordering = ['name'] + + def __unicode__(self): + return self.label + + +class ContactType(Object): + "Defines a type of Contact entities" + name = models.CharField(max_length=256) + slug = models.CharField(max_length=256) + details = models.TextField(blank=True, null=True) + fields = models.ManyToManyField(ContactField, blank=True, null=True) + + class Meta: + "ContactType" + ordering = ['name'] + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + "Returns absolute URL of the object" + try: + return reverse('identities_index_by_type', args=[self.slug]) + except Exception: + return "" + + def save(self, *args, **kwargs): + "Override to auto-set slug" + self.slug = unicode(self.name).replace(" ", "-") + self.slug = defaultfilters.slugify(unidecode(self.slug)) + super(ContactType, self).save(*args, **kwargs) + + +class Contact(Object): + """Information about a company, group or user. By design allows custom fields defined in ContactField""" + contact_type = models.ForeignKey(ContactType) + name = models.CharField(max_length=256) + parent = models.ForeignKey( + 'self', blank=True, null=True, related_name='child_set') + related_user = models.ForeignKey( + AccessEntity, blank=True, null=True, on_delete=models.SET_NULL) + + access_inherit = ('parent', '*module', '*user') + + class Meta: + "Contact" + ordering = ['name'] + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + "Returns absolute URL of the object" + try: + return reverse('identities_contact_view', args=[self.id]) + except Exception: + return "" + + def get_email(self): + "Returns the first available e-mail" + values = self.contactvalue_set.filter( + field__field_type='email', value__isnull=False) + if values: + return values[0].value + return '' + + def get_picture(self): + values = self.contactvalue_set.filter( + field__field_type='picture', value__isnull=False) + if values and values[0].value: + return values[0].value + + return reverse('identities_contact_view_picture', args=[self.id]) + + def get_or_create_by_email(email, name=None, contact_type=None): + """ + Using a given email tries to find an existing Contact or create new one if not found. + If name is not specified the given email address is used for name instead. + """ + created = False + if not contact_type: + try: + contact_type = ContactType.objects.get(slug='person') + except ContactType.DoesNotExist: + try: + contact_type = ContactType.objects.all()[0] + except KeyError: + return None, created + + if not name: + name = email + + contact = Contact.objects.filter( + contactvalue__value=email, contactvalue__field__field_type='email')[:1] + if contact: + return contact[0], created + else: + contact = Contact(contact_type=contact_type, name=name) + contact.save() + created = True + try: + emailfield = contact_type.fields.filter( + field_type='email')[:1][0] + ContactValue( + field=emailfield, contact=contact, value=email).save() + except IndexError: + pass + + return contact, created + + get_or_create_by_email = staticmethod(get_or_create_by_email) + + +class ContactValue(models.Model): + "A value selected for a Contact" + field = models.ForeignKey(ContactField) + contact = models.ForeignKey(Contact) + value = models.CharField(max_length=1024, null=True, blank=True) + + def __unicode__(self): + return self.value + + def name(self): + return self.field.name + + +def contact_autocreate_handler(sender, instance, created, **kwargs): + "When a User is created, automatically create a Contact of type Person" + if created: + try: + contact_type = ContactType.objects.filter( + models.Q(name='Person') | models.Q(slug='person'))[0] + contact = Contact( + contact_type=contact_type, name=instance.name, related_user=instance) + contact.save() + except: + pass + + +# Autocreate a Contact when Hardtree user is created +if getattr(settings, 'HARDTREE_SIGNALS_AUTOCREATE_CONTACT', True): + post_save.connect(contact_autocreate_handler, sender=User) diff --git a/data/treeio/treeio/treeio/identities/objects.py b/data/treeio/treeio/treeio/identities/objects.py new file mode 100644 index 0000000..c25314c --- /dev/null +++ b/data/treeio/treeio/treeio/identities/objects.py @@ -0,0 +1,142 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Contact module helpers +""" +from treeio.core.models import Module +from django.utils.translation import ugettext_lazy as _ + + +def _get_module_objects(module, current_user, related, getter_name): + + try: + import_name = module.name + ".identities" + modident = __import__(import_name, fromlist=[str(module.name)]) + getter = getattr(modident, getter_name) + return getter(current_user, related) + except ImportError: + pass + except AttributeError: + pass + except KeyError: + pass + + return {} + + +def _preformat_objects(modules, objects): + """ + Formats objects returned from get_contact_objects or get_user_objects + for a more convenient output and templating, structured by module + """ + + output = {} + + if objects: + for module in modules: + output[module.name] = {'module': module, + 'label': _(module.title), + 'count': 0, + 'objects': {}} + for key in objects: + if objects[key]['module'] == module: + if hasattr(objects[key]['objects'], 'count'): + output[module.name][ + 'count'] += objects[key]['objects'].count() + objects[key]['label'] = _(objects[key]['label']) + output[module.name]['objects'][key] = objects[key] + + return output + + +def get_contact_objects(current_user, contact, module=None, preformat=False): + """ + Returns a dictionary with keys specified as contact attributes + and values as dictionaries with labels and set of relevant objects. + + Only modules enabled for the current_user are considered. + """ + + objects = dict() + + if module: + contact_objects = _get_module_objects( + module, current_user, contact, 'get_contact_objects') + if contact_objects: + for key in contact_objects: + contact_objects[key]['module'] = module + objects.update(contact_objects) + if contact.related_user: + try: + objects.update( + get_user_objects(current_user, contact.related_user.user, module)) + except: + pass + modules = [module] + else: + perspective = current_user.get_perspective() + + modules = perspective.modules.filter(display=True).order_by('title') + if not modules: + modules = Module.objects.filter(display=True).order_by('title') + + for module in modules: + contact_objects = _get_module_objects( + module, current_user, contact, 'get_contact_objects') + if contact_objects: + for key in contact_objects: + contact_objects[key]['module'] = module + objects.update(contact_objects) + if contact.related_user: + try: + objects.update( + get_user_objects(current_user, contact.related_user.user, module)) + except: + pass + + if preformat: + return _preformat_objects(modules, objects) + + return objects + + +def get_user_objects(current_user, user, module=None, preformat=False): + """ + Returns a dictionary with keys specified as user attributes + and values as dictionaries with labels, number of relevant objects, + and optionally the actual set of relevant objects. + + Only modules enabled for the current_user are considered. + """ + + objects = dict() + + if module: + user_objects = _get_module_objects( + module, current_user, user, 'get_user_objects') + if user_objects: + for key in user_objects: + user_objects[key]['module'] = module + objects['related_user.' + key] = user_objects[key] + modules = [module] + else: + perspective = current_user.get_perspective() + + modules = perspective.modules.filter(display=True).order_by('title') + if not modules: + modules = Module.objects.filter(display=True).order_by('title') + + for module in modules: + user_objects = _get_module_objects( + module, current_user, user, 'get_user_objects') + if user_objects: + for key in user_objects: + user_objects[key]['module'] = module + objects['related_user.' + key] = user_objects[key] + + if preformat: + return _preformat_objects(modules, objects) + return objects diff --git a/data/treeio/treeio/treeio/identities/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/identities/south_migrations/0001_initial.py new file mode 100644 index 0000000..107d252 --- /dev/null +++ b/data/treeio/treeio/treeio/identities/south_migrations/0001_initial.py @@ -0,0 +1,221 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'ContactField' + db.create_table('identities_contactfield', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('label', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('field_type', self.gf( + 'django.db.models.fields.CharField')(max_length=64)), + ('required', self.gf( + 'django.db.models.fields.BooleanField')(default=False)), + ('allowed_values', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('identities', ['ContactField']) + + # Adding model 'ContactType' + db.create_table('identities_contacttype', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('slug', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('identities', ['ContactType']) + + # Adding M2M table for field fields on 'ContactType' + db.create_table('identities_contacttype_fields', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('contacttype', models.ForeignKey( + orm['identities.contacttype'], null=False)), + ('contactfield', models.ForeignKey( + orm['identities.contactfield'], null=False)) + )) + db.create_unique( + 'identities_contacttype_fields', ['contacttype_id', 'contactfield_id']) + + # Adding model 'Contact' + db.create_table('identities_contact', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('contact_type', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['identities.ContactType'])), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['identities.Contact'])), + ('related_user', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.User'], null=True, blank=True)), + ('related_group', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.Group'], null=True, blank=True)), + )) + db.send_create_signal('identities', ['Contact']) + + # Adding model 'ContactValue' + db.create_table('identities_contactvalue', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('field', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['identities.ContactField'])), + ('contact', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['identities.Contact'])), + ('value', self.gf('django.db.models.fields.CharField') + (max_length=1024, null=True, blank=True)), + )) + db.send_create_signal('identities', ['ContactValue']) + + def backwards(self, orm): + + # Deleting model 'ContactField' + db.delete_table('identities_contactfield') + + # Deleting model 'ContactType' + db.delete_table('identities_contacttype') + + # Removing M2M table for field fields on 'ContactType' + db.delete_table('identities_contacttype_fields') + + # Deleting model 'Contact' + db.delete_table('identities_contact') + + # Deleting model 'ContactValue' + db.delete_table('identities_contactvalue') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'identities.contactvalue': { + 'Meta': {'object_name': 'ContactValue'}, + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'field': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactField']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['identities'] diff --git a/data/treeio/treeio/treeio/identities/south_migrations/0002_auto__chg_field_contact_related_user.py b/data/treeio/treeio/treeio/identities/south_migrations/0002_auto__chg_field_contact_related_user.py new file mode 100644 index 0000000..a659c05 --- /dev/null +++ b/data/treeio/treeio/treeio/identities/south_migrations/0002_auto__chg_field_contact_related_user.py @@ -0,0 +1,158 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'Contact.related_user' + db.alter_column('identities_contact', 'related_user_id', self.gf( + 'django.db.models.fields.related.ForeignKey')(to=orm['core.AccessEntity'], null=True)) + + def backwards(self, orm): + + # Changing field 'Contact.related_user' + db.alter_column('identities_contact', 'related_user_id', self.gf( + 'django.db.models.fields.related.ForeignKey')(to=orm['core.User'], null=True)) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'identities.contactvalue': { + 'Meta': {'object_name': 'ContactValue'}, + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'field': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactField']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['identities'] diff --git a/data/treeio/treeio/treeio/identities/south_migrations/0003_related_accessentity.py b/data/treeio/treeio/treeio/identities/south_migrations/0003_related_accessentity.py new file mode 100644 index 0000000..6889f6d --- /dev/null +++ b/data/treeio/treeio/treeio/identities/south_migrations/0003_related_accessentity.py @@ -0,0 +1,156 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + +class Migration(DataMigration): + + def forwards(self, orm): + "Migrate Contacts to have relate to an AccessEntity instead of User/Group" + for contact in orm['identities.Contact'].objects.all(): + if not contact.related_user and contact.related_group: + contact.related_user = contact.related_group.accessentity_ptr + contact.save() + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'identities.contactvalue': { + 'Meta': {'object_name': 'ContactValue'}, + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'field': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactField']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['identities'] diff --git a/data/treeio/treeio/treeio/identities/south_migrations/0004_auto__del_field_contact_related_group.py b/data/treeio/treeio/treeio/identities/south_migrations/0004_auto__del_field_contact_related_group.py new file mode 100644 index 0000000..cb2de44 --- /dev/null +++ b/data/treeio/treeio/treeio/identities/south_migrations/0004_auto__del_field_contact_related_group.py @@ -0,0 +1,154 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting field 'Contact.related_group' + db.delete_column('identities_contact', 'related_group_id') + + def backwards(self, orm): + + # Adding field 'Contact.related_group' + db.add_column('identities_contact', 'related_group', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.Group'], null=True, blank=True), keep_default=False) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'identities.contactvalue': { + 'Meta': {'object_name': 'ContactValue'}, + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'field': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactField']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['identities'] diff --git a/data/treeio/treeio/treeio/infrastructure/api/handlers.py b/data/treeio/treeio/treeio/infrastructure/api/handlers.py new file mode 100644 index 0000000..4295ea6 --- /dev/null +++ b/data/treeio/treeio/treeio/infrastructure/api/handlers.py @@ -0,0 +1,164 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, with_statement + +__all__ = ['ItemFieldHandler', 'ItemTypeHandler', 'ItemTypeHandler', + 'ItemStatusHandler', 'ItemServicingHandler', 'ItemHandler', + 'LocationHandler'] + +from treeio.core.api.utils import rc +from treeio.core.api.handlers import ObjectHandler, getOrNone +from treeio.core.models import Location +from treeio.infrastructure.models import Item, ItemField, ItemType, ItemStatus, ItemServicing +from treeio.core.forms import LocationForm +from treeio.infrastructure.forms import ItemForm, ItemTypeForm, ItemStatusForm, ItemFieldForm, ServiceRecordForm + + +class InfrastructureCommonHandler(ObjectHandler): + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.infrastructure') + + +class ItemFieldHandler(InfrastructureCommonHandler): + "Entrypoint for ItemField model." + model = ItemField + form = ItemFieldForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_infrastructure_fields', [object_id]) + + def flatten_dict(self, request): + return {'data': super(ObjectHandler, self).flatten_dict(request.data)} + + +class ItemTypeHandler(InfrastructureCommonHandler): + "Entrypoint for ItemType model." + model = ItemType + form = ItemTypeForm + + fields = ('id',) + ItemTypeForm._meta.fields + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_infrastructure_types', [object_id]) + + +class ItemStatusHandler(InfrastructureCommonHandler): + "Entrypoint for ItemStatus model." + model = ItemStatus + form = ItemStatusForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_infrastructure_statuses', [object_id]) + + def flatten_dict(self, request): + return {'data': super(ObjectHandler, self).flatten_dict(request.data)} + + +class ItemServicingHandler(InfrastructureCommonHandler): + "Entrypoint for ItemServicing model." + model = ItemServicing + form = ServiceRecordForm + fields = ('id',) + form._meta.fields + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_infrastructure_service_records', [object_id]) + + +class ItemHandler(ObjectHandler): + "Entrypoint for Item model." + model = Item + form = ItemForm + + fields = ['id', ('itemvalue_set', ('name', 'value'))] + \ + [i.name for i in Item._meta.local_fields if i.name != 'object_ptr'] + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_infrastructure_items', [object_id]) + + def create(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + item_type = getOrNone(ItemType, request.data.get('type')) + if not item_type or not request.user.profile.has_permission(item_type, mode='x'): + return rc.FORBIDDEN + + attrs = self.flatten_dict(request) + + form = ItemForm(item_type=item_type, **attrs) + if form.is_valid(): + item = form.save(request) + return item + else: + self.status = 400 + return form.errors + + def update(self, request, *args, **kwargs): + if request.data is None: + return rc.BAD_REQUEST + + pkfield = kwargs.get(self.model._meta.pk.name) or request.data.get( + self.model._meta.pk.name) + + if not pkfield: + return rc.BAD_REQUEST + + item = getOrNone(self.model, pkfield) + if not item: + return rc.NOT_FOUND + + if not request.user.profile.has_permission(item, mode="w"): + return rc.FORBIDDEN + + attrs = self.flatten_dict(request) + + form = ItemForm(item_type=item.item_type, instance=item, **attrs) + if form.is_valid(): + item = form.save(request) + return item + else: + self.status = 400 + return form.errors + + +class LocationHandler(ObjectHandler): + "Entrypoint for Location model." + model = Location + form = LocationForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_infrastructure_locations', [object_id]) + + def flatten_dict(self, request): + dct = super(LocationHandler, self).flatten_dict(request) + dct['location_id'] = None + return dct diff --git a/data/treeio/treeio/treeio/infrastructure/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/infrastructure/south_migrations/0001_initial.py new file mode 100644 index 0000000..9433dd7 --- /dev/null +++ b/data/treeio/treeio/treeio/infrastructure/south_migrations/0001_initial.py @@ -0,0 +1,406 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'ItemField' + db.create_table('infrastructure_itemfield', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('label', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('field_type', self.gf( + 'django.db.models.fields.CharField')(max_length=64)), + ('required', self.gf( + 'django.db.models.fields.BooleanField')(default=False)), + ('allowed_values', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('infrastructure', ['ItemField']) + + # Adding model 'ItemType' + db.create_table('infrastructure_itemtype', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['infrastructure.ItemType'])), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('infrastructure', ['ItemType']) + + # Adding M2M table for field fields on 'ItemType' + db.create_table('infrastructure_itemtype_fields', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('itemtype', models.ForeignKey( + orm['infrastructure.itemtype'], null=False)), + ('itemfield', models.ForeignKey( + orm['infrastructure.itemfield'], null=False)) + )) + db.create_unique( + 'infrastructure_itemtype_fields', ['itemtype_id', 'itemfield_id']) + + # Adding model 'ItemStatus' + db.create_table('infrastructure_itemstatus', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('active', self.gf('django.db.models.fields.BooleanField') + (default=True)), + ('hidden', self.gf('django.db.models.fields.BooleanField') + (default=False)), + )) + db.send_create_signal('infrastructure', ['ItemStatus']) + + # Adding model 'Item' + db.create_table('infrastructure_item', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('item_type', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['infrastructure.ItemType'])), + ('status', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['infrastructure.ItemStatus'])), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['infrastructure.Item'])), + ('manufacturer', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='items_manufactured', null=True, to=orm['identities.Contact'])), + ('supplier', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='items_supplied', null=True, to=orm['identities.Contact'])), + ('location', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['core.Location'], null=True, blank=True)), + ('owner', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='items_owned', null=True, to=orm['identities.Contact'])), + ('asset', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['finance.Asset'], null=True, blank=True)), + )) + db.send_create_signal('infrastructure', ['Item']) + + # Adding model 'ItemValue' + db.create_table('infrastructure_itemvalue', ( + ('id', self.gf('django.db.models.fields.AutoField') + (primary_key=True)), + ('field', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['infrastructure.ItemField'])), + ('item', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['infrastructure.Item'])), + ('value', self.gf( + 'django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('infrastructure', ['ItemValue']) + + # Adding model 'ItemServicing' + db.create_table('infrastructure_itemservicing', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('supplier', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='itemservice_supplied', null=True, to=orm['identities.Contact'])), + ('start_date', self.gf('django.db.models.fields.DateField') + (null=True, blank=True)), + ('expiry_date', self.gf('django.db.models.fields.DateField') + (null=True, blank=True)), + ('details', self.gf( + 'django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('infrastructure', ['ItemServicing']) + + # Adding M2M table for field items on 'ItemServicing' + db.create_table('infrastructure_itemservicing_items', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('itemservicing', models.ForeignKey( + orm['infrastructure.itemservicing'], null=False)), + ('item', models.ForeignKey(orm['infrastructure.item'], null=False)) + )) + db.create_unique( + 'infrastructure_itemservicing_items', ['itemservicing_id', 'item_id']) + + # Adding M2M table for field billing on 'ItemServicing' + db.create_table('infrastructure_itemservicing_payments', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('itemservicing', models.ForeignKey( + orm['infrastructure.itemservicing'], null=False)), + ('transaction', models.ForeignKey( + orm['finance.transaction'], null=False)) + )) + db.create_unique( + 'infrastructure_itemservicing_payments', ['itemservicing_id', 'transaction_id']) + + def backwards(self, orm): + + # Deleting model 'ItemField' + db.delete_table('infrastructure_itemfield') + + # Deleting model 'ItemType' + db.delete_table('infrastructure_itemtype') + + # Removing M2M table for field fields on 'ItemType' + db.delete_table('infrastructure_itemtype_fields') + + # Deleting model 'ItemStatus' + db.delete_table('infrastructure_itemstatus') + + # Deleting model 'Item' + db.delete_table('infrastructure_item') + + # Deleting model 'ItemValue' + db.delete_table('infrastructure_itemvalue') + + # Deleting model 'ItemServicing' + db.delete_table('infrastructure_itemservicing') + + # Removing M2M table for field items on 'ItemServicing' + db.delete_table('infrastructure_itemservicing_items') + + # Removing M2M table for field billing on 'ItemServicing' + db.delete_table('infrastructure_itemservicing_payments') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.location': { + 'Meta': {'object_name': 'Location', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Location']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'finance.account': { + 'Meta': {'ordering': "['name']", 'object_name': 'Account', '_ormbases': ['core.Object']}, + 'balance': ('django.db.models.fields.FloatField', [], {'default': '0'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}) + }, + 'finance.asset': { + 'Meta': {'ordering': "['-purchase_date']", 'object_name': 'Asset', '_ormbases': ['core.Object']}, + 'asset_type': ('django.db.models.fields.CharField', [], {'default': "'fixed'", 'max_length': '32'}), + 'current_value': ('django.db.models.fields.FloatField', [], {'default': '0'}), + 'depreciation_rate': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'depreciation_type': ('django.db.models.fields.CharField', [], {'default': "'straight'", 'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'endlife_value': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'initial_value': ('django.db.models.fields.FloatField', [], {'default': '0'}), + 'lifetime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'purchase_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'blank': 'True'}) + }, + 'finance.category': { + 'Meta': {'object_name': 'Category', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'finance.liability': { + 'Meta': {'ordering': "['-due_date']", 'object_name': 'Liability', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'due_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.FloatField', [], {}) + }, + 'finance.transaction': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'Transaction', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'liability': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Liability']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.FloatField', [], {}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'infrastructure.item': { + 'Meta': {'ordering': "['name']", 'object_name': 'Item', '_ormbases': ['core.Object']}, + 'asset': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Asset']", 'null': 'True', 'blank': 'True'}), + 'item_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['infrastructure.ItemType']"}), + 'location': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Location']", 'null': 'True', 'blank': 'True'}), + 'manufacturer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'items_manufactured'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'items_owned'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['infrastructure.Item']"}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['infrastructure.ItemStatus']"}), + 'supplier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'items_supplied'", 'null': 'True', 'to': "orm['identities.Contact']"}) + }, + 'infrastructure.itemfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ItemField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'infrastructure.itemservicing': { + 'Meta': {'ordering': "['-expiry_date']", 'object_name': 'ItemServicing', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'expiry_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'items': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['infrastructure.Item']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'payments': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['finance.Transaction']", 'null': 'True', 'blank': 'True'}), + 'start_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'supplier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'itemservice_supplied'", 'null': 'True', 'to': "orm['identities.Contact']"}) + }, + 'infrastructure.itemstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'ItemStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'infrastructure.itemtype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ItemType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['infrastructure.ItemField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['infrastructure.ItemType']"}) + }, + 'infrastructure.itemvalue': { + 'Meta': {'object_name': 'ItemValue'}, + 'field': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['infrastructure.ItemField']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'item': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['infrastructure.Item']"}), + 'value': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + } + } + + complete_apps = ['infrastructure'] diff --git a/data/treeio/treeio/treeio/knowledge/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/knowledge/south_migrations/0001_initial.py new file mode 100644 index 0000000..b787689 --- /dev/null +++ b/data/treeio/treeio/treeio/knowledge/south_migrations/0001_initial.py @@ -0,0 +1,176 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'KnowledgeFolder' + db.create_table('knowledge_knowledgefolder', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('details', self.gf('django.db.models.fields.TextField') + (max_length=255, null=True, blank=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['knowledge.KnowledgeFolder'])), + ('treepath', self.gf('django.db.models.fields.CharField') + (max_length=800)), + )) + db.send_create_signal('knowledge', ['KnowledgeFolder']) + + # Adding model 'KnowledgeCategory' + db.create_table('knowledge_knowledgecategory', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('details', self.gf('django.db.models.fields.TextField') + (max_length=255, null=True, blank=True)), + ('treepath', self.gf('django.db.models.fields.CharField') + (max_length=800)), + )) + db.send_create_signal('knowledge', ['KnowledgeCategory']) + + # Adding model 'KnowledgeItem' + db.create_table('knowledge_knowledgeitem', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('folder', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['knowledge.KnowledgeFolder'])), + ('category', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['knowledge.KnowledgeCategory'], null=True, blank=True)), + ('body', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('treepath', self.gf('django.db.models.fields.CharField') + (max_length=800)), + )) + db.send_create_signal('knowledge', ['KnowledgeItem']) + + def backwards(self, orm): + + # Deleting model 'KnowledgeFolder' + db.delete_table('knowledge_knowledgefolder') + + # Deleting model 'KnowledgeCategory' + db.delete_table('knowledge_knowledgecategory') + + # Deleting model 'KnowledgeItem' + db.delete_table('knowledge_knowledgeitem') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'knowledge.knowledgecategory': { + 'Meta': {'ordering': "['name']", 'object_name': 'KnowledgeCategory', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'treepath': ('django.db.models.fields.CharField', [], {'max_length': '800'}) + }, + 'knowledge.knowledgefolder': { + 'Meta': {'ordering': "['name']", 'object_name': 'KnowledgeFolder', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['knowledge.KnowledgeFolder']"}), + 'treepath': ('django.db.models.fields.CharField', [], {'max_length': '800'}) + }, + 'knowledge.knowledgeitem': { + 'Meta': {'ordering': "['-last_updated']", 'object_name': 'KnowledgeItem', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['knowledge.KnowledgeCategory']", 'null': 'True', 'blank': 'True'}), + 'folder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['knowledge.KnowledgeFolder']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'treepath': ('django.db.models.fields.CharField', [], {'max_length': '800'}) + } + } + + complete_apps = ['knowledge'] diff --git a/data/treeio/treeio/treeio/messaging/api/handlers.py b/data/treeio/treeio/treeio/messaging/api/handlers.py new file mode 100644 index 0000000..3c31bdc --- /dev/null +++ b/data/treeio/treeio/treeio/messaging/api/handlers.py @@ -0,0 +1,190 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, with_statement + +__all__ = ['MailingListHandler', 'MessageStreamHandler', 'MessageHandler'] + +import re +from treeio.core.api.utils import rc +from treeio.core.models import ModuleSetting +from django.core.exceptions import ObjectDoesNotExist +from treeio.identities.models import ContactType, Contact +from treeio.core.api.handlers import ObjectHandler, getOrNone +from treeio.messaging.models import Message, MessageStream, MailingList +from treeio.messaging.forms import MessageForm, MessageStreamForm, MessageReplyForm, MailingListForm + + +class MailingListHandler(ObjectHandler): + "Entrypoint for MailingList model." + model = MailingList + form = MailingListForm + + fields = ('id',) + MailingListForm._meta.fields + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_messaging_mlist', [object_id]) + + def check_create_permission(self, request, mode): + return True + + +class MessageStreamHandler(ObjectHandler): + "Entrypoint for MessageStream model." + model = MessageStream + form = MessageStreamForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_messaging_streams', [object_id]) + + def check_create_permission(self, request, mode): + return True + + +class MessageHandler(ObjectHandler): + "Entrypoint for Message model." + model = Message + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_messaging_messages', [object_id]) + + def create(self, request, *args, **kwargs): + "Send email to some recipients" + + user = request.user.profile + + if request.data is None: + return rc.BAD_REQUEST + + if 'stream' in request.data: + stream = getOrNone(MessageStream, request.data['stream']) + if stream and not user.has_permission(stream, mode='x'): + return rc.FORBIDDEN + + message = Message() + message.author = user.get_contact() + if not message.author: + return rc.FORBIDDEN + + form = MessageForm(user, None, None, request.data, instance=message) + if form.is_valid(): + message = form.save() + message.recipients.add(user.get_contact()) + message.set_user_from_request(request) + message.read_by.add(user) + try: + # if email entered create contact and add to recipients + if 'multicomplete_recipients' in request.POST and request.POST['multicomplete_recipients']: + try: + conf = ModuleSetting.get_for_module( + 'treeio.messaging', 'default_contact_type')[0] + default_contact_type = ContactType.objects.get( + pk=long(conf.value)) + except Exception: + default_contact_type = None + emails = request.POST[ + 'multicomplete_recipients'].split(',') + for email in emails: + emailstr = unicode(email).strip() + if re.match('[a-zA-Z0-9+_\-\.]+@[0-9a-zA-Z][.-0-9a-zA-Z]*.[a-zA-Z]+', emailstr): + contact, created = Contact.get_or_create_by_email( + emailstr, contact_type=default_contact_type) + message.recipients.add(contact) + if created: + contact.set_user_from_request(request) + except: + pass + # send email to all recipients + message.send_email() + return message + else: + self.status = 400 + return form.errors + + def update(self, request, *args, **kwargs): + "Reply to message" + + if request.data is None: + return rc.BAD_REQUEST + + pkfield = kwargs.get(self.model._meta.pk.name) or request.data.get( + self.model._meta.pk.name) + + if not pkfield: + return rc.BAD_REQUEST + + user = request.user.profile + + try: + message = self.model.objects.get(pk=pkfield) + except ObjectDoesNotExist: + return rc.NOT_FOUND + + if not user.has_permission(message): + return rc.FORBIDDEN + + reply = Message() + reply.author = user.get_contact() + if not reply.author: + return rc.FORBIDDEN + + reply.reply_to = message + form = MessageReplyForm( + user, message.stream_id, message, request.data, instance=reply) + if form.is_valid(): + reply = form.save() + reply.set_user_from_request(request) + # Add author to recipients + reply.recipients.add(reply.author) + message.read_by.clear() + + try: + # if email entered create contact and add to recipients + if 'multicomplete_recipients' in request.POST and request.POST['multicomplete_recipients']: + try: + conf = ModuleSetting.get_for_module( + 'treeio.messaging', 'default_contact_type')[0] + default_contact_type = ContactType.objects.get( + pk=long(conf.value)) + except Exception: + default_contact_type = None + emails = request.POST[ + 'multicomplete_recipients'].split(',') + for email in emails: + emailstr = unicode(email).strip() + if re.match('[a-zA-Z0-9+_\-\.]+@[0-9a-zA-Z][.-0-9a-zA-Z]*.[a-zA-Z]+', emailstr): + contact, created = Contact.get_or_create_by_email( + emailstr, contact_type=default_contact_type) + reply.recipients.add(contact) + if created: + contact.set_user_from_request(request) + except: + pass + + # Add each recipient of the reply to the original message + for recipient in reply.recipients.all(): + message.recipients.add(recipient) + + # send email to all recipients + reply.send_email() + return reply + + else: + self.status = 400 + return form.errors diff --git a/data/treeio/treeio/treeio/messaging/emails.py b/data/treeio/treeio/treeio/messaging/emails.py new file mode 100644 index 0000000..6708b82 --- /dev/null +++ b/data/treeio/treeio/treeio/messaging/emails.py @@ -0,0 +1,148 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Messaging Emails +""" + +from threading import Thread +from django.db.models import Q +from django.utils.html import strip_tags +from treeio.core.models import ModuleSetting +from treeio.core.mail import BaseEmail, SystemEmail +from treeio.identities.models import Contact, ContactType + +from treeio.core.mail import EmailReceiver + + +class EmailStream(EmailReceiver): + + "EmailStream" + active = False + + def __init__(self, stream): + self.stream = stream + self.active = True + try: + conf = ModuleSetting.get_for_module( + 'treeio.messaging', 'default_imap_folder')[0] + folder_name = conf.value + except: + folder_name = None + super(EmailStream, self).__init__(stream.incoming_server_type, stream.incoming_server_name, + stream.incoming_server_username, stream.incoming_password, folder_name) + + def process_msg(self, msg, attrs, attachments): + "Save message, Cap!" + from treeio.messaging.models import Message + + try: + conf = ModuleSetting.get_for_module( + 'treeio.messaging', 'default_contact_type')[0] + default_contact_type = ContactType.objects.get(pk=long(conf.value)) + except: + default_contact_type = None + + email_author, created = Contact.get_or_create_by_email( + attrs.author_email, attrs.author_name, default_contact_type) + if created: + email_author.copy_permissions(self.stream) + + # check if the message is already retrieved + existing = Message.objects.filter( + stream=self.stream, title=attrs.subject, author=email_author, body=attrs.body).exists() + if not existing: + message = None + if attrs.subject[:3] == 'Re:': + # process replies + if attrs.subject[:4] == 'Re: ': + original_subject = attrs.subject[4:] + else: + original_subject = attrs.subject[3:] + + try: + query = Q(reply_to__isnull=True) & Q(recipients=email_author) & ( + Q(title=original_subject) | Q(title=attrs.subject)) + original = Message.objects.filter( + query).order_by('-date_created')[:1][0] + message = Message(title=attrs.subject, body=attrs.body, author=email_author, + stream=self.stream, reply_to=original) + if attrs.email_date: + message.date_created = attrs.email_date + + message.save() + message.copy_permissions(original) + original.read_by.clear() + except IndexError: + pass + if not message: + message = Message( + title=attrs.subject, body=attrs.body, author=email_author, stream=self.stream) + if attrs.email_date: + message.date_created = attrs.email_date + message.save() + message.copy_permissions(self.stream) + message.recipients.add(email_author) + + +class EmailMessage(Thread): + + "Email Message" + + def __init__(self, message): + Thread.__init__(self) + self.message = message + + def run(self): + "Run" + self.process_email() + + def send_email(self): + "Send email" + self.process_email() + + def get_smtp_port(self): + "Returns tuple (port, ssl) for current message's stream" + + port = 25 + ssl = False + + if self.message.stream.outgoing_server_type == "SMTP-SSL": + ssl = True + + return port, ssl + + def process_email(self): + "Process email" + message = self.message + if message.reply_to: + subject = "Re: %s" % message.reply_to.title + else: + subject = message.title + body = strip_tags(message.body) + html = message.body + + for recipient in message.recipients.all(): + if recipient.id == message.author_id: + # don't send email to message author + continue + + toaddr = recipient.get_email() + + if message.stream and message.stream.outgoing_server_name: + fromaddr = unicode(message.author) + ' <' + \ + unicode(message.stream.outgoing_email) + '>' + login = message.stream.outgoing_server_username + password = message.stream.outgoing_password + + port, ssl = self.get_smtp_port() + + BaseEmail(message.stream.outgoing_server_name, + login, password, fromaddr, toaddr, subject, + body, signature=None, html=html, port=port, ssl=ssl).send_email() + + else: + SystemEmail( + toaddr, subject, body, signature=None, html=html).send_email() diff --git a/data/treeio/treeio/treeio/messaging/forms.py b/data/treeio/treeio/treeio/messaging/forms.py new file mode 100644 index 0000000..81681aa --- /dev/null +++ b/data/treeio/treeio/treeio/messaging/forms.py @@ -0,0 +1,342 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Messaging model forms +""" +from django import forms +from django.utils.translation import ugettext as _ +from django.core.urlresolvers import reverse +from treeio.core.models import Object, ModuleSetting +from treeio.core.conf import settings +from treeio.core.decorators import preprocess_form +from treeio.messaging.models import Message, MessageStream, MailingList +from django.db.models import Q +from treeio.identities.models import ContactType, Contact + +preprocess_form() + + +class SettingsForm(forms.Form): + """ Administration settings form """ + + default_contact_type = forms.ModelChoiceField( + label='Default Contact Type', queryset=[]) + default_imap_folder = forms.ChoiceField(label='Default IMAP Folder', choices=(('ALL', 'ALL'), + ('UNSEEN', _('UNSEEN'))), + required=False) + signature = forms.CharField(widget=forms.Textarea, required=False) + + def __init__(self, user, *args, **kwargs): + "Sets choices and initial value" + super(SettingsForm, self).__init__(*args, **kwargs) + self.user = user + + self.fields['default_contact_type'].label = _('Default Contact Type') + self.fields['default_contact_type'].queryset = Object.filter_permitted(user, + ContactType.objects, mode='x') + try: + conf = ModuleSetting.get_for_module('treeio.messaging', 'default_contact_type', + user=user)[0] + default_contact_type = ContactType.objects.get(pk=long(conf.value)) + self.fields[ + 'default_contact_type'].initial = default_contact_type.id + except: + pass + + self.fields['default_imap_folder'].label = _('Default IMAP Folder') + try: + conf = ModuleSetting.get_for_module('treeio.messaging', 'default_imap_folder', + user=user)[0] + self.fields['default_imap_folder'].initial = conf.value + except: + self.fields[ + 'default_imap_folder'].initial = settings.HARDTREE_MESSAGING_IMAP_DEFAULT_FOLDER_NAME + + self.fields['signature'].label = _('Signature') + try: + conf = ModuleSetting.get_for_module('treeio.messaging', 'signature', + user=user, strict=True)[0] + signature = conf.value + self.fields['signature'].initial = signature + except: + pass + + def save(self): + "Form processor" + try: + ModuleSetting.set_for_module('default_contact_type', + self.cleaned_data[ + 'default_contact_type'].id, + 'treeio.messaging') + except: + pass + + try: + ModuleSetting.set_for_module('default_imap_folder', + self.cleaned_data[ + 'default_imap_folder'], + 'treeio.messaging') + except: + pass + + try: + ModuleSetting.set_for_module('signature', + self.cleaned_data['signature'], + 'treeio.messaging', user=self.user) + except: + pass + + +class MassActionForm(forms.Form): + """ Mass action form for Messages """ + + mark = forms.ChoiceField(label=_("With selected"), choices=(('', '-----'), ('read', _('Mark Read')), + ('unread', _('Mark Unread')), ( + 'delete', _('Delete Completely')), + ('trash', _('Move to Trash'))), required=False) + stream = forms.ModelChoiceField(queryset=[], required=False) + user = None + markall = forms.ChoiceField(label=_("Mark all"), choices=(('', '-----'), ('markall', _('Mark all as Read'))), + required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + self.user = user + + super(MassActionForm, self).__init__(*args, **kwargs) + + self.fields['stream'].queryset = Object.filter_permitted( + user, MessageStream.objects, mode='x') + self.fields['stream'].label = _("Move to") + self.fields['mark'] = forms.ChoiceField(label=_("With selected"), + choices=(('', '-----'), + ('read', _('Mark Read')), + ('unread', _('Mark Unread')), + ('delete', _('Delete Completely')), + ('trash', _('Move to Trash'))), + required=False) + self.fields['markall'] = forms.ChoiceField(label=_("Mark all"), + choices=(('', '-----'), ('markall', _('Mark all as Read'))), + required=False) + + def save(self, *args, **kwargs): + "Save override to omit empty fields" + if self.instance: + if self.is_valid(): + if self.cleaned_data['stream']: + self.instance.stream = self.cleaned_data['stream'] + if self.user and self.cleaned_data['mark']: + if self.cleaned_data['mark'] == 'read': + try: + self.instance.read_by.add(self.user) + except Exception: + pass + if self.cleaned_data['mark'] == 'unread': + try: + self.instance.read_by.remove(self.user) + except Exception: + pass + self.instance.save() + if self.user and self.cleaned_data['mark']: + if self.cleaned_data['mark'] == 'delete': + self.instance.delete() + if self.cleaned_data['mark'] == 'trash': + self.instance.trash = True + self.instance.save() + else: + if self.user and self.cleaned_data['markall']: + query = Q(reply_to__isnull=True) & ~Q(read_by=self.user) + for message in Object.filter_permitted(self.user, Message.objects.filter(query), mode='x'): + try: + message.read_by.add(self.user) + except Exception: + pass + + +class MessageForm(forms.ModelForm): + """ Message form """ + + def __init__(self, user, stream_id, message=None, *args, **kwargs): + super(MessageForm, self).__init__(*args, **kwargs) + + self.fields['title'].label = _("Subject") + self.fields['title'].widget = forms.TextInput(attrs={'size': '40'}) + self.fields['stream'].queryset = Object.filter_permitted( + user, MessageStream.objects, mode='x') + self.fields['stream'].label = _("Stream") + + self.fields['recipients'].label = _("To") + self.fields['recipients'].help_text = "" + self.fields['recipients'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + if stream_id: + self.fields['stream'].initial = stream_id + self.fields['stream'].widget = forms.HiddenInput() + elif self.fields['stream'].queryset: + self.fields['stream'].initial = self.fields[ + 'stream'].queryset[0].id + + self.fields['body'].label = _("Body") + # signature + try: + conf = ModuleSetting.get_for_module('treeio.messaging', 'signature', + user=user, strict=True)[0] + signature = conf.value + self.fields['body'].initial = signature + except: + pass + + class Meta: + + "Message" + model = Message + fields = ('recipients', 'title', 'stream', 'body') + + +class MessageReplyForm(forms.ModelForm): + """ Message reply form """ + + def __init__(self, user, stream_id, message=None, *args, **kwargs): + super(MessageReplyForm, self).__init__(*args, **kwargs) + + self.fields['recipients'].label = _("To") + self.fields['recipients'].help_text = "" + self.fields['recipients'].initial = [ + contact.id for contact in message.recipients.all()] + try: + user_contact = user.get_contact() + self.fields['recipients'].initial.pop( + self.fields['recipients'].initial.index(user_contact.id)) + except: + pass + self.fields['recipients'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + self.fields['stream'].widget = forms.HiddenInput() + if stream_id: + self.fields['stream'].initial = stream_id + elif self.fields['stream'].queryset: + self.fields['stream'].initial = self.fields[ + 'stream'].queryset[0].id + + self.fields['body'].label = _("Body") + + class Meta: + + "Message Reply" + model = Message + fields = ('recipients', 'stream', 'body') + + +class MessageStreamForm(forms.ModelForm): + """ Message Stream form """ + + def __init__(self, user, *args, **kwargs): + super(MessageStreamForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + self.fields['name'].widget = forms.TextInput(attrs={'size': '30'}) + + self.fields['incoming_server_name'].label = _("Incoming Mail Server") + self.fields['incoming_server_type'].label = _("Server Type") + self.fields['incoming_server_username'].label = _("User Name") + self.fields['incoming_password'].label = _("Password") + self.fields['incoming_password'].widget = forms.PasswordInput() + + self.fields['outgoing_email'].label = _("From Address") + self.fields['outgoing_server_name'].label = _("Outgoing Mail Server") + self.fields['outgoing_server_type'].label = _("Server Type") + self.fields['outgoing_server_username'].label = _("User Name") + self.fields['outgoing_password'].label = _("Password") + self.fields['outgoing_password'].widget = forms.PasswordInput() + + def clean_incoming_password(self): + password = self.cleaned_data['incoming_password'] + if not password and hasattr(self, 'instance') and self.instance.id: + return self.instance.incoming_password + return password + + def clean_outgoing_password(self): + password = self.cleaned_data['outgoing_password'] + if not password and hasattr(self, 'instance') and self.instance.id: + return self.instance.outgoing_password + return password + + class Meta: + + "Message Stream" + model = MessageStream + fields = ('name', 'incoming_server_name', 'incoming_server_type', + 'incoming_server_username', 'incoming_password', 'outgoing_email', + 'outgoing_server_name', 'outgoing_server_username', 'outgoing_password', + 'outgoing_server_type') + + +class MailingListForm(forms.ModelForm): + """ Message Stream form """ + + def __init__(self, user, *args, **kwargs): + super(MailingListForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + self.fields['name'].widget = forms.TextInput(attrs={'size': '30'}) + self.fields['description'].label = _("Description") + self.fields['from_contact'].label = _("Sender Contact Details") + self.fields['members'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['members'].label = _("Members") + self.fields['members'].help_text = None + self.fields['opt_in'].label = _("Opt-In Template") + + class Meta: + "Message Stream" + model = MailingList + fields = ('name', 'description', 'from_contact', 'opt_in', 'members') + + +class FilterForm(forms.ModelForm): + """ Filter form definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(FilterForm, self).__init__(*args, **kwargs) + + if 'title' in skip: + del self.fields['title'] + else: + self.fields['title'].required = False + self.fields['title'].label = _("Title") + + if 'stream' in skip: + del self.fields['stream'] + else: + self.fields['stream'].queryset = Object.filter_permitted( + user, MessageStream.objects, mode='x') + self.fields['stream'].required = False + self.fields['stream'].label = _("Stream") + + if 'author' in skip: + del self.fields['author'] + else: + self.fields['author'].required = False + self.fields['author'].label = _("Author") + self.fields['author'].queryset = Object.filter_permitted( + user, Contact.objects, mode='x') + self.fields['author'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + + class Meta: + + "Filter" + model = Message + fields = ('title', 'author', 'stream') diff --git a/data/treeio/treeio/treeio/messaging/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/messaging/south_migrations/0001_initial.py new file mode 100644 index 0000000..65cfa69 --- /dev/null +++ b/data/treeio/treeio/treeio/messaging/south_migrations/0001_initial.py @@ -0,0 +1,226 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'EmailBox' + db.create_table('messaging_emailbox', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('email_name', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('email_type', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('server_name', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('server_type', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('server_username', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('server_password', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('last_checked', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + )) + db.send_create_signal('messaging', ['EmailBox']) + + # Adding model 'MessageStream' + db.create_table('messaging_messagestream', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('email_incoming', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='incoming', null=True, to=orm['messaging.EmailBox'])), + ('email_outgoing', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='outgoing', null=True, to=orm['messaging.EmailBox'])), + )) + db.send_create_signal('messaging', ['MessageStream']) + + # Adding model 'Message' + db.create_table('messaging_message', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField') + (max_length=255, null=True, blank=True)), + ('body', self.gf('django.db.models.fields.TextField')()), + ('author', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['identities.Contact'])), + ('stream', self.gf('django.db.models.fields.related.ForeignKey') + (related_name='stream', to=orm['messaging.MessageStream'])), + ('reply_to', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['messaging.Message'])), + )) + db.send_create_signal('messaging', ['Message']) + + # Adding M2M table for field read_by on 'Message' + db.create_table('messaging_message_read_by', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('message', models.ForeignKey( + orm['messaging.message'], null=False)), + ('user', models.ForeignKey(orm['core.user'], null=False)) + )) + db.create_unique( + 'messaging_message_read_by', ['message_id', 'user_id']) + + def backwards(self, orm): + + # Deleting model 'EmailBox' + db.delete_table('messaging_emailbox') + + # Deleting model 'MessageStream' + db.delete_table('messaging_messagestream') + + # Deleting model 'Message' + db.delete_table('messaging_message') + + # Removing M2M table for field read_by on 'Message' + db.delete_table('messaging_message_read_by') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'messaging.emailbox': { + 'Meta': {'ordering': "['last_updated']", 'object_name': 'EmailBox', '_ormbases': ['core.Object']}, + 'email_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'email_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'server_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_password': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_username': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'messaging.message': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Message', '_ormbases': ['core.Object']}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'body': ('django.db.models.fields.TextField', [], {}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'read_by': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read_by_user'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['messaging.Message']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stream'", 'to': "orm['messaging.MessageStream']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.messagestream': { + 'Meta': {'ordering': "['name']", 'object_name': 'MessageStream', '_ormbases': ['core.Object']}, + 'email_incoming': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'incoming'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'email_outgoing': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'outgoing'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['messaging'] diff --git a/data/treeio/treeio/treeio/messaging/south_migrations/0002_auto__add_mailinglist__add_template__add_field_message_mlist__chg_fiel.py b/data/treeio/treeio/treeio/messaging/south_migrations/0002_auto__add_mailinglist__add_template__add_field_message_mlist__chg_fiel.py new file mode 100644 index 0000000..7498106 --- /dev/null +++ b/data/treeio/treeio/treeio/messaging/south_migrations/0002_auto__add_mailinglist__add_template__add_field_message_mlist__chg_fiel.py @@ -0,0 +1,353 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'MailingList' + db.create_table('messaging_mailinglist', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('description', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('from_contact', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='from_contact_set', to=orm['identities.Contact'])), + ('opt_in', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['messaging.Template'], null=True, blank=True)), + )) + db.send_create_signal('messaging', ['MailingList']) + + # Adding M2M table for field members on 'MailingList' + db.create_table('messaging_mailinglist_members', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('mailinglist', models.ForeignKey( + orm['messaging.mailinglist'], null=False)), + ('contact', models.ForeignKey( + orm['identities.contact'], null=False)) + )) + db.create_unique( + 'messaging_mailinglist_members', ['mailinglist_id', 'contact_id']) + + # Adding model 'Template' + db.create_table('messaging_template', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('body', self.gf('django.db.models.fields.TextField')()), + ('subject', self.gf('django.db.models.fields.CharField') + (max_length=255)), + )) + db.send_create_signal('messaging', ['Template']) + + # Adding field 'Message.mlist' + db.add_column('messaging_message', 'mlist', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='mlist', null=True, to=orm['messaging.MailingList']), keep_default=False) + + # Adding M2M table for field recipients on 'Message' + db.create_table('messaging_message_recipients', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('message', models.ForeignKey( + orm['messaging.message'], null=False)), + ('contact', models.ForeignKey( + orm['identities.contact'], null=False)) + )) + db.create_unique( + 'messaging_message_recipients', ['message_id', 'contact_id']) + + # Changing field 'Message.stream' + db.alter_column('messaging_message', 'stream_id', self.gf( + 'django.db.models.fields.related.ForeignKey')(null=True, to=orm['messaging.MessageStream'])) + + # Adding field 'MessageStream.incoming_server_name' + db.add_column('messaging_messagestream', 'incoming_server_name', self.gf( + 'django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.incoming_server_type' + db.add_column('messaging_messagestream', 'incoming_server_type', self.gf( + 'django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.incoming_server_username' + db.add_column('messaging_messagestream', 'incoming_server_username', self.gf( + 'django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.incoming_password' + db.add_column('messaging_messagestream', 'incoming_password', self.gf( + 'django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.outgoing_email' + db.add_column('messaging_messagestream', 'outgoing_email', self.gf( + 'django.db.models.fields.EmailField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.outgoing_server_name' + db.add_column('messaging_messagestream', 'outgoing_server_name', self.gf( + 'django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.outgoing_server_type' + db.add_column('messaging_messagestream', 'outgoing_server_type', self.gf( + 'django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.outgoing_server_username' + db.add_column('messaging_messagestream', 'outgoing_server_username', self.gf( + 'django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.outgoing_password' + db.add_column('messaging_messagestream', 'outgoing_password', self.gf( + 'django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) + + # Adding field 'MessageStream.faulty' + db.add_column('messaging_messagestream', 'faulty', self.gf( + 'django.db.models.fields.BooleanField')(default=False), keep_default=False) + + # Adding field 'MessageStream.last_checked' + db.add_column('messaging_messagestream', 'last_checked', self.gf( + 'django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False) + + def backwards(self, orm): + + # Deleting model 'MailingList' + db.delete_table('messaging_mailinglist') + + # Removing M2M table for field members on 'MailingList' + db.delete_table('messaging_mailinglist_members') + + # Deleting model 'Template' + db.delete_table('messaging_template') + + # Deleting field 'Message.mlist' + db.delete_column('messaging_message', 'mlist_id') + + # Removing M2M table for field recipients on 'Message' + db.delete_table('messaging_message_recipients') + + # User chose to not deal with backwards NULL issues for + # 'Message.stream' + raise RuntimeError( + "Cannot reverse this migration. 'Message.stream' and its values cannot be restored.") + + # Deleting field 'MessageStream.incoming_server_name' + db.delete_column('messaging_messagestream', 'incoming_server_name') + + # Deleting field 'MessageStream.incoming_server_type' + db.delete_column('messaging_messagestream', 'incoming_server_type') + + # Deleting field 'MessageStream.incoming_server_username' + db.delete_column('messaging_messagestream', 'incoming_server_username') + + # Deleting field 'MessageStream.incoming_password' + db.delete_column('messaging_messagestream', 'incoming_password') + + # Deleting field 'MessageStream.outgoing_email' + db.delete_column('messaging_messagestream', 'outgoing_email') + + # Deleting field 'MessageStream.outgoing_server_name' + db.delete_column('messaging_messagestream', 'outgoing_server_name') + + # Deleting field 'MessageStream.outgoing_server_type' + db.delete_column('messaging_messagestream', 'outgoing_server_type') + + # Deleting field 'MessageStream.outgoing_server_username' + db.delete_column('messaging_messagestream', 'outgoing_server_username') + + # Deleting field 'MessageStream.outgoing_password' + db.delete_column('messaging_messagestream', 'outgoing_password') + + # Deleting field 'MessageStream.faulty' + db.delete_column('messaging_messagestream', 'faulty') + + # Deleting field 'MessageStream.last_checked' + db.delete_column('messaging_messagestream', 'last_checked') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'messaging.emailbox': { + 'Meta': {'ordering': "['last_updated']", 'object_name': 'EmailBox', '_ormbases': ['core.Object']}, + 'email_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'email_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'server_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_password': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_username': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'messaging.mailinglist': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'MailingList', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_contact_set'", 'to': "orm['identities.Contact']"}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'members_set'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opt_in': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Template']", 'null': 'True', 'blank': 'True'}) + }, + 'messaging.message': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Message', '_ormbases': ['core.Object']}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'body': ('django.db.models.fields.TextField', [], {}), + 'mlist': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'mlist'", 'null': 'True', 'to': "orm['messaging.MailingList']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'read_by': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read_by_user'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'message_recipients'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['messaging.Message']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'stream'", 'null': 'True', 'to': "orm['messaging.MessageStream']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.messagestream': { + 'Meta': {'ordering': "['name', 'last_updated']", 'object_name': 'MessageStream', '_ormbases': ['core.Object']}, + 'email_incoming': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'incoming'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'email_outgoing': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'outgoing'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'faulty': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'incoming_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'outgoing_email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.template': { + 'Meta': {'object_name': 'Template', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['messaging'] diff --git a/data/treeio/treeio/treeio/messaging/south_migrations/0003_merge_emailbox_stream.py b/data/treeio/treeio/treeio/messaging/south_migrations/0003_merge_emailbox_stream.py new file mode 100644 index 0000000..dd036d8 --- /dev/null +++ b/data/treeio/treeio/treeio/messaging/south_migrations/0003_merge_emailbox_stream.py @@ -0,0 +1,213 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + +class Migration(DataMigration): + + def forwards(self, orm): + "Migrate EmailBox: merge with Stream" + for stream in orm['messaging.MessageStream'].objects.all(): + if stream.email_incoming: + stream.incoming_server_name = stream.email_incoming.server_name + stream.incoming_server_type = stream.email_incoming.server_type + stream.incoming_server_username = stream.email_incoming.server_username + stream.incoming_password = stream.email_incoming.server_password + stream.save() + if stream.email_outgoing: + stream.outgoing_email = stream.email_outgoing.server_username + stream.outgoing_server_name = stream.email_outgoing.server_name + stream.outgoing_server_type = stream.email_outgoing.server_type + stream.outgoing_server_username = stream.email_outgoing.server_username + stream.outgoing_password = stream.email_outgoing.server_password + stream.save() + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'messaging.emailbox': { + 'Meta': {'ordering': "['last_updated']", 'object_name': 'EmailBox', '_ormbases': ['core.Object']}, + 'email_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'email_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'server_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_password': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_username': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'messaging.mailinglist': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'MailingList', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_contact_set'", 'to': "orm['identities.Contact']"}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'members_set'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opt_in': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Template']", 'null': 'True', 'blank': 'True'}) + }, + 'messaging.message': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Message', '_ormbases': ['core.Object']}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'body': ('django.db.models.fields.TextField', [], {}), + 'mlist': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'mlist'", 'null': 'True', 'to': "orm['messaging.MailingList']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'read_by': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read_by_user'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'message_recipients'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['messaging.Message']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'stream'", 'null': 'True', 'to': "orm['messaging.MessageStream']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.messagestream': { + 'Meta': {'ordering': "['name', 'last_updated']", 'object_name': 'MessageStream', '_ormbases': ['core.Object']}, + 'email_incoming': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'incoming'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'email_outgoing': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'outgoing'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'faulty': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'incoming_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'outgoing_email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.template': { + 'Meta': {'object_name': 'Template', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['messaging'] diff --git a/data/treeio/treeio/treeio/messaging/south_migrations/0004_auto__del_emailbox__del_field_messagestream_email_outgoing__del_field_.py b/data/treeio/treeio/treeio/messaging/south_migrations/0004_auto__del_emailbox__del_field_messagestream_email_outgoing__del_field_.py new file mode 100644 index 0000000..29aa0ba --- /dev/null +++ b/data/treeio/treeio/treeio/messaging/south_migrations/0004_auto__del_emailbox__del_field_messagestream_email_outgoing__del_field_.py @@ -0,0 +1,222 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting model 'EmailBox' + db.delete_table('messaging_emailbox') + + # Deleting field 'MessageStream.email_outgoing' + db.delete_column('messaging_messagestream', 'email_outgoing_id') + + # Deleting field 'MessageStream.email_incoming' + db.delete_column('messaging_messagestream', 'email_incoming_id') + + def backwards(self, orm): + + # Adding model 'EmailBox' + db.create_table('messaging_emailbox', ( + ('server_password', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('email_type', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('last_checked', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + ('server_type', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('email_name', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('server_name', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('server_username', self.gf( + 'django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('messaging', ['EmailBox']) + + # Adding field 'MessageStream.email_outgoing' + db.add_column('messaging_messagestream', 'email_outgoing', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='outgoing', null=True, to=orm['messaging.EmailBox'], blank=True), keep_default=False) + + # Adding field 'MessageStream.email_incoming' + db.add_column('messaging_messagestream', 'email_incoming', self.gf('django.db.models.fields.related.ForeignKey')( + related_name='incoming', null=True, to=orm['messaging.EmailBox'], blank=True), keep_default=False) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'messaging.mailinglist': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'MailingList', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_contact_set'", 'to': "orm['identities.Contact']"}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'members_set'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opt_in': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Template']", 'null': 'True', 'blank': 'True'}) + }, + 'messaging.message': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Message', '_ormbases': ['core.Object']}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'body': ('django.db.models.fields.TextField', [], {}), + 'mlist': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'mlist'", 'null': 'True', 'to': "orm['messaging.MailingList']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'read_by': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read_by_user'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'message_recipients'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['messaging.Message']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'stream'", 'null': 'True', 'to': "orm['messaging.MessageStream']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.messagestream': { + 'Meta': {'ordering': "['name', 'last_updated']", 'object_name': 'MessageStream', '_ormbases': ['core.Object']}, + 'faulty': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'incoming_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'outgoing_email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.template': { + 'Meta': {'object_name': 'Template', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['messaging'] diff --git a/data/treeio/treeio/treeio/messaging/views.py b/data/treeio/treeio/treeio/messaging/views.py new file mode 100644 index 0000000..3498898 --- /dev/null +++ b/data/treeio/treeio/treeio/messaging/views.py @@ -0,0 +1,764 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Messaging module views +""" +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ +from django.contrib import messages +from django.db.models import Q +from treeio.core.models import Object, ModuleSetting +from treeio.core.views import user_denied +from treeio.core.conf import settings +from treeio.core.rendering import render_to_response +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.identities.models import ContactType, Contact +from treeio.messaging.models import Message, MessageStream, MailingList +from treeio.messaging.forms import MessageForm, MessageStreamForm, FilterForm, MassActionForm, SettingsForm, \ + MessageReplyForm, MailingListForm +import re + + +def _get_filter_query(args): + "Creates a query to filter Messages based on FilterForm arguments" + query = Q() + + for arg in args: + if hasattr(Message, arg) and args[arg]: + kwargs = {str(arg + '__id'): long(args[arg])} + query = query & Q(**kwargs) + + return query + + +def _get_default_context(request): + "Returns default context as a dict()" + streams = Object.filter_by_request(request, MessageStream.objects) + mlists = MailingList.objects.all() + massform = MassActionForm(request.user.profile) + + context = {'streams': streams, + 'mlists': mlists, + 'massform': massform} + + return context + + +def _process_mass_form(f): + "Pre-process request to handle mass action form for Messages" + + def wrap(request, *args, **kwargs): + "Wrap" + user = request.user.profile + if 'massform' in request.POST: + for key in request.POST: + if 'mass-message' in key: + try: + message = Message.objects.get(pk=request.POST[key]) + form = MassActionForm( + user, request.POST, instance=message) + if form.is_valid() and user.has_permission(message, mode='w'): + form.save() + except Exception: + pass + try: + form = MassActionForm(request.user.profile, request.POST) + if form.is_valid(): + form.save() + except Exception: + pass + + return f(request, *args, **kwargs) + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + + return wrap + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index(request, response_format='html'): + "Messaging index page" + + query = Q(reply_to__isnull=True) + if request.GET: + query = query & _get_filter_query(request.GET) + objects = Object.filter_by_request( + request, Message.objects.filter(query)) + else: + objects = Object.filter_by_request( + request, Message.objects.filter(query)) + + filters = FilterForm(request.user.profile, 'title', request.GET) + + context = _get_default_context(request) + context.update({'filters': filters, + 'messages': objects}) + + return render_to_response('messaging/index', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_sent(request, response_format='html'): + "Sent messages index page" + + query = Q(reply_to__isnull=True) & Q( + author=request.user.profile.get_contact()) + if request.GET: + query = query & _get_filter_query(request.GET) + objects = Object.filter_by_request( + request, Message.objects.filter(query)) + else: + objects = Object.filter_by_request( + request, Message.objects.filter(query)) + + filters = FilterForm(request.user.profile, 'title', request.GET) + + context = _get_default_context(request) + context.update({'filters': filters, + 'messages': objects}) + + return render_to_response('messaging/index_sent', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_inbox(request, response_format='html'): + "Received messages index page" + + query = Q(reply_to__isnull=True) & ~Q( + author=request.user.profile.get_contact()) + if request.GET: + query = query & _get_filter_query(request.GET) + objects = Object.filter_by_request( + request, Message.objects.filter(query)) + else: + objects = Object.filter_by_request( + request, Message.objects.filter(query)) + + filters = FilterForm(request.user.profile, 'title', request.GET) + context = _get_default_context(request) + context.update({'filters': filters, + 'messages': objects}) + + return render_to_response('messaging/index_inbox', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_unread(request, response_format='html'): + "Messaging unread page" + + user = request.user.profile + + query = Q(reply_to__isnull=True) & ~Q(read_by=user) + if request.GET: + query = query & _get_filter_query(request.GET) + objects = Object.filter_by_request( + request, Message.objects.filter(query)) + else: + objects = Object.filter_by_request( + request, Message.objects.filter(query)) + + filters = FilterForm(request.user.profile, 'title', request.GET) + + context = _get_default_context(request) + context.update({'filters': filters, + 'messages': objects}) + + return render_to_response('messaging/unread', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def stream_add(request, response_format='html'): + "New message stream" + user = request.user.profile + + if request.POST: + if 'cancel' not in request.POST: + stream = MessageStream() + form = MessageStreamForm(user, request.POST, instance=stream) + if form.is_valid(): + stream = form.save() + stream.set_user_from_request(request) + return HttpResponseRedirect(reverse('messaging')) + else: + return HttpResponseRedirect(reverse('messaging')) + else: + form = MessageStreamForm(user) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('messaging/stream_add', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def stream_view(request, stream_id, response_format='html'): + "Stream view page" + + user = request.user.profile + + stream = get_object_or_404(MessageStream, pk=stream_id) + if not request.user.profile.has_permission(stream): + return user_denied(request, message="You don't have access to this Stream", + response_format=response_format) + + if request.user.profile.has_permission(stream, mode='x'): + if request.POST: + message = Message() + message.author = user.get_contact() + if not message.author: + return user_denied(request, + message="You can't send message without a Contact Card assigned to you.", + response_format=response_format) + + form = MessageForm( + request.user.profile, None, None, request.POST, instance=message) + if form.is_valid(): + message = form.save() + message.recipients.add(user.get_contact()) + message.set_user_from_request(request) + message.read_by.add(user) + try: + # if email entered create contact and add to recipients + if 'multicomplete_recipients' in request.POST and request.POST['multicomplete_recipients']: + try: + conf = ModuleSetting.get_for_module( + 'treeio.messaging', 'default_contact_type')[0] + default_contact_type = ContactType.objects.get( + pk=long(conf.value)) + except Exception: + default_contact_type = None + emails = request.POST[ + 'multicomplete_recipients'].split(',') + for email in emails: + emailstr = unicode(email).strip() + if re.match('[a-zA-Z0-9+_\-\.]+@[0-9a-zA-Z][.-0-9a-zA-Z]*.[a-zA-Z]+', emailstr): + contact, created = Contact.get_or_create_by_email( + emailstr, contact_type=default_contact_type) + message.recipients.add(contact) + if created: + contact.set_user_from_request(request) + except: + pass + # send email to all recipients + message.send_email() + + return HttpResponseRedirect(reverse('messaging_stream_view', args=[stream.id])) + else: + form = MessageForm(request.user.profile, stream_id) + + else: + form = None + + objects = Object.filter_by_request(request, Message.objects.filter( + reply_to__isnull=True, stream=stream).order_by('-date_created')) + context = _get_default_context(request) + context.update({'messages': objects, + 'form': form, + 'stream': stream}) + + return render_to_response('messaging/stream_view', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def stream_edit(request, stream_id, response_format='html'): + "Stream edit page" + user = request.user.profile + + stream = get_object_or_404(MessageStream, pk=stream_id) + if not request.user.profile.has_permission(stream, mode="w"): + return user_denied(request, message="You don't have access to this Stream", + response_format=response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = MessageStreamForm(user, request.POST, instance=stream) + if form.is_valid(): + stream = form.save() + return HttpResponseRedirect(reverse('messaging_stream_view', args=[stream.id])) + else: + return HttpResponseRedirect(reverse('messaging_stream_view', args=[stream.id])) + else: + form = MessageStreamForm(user, instance=stream) + + context = _get_default_context(request) + context.update({'form': form, + 'stream': stream}) + + return render_to_response('messaging/stream_edit', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def stream_checkmail(request, stream_id, response_format='html'): + "Stream check mail" + user = request.user.profile + + stream = get_object_or_404(MessageStream, pk=stream_id) + if not user.has_permission(stream): + return user_denied(request, message="You don't have access to this Stream", + response_format=response_format) + + try: + stream.process_email() + messages.add_message( + request, messages.INFO, _("E-mails fetched successfully."), fail_silently=True) + except: + try: + messages.add_message(request, messages.ERROR, _( + "Failed to retrieve messages for this stream. Please check stream settings"), fail_silently=True) + except: + pass + + return HttpResponseRedirect(reverse('messaging_stream_view', args=[stream.id])) + + +@handle_response_format +@treeio_login_required +def stream_delete(request, stream_id, response_format='html'): + "Delete stream page" + + stream = get_object_or_404(MessageStream, pk=stream_id) + if not request.user.profile.has_permission(stream, mode="w"): + return user_denied(request, message="You don't have access to this Stream", + response_format=response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + stream.trash = True + stream.save() + else: + stream.delete() + return HttpResponseRedirect('/messaging/') + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('messaging_stream_view', args=[stream.id])) + + context = _get_default_context(request) + context.update({'stream': stream}) + + return render_to_response('messaging/stream_delete', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def messaging_compose(request, response_format='html'): + "New message page" + + user = request.user.profile + + if request.POST: + if 'cancel' not in request.POST: + message = Message() + message.author = user.get_contact() + if not message.author: + return user_denied(request, + message="You can't send message without a Contact Card assigned to you.", + response_format=response_format) + + form = MessageForm( + request.user.profile, None, None, request.POST, instance=message) + if form.is_valid(): + message = form.save() + message.recipients.add(user.get_contact()) + message.set_user_from_request(request) + message.read_by.add(user) + try: + # if email entered create contact and add to recipients + if 'multicomplete_recipients' in request.POST and request.POST['multicomplete_recipients']: + try: + conf = ModuleSetting.get_for_module( + 'treeio.messaging', 'default_contact_type')[0] + default_contact_type = ContactType.objects.get( + pk=long(conf.value)) + except Exception: + default_contact_type = None + emails = request.POST[ + 'multicomplete_recipients'].split(',') + for email in emails: + emailstr = unicode(email).strip() + if re.match('[a-zA-Z0-9+_\-\.]+@[0-9a-zA-Z][.-0-9a-zA-Z]*.[a-zA-Z]+', emailstr): + contact, created = Contact.get_or_create_by_email( + emailstr, contact_type=default_contact_type) + message.recipients.add(contact) + if created: + contact.set_user_from_request(request) + except: + pass + # send email to all recipients + message.send_email() + + return HttpResponseRedirect(reverse('messaging')) + else: + return HttpResponseRedirect(reverse('messaging')) + + else: + form = MessageForm(request.user.profile, None) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('messaging/message_compose', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def messaging_view(request, message_id, response_format='html'): + "Single message page" + + message = get_object_or_404(Message, pk=message_id) + user = request.user.profile + + if not user.has_permission(message): + return user_denied(request, message="You don't have access to this Message", + response_format=response_format) + + message.read_by.add(user) + + if request.POST and request.POST.get('body', False): + "Unread message" + + reply = Message() + reply.author = user.get_contact() + if not reply.author: + return user_denied(request, + message="You can't send message without a Contact Card assigned to you.", + response_format=response_format) + reply.reply_to = message + form = MessageReplyForm( + user, message.stream_id, message, request.POST, instance=reply) + if form.is_valid(): + reply = form.save() + reply.set_user_from_request(request) + # Add author to recipients + reply.recipients.add(reply.author) + message.read_by.clear() + + try: + # if email entered create contact and add to recipients + if 'multicomplete_recipients' in request.POST and request.POST['multicomplete_recipients']: + try: + conf = ModuleSetting.get_for_module( + 'treeio.messaging', 'default_contact_type')[0] + default_contact_type = ContactType.objects.get( + pk=long(conf.value)) + except Exception: + default_contact_type = None + emails = request.POST[ + 'multicomplete_recipients'].split(',') + for email in emails: + emailstr = unicode(email).strip() + if re.match('[a-zA-Z0-9+_\-\.]+@[0-9a-zA-Z][.-0-9a-zA-Z]*.[a-zA-Z]+', emailstr): + contact, created = Contact.get_or_create_by_email( + emailstr, contact_type=default_contact_type) + reply.recipients.add(contact) + if created: + contact.set_user_from_request(request) + except: + pass + + # Add each recipient of the reply to the original message + for recipient in reply.recipients.all(): + message.recipients.add(recipient) + + # send email to all recipients + reply.send_email() + + return HttpResponseRedirect(reverse('messaging_message_view', args=[message.id])) + + else: + form = MessageReplyForm( + request.user.profile, message.stream_id, message) + + replies = Object.filter_by_request(request, + Message.objects.filter(reply_to=message).order_by('date_created')) + + context = _get_default_context(request) + context.update({'message': message, + 'messages': replies, + 'form': form}) + + return render_to_response('messaging/message_view', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def messaging_delete(request, message_id, response_format='html'): + "Delete message page" + + message = get_object_or_404(Message, pk=message_id) + + if not request.user.profile.has_permission(message, mode="w"): + return user_denied(request, message="You don't have access to this Message", + response_format=response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + message.trash = True + message.save() + else: + message.delete() + return HttpResponseRedirect('/messaging/') + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('messaging_stream_view', args=[message.stream.id])) + + context = _get_default_context(request) + context.update({'message': message}) + + return render_to_response('messaging/message_delete', context, + context_instance=RequestContext(request), + response_format=response_format) + +""" +Mailing Lists +""" + + +@handle_response_format +@treeio_login_required +def mlist_add(request, response_format='html'): + "New message mlist" + user = request.user.profile + + if request.POST: + + mlist = MailingList() + + form = MailingListForm(user, request.POST, instance=mlist) + if form.is_valid(): + mlist = form.save() + mlist.set_user_from_request(request) + return HttpResponseRedirect('/messaging/') + else: + form = MailingListForm(user) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('messaging/mlist_add', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def mlist_view(request, mlist_id, response_format='html'): + "Mailing List view page" + + user = request.user.profile + + mlist = get_object_or_404(MailingList, pk=mlist_id) + if not request.user.profile.has_permission(mlist): + return user_denied(request, message="You don't have access to this Mailing List", + response_format=response_format) + + if request.user.profile.has_permission(mlist, mode='x'): + if request.POST: + message = Message() + message.author = request.user.profile.get_contact() + if not message.author: + return user_denied(request, + message="You can't send message without a Contact Card assigned to you.", + response_format=response_format) + form = MessageForm( + request.user.profile, mlist_id, None, request.POST, instance=message) + if form.is_valid(): + message = form.save() + message.set_user_from_request(request) + message.read_by.add(user) + return HttpResponseRedirect(reverse('messaging_mlist_view', args=[mlist.id])) + else: + form = MessageForm(request.user.profile, mlist_id) + + else: + form = None + + messages = Object.filter_by_request(request, + Message.objects.filter(reply_to__isnull=True, + mlist=mlist).order_by('-date_created')) + context = _get_default_context(request) + context.update({'messages': messages, + 'form': form, + 'mlist': mlist}) + + return render_to_response('messaging/mlist_view', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def mlist_edit(request, mlist_id, response_format='html'): + "MailingList edit page" + user = request.user.profile + + mlist = get_object_or_404(MailingList, pk=mlist_id) + if not user.has_permission(mlist, mode="w"): + return user_denied(request, message="You don't have access to this Mailing List", + response_format=response_format) + + context = _get_default_context(request) + context.update({'mlist': mlist}) + + return render_to_response('messaging/mlist_edit', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def mlist_delete(request, mlist_id, response_format='html'): + "Delete mlist page" + + mlist = get_object_or_404(MailingList, pk=mlist_id) + if not request.user.profile.has_permission(mlist, mode="w"): + return user_denied(request, message="You don't have access to this Mailing List", + response_format=response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + mlist.trash = True + mlist.save() + else: + mlist.delete() + return HttpResponseRedirect('/messaging/') + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('messaging_mlist_view', args=[mlist.id])) + + context = _get_default_context(request) + context.update({'mlist': mlist}) + + return render_to_response('messaging/mlist_delete', context, + context_instance=RequestContext(request), + response_format=response_format) + + +""" +Settings +""" + + +@handle_response_format +@treeio_login_required +def settings_view(request, response_format='html'): + "Settings admin view" + + # default content type + try: + conf = ModuleSetting.get_for_module('treeio.messaging', 'default_contact_type', + user=request.user.profile)[0] + default_contact_type = ContactType.objects.get(pk=long(conf.value)) + except: + default_contact_type = None + + # default imap folder + try: + conf = ModuleSetting.get_for_module( + 'treeio.messaging', 'default_imap_folder')[0] + default_imap_folder = conf.value + except: + default_imap_folder = getattr( + settings, 'HARDTREE_MESSAGING_IMAP_DEFAULT_FOLDER_NAME', 'UNSEEN') + + # signature + try: + conf = ModuleSetting.get_for_module('treeio.messaging', 'signature', + user=request.user.profile, strict=True)[0] + signature = conf.value + except: + signature = '' + + types = Object.filter_by_request( + request, ContactType.objects.order_by('name')) + + context = _get_default_context(request) + context.update({'types': types, + 'signature': signature, + 'default_contact_type': default_contact_type, + 'default_imap_folder': default_imap_folder}) + + return render_to_response('messaging/settings_view', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +def settings_edit(request, response_format='html'): + "Settings admin view" + + if request.POST: + if 'cancel' not in request.POST: + form = SettingsForm(request.user.profile, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('messaging_settings_view')) + else: + return HttpResponseRedirect(reverse('messaging_settings_view')) + else: + form = SettingsForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('messaging/settings_edit', context, + context_instance=RequestContext(request), + response_format=response_format) + + +# +# Widgets +# + +@treeio_login_required +def widget_new_messages(request, response_format='html'): + "A list of new messages. Limit by 5." + + query = Q(reply_to__isnull=True) & ~Q(read_by=request.user.profile) + + messages = Object.filter_by_request( + request, Message.objects.filter(query))[:5] + + return render_to_response('messaging/widgets/new_messages', + {'messages': messages}, + context_instance=RequestContext(request), response_format=response_format) diff --git a/data/treeio/treeio/treeio/news/forms.py b/data/treeio/treeio/treeio/news/forms.py new file mode 100644 index 0000000..da053ca --- /dev/null +++ b/data/treeio/treeio/treeio/news/forms.py @@ -0,0 +1,83 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +News module forms +""" + +from django import forms +from django.utils.translation import ugettext as _ +from django.core.urlresolvers import reverse +from treeio.core.conf import settings +from treeio.core.models import UpdateRecord, ModuleSetting, Object + + +class UpdateRecordForm(forms.ModelForm): + + """ UpdateRecord form """ + + def __init__(self, *args, **kwargs): + + self.user = kwargs.pop('user', None) + super(UpdateRecordForm, self).__init__(*args, **kwargs) + + self.fields['body'].required = True + self.fields['body'].label = _("Details") + + self.fields['recipients'].help_text = "" + self.fields['recipients'].required = False + self.fields['recipients'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_access_lookup')}) + + # get default permissions from settings + try: + conf = ModuleSetting.get_for_module( + 'treeio.core', 'default_permissions')[0] + default_permissions = conf.value + except: + default_permissions = settings.HARDTREE_DEFAULT_PERMISSIONS + + if self.user and 'userallgroups' in default_permissions: + self.fields['recipients'].initial = [ + i.id for i in self.user.other_groups.all().only('id')] + self.fields['recipients'].initial.append( + self.user.default_group.id) + elif self.user and 'usergroup' in default_permissions: + self.fields['recipients'].initial = [self.user.default_group.id] + + class Meta: + + "TaskRecordForm" + model = UpdateRecord + fields = ['body', 'recipients'] + + +class UpdateRecordFilterForm(forms.ModelForm): + + """ Filter form definition """ + + def __init__(self, user, *args, **kwargs): + super(UpdateRecordFilterForm, self).__init__(*args, **kwargs) + + self.fields['author'].label = _("Author") + self.fields['about'].label = _("About") + + self.fields['author'].required = False + self.fields['author'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_user_lookup')}) + + self.fields['about'].queryset = Object.filter_permitted( + user, Object.objects, mode='x') + self.fields['about'].required = False + self.fields['about'].null = True + self.fields['about'].help_text = "" + self.fields['about'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('core_ajax_object_lookup')}) + + class Meta: + + "Filter" + model = UpdateRecord + fields = ('author', 'about') diff --git a/data/treeio/treeio/treeio/news/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/news/south_migrations/0001_initial.py new file mode 100644 index 0000000..31e00cc --- /dev/null +++ b/data/treeio/treeio/treeio/news/south_migrations/0001_initial.py @@ -0,0 +1,25 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + pass + + def backwards(self, orm): + pass + + models = { + + } + + complete_apps = ['news'] diff --git a/data/treeio/treeio/treeio/projects/ajax.py b/data/treeio/treeio/treeio/projects/ajax.py new file mode 100644 index 0000000..5c94d3f --- /dev/null +++ b/data/treeio/treeio/treeio/projects/ajax.py @@ -0,0 +1,31 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +from datetime import datetime +from dajaxice.core import dajaxice_functions +from dajax.core import Dajax +from treeio.projects.models import Task, Milestone +from django.contrib import messages +from django.utils.translation import ugettext as _ + + +def gantt(request, task, start, end): + dajax = Dajax() + try: + t = Task.objects.get(pk=task) + ot = _("Task") + except: + t = Milestone.objects.get(pk=task) + ot = _("Milestone") + s = datetime.strptime(start, '%Y-%m-%d').replace(hour=12) + e = datetime.strptime(end, '%Y-%m-%d').replace(hour=12) + t.start_date = s + t.end_date = e + t.save() + messages.add_message(request, messages.INFO, _( + "%(ot)s \"%(t)s\" dates have been updated.") % {'ot': ot, 't': unicode(t)}) + return dajax.json() + +dajaxice_functions.register(gantt) diff --git a/data/treeio/treeio/treeio/projects/identities.py b/data/treeio/treeio/treeio/projects/identities.py new file mode 100644 index 0000000..4999add --- /dev/null +++ b/data/treeio/treeio/treeio/projects/identities.py @@ -0,0 +1,65 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Handle objects from this module relevant to a Contact or a User +""" +from treeio.core.models import Object +from treeio.projects.templatetags.projects import projects_task_list + +CONTACT_OBJECTS = {'manager': {'label': 'Managed Projects', + 'objects': [], + 'templatetag': None}, 'client': {'label': 'Ordered Projects', + 'objects': [], + 'templatetag': None}, + 'task_set': {'label': 'Managed Tasks', + 'objects': [], + 'templatetag': projects_task_list}} + +USER_OBJECTS = {'task_set': {'label': 'Assigned Tasks', + 'objects': [], + 'templatetag': projects_task_list}} + + +def get_contact_objects(current_user, contact): + """ + Returns a dictionary with keys specified as contact attributes + and values as dictionaries with labels and set of relevant objects. + """ + + objects = dict(CONTACT_OBJECTS) + + for key in objects: + if hasattr(contact, key): + manager = getattr(contact, key) + try: + manager = manager.filter(status__hidden=False) + except: + pass + objects[key]['objects'] = Object.filter_permitted( + current_user, manager) + + return objects + + +def get_user_objects(current_user, user): + """ + Returns a dictionary with keys specified as contact attributes + and values as dictionaries with labels and set of relevant objects. + """ + + objects = dict(USER_OBJECTS) + + for key in objects: + if hasattr(user, key): + manager = getattr(user, key) + try: + manager = manager.filter(status__hidden=False) + except: + pass + objects[key]['objects'] = Object.filter_permitted( + current_user, manager) + + return objects diff --git a/data/treeio/treeio/treeio/projects/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/projects/south_migrations/0001_initial.py new file mode 100644 index 0000000..fad4e33 --- /dev/null +++ b/data/treeio/treeio/treeio/projects/south_migrations/0001_initial.py @@ -0,0 +1,322 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Project' + db.create_table('projects_project', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['projects.Project'])), + ('manager', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='manager', null=True, to=orm['identities.Contact'])), + ('client', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='client', null=True, to=orm['identities.Contact'])), + ('details', self.gf('django.db.models.fields.TextField') + (max_length=255, null=True, blank=True)), + )) + db.send_create_signal('projects', ['Project']) + + # Adding model 'TaskStatus' + db.create_table('projects_taskstatus', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('details', self.gf('django.db.models.fields.TextField') + (max_length=255, null=True, blank=True)), + ('active', self.gf('django.db.models.fields.BooleanField') + (default=False)), + ('hidden', self.gf('django.db.models.fields.BooleanField') + (default=False)), + )) + db.send_create_signal('projects', ['TaskStatus']) + + # Adding model 'Milestone' + db.create_table('projects_milestone', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('project', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['projects.Project'])), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('status', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['projects.TaskStatus'])), + ('details', self.gf('django.db.models.fields.TextField') + (max_length=255, null=True, blank=True)), + ('start_date', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + ('end_date', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + )) + db.send_create_signal('projects', ['Milestone']) + + # Adding model 'Task' + db.create_table('projects_task', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['projects.Task'])), + ('project', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['projects.Project'])), + ('milestone', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['projects.Milestone'], null=True, blank=True)), + ('status', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['projects.TaskStatus'])), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('details', self.gf('django.db.models.fields.TextField') + (max_length=255, null=True, blank=True)), + ('caller', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['identities.Contact'], null=True, blank=True)), + ('start_date', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + ('end_date', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + ('priority', self.gf( + 'django.db.models.fields.IntegerField')(default=3)), + ('estimated_time', self.gf('django.db.models.fields.IntegerField') + (null=True, blank=True)), + )) + db.send_create_signal('projects', ['Task']) + + # Adding M2M table for field assigned on 'Task' + db.create_table('projects_task_assigned', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('task', models.ForeignKey(orm['projects.task'], null=False)), + ('user', models.ForeignKey(orm['core.user'], null=False)) + )) + db.create_unique('projects_task_assigned', ['task_id', 'user_id']) + + # Adding model 'TaskTimeSlot' + db.create_table('projects_tasktimeslot', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('task', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['projects.Task'])), + ('time_from', self.gf('django.db.models.fields.DateTimeField')()), + ('time_to', self.gf('django.db.models.fields.DateTimeField') + (null=True, blank=True)), + ('timezone', self.gf( + 'django.db.models.fields.IntegerField')(default=0)), + ('details', self.gf('django.db.models.fields.TextField') + (max_length=255, null=True, blank=True)), + )) + db.send_create_signal('projects', ['TaskTimeSlot']) + + # Adding model 'TaskRecord' + db.create_table('projects_taskrecord', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('task', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['projects.Task'])), + ('record_type', self.gf( + 'django.db.models.fields.CharField')(max_length=256)), + ('details', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('projects', ['TaskRecord']) + + def backwards(self, orm): + + # Deleting model 'Project' + db.delete_table('projects_project') + + # Deleting model 'TaskStatus' + db.delete_table('projects_taskstatus') + + # Deleting model 'Milestone' + db.delete_table('projects_milestone') + + # Deleting model 'Task' + db.delete_table('projects_task') + + # Removing M2M table for field assigned on 'Task' + db.delete_table('projects_task_assigned') + + # Deleting model 'TaskTimeSlot' + db.delete_table('projects_tasktimeslot') + + # Deleting model 'TaskRecord' + db.delete_table('projects_taskrecord') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'projects.milestone': { + 'Meta': {'ordering': "['name']", 'object_name': 'Milestone', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project', '_ormbases': ['core.Object']}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'manager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'manager'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Project']"}) + }, + 'projects.task': { + 'Meta': {'ordering': "('-priority', 'name')", 'object_name': 'Task', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'estimated_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Milestone']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Task']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.taskrecord': { + 'Meta': {'object_name': 'TaskRecord', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}) + }, + 'projects.taskstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TaskStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'projects.tasktimeslot': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'TaskTimeSlot', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}), + 'time_from': ('django.db.models.fields.DateTimeField', [], {}), + 'time_to': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'timezone': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + } + } + + complete_apps = ['projects'] diff --git a/data/treeio/treeio/treeio/projects/south_migrations/0002_updaterecords.py b/data/treeio/treeio/treeio/projects/south_migrations/0002_updaterecords.py new file mode 100644 index 0000000..2c4d855 --- /dev/null +++ b/data/treeio/treeio/treeio/projects/south_migrations/0002_updaterecords.py @@ -0,0 +1,236 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + +class Migration(DataMigration): + + def forwards(self, orm): + "Migrate the UpdateRecords" + for record in orm['projects.TaskRecord'].objects.all(): + update = orm['core.UpdateRecord'].objects.create() + update.author = record.creator + if record.record_type == 'manual': + update.record_type = 'manual' + else: + update.record_type = 'update' + update.body = record.details + update.date_created = record.date_created + update.save() + try: + update.about.add(record.task) + except: + pass + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'projects.milestone': { + 'Meta': {'ordering': "['name']", 'object_name': 'Milestone', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project', '_ormbases': ['core.Object']}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'manager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'manager'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Project']"}) + }, + 'projects.task': { + 'Meta': {'ordering': "('-priority', 'name')", 'object_name': 'Task', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'estimated_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Milestone']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Task']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.taskrecord': { + 'Meta': {'object_name': 'TaskRecord', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}) + }, + 'projects.taskstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TaskStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'projects.tasktimeslot': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'TaskTimeSlot', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}), + 'time_from': ('django.db.models.fields.DateTimeField', [], {}), + 'time_to': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'timezone': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + } + } + + complete_apps = ['projects'] diff --git a/data/treeio/treeio/treeio/projects/south_migrations/0003_auto__add_field_tasktimeslot_user.py b/data/treeio/treeio/treeio/projects/south_migrations/0003_auto__add_field_tasktimeslot_user.py new file mode 100644 index 0000000..1296752 --- /dev/null +++ b/data/treeio/treeio/treeio/projects/south_migrations/0003_auto__add_field_tasktimeslot_user.py @@ -0,0 +1,210 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'TaskTimeSlot.user' + db.add_column('projects_tasktimeslot', 'user', self.gf( + 'django.db.models.fields.related.ForeignKey')(default=1, to=orm['core.User']), keep_default=False) + + def backwards(self, orm): + + # Deleting field 'TaskTimeSlot.user' + db.delete_column('projects_tasktimeslot', 'user_id') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'projects.milestone': { + 'Meta': {'ordering': "['name']", 'object_name': 'Milestone', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project', '_ormbases': ['core.Object']}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'manager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'manager'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Project']"}) + }, + 'projects.task': { + 'Meta': {'ordering': "('-priority', 'name')", 'object_name': 'Task', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'estimated_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Milestone']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Task']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.taskrecord': { + 'Meta': {'object_name': 'TaskRecord', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}) + }, + 'projects.taskstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TaskStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'projects.tasktimeslot': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'TaskTimeSlot', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}), + 'time_from': ('django.db.models.fields.DateTimeField', [], {}), + 'time_to': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'timezone': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + } + } + + complete_apps = ['projects'] diff --git a/data/treeio/treeio/treeio/projects/south_migrations/0004_timeslots.py b/data/treeio/treeio/treeio/projects/south_migrations/0004_timeslots.py new file mode 100644 index 0000000..e17bdab --- /dev/null +++ b/data/treeio/treeio/treeio/projects/south_migrations/0004_timeslots.py @@ -0,0 +1,211 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + +class Migration(DataMigration): + + def forwards(self, orm): + "Migrate TimeSlots to set .user" + for obj in orm['projects.TaskTimeSlot'].objects.all(): + if obj.object_ptr.creator: + obj.user = obj.object_ptr.creator + else: + obj.user = orm['core.User'].objects.all()[0] + obj.save() + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'projects.milestone': { + 'Meta': {'ordering': "['name']", 'object_name': 'Milestone', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project', '_ormbases': ['core.Object']}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'manager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'manager'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Project']"}) + }, + 'projects.task': { + 'Meta': {'ordering': "('-priority', 'name')", 'object_name': 'Task', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'estimated_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Milestone']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Task']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.taskrecord': { + 'Meta': {'object_name': 'TaskRecord', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}) + }, + 'projects.taskstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TaskStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'projects.tasktimeslot': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'TaskTimeSlot', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}), + 'time_from': ('django.db.models.fields.DateTimeField', [], {}), + 'time_to': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'timezone': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + } + } + + complete_apps = ['projects'] diff --git a/data/treeio/treeio/treeio/projects/south_migrations/0005_auto__del_taskrecord.py b/data/treeio/treeio/treeio/projects/south_migrations/0005_auto__del_taskrecord.py new file mode 100644 index 0000000..edc772f --- /dev/null +++ b/data/treeio/treeio/treeio/projects/south_migrations/0005_auto__del_taskrecord.py @@ -0,0 +1,208 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting model 'TaskRecord' + db.delete_table('projects_taskrecord') + + def backwards(self, orm): + + # Adding model 'TaskRecord' + db.create_table('projects_taskrecord', ( + ('record_type', self.gf( + 'django.db.models.fields.CharField')(max_length=256)), + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('details', self.gf('django.db.models.fields.TextField')()), + ('task', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['projects.Task'])), + )) + db.send_create_signal('projects', ['TaskRecord']) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'projects.milestone': { + 'Meta': {'ordering': "['name']", 'object_name': 'Milestone', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project', '_ormbases': ['core.Object']}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'manager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'manager'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Project']"}) + }, + 'projects.task': { + 'Meta': {'ordering': "('-priority', 'name')", 'object_name': 'Task', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'estimated_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Milestone']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Task']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.taskstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TaskStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'projects.tasktimeslot': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'TaskTimeSlot', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}), + 'time_from': ('django.db.models.fields.DateTimeField', [], {}), + 'time_to': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'timezone': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + } + } + + complete_apps = ['projects'] diff --git a/data/treeio/treeio/treeio/projects/south_migrations/0006_auto__add_field_task_depends.py b/data/treeio/treeio/treeio/projects/south_migrations/0006_auto__add_field_task_depends.py new file mode 100644 index 0000000..b58068a --- /dev/null +++ b/data/treeio/treeio/treeio/projects/south_migrations/0006_auto__add_field_task_depends.py @@ -0,0 +1,196 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'Task.depends' + db.add_column('projects_task', 'depends', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['projects.Task'], null=True, blank=True), keep_default=False) + + def backwards(self, orm): + + # Deleting field 'Task.depends' + db.delete_column('projects_task', 'depends_id') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'projects.milestone': { + 'Meta': {'ordering': "['start_date', 'name']", 'object_name': 'Milestone', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.TaskStatus']"}) + }, + 'projects.project': { + 'Meta': {'ordering': "['name']", 'object_name': 'Project', '_ormbases': ['core.Object']}, + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'manager': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'manager'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Project']"}) + }, + 'projects.task': { + 'Meta': {'ordering': "('-priority', 'name')", 'object_name': 'Task', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'depends': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'estimated_time': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'milestone': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Milestone']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['projects.Task']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}), + 'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'default': '26', 'to': "orm['projects.TaskStatus']"}) + }, + 'projects.taskstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TaskStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'projects.tasktimeslot': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'TaskTimeSlot', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'task': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Task']"}), + 'time_from': ('django.db.models.fields.DateTimeField', [], {}), + 'time_to': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'timezone': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + } + } + + complete_apps = ['projects'] diff --git a/data/treeio/treeio/treeio/projects/views.py b/data/treeio/treeio/treeio/projects/views.py new file mode 100644 index 0000000..0b2cec0 --- /dev/null +++ b/data/treeio/treeio/treeio/projects/views.py @@ -0,0 +1,1301 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Project Management module views +""" + +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.http import HttpResponseRedirect, Http404 +from django.core.urlresolvers import reverse +from django.db.models import Q +from treeio.core.models import Object, ModuleSetting, UpdateRecord +from treeio.core.views import user_denied +from treeio.core.rendering import render_to_response +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.projects.models import Project, Milestone, Task, TaskStatus, TaskTimeSlot +from treeio.projects.forms import ProjectForm, MilestoneForm, TaskForm, FilterForm, TaskRecordForm, \ + MassActionForm, TaskTimeSlotForm, TaskStatusForm, SettingsForm +from django.utils.translation import ugettext as _ +from datetime import datetime +import json + + +def _get_filter_query(args): + "Creates a query to filter Tasks based on FilterForm arguments" + query = Q() + + for arg in args: + if hasattr(Task, arg) and args[arg]: + kwargs = {str(arg + '__id'): long(args[arg])} + query = query & Q(**kwargs) + + return query + + +def _get_default_context(request): + "Returns default context as a dict()" + + projects = Object.filter_by_request(request, Project.objects) + statuses = Object.filter_by_request(request, TaskStatus.objects) + massform = MassActionForm(request.user.profile) + + context = {'projects': projects, + 'statuses': statuses, + 'massform': massform} + + return context + + +def _process_mass_form(f): + "Pre-process request to handle mass action form for Tasks and Milestones" + + def wrap(request, *args, **kwargs): + "Wrap" + if 'massform' in request.POST: + for key in request.POST: + if 'mass-milestone' in key: + try: + milestone = Milestone.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=milestone) + if form.is_valid() and request.user.profile.has_permission(milestone, mode='w'): + form.save() + except Exception: + pass + for key in request.POST: + if 'mass-task' in key: + try: + task = Task.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=task) + if form.is_valid() and request.user.profile.has_permission(task, mode='w'): + form.save() + except Exception: + pass + + return f(request, *args, **kwargs) + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + + return wrap + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index(request, response_format='html'): + """Project Management index page""" + + query = Q(parent__isnull=True) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + milestones = Object.filter_by_request( + request, Milestone.objects.filter(status__hidden=False)) + filters = FilterForm(request.user.profile, '', request.GET) + + context = _get_default_context(request) + context.update({'milestones': milestones, + 'tasks': tasks, + 'filters': filters}) + + return render_to_response('projects/index', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_owned(request, response_format='html'): + """Tasks owned by current user""" + + query = Q( + parent__isnull=True, caller__related_user=request.user.profile) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + milestones = Object.filter_by_request( + request, Milestone.objects.filter(status__hidden=False)) + filters = FilterForm(request.user.profile, 'status', request.GET) + + context = _get_default_context(request) + context.update({'milestones': milestones, + 'tasks': tasks, + 'filters': filters}) + + return render_to_response('projects/index_owned', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_assigned(request, response_format='html'): + """Tasks assigned to current user""" + + query = Q(parent__isnull=True, assigned=request.user.profile) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + + milestones = Object.filter_by_request( + request, Milestone.objects.filter(status__hidden=False)) + filters = FilterForm(request.user.profile, 'assigned', request.GET) + + context = _get_default_context(request) + context.update({'milestones': milestones, + 'tasks': tasks, + 'filters': filters}) + + return render_to_response('projects/index_assigned', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_by_status(request, status_id, response_format='html'): + """Sort tasks by status""" + + status = get_object_or_404(TaskStatus, pk=status_id) + + if not request.user.profile.has_permission(status): + return user_denied(request, message="You don't have access to this Task Status") + + query = Q(parent__isnull=True, status=status) + if request.GET: + query = query & _get_filter_query(request.GET) + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + + milestones = Object.filter_by_request( + request, Milestone.objects.filter(task__status=status).distinct()) + filters = FilterForm(request.user.profile, 'status', request.GET) + + context = _get_default_context(request) + context.update({'milestones': milestones, + 'tasks': tasks, + 'status': status, + 'filters': filters}) + + return render_to_response('projects/index_by_status', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_in_progress(request, response_format='html'): + """A page with a list of tasks in progress""" + + query = Q(parent__isnull=True) + if request.GET: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + + milestones = Object.filter_by_request( + request, Milestone.objects.filter(status__hidden=False)) + filters = FilterForm(request.user.profile, 'status', request.GET) + time_slots = Object.filter_by_request( + request, TaskTimeSlot.objects.filter(time_from__isnull=False, time_to__isnull=True)) + + context = _get_default_context(request) + context.update({'milestones': milestones, + 'tasks': tasks, + 'filters': filters, + 'time_slots': time_slots}) + + return render_to_response('projects/index_in_progress', context, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Projects +# +@handle_response_format +@treeio_login_required +def project_add(request, response_format='html'): + """New project form""" + + if request.POST: + if 'cancel' not in request.POST: + project = Project() + form = ProjectForm( + request.user.profile, None, request.POST, instance=project) + if form.is_valid(): + project = form.save() + project.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_project_view', args=[project.id])) + else: + return HttpResponseRedirect(reverse('projects')) + else: + form = ProjectForm(request.user.profile, None) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('projects/project_add', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def project_add_typed(request, project_id, response_format='html'): + """Project add to preselected parent project""" + + parent_project = None + if project_id: + parent_project = get_object_or_404(Project, pk=project_id) + if not request.user.profile.has_permission(parent_project, mode='x'): + parent_project = None + + if request.POST: + if 'cancel' not in request.POST: + project = Project() + form = ProjectForm( + request.user.profile, project_id, request.POST, instance=project) + if form.is_valid(): + project = form.save() + project.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_project_view', args=[project.id])) + else: + return HttpResponseRedirect(reverse('projects')) + else: + form = ProjectForm(request.user.profile, project_id) + + context = _get_default_context(request) + context.update({'form': form, 'project': parent_project}) + + return render_to_response('projects/project_add_typed', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def project_view(request, project_id, response_format='html'): + """Single project view page""" + + project = get_object_or_404(Project, pk=project_id) + if not request.user.profile.has_permission(project): + return user_denied(request, message="You don't have access to this Project") + + query = Q(parent__isnull=True, project=project) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + + if request.user.profile.has_permission(project, mode='r'): + if request.POST: + record = UpdateRecord() + record.record_type = 'manual' + form = TaskRecordForm( + request.user.profile, request.POST, instance=record) + if form.is_valid(): + record = form.save() + record.set_user_from_request(request) + record.save() + record.about.add(project) + project.set_last_updated() + return HttpResponseRedirect(reverse('projects_project_view', args=[project.id])) + else: + form = TaskRecordForm(request.user.profile) + else: + form = None + + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + + tasks_progress = float(0) + tasks_progress_query = Object.filter_by_request( + request, Task.objects.filter(Q(parent__isnull=True, project=project))) + if tasks_progress_query: + for task in tasks_progress_query: + if not task.status.active: + tasks_progress += 1 + tasks_progress = (tasks_progress / len(tasks_progress_query)) * 100 + tasks_progress = round(tasks_progress, ndigits=1) + + filters = FilterForm(request.user.profile, 'project', request.GET) + + milestones = Object.filter_by_request(request, + Milestone.objects.filter(project=project).filter(status__hidden=False)) + subprojects = Project.objects.filter(parent=project) + + context = _get_default_context(request) + context.update({'project': project, + 'milestones': milestones, + 'tasks': tasks, + 'tasks_progress': tasks_progress, + 'record_form': form, + 'subprojects': subprojects, + 'filters': filters}) + + return render_to_response('projects/project_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def project_edit(request, project_id, response_format='html'): + """Project edit page""" + + project = get_object_or_404(Project, pk=project_id) + if not request.user.profile.has_permission(project, mode='w'): + return user_denied(request, message="You don't have access to this Project") + + if request.POST: + if 'cancel' not in request.POST: + form = ProjectForm( + request.user.profile, None, request.POST, instance=project) + if form.is_valid(): + project = form.save() + return HttpResponseRedirect(reverse('projects_project_view', args=[project.id])) + else: + return HttpResponseRedirect(reverse('projects_project_view', args=[project.id])) + else: + form = ProjectForm(request.user.profile, None, instance=project) + + context = _get_default_context(request) + context.update({'form': form, 'project': project}) + + return render_to_response('projects/project_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def project_delete(request, project_id, response_format='html'): + """Project delete""" + + project = get_object_or_404(Project, pk=project_id) + if not request.user.profile.has_permission(project, mode='w'): + return user_denied(request, message="You don't have access to this Project") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + project.trash = True + project.save() + else: + project.delete() + return HttpResponseRedirect(reverse('projects_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('projects_project_view', args=[project.id])) + + context = _get_default_context(request) + context.update({'project': project}) + + return render_to_response('projects/project_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Milestones +# + +@handle_response_format +@treeio_login_required +def milestone_add(request, response_format='html'): + """New milestone form""" + + if request.POST: + if 'cancel' not in request.POST: + milestone = Milestone() + form = MilestoneForm( + request.user.profile, None, request.POST, instance=milestone) + if form.is_valid(): + milestone = form.save() + milestone.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_milestone_view', args=[milestone.id])) + else: + return HttpResponseRedirect(reverse('projects')) + else: + form = MilestoneForm(request.user.profile, None) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('projects/milestone_add', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def milestone_add_typed(request, project_id=None, response_format='html'): + """Milestone add to preselected project""" + + project = None + if project_id: + project = get_object_or_404(Project, pk=project_id) + if not request.user.profile.has_permission(project, mode='x'): + project = None + + if request.POST: + if 'cancel' not in request.POST: + milestone = Milestone() + form = MilestoneForm( + request.user.profile, project_id, request.POST, instance=milestone) + if form.is_valid(): + milestone = form.save() + milestone.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_milestone_view', args=[milestone.id])) + else: + return HttpResponseRedirect(reverse('projects')) + else: + form = MilestoneForm(request.user.profile, project_id) + + context = _get_default_context(request) + context.update({'form': form, 'project': project}) + + return render_to_response('projects/milestone_add_typed', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def milestone_view(request, milestone_id, response_format='html'): + """Single milestone view page""" + + milestone = get_object_or_404(Milestone, pk=milestone_id) + project = milestone.project + if not request.user.profile.has_permission(milestone): + return user_denied(request, message="You don't have access to this Milestone") + + query = Q(milestone=milestone, parent__isnull=True) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + else: + tasks = Object.filter_by_request(request, + Task.objects.filter(query & Q(status__hidden=False))) + + filters = FilterForm(request.user.profile, 'milestone', request.GET) + + tasks_progress = float(0) + tasks_progress_query = Object.filter_by_request( + request, Task.objects.filter(Q(parent__isnull=True, milestone=milestone))) + if tasks_progress_query: + for task in tasks_progress_query: + if not task.status.active: + tasks_progress += 1 + tasks_progress = (tasks_progress / len(tasks_progress_query)) * 100 + tasks_progress = round(tasks_progress, ndigits=1) + + context = _get_default_context(request) + context.update({'milestone': milestone, + 'tasks': tasks, + 'tasks_progress': tasks_progress, + 'filters': filters, + 'project': project}) + + return render_to_response('projects/milestone_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def milestone_edit(request, milestone_id, response_format='html'): + """Milestone edit page""" + + milestone = get_object_or_404(Milestone, pk=milestone_id) + project = milestone.project + if not request.user.profile.has_permission(milestone, mode='w'): + return user_denied(request, message="You don't have access to this Milestone") + + if request.POST: + if 'cancel' not in request.POST: + form = MilestoneForm( + request.user.profile, None, request.POST, instance=milestone) + if form.is_valid(): + milestone = form.save() + return HttpResponseRedirect(reverse('projects_milestone_view', args=[milestone.id])) + else: + return HttpResponseRedirect(reverse('projects_milestone_view', args=[milestone.id])) + else: + form = MilestoneForm( + request.user.profile, None, instance=milestone) + + context = _get_default_context(request) + context.update({'form': form, + 'milestone': milestone, + 'project': project}) + + return render_to_response('projects/milestone_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def milestone_delete(request, milestone_id, response_format='html'): + """Milestone delete""" + + milestone = get_object_or_404(Milestone, pk=milestone_id) + project = milestone.project + if not request.user.profile.has_permission(milestone, mode='w'): + return user_denied(request, message="You don't have access to this Milestone") + + query = Q(milestone=milestone, parent__isnull=True) + if request.GET: + query = query & _get_filter_query(request.GET) + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + milestone.trash = True + milestone.save() + else: + milestone.delete() + return HttpResponseRedirect(reverse('projects_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('projects_milestone_view', args=[milestone.id])) + + context = _get_default_context(request) + context.update({'milestone': milestone, + 'tasks': tasks, + 'project': project}) + + return render_to_response('projects/milestone_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def milestone_set_status(request, milestone_id, status_id, response_format='html'): + """Milestone quick set: Status""" + + milestone = get_object_or_404(Milestone, pk=milestone_id) + if not request.user.profile.has_permission(milestone, mode='x'): + return user_denied(request, message="You don't have access to this Milestone") + + status = get_object_or_404(TaskStatus, pk=status_id) + if not request.user.profile.has_permission(status): + return user_denied(request, message="You don't have access to this Milestone Status") + + if not milestone.status == status: + milestone.status = status + milestone.save() + + return milestone_view(request, milestone_id, response_format) + +# +# Tasks +# + + +@handle_response_format +@treeio_login_required +def task_add(request, response_format='html'): + """New task form""" + + if request.POST: + if 'cancel' not in request.POST: + task = Task() + form = TaskForm( + request.user.profile, None, None, None, request.POST, instance=task) + if form.is_valid(): + task = form.save() + task.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + else: + return HttpResponseRedirect(reverse('projects')) + else: + form = TaskForm(request.user.profile, None, None, None) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('projects/task_add', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_add_typed(request, project_id=None, response_format='html'): + """Task add to preselected project""" + + project = None + if project_id: + project = get_object_or_404(Project, pk=project_id) + if not request.user.profile.has_permission(project, mode='x'): + project = None + + if request.POST: + if 'cancel' not in request.POST: + task = Task() + form = TaskForm( + request.user.profile, None, project_id, None, request.POST, instance=task) + if form.is_valid(): + task = form.save() + task.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + else: + return HttpResponseRedirect(reverse('projects_project_view', args=[project.id])) + else: + form = TaskForm(request.user.profile, None, project_id, None) + + context = _get_default_context(request) + context.update({'form': form, + 'project': project}) + + return render_to_response('projects/task_add_typed', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_add_to_milestone(request, milestone_id=None, response_format='html'): + """Task add to preselected project""" + + milestone = None + if milestone_id: + milestone = get_object_or_404(Milestone, pk=milestone_id) + if not request.user.profile.has_permission(milestone, mode='x'): + milestone = None + + project = milestone.project + project_id = milestone.project.id + + if request.POST: + if 'cancel' not in request.POST: + task = Task() + form = TaskForm(request.user.profile, None, + project_id, milestone_id, request.POST, instance=task) + if form.is_valid(): + task = form.save() + task.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + else: + return HttpResponseRedirect(reverse('projects_milestone_view', args=[milestone.id])) + else: + form = TaskForm( + request.user.profile, None, project_id, milestone_id) + + context = _get_default_context(request) + context.update({'form': form, + 'project': project, + 'milestone': milestone}) + + return render_to_response('projects/task_add_to_milestone', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_add_subtask(request, task_id=None, response_format='html'): + """New subtask form""" + + parent = None + if task_id: + parent = get_object_or_404(Task, pk=task_id) + if not request.user.profile.has_permission(parent, mode='x'): + parent = None + + if request.POST: + if 'cancel' not in request.POST: + task = Task() + form = TaskForm( + request.user.profile, parent, None, None, request.POST, instance=task) + if form.is_valid(): + task = form.save() + task.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_task_view', args=[parent.id])) + else: + return HttpResponseRedirect(reverse('projects_task_view', args=[parent.id])) + else: + form = TaskForm(request.user.profile, parent, None, None) + + context = _get_default_context(request) + context.update({'form': form, + 'task': parent}) + + return render_to_response('projects/task_add_subtask', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def task_view(request, task_id, response_format='html'): + """Single task view page""" + + task = get_object_or_404(Task, pk=task_id) + if not request.user.profile.has_permission(task): + return user_denied(request, message="You don't have access to this Task") + + if request.user.profile.has_permission(task, mode='x'): + if request.POST: + if 'add-work' in request.POST: + return HttpResponseRedirect(reverse('projects_task_time_slot_add', args=[task.id])) + elif 'start-work' in request.POST: + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + record = UpdateRecord() + record.record_type = 'manual' + form = TaskRecordForm( + request.user.profile, request.POST, instance=record) + if form.is_valid(): + record = form.save() + record.set_user_from_request(request) + record.save() + record.about.add(task) + task.set_last_updated() + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + else: + form = TaskRecordForm(request.user.profile) + else: + form = None + + subtasks = Object.filter_by_request( + request, Task.objects.filter(parent=task)) + time_slots = Object.filter_by_request( + request, TaskTimeSlot.objects.filter(task=task)) + + context = _get_default_context(request) + context.update({'task': task, + 'subtasks': subtasks, + 'record_form': form, + 'time_slots': time_slots}) + + if 'massform' in context and 'project' in context['massform'].fields: + del context['massform'].fields['project'] + + return render_to_response('projects/task_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_edit(request, task_id, response_format='html'): + """Task edit page""" + + task = get_object_or_404(Task, pk=task_id) + if not request.user.profile.has_permission(task, mode='w'): + return user_denied(request, message="You don't have access to this Task") + + if request.POST: + if 'cancel' not in request.POST: + form = TaskForm( + request.user.profile, None, None, None, request.POST, instance=task) + if form.is_valid(): + task = form.save() + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + else: + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + else: + form = TaskForm( + request.user.profile, None, None, None, instance=task) + + context = _get_default_context(request) + context.update({'form': form, + 'task': task}) + + return render_to_response('projects/task_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_delete(request, task_id, response_format='html'): + """Task delete""" + + task = get_object_or_404(Task, pk=task_id) + if not request.user.profile.has_permission(task, mode='w'): + return user_denied(request, message="You don't have access to this Task") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + task.trash = True + task.save() + else: + task.delete() + return HttpResponseRedirect(reverse('projects_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + + subtasks = Object.filter_by_request( + request, Task.objects.filter(parent=task)) + time_slots = Object.filter_by_request( + request, TaskTimeSlot.objects.filter(task=task)) + + context = _get_default_context(request) + context.update({'task': task, + 'subtasks': subtasks, + 'time_slots': time_slots}) + + return render_to_response('projects/task_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_set_status(request, task_id, status_id, response_format='html'): + """Task quick set: Status""" + + task = get_object_or_404(Task, pk=task_id) + if not request.user.profile.has_permission(task, mode='x'): + return user_denied(request, message="You don't have access to this Task") + + status = get_object_or_404(TaskStatus, pk=status_id) + if not request.user.profile.has_permission(status): + return user_denied(request, message="You don't have access to this Task Status") + + if not task.status == status: + task.status = status + task.save() + + return task_view(request, task_id, response_format) + + +# +# Task Time Slots +# + +@handle_response_format +@treeio_login_required +def task_time_slot_start(request, task_id, response_format='html'): + """Start TaskTimeSlot for preselected Task""" + + task = get_object_or_404(Task, pk=task_id) + if not request.user.profile.has_permission(task, mode='x'): + return user_denied(request, message="You don't have access to this Task") + + if not task.is_being_done_by(request.user.profile): + task_time_slot = TaskTimeSlot( + task=task, time_from=datetime.now(), user=request.user.profile) + task_time_slot.save() + task_time_slot.set_user_from_request(request) + + return HttpResponseRedirect(reverse('projects_task_view', args=[task_id])) + + +@handle_response_format +@treeio_login_required +def task_time_slot_stop(request, slot_id, response_format='html'): + """Stop TaskTimeSlot for preselected Task""" + + slot = get_object_or_404(TaskTimeSlot, pk=slot_id) + if not request.user.profile.has_permission(slot, mode='w'): + return user_denied(request, message="You don't have access to this TaskTimeSlot") + + if request.POST and 'stop' in request.POST: + slot.time_to = datetime.now() + slot.details = request.POST['details'] + slot.save() + + return HttpResponseRedirect(reverse('projects_task_view', args=[slot.task_id])) + + +@handle_response_format +@treeio_login_required +def task_time_slot_add(request, task_id, response_format='html'): + """Time slot add to preselected task""" + + task = get_object_or_404(Task, pk=task_id) + if not request.user.profile.has_permission(task, mode='x'): + return user_denied(request, message="You don't have access to this Task") + + if request.POST: + task_time_slot = TaskTimeSlot( + task=task, time_to=datetime.now(), user=request.user.profile) + form = TaskTimeSlotForm( + request.user.profile, task_id, request.POST, instance=task_time_slot) + if 'cancel' in request.POST: + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + elif form.is_valid(): + task_time_slot = form.save() + task_time_slot.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + else: + form = TaskTimeSlotForm(request.user.profile, task_id) + + subtasks = Object.filter_by_request( + request, Task.objects.filter(parent=task)) + time_slots = Object.filter_by_request( + request, TaskTimeSlot.objects.filter(task=task)) + + context = _get_default_context(request) + context.update({'form': form, + 'task': task, + 'subtasks': subtasks, + 'time_slots': time_slots}) + + return render_to_response('projects/task_time_add', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_time_slot_view(request, time_slot_id, response_format='html'): + """Task time slot view page""" + + task_time_slot = get_object_or_404(TaskTimeSlot, pk=time_slot_id) + task = task_time_slot.task + if not request.user.profile.has_permission(task_time_slot) \ + and not request.user.profile.has_permission(task): + return user_denied(request, message="You don't have access to this Task Time Slot") + + context = _get_default_context(request) + context.update({'task_time_slot': task_time_slot, + 'task': task}) + + return render_to_response('projects/task_time_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_time_slot_edit(request, time_slot_id, response_format='html'): + """Task time slot edit page""" + + task_time_slot = get_object_or_404(TaskTimeSlot, pk=time_slot_id) + task = task_time_slot.task + + if not request.user.profile.has_permission(task_time_slot, mode='w') \ + and not request.user.profile.has_permission(task, mode='w'): + return user_denied(request, message="You don't have access to this Task Time Slot") + + if request.POST: + form = TaskTimeSlotForm( + request.user.profile, None, request.POST, instance=task_time_slot) + if form.is_valid(): + task_time_slot = form.save() + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + else: + form = TaskTimeSlotForm( + request.user.profile, None, instance=task_time_slot) + + context = _get_default_context(request) + context.update({'form': form, + 'task_time_slot': task_time_slot, + 'task': task}) + + return render_to_response('projects/task_time_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_time_slot_delete(request, time_slot_id, response_format='html'): + """Task time slot delete""" + + task_time_slot = get_object_or_404(TaskTimeSlot, pk=time_slot_id) + task = task_time_slot.task + + if not request.user.profile.has_permission(task_time_slot, mode='w') \ + and not request.user.profile.has_permission(task, mode='w'): + return user_denied(request, message="You don't have access to this Task Time Slot") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + task_time_slot.trash = True + task_time_slot.save() + else: + task_time_slot.delete() + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('projects_task_view', args=[task.id])) + + context = _get_default_context(request) + context.update({'task_time_slot': task_time_slot, + 'task': task}) + + return render_to_response('projects/task_time_delete', context, + context_instance=RequestContext(request), response_format=response_format) + +# +# Task Statuses +# + + +@handle_response_format +@treeio_login_required +def task_status_add(request, response_format='html'): + """TaskStatus add""" + + if not request.user.profile.is_admin('treeio.projects'): + return user_denied(request, message="You don't have administrator access to the Projects module") + + if request.POST: + if 'cancel' not in request.POST: + status = TaskStatus() + form = TaskStatusForm( + request.user.profile, request.POST, instance=status) + if form.is_valid(): + status = form.save() + status.set_user_from_request(request) + return HttpResponseRedirect(reverse('projects_index_by_status', args=[status.id])) + else: + return HttpResponseRedirect(reverse('projects_settings_view')) + else: + form = TaskStatusForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('projects/status_add', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_status_edit(request, status_id, response_format='html'): + """TaskStatus edit""" + + status = get_object_or_404(TaskStatus, pk=status_id) + if not request.user.profile.has_permission(status, mode='w'): + return user_denied(request, message="You don't have access to this Task Status") + + if request.POST: + if 'cancel' not in request.POST: + form = TaskStatusForm( + request.user.profile, request.POST, instance=status) + if form.is_valid(): + status = form.save() + return HttpResponseRedirect(reverse('projects_index_by_status', args=[status.id])) + else: + return HttpResponseRedirect(reverse('projects_index_by_status', args=[status.id])) + else: + form = TaskStatusForm(request.user.profile, instance=status) + + context = _get_default_context(request) + context.update({'form': form, + 'status': status}) + + return render_to_response('projects/status_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def task_status_delete(request, status_id, response_format='html'): + """TaskStatus delete""" + + status = get_object_or_404(TaskStatus, pk=status_id) + if not request.user.profile.has_permission(status, mode='w'): + return user_denied(request, message="You don't have access to this Task Status") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + status.trash = True + status.save() + else: + status.delete() + return HttpResponseRedirect(reverse('projects_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('projects_index_by_status', args=[status.id])) + + milestones = Object.filter_by_request(request, Milestone.objects) + + context = _get_default_context(request) + context.update({'status': status, + 'milestones': milestones}) + + return render_to_response('projects/status_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Settings +# + +@handle_response_format +@treeio_login_required +def settings_view(request, response_format='html'): + """Settings""" + + if not request.user.profile.is_admin('treeio.projects'): + return user_denied(request, message="You don't have administrator access to the Projects module") + + # default task status + try: + conf = ModuleSetting.get_for_module( + 'treeio.projects', 'default_task_status')[0] + default_task_status = TaskStatus.objects.get( + pk=long(conf.value), trash=False) + except Exception: + default_task_status = None + + statuses = TaskStatus.objects.filter(trash=False) + context = _get_default_context(request) + context.update({'default_task_status': default_task_status, + 'statuses': statuses}) + + return render_to_response('projects/settings_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def settings_edit(request, response_format='html'): + """Settings""" + + if not request.user.profile.is_admin('treeio.projects'): + return user_denied(request, message="You don't have administrator access to the Projects module") + + form = None + if request.POST: + if 'cancel' not in request.POST: + form = SettingsForm(request.user.profile, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('projects_settings_view')) + else: + return HttpResponseRedirect(reverse('projects_settings_view')) + else: + form = SettingsForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('projects/settings_edit', context, + context_instance=RequestContext(request), response_format=response_format) + +# +# AJAX lookups +# + + +@treeio_login_required +def ajax_task_lookup(request, response_format='html'): + """Returns a list of matching tasks""" + + if request.GET and 'term' in request.GET: + tasks = Task.objects.filter(name__icontains=request.GET['term'])[:10] + else: + tasks = [] + + return render_to_response('projects/ajax_task_lookup', + {'tasks': tasks}, + context_instance=RequestContext(request), + response_format=response_format) + + +# +# Widgets +# + +@treeio_login_required +def widget_tasks_assigned_to_me(request, response_format='html'): + "A list of tasks assigned to current user" + + query = Q(parent__isnull=True) & Q(status__hidden=False) + + tasks = Object.filter_by_request(request, Task.objects.filter(query)) + + return render_to_response('projects/widgets/tasks_assigned_to_me', + {'tasks': tasks}, + context_instance=RequestContext(request), response_format=response_format) + +# +# Gantt Chart +# + + +@treeio_login_required +def gantt_view(request, project_id, response_format='html'): + projects = Project.objects.filter(trash=False) + project = projects.filter(pk=project_id)[0] + if not project: + raise Http404 + ganttData = [] + + # generate json + milestones = Milestone.objects.filter(project=project).filter(trash=False) + for milestone in milestones: + tasks = Task.objects.filter(milestone=milestone).filter( + start_date__isnull=False).filter(end_date__isnull=False).filter(trash=False) + series = [] + for task in tasks: + tlabel = ( + task.name[:30] + '..') if len(task.name) > 30 else task.name + tn = '%s' % ( + reverse('projects_task_view', args=[task.id]), tlabel) + series.append({'id': task.id, + 'name': tn, + 'label': tlabel, + 'start': task.start_date.date().isoformat(), + 'end': task.end_date.date().isoformat()}) + mlabel = ( + milestone.name[:30] + '..') if len(milestone.name) > 30 else milestone.name + mn = '%s' % ( + reverse('projects_milestone_view', args=[milestone.id]), mlabel) + a = {'id': milestone.id, 'name': mn, 'label': mlabel} + if series: + a['series'] = series + else: + a['series'] = [] + if milestone.start_date and milestone.end_date: + a['start'] = milestone.start_date.date().isoformat() + a['end'] = milestone.end_date.date().isoformat() + a['color'] = '#E3F3D9' + if series or (milestone.start_date and milestone.end_date): + ganttData.append(a) + unclassified = Task.objects.filter(project=project).filter(milestone__isnull=True).filter( + start_date__isnull=False).filter(end_date__isnull=False).filter(trash=False) + series = [] + for task in unclassified: + tlabel = (task.name[:30] + '..') if len(task.name) > 30 else task.name + tn = '%s' % ( + reverse('projects_task_view', args=[task.id]), tlabel) + series.append({'id': task.id, + 'name': tn, + 'label': tlabel, + 'start': task.start_date.date().isoformat(), + 'end': task.end_date.date().isoformat()}) + if series: + ganttData.append( + {'id': 0, 'name': _('Unclassified Tasks'), 'series': series}) + if ganttData: + jdata = json.dumps(ganttData) + else: + jdata = None + + return render_to_response('projects/gantt_view', + {'jdata': jdata, + 'project': project, + 'projects': projects}, + context_instance=RequestContext(request), response_format=response_format) diff --git a/data/treeio/treeio/treeio/reports/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/reports/south_migrations/0001_initial.py new file mode 100644 index 0000000..6d8f7a4 --- /dev/null +++ b/data/treeio/treeio/treeio/reports/south_migrations/0001_initial.py @@ -0,0 +1,144 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'Template' + db.create_table('reports_template', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('model', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('reports', ['Template']) + + # Adding model 'Report' + db.create_table('reports_report', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + ('template', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['reports.Template'], null=True, blank=True)), + ('content', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('reports', ['Report']) + + def backwards(self, orm): + + # Deleting model 'Template' + db.delete_table('reports_template') + + # Deleting model 'Report' + db.delete_table('reports_report') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'reports.report': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Report', '_ormbases': ['core.Object']}, + 'content': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'template': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reports.Template']", 'null': 'True', 'blank': 'True'}) + }, + 'reports.template': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Template', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'model': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['reports'] diff --git a/data/treeio/treeio/treeio/reports/south_migrations/0002_auto__del_template__add_chart__del_field_report_template__add_field_re.py b/data/treeio/treeio/treeio/reports/south_migrations/0002_auto__del_template__add_chart__del_field_report_template__add_field_re.py new file mode 100644 index 0000000..3b692d8 --- /dev/null +++ b/data/treeio/treeio/treeio/reports/south_migrations/0002_auto__del_template__add_chart__del_field_report_template__add_field_re.py @@ -0,0 +1,174 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting model 'Template' + db.delete_table('reports_template') + + # Adding model 'Chart' + db.create_table('reports_chart', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=255)), + ('report', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['reports.Report'])), + ('options', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('reports', ['Chart']) + + # Deleting field 'Report.template' + db.delete_column('reports_report', 'template_id') + + # Adding field 'Report.model' + db.add_column('reports_report', 'model', self.gf( + 'django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) + + def backwards(self, orm): + + # Adding model 'Template' + db.create_table('reports_template', ( + ('model', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=512)), + )) + db.send_create_signal('reports', ['Template']) + + # Deleting model 'Chart' + db.delete_table('reports_chart') + + # Adding field 'Report.template' + db.add_column('reports_report', 'template', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['reports.Template'], null=True, blank=True), keep_default=False) + + # Deleting field 'Report.model' + db.delete_column('reports_report', 'model') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'reports.chart': { + 'Meta': {'object_name': 'Chart', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'options': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reports.Report']"}) + }, + 'reports.report': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Report', '_ormbases': ['core.Object']}, + 'content': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'model': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['reports'] diff --git a/data/treeio/treeio/treeio/reports/south_migrations/0003_delete_old.py b/data/treeio/treeio/treeio/reports/south_migrations/0003_delete_old.py new file mode 100644 index 0000000..4d8e439 --- /dev/null +++ b/data/treeio/treeio/treeio/reports/south_migrations/0003_delete_old.py @@ -0,0 +1,134 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + +class Migration(DataMigration): + + def forwards(self, orm): + "Removes all old reports (these need to be re-created manually)" + for report in orm['reports.Report'].objects.all(): + report.delete() + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'reports.chart': { + 'Meta': {'object_name': 'Chart', '_ormbases': ['core.Object']}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'options': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'report': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reports.Report']"}) + }, + 'reports.report': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Report', '_ormbases': ['core.Object']}, + 'content': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'model': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['reports'] diff --git a/data/treeio/treeio/treeio/reports/templatetags/reports.py b/data/treeio/treeio/treeio/reports/templatetags/reports.py new file mode 100644 index 0000000..bbbdcf8 --- /dev/null +++ b/data/treeio/treeio/treeio/reports/templatetags/reports.py @@ -0,0 +1,237 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +from __future__ import division + +""" +Reports templatetags +""" +from coffin import template +from treeio.core.rendering import render_to_string +from django.utils.translation import ugettext as _ +from jinja2 import contextfunction, Markup +from jinja2.utils import internalcode +from django.template import RequestContext +from random import random +from datetime import datetime +import hashlib +from treeio.reports.helpers import loads, aggregate_functions, number_field_regex +from treeio.reports.views import _get_report_content +import json + +register = template.Library() + + +@contextfunction +def display_chart(context, chart, skip_group=False): + "Return HTML for chart" + + request = context['request'] + + response_format = 'html' + if 'response_format' in context: + response_format = context['response_format'] + + options = loads(chart.options) + + content = _get_report_content(chart.report, request) + + objs = content['set'] + + chart_dict = {} + + field_name = options['grouping'] + + model = loads(chart.report.model) + + chart_dict['yAxis'] = {'allowDecimals': False, + 'title': { + 'text': model.name.split('.')[-1] + " Count vs. " + field_name.replace('_', ' ').title()} + } + chart_dict['xAxis'] = {} + try: + xfield = objs[0]._meta.get_field_by_name(field_name)[0] + except: + chart.delete() + return + + def get_date(g, mindate): + if g and g != datetime.min.date(): + return g + else: + return mindate + + if xfield.get_internal_type() == 'ManyToManyField': + l = [] + for obj in objs: + for mi in getattr(obj, field_name).all(): + l.append(unicode(mi)) + elif xfield.get_internal_type() == 'DateTimeField' or xfield.get_internal_type() == 'DateField': + chart_dict['xAxis']['labels'] = { # 'rotation':90, + 'align': 'left', + 'x': 3, + 'y': 15} + l, m, datelist = [], [], [] + maxdate = None + mindate = None + for obj in objs: + if getattr(obj, field_name): + x = getattr(obj, field_name) + if xfield.get_internal_type() == 'DateTimeField': + x = x.date() + if not maxdate or x > maxdate: + maxdate = x + if not mindate or x < mindate: + mindate = x + datelist.append(x) + if unicode(x) not in m: + m.append(unicode(x)) + else: + datelist.append(datetime.min.date()) + while datetime.min.date() in datelist: + datelist.append(mindate) + datelist.remove(datetime.min.date()) + datelist = sorted(datelist, key=lambda g: get_date(g, mindate)) + l = [unicode(g) for g in datelist] + + # chart_dict['xAxis']['categories']=m + + chart_dict['xAxis']['type'] = 'datetime' + td = maxdate - mindate + # print (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 + chart_dict['zoomType'] = 'x' + chart_dict['xAxis']['tickInterval'] = ( + td.microseconds + ( + td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 4 + # chart_dict['xAxis']['tickWidth']= 0, + chart_dict['maxZoom'] = 14 * 24 * 3600000 # 2wks + # chart_dict['xAxis']['gridLineWidth']= 1, + chart_dict['series'] = [{'name': model.name.split('.')[-1], 'data': []}] + for x in set(l): + chart_dict['series'][0]['data'].append(('%s UTC' % x, l.count(x))) + + else: + l = [unicode(obj.get_field_value(field_name)) for obj in objs] + + if 'series' not in chart_dict: + chart_dict['series'] = [] + # chart_dict['series'].append({'name':field_name, 'data': [{'name': x, 'y':l.count(x)} for x in set(l)]}) + chart_dict['series'].append({'name': field_name.replace( + '_', ' ').title(), 'data': [[x, l.count(x)] for x in set(l)]}) + # for x in set(l): + # chart_dict['series'].append({'name':x, 'data': l.count(x)}) + # chart_dict['series'].append({'data':[{'name':x, 'y': [l.count(x)]} for x in set(l)]}) + if 'xAxis' not in chart_dict: + chart_dict['xAxis']['categories'] = [x for x in set(l)] + # Chart type specific options + + if 'legend' in options and options['legend'] == 'on': + chart_dict['legend'] = { + 'layout': 'vertical', + 'align': 'right', + 'verticalAlign': 'top', + 'x': -10, + 'y': 100, + 'borderWidth': 0 + } + if 'title' in options: + chart_dict['title'] = {'text': options['title']} + + # Create a hash and use it as a unqiue div id and var name for the chart. + hasher = hashlib.md5() + hasher.update(str(random())) + id = 'chartcontainer' + str(hasher.hexdigest()) + # Disable animation for when saving as PDF + chart_dict['chart'] = {'renderTo': id, + 'defaultSeriesType': options['type']} + # chart_dict['plotOptions'] = {'series': {'animation': False}} + + chart_dict['plotOptions'] = {'pie': { + 'allowPointSelect': True, + 'cursor': 'pointer', + 'dataLabels': { + 'enabled': False + }, + 'showInLegend': True + }} + + chart_dict['credits'] = {'enabled': False} + + rendered_options = json.dumps(chart_dict) + + rendered_options = rendered_options[ + :-1] + ", tooltip: {formatter: function() {return ''+ this.point.name +': '+ this.y;}}}" + + if 'type' in chart_dict['xAxis'] and chart_dict['xAxis']['type'] == 'datetime': + rendered_options += """ + datedata = []; + jQuery.each(options.series[0].data, function(i,item){ + date = Date.parse(item[0]); + count = item[1]; + datedata.push([date, count]); + }); + options.series[0].data = datedata; + + function merge_options(obj1,obj2){ + var obj3 = {}; + for (attrname in obj1) { obj3[attrname] = obj1[attrname]; } + for (attrname in obj2) { obj3[attrname] = obj2[attrname]; } + return obj3; + } + var dateoptions = { + + + tooltip: { + shared: true, + crosshairs: true + + }, + + }; + + + options = merge_options(options, dateoptions); + + """ + + return Markup(render_to_string('reports/tags/chart', + {'rendered_options': rendered_options, + 'id': id, + 'chart_id': chart.id, + 'chart': chart, + 'name': options['title']}, + context_instance=RequestContext(request), + response_format=response_format)) + + +register.object(display_chart) + + +@internalcode +def is_field_number(report, field_name): + model = loads(report.model) + + classobj = model.get_class_object() + field = classobj._meta.get_field_by_name(field_name)[0] + + if number_field_regex.match(field.get_internal_type()): + return True + return False + + +register.object(is_field_number) + + +@contextfunction +def select_for_aggregation(context, field_name, value): + select_str = '' + options = ''.join(['' % (name, ' selected' if value == name else '', + _(func['description'])) for name, func in + aggregate_functions.items()]) + return Markup(select_str % {'field_name': field_name, + 'options': options}) + + +register.object(select_for_aggregation) diff --git a/data/treeio/treeio/treeio/sales/forms.py b/data/treeio/treeio/treeio/sales/forms.py new file mode 100644 index 0000000..4361df4 --- /dev/null +++ b/data/treeio/treeio/treeio/sales/forms.py @@ -0,0 +1,1095 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding: utf-8 -*- +""" +Sales module forms +""" +from django.shortcuts import get_object_or_404 +from django import forms +from django.db.models import Q +from treeio.sales.models import Product, SaleOrder, SaleSource, Lead, Opportunity, \ + SaleStatus, OrderedProduct, Subscription, Currency +from treeio.identities.models import Contact +from treeio.core.models import Object, ModuleSetting, User, UpdateRecord +from django.core.urlresolvers import reverse +from treeio.core.decorators import preprocess_form +from django.utils.translation import ugettext as _ + +preprocess_form() + +standard_currencies = ( + ("AED", "AED United Arab Emirates, Dirhams"), + ("AFN", "AFN Afghanistan, Afghanis"), + ("ALL", "ALL Albania, Leke"), + ("AMD", "AMD Armenia, Drams"), + ("ANG", "ANG Netherlands Antilles, Guilders (also called Florins)"), + ("AOA", "AOA Angola, Kwanza"), + ("ARS", "ARS Argentina, Pesos"), + ("AUD", "AUD Australia, Dollars"), + ("AWG", "AWG Aruba, Guilders (also called Florins)"), + ("AZN", "AZN Azerbaijan, New Manats"), + ("BAM", "BAM Bosnia and Herzegovina, Convertible Marka"), + ("BBD", "BBD Barbados, Dollars"), + ("BDT", "BDT Bangladesh, Taka"), + ("BGN", "BGN Bulgaria, Leva"), + ("BHD", "BHD Bahrain, Dinars"), + ("BIF", "BIF Burundi, Francs"), + ("BMD", "BMD Bermuda, Dollars"), + ("BND", "BND Brunei Darussalam, Dollars"), + ("BOB", "BOB Bolivia, Bolivianos"), + ("BRL", "BRL Brazil, Brazil Real"), + ("BSD", "BSD Bahamas, Dollars"), + ("BTN", "BTN Bhutan, Ngultrum"), + ("BWP", "BWP Botswana, Pulas"), + ("BYR", "BYR Belarus, Rubles"), + ("BZD", "BZD Belize, Dollars"), + ("CAD", "CAD Canada, Dollars"), + ("CDF", "CDF Congo/Kinshasa, Congolese Francs"), + ("CHF", "CHF Switzerland, Francs"), + ("CLP", "CLP Chile, Pesos"), + ("CNY", "CNY China, Yuan Renminbi"), + ("COP", "COP Colombia, Pesos"), + ("CRC", "CRC Costa Rica, Colones"), + ("CUP", "CUP Cuba, Pesos"), + ("CVE", "CVE Cape Verde, Escudos"), + ("CZK", "CZK Czech Republic, Koruny"), + ("DJF", "DJF Djibouti, Francs"), + ("DKK", "DKK Denmark, Kroner"), + ("DOP", "DOP Dominican Republic, Pesos"), + ("DZD", "DZD Algeria, Algeria Dinars"), + ("EGP", "EGP Egypt, Pounds"), + ("ERN", "ERN Eritrea, Nakfa"), + ("ETB", "ETB Ethiopia, Birr"), + ("EUR", "EUR Euro Member Countries, Euro"), + ("FJD", "FJD Fiji, Dollars"), + ("FKP", "FKP Falkland Islands (Malvinas), Pounds"), + ("GBP", "GBP United Kingdom, Pounds"), + ("GEL", "GEL Georgia, Lari"), + ("GGP", "GGP Guernsey, Pounds"), + ("GHS", "GHS Ghana, Cedis"), + ("GIP", "GIP Gibraltar, Pounds"), + ("GMD", "GMD Gambia, Dalasi"), + ("GNF", "GNF Guinea, Francs"), + ("GTQ", "GTQ Guatemala, Quetzales"), + ("GYD", "GYD Guyana, Dollars"), + ("HKD", "HKD Hong Kong, Dollars"), + ("HNL", "HNL Honduras, Lempiras"), + ("HRK", "HRK Croatia, Kuna"), + ("HTG", "HTG Haiti, Gourdes"), + ("HUF", "HUF Hungary, Forint"), + ("IDR", "IDR Indonesia, Rupiahs"), + ("ILS", "ILS Israel, New Shekels"), + ("IMP", "IMP Isle of Man, Pounds"), + ("INR", "INR India, Rupees"), + ("IQD", "IQD Iraq, Dinars"), + ("IRR", "IRR Iran, Rials"), + ("ISK", "ISK Iceland, Kronur"), + ("JEP", "JEP Jersey, Pounds"), + ("JMD", "JMD Jamaica, Dollars"), + ("JOD", "JOD Jordan, Dinars"), + ("JPY", "JPY Japan, Yen"), + ("KES", "KES Kenya, Shillings"), + ("KGS", "KGS Kyrgyzstan, Soms"), + ("KHR", "KHR Cambodia, Riels"), + ("KMF", "KMF Comoros, Francs"), + ("KPW", "KPW Korea (North), Won"), + ("KRW", "KRW Korea (South), Won"), + ("KWD", "KWD Kuwait, Dinars"), + ("KYD", "KYD Cayman Islands, Dollars"), + ("KZT", "KZT Kazakhstan, Tenge"), + ("LAK", "LAK Laos, Kips"), + ("LBP", "LBP Lebanon, Pounds"), + ("LKR", "LKR Sri Lanka, Rupees"), + ("LRD", "LRD Liberia, Dollars"), + ("LSL", "LSL Lesotho, Maloti"), + ("LTL", "LTL Lithuania, Litai"), + ("LVL", "LVL Latvia, Lati"), + ("LYD", "LYD Libya, Dinars"), + ("MAD", "MAD Morocco, Dirhams"), + ("MDL", "MDL Moldova, Lei"), + ("MGA", "MGA Madagascar, Ariary"), + ("MKD", "MKD Macedonia, Denars"), + ("MMK", "MMK Myanmar (Burma), Kyats"), + ("MNT", "MNT Mongolia, Tugriks"), + ("MOP", "MOP Macau, Patacas"), + ("MRO", "MRO Mauritania, Ouguiyas"), + ("MUR", "MUR Mauritius, Rupees"), + ("MVR", "MVR Maldives (Maldive Islands), Rufiyaa"), + ("MWK", "MWK Malawi, Kwachas"), + ("MXN", "MXN Mexico, Pesos"), + ("MYR", "MYR Malaysia, Ringgits"), + ("MZN", "MZN Mozambique, Meticais"), + ("NAD", "NAD Namibia, Dollars"), + ("NGN", "NGN Nigeria, Nairas"), + ("NIO", "NIO Nicaragua, Cordobas"), + ("NOK", "NOK Norway, Krone"), + ("NPR", "NPR Nepal, Nepal Rupees"), + ("NZD", "NZD New Zealand, Dollars"), + ("OMR", "OMR Oman, Rials"), + ("PAB", "PAB Panama, Balboa"), + ("PEN", "PEN Peru, Nuevos Soles"), + ("PGK", "PGK Papua New Guinea, Kina"), + ("PHP", "PHP Philippines, Pesos"), + ("PKR", "PKR Pakistan, Rupees"), + ("PLN", "PLN Poland, Zlotych"), + ("PYG", "PYG Paraguay, Guarani"), + ("QAR", "QAR Qatar, Rials"), + ("RON", "RON Romania, New Lei"), + ("RSD", "RSD Serbia, Dinars"), + ("RUB", "RUB Russia, Rubles"), + ("RWF", "RWF Rwanda, Rwanda Francs"), + ("SAR", "SAR Saudi Arabia, Riyals"), + ("SBD", "SBD Solomon Islands, Dollars"), + ("SCR", "SCR Seychelles, Rupees"), + ("SDG", "SDG Sudan, Pounds"), + ("SEK", "SEK Sweden, Kronor"), + ("SGD", "SGD Singapore, Dollars"), + ("SHP", "SHP Saint Helena, Pounds"), + ("SLL", "SLL Sierra Leone, Leones"), + ("SOS", "SOS Somalia, Shillings"), + ("SPL", "SPL Seborga, Luigini"), + ("SRD", "SRD Suriname, Dollars"), + ("STD", "STD Sao Tome and Principe, Dobras"), + ("SVC", "SVC El Salvador, Colones"), + ("SYP", "SYP Syria, Pounds"), + ("SZL", "SZL Swaziland, Emalangeni"), + ("THB", "THB Thailand, Baht"), + ("TJS", "TJS Tajikistan, Somoni"), + ("TMM", "TMM Turkmenistan, Manats"), + ("TND", "TND Tunisia, Dinars"), + ("TOP", "TOP Tonga, Pa'anga"), + ("TRY", "TRY Turkey, New Lira"), + ("TTD", "TTD Trinidad and Tobago, Dollars"), + ("TVD", "TVD Tuvalu, Tuvalu Dollars"), + ("TWD", "TWD Taiwan, New Dollars"), + ("TZS", "TZS Tanzania, Shillings"), + ("UAH", "UAH Ukraine, Hryvnia"), + ("UGX", "UGX Uganda, Shillings"), + ("USD", "USD United States of America, Dollars"), + ("UYU", "UYU Uruguay, Pesos"), + ("UZS", "UZS Uzbekistan, Sums"), + ("VEF", "VEF Venezuela, Bolivares Fuertes"), + ("VND", "VND Viet Nam, Dong"), + ("VUV", "VUV Vanuatu, Vatu"), + ("WST", "WST Samoa, Tala"), + ("XAF", "XAF Communaute Financiere Africaine BEAC, Francs"), + ("XAG", "XAG Silver, Ounces"), + ("XAU", "XAU Gold, Ounces"), + ("XCD", "XCD East Caribbean Dollars"), + ("XDR", "XDR International Monetary Fund (IMF) Special Drawing Rights"), + ("XOF", "XOF Communaute Financiere Africaine BCEAO, Francs"), + ("XPD", "XPD Palladium Ounces"), + ("XPF", "XPF Comptoirs Francais du Pacifique Francs"), + ("XPT", "XPT Platinum, Ounces"), + ("YER", "YER Yemen, Rials"), + ("ZAR", "ZAR South Africa, Rand"), + ("ZMK", "ZMK Zambia, Kwacha"), + ("ZWD", "ZWD Zimbabwe, Zimbabwe Dollars") +) + +dict_currencies = dict(standard_currencies) + + +class SettingsForm(forms.Form): + + """ Administration settings form """ + + default_currency = forms.ModelChoiceField( + label=_('Base Currency'), queryset=Currency.objects) + default_lead_status = forms.ModelChoiceField( + label=_('Default Lead Status'), queryset=[]) + default_opportunity_status = forms.ModelChoiceField( + label=_('Default Opportunity Status'), queryset=[]) + default_order_status = forms.ModelChoiceField( + label=_('Default Order Status'), queryset=[]) + default_order_source = forms.ModelChoiceField( + label=_('Default Order Source'), queryset=[]) + default_order_product = forms.ModelChoiceField( + label=_('Default Order Product'), queryset=[], required=False) + order_fulfil_status = forms.ModelChoiceField( + label=_('Order Fulfilment Status'), queryset=[]) + + def __init__(self, user, *args, **kwargs): + "Sets choices and initial value" + super(SettingsForm, self).__init__(*args, **kwargs) + self.fields['default_lead_status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_leads=True)) + self.fields['default_opportunity_status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_opportunities=True)) + self.fields['default_order_status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_sales=True)) + self.fields['default_order_source'].queryset = Object.filter_permitted(user, + SaleSource.objects.all()) + self.fields['order_fulfil_status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_sales=True)) + self.fields['default_order_product'].queryset = Object.filter_permitted(user, + Product.objects.filter(active=True)) + + # Translation + + self.fields['default_currency'].label = _('Base Currency') + self.fields['default_lead_status'].label = _('Default Lead Status') + self.fields['default_opportunity_status'].label = _( + 'Default Opportunity Status') + self.fields['default_order_status'].label = _('Default Order Status') + self.fields['default_order_source'].label = _('Default Order Source') + self.fields['default_order_product'].label = _('Default Order Product') + self.fields['order_fulfil_status'].label = _('Order Fulfilment Status') + + try: + self.fields['default_currency'].queryset = Currency.objects + self.fields['default_currency'].initial = Currency.objects.get( + is_default__exact=True) + self.fields['default_currency'].widget.attrs.update( + {'popuplink': reverse('sales_currency_add')}) + except: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_opportunity_status')[0] + default_opportunity_status = SaleStatus.objects.get( + pk=long(conf.value)) + self.fields[ + 'default_opportunity_status'].initial = default_opportunity_status.id + except: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_lead_status')[0] + default_lead_status = SaleStatus.objects.get(pk=long(conf.value)) + self.fields['default_lead_status'].initial = default_lead_status.id + except: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_status')[0] + default_order_status = SaleStatus.objects.get(pk=long(conf.value)) + self.fields[ + 'default_order_status'].initial = default_order_status.id + except: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_source')[0] + default_order_source = SaleSource.objects.get(pk=long(conf.value)) + self.fields[ + 'default_order_source'].initial = default_order_source.id + except: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_product')[0] + default_order_product = Product.objects.get(pk=long(conf.value)) + self.fields[ + 'default_order_product'].initial = default_order_product.id + except: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'order_fulfil_status')[0] + order_fulfil_status = SaleStatus.objects.get(pk=long(conf.value)) + self.fields['order_fulfil_status'].initial = order_fulfil_status.id + except: + pass + + def save(self): + "Form processor" + fields = self.fields + try: + for field in fields: + if self.cleaned_data[field]: + if field == 'default_currency': + ModuleSetting.set_for_module('default_currency', + self.cleaned_data[ + 'default_currency'], + 'treeio.sales') + currency = Currency.objects.get( + pk=self.cleaned_data['default_currency']) + currency.is_default = True + currency.save() + else: + ModuleSetting.set_for_module(field, self.cleaned_data[field].id, + 'treeio.sales') + return True + except: + return False + + +class MassActionForm(forms.Form): + + """ Mass action form for Orders """ + + status = forms.ModelChoiceField(queryset=[], required=False) + assignedto = forms.ModelChoiceField(queryset=[], required=False) + delete = forms.ChoiceField(label=_("Delete"), choices=(('', '-----'), ('delete', _('Delete Completely')), + ('trash', _('Move to Trash'))), required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(MassActionForm, self).__init__(*args, **kwargs) + + self.fields['status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter( + use_sales=True), + mode='x') + self.fields['status'].label = _("Status:") + self.fields['delete'] = forms.ChoiceField(label=_("Delete"), choices=(('', '-----'), + ('delete', _( + 'Delete Completely')), + ('trash', _('Move to Trash'))), required=False) + + self.fields['assignedto'].queryset = User.objects + self.fields['assignedto'].label = _("Assign To:") + # self.fields['assignedto'].widget.attrs.update({'class': 'autocomplete', + # 'callback': reverse('identities_ajax_user_lookup')}) + + def save(self, *args, **kwargs): + "Process form" + + if self.instance: + if self.is_valid(): + if self.cleaned_data['status']: + self.instance.status = self.cleaned_data['status'] + if self.cleaned_data['assignedto']: + self.instance.assigned.add(self.cleaned_data['assignedto']) + self.instance.save() + if self.cleaned_data['delete']: + if self.cleaned_data['delete'] == 'delete': + self.instance.delete() + if self.cleaned_data['delete'] == 'trash': + self.instance.trash = True + self.instance.save() + + +class LeadMassActionForm(forms.Form): + + """ Mass action form for Orders """ + + status = forms.ModelChoiceField(queryset=[], required=False) + assignedto = forms.ModelChoiceField(queryset=[], required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(LeadMassActionForm, self).__init__(*args, **kwargs) + + self.fields['status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter( + use_leads=True), + mode='x') + self.fields['status'].label = _("Status:") + + self.fields['assignedto'].queryset = User.objects + self.fields['assignedto'].label = _("Assign To:") + # self.fields['assignedto'].widget.attrs.update({'class': 'autocomplete', + # 'callback': reverse('identities_ajax_user_lookup')}) + + def save(self, *args, **kwargs): + "Process form" + + if self.instance: + if self.is_valid(): + if self.cleaned_data['status']: + self.instance.status = self.cleaned_data['status'] + if self.cleaned_data['assignedto']: + self.instance.assigned.add(self.cleaned_data['assignedto']) + self.instance.save() + + +class OpportunityMassActionForm(forms.Form): + + """ Mass action form for Orders """ + + status = forms.ModelChoiceField(queryset=[], required=False) + assignedto = forms.ModelChoiceField(queryset=[], required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(OpportunityMassActionForm, self).__init__(*args, **kwargs) + + self.fields['status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter( + use_opportunities=True), + mode='x') + self.fields['status'].label = _("Status:") + + self.fields['assignedto'].queryset = User.objects + self.fields['assignedto'].label = _("Assign To:") + # self.fields['assignedto'].widget.attrs.update({'class': 'autocomplete', + # 'callback': reverse('identities_ajax_user_lookup')}) + + def save(self, *args, **kwargs): + "Process form" + + if self.instance: + if self.is_valid(): + if self.cleaned_data['status']: + self.instance.status = self.cleaned_data['status'] + if self.cleaned_data['assignedto']: + self.instance.assigned.add(self.cleaned_data['assignedto']) + self.instance.save() + + +class ProductMassActionForm(forms.Form): + + """ Mass action form for Products """ + + active = forms.ChoiceField(label=_("Action"), choices=(('', '-------'), ('active', 'Mark as Active'), + ('inactive', 'Mark as Inactive')), required=False) + + instance = None + + def __init__(self, user, *args, **kwargs): + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(ProductMassActionForm, self).__init__(*args, **kwargs) + + # Translation + self.fields['active'].label = _("Action") + + def save(self, *args, **kwargs): + "Process form" + if self.instance: + if self.is_valid(): + if self.cleaned_data['active'] == 'active': + self.instance.active = True + if self.cleaned_data['active'] == 'inactive': + self.instance.active = False + self.instance.save() + + +class SaleStatusForm(forms.ModelForm): + + """ Status form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '40'})) + + def __init__(self, user, *args, **kwargs): + super(SaleStatusForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + self.fields['use_leads'].label = _("Enabled for Leads") + self.fields['use_opportunities'].label = _("Enabled for Opportunities") + self.fields['use_sales'].label = _("Enabled for Sales") + + self.fields['active'].label = _("Active") + self.fields['hidden'].label = _("Hidden") + self.fields['details'].label = _("Details") + + self.fields['active'].initial = True + + class Meta: + + "Sales Status Form" + model = SaleStatus + fields = ('name', 'use_leads', 'use_opportunities', + 'use_sales', 'active', 'hidden', 'details') + + +class SaleSourceForm(forms.ModelForm): + + """ Status form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '40'})) + + def __init__(self, user, *args, **kwargs): + super(SaleSourceForm, self).__init__(*args, **kwargs) + + self.fields['active'].initial = True + self.fields['name'].label = _("Name") + self.fields['active'].label = _("Active") + self.fields['details'].label = _("Details") + + class Meta: + + "Sale Source Form" + model = SaleSource + fields = ('name', 'active', 'details') + + +class ProductForm(forms.ModelForm): + + """ Product form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '40'})) + + def __init__(self, user, parent=None, *args, **kwargs): + super(ProductForm, self).__init__(*args, **kwargs) + + self.fields['supplier'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['supplier'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['supplier'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['supplier'].label = _("Supplier") + self.fields['active'].initial = True + self.fields['active'].label = _("Active") + + manager = Product.objects.filter(active=True) + if 'instance' in kwargs: + instance = kwargs['instance'] + manager = manager.exclude(Q(parent=instance) & Q(pk=instance.id)) + self.fields['parent'].queryset = Object.filter_permitted( + user, manager, mode='x') + + if parent: + self.fields['parent'].initial = get_object_or_404( + Product, pk=parent) + self.fields['parent'].label = _("Parent") + + self.fields['product_type'].label = _("Product type") + self.fields['code'].label = _("Code") + self.fields['supplier_code'].label = _("Supplier code") + self.fields['buy_price'].label = _("Buy price") + self.fields['sell_price'].label = _("Sell price") + self.fields['stock_quantity'].label = _("Stock quantity") + self.fields['runout_action'].label = _("Runout action") + self.fields['details'].label = _("Details") + + class Meta: + + "ProductForm" + model = Product + fields = ('name', 'parent', 'product_type', 'code', 'supplier', 'supplier_code', 'buy_price', + 'sell_price', 'stock_quantity', 'active', 'runout_action', 'details') + + +class ProductFilterForm(forms.ModelForm): + + """ Ticket Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(ProductFilterForm, self).__init__(*args, **kwargs) + + self.fields['product_type'].queryset = Object.filter_permitted(user, + Product.objects.filter(active=True)) + self.fields['product_type'].required = False + self.fields['product_type'].label = _("Product type") + + self.fields['supplier'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['supplier'].required = False + self.fields['supplier'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['supplier'].label = _("Supplier") + + self.fields['active'].required = False + self.fields['active'].initial = True + self.fields['active'].label = _("Active") + + class Meta: + + "Product Filter Form" + model = Product + fields = ('product_type', 'supplier', 'active') + + +class UpdateRecordForm(forms.ModelForm): + + "UpdateRecord form" + + def __init__(self, *args, **kwargs): + super(UpdateRecordForm, self).__init__(*args, **kwargs) + + self.fields['body'].label = _("Details") + self.fields['body'].required = True + + class Meta: + + "UpdateRecordForm" + model = UpdateRecord + fields = ['body'] + + +class OrderedProductForm(forms.ModelForm): + + """ Add New Ordered Product """ + + def __init__(self, user, order, *args, **kwargs): + + super(OrderedProductForm, self).__init__(*args, **kwargs) + + self.fields['subscription'].queryset = Object.filter_permitted( + user, Subscription.objects) + self.fields['subscription'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('sales_ajax_subscription_lookup')}) + self.fields['subscription'].widget.attrs.update( + {'popuplink': reverse('sales_subscription_add')}) + self.fields['subscription'].label = _("Subscription") + + self.fields['product'].queryset = Object.filter_permitted( + user, Product.objects.filter(active=True)) + if user.is_admin('treeio.sales'): + self.fields['product'].widget.attrs.update( + {'popuplink': reverse('sales_product_add')}) + self.fields['product'].label = _("Product") + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_product')[0] + # AJAX to set the initial rate as the currency converted value of + # product sell price + self.fields['product'].initial = long(conf.value) + except: + pass + + # Tax + self.fields['tax'].widget.attrs.update( + {'popuplink': reverse('finance_tax_add')}) + + # TODO: rate + # self.fields['rate_display'].label = _("Rate") + # self.fields['rate_display'].help_text = order.currency.code + + self.fields['quantity'].label = _("Quantity") + self.fields['quantity'].initial = 1 + self.fields['discount'].label = _("Discount") + self.fields['discount'].help_text = "%" + + def save(self, *args, **kwargs): + "Set Rate" + instance = super(OrderedProductForm, self).save(commit=False) + if 'product' in self.cleaned_data and self.cleaned_data['product']: + instance.rate = self.cleaned_data['product'].sell_price + instance.rate_display = instance.rate + + return instance + + class Meta: + + "OrderedProductForm" + model = OrderedProduct + fields = ('product', 'quantity', 'subscription', + 'tax', 'discount', 'description') + + +class SubscriptionForm(forms.ModelForm): + + """ Add New Subscription """ + + def __init__(self, user, *args, **kwargs): + super(SubscriptionForm, self).__init__(*args, **kwargs) + + del self.fields['cycle_end'] + + self.fields['product'].queryset = Object.filter_permitted( + user, Product.objects) + self.fields['product'].label = _("Product") + + self.fields['client'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['client'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['client'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['client'].label = _("Client") + + self.fields['start'].widget.attrs.update({'class': 'datepicker'}) + self.fields['start'].label = _("Start") + self.fields['expiry'].widget.attrs.update({'class': 'datepicker'}) + self.fields['expiry'].label = _("Expiry") + + if 'instance' in kwargs: + self.instance = kwargs['instance'] + self.fields['start'].widget.attrs['readonly'] = True + del kwargs['instance'] + + self.fields['active'].initial = True + self.fields['active'].label = _("Active") + self.fields['cycle_period'].label = _("Cycle period") + self.fields['details'].label = _("Details") + + class Meta: + + "Subscription Form" + model = Subscription + fields = ('client', 'product', 'start', 'expiry', + 'cycle_period', 'cycle_end', 'active', 'details') + + +class OrderForm(forms.ModelForm): + + """ Order form """ + + def __init__(self, user, lead=None, opportunity=None, *args, **kwargs): + super(OrderForm, self).__init__(*args, **kwargs) + + self.fields['reference'].required = False + self.fields['reference'].label = _("Reference") + if hasattr(self, 'instance') and not self.instance.reference: + next_ref = self.instance.get_next_reference() + if next_ref: + self.fields['reference'].initial = next_ref + + self.fields['client'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['client'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['client'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['client'].label = _("Client") + + self.fields['source'].queryset = Object.filter_permitted( + user, SaleSource.objects.filter(active=True)) + self.fields['source'].label = _("Source") + + # Currency + self.fields['currency'].label = _('Currency') + instance = getattr(self, 'instance', None) + if instance and instance.id: + del self.fields['currency'] + else: + self.fields['currency'].widget.attrs.update( + {'popuplink': reverse('finance_currency_add')}) + self.fields['currency'].initial = Currency.objects.get( + is_default=True) + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_source')[0] + self.fields['source'].initial = long(conf.value) + except: + pass + + self.fields['status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_sales=True)) + self.fields['status'].label = _("Status") + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_status')[0] + self.fields['status'].initial = long(conf.value) + except: + pass + + if opportunity: + self.fields['opportunity'].queryset = Object.filter_permitted( + user, Opportunity.objects) + self.fields['opportunity'].label = _("Opportunity") + self.fields['opportunity'].initial = opportunity.id + self.fields['client'].initial = opportunity.contact_id + self.fields['source'].initial = opportunity.source_id + self.fields['assigned'].initial = [ + i.id for i in opportunity.assigned.only('id')] + else: + del self.fields['opportunity'] + + if lead: + self.fields['client'].initial = lead.contact_id + self.fields['source'].initial = lead.source_id + self.fields['assigned'].initial = [ + i.id for i in lead.assigned.only('id')] + + self.fields['assigned'].help_text = "" + self.fields['assigned'].label = _("Assigned to") + self.fields['assigned'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_user_lookup')}) + + self.fields['datetime'].label = _("Date") + self.fields['datetime'].widget.attrs.update( + {'class': 'datetimepicker'}) + self.fields['details'].label = _("Details") + + class Meta: + + "Sale Order Form" + model = SaleOrder + fields = ('reference', 'client', 'opportunity', 'currency', 'source', + 'assigned', 'status', 'datetime', 'details') + + +class OrderFilterForm(forms.ModelForm): + + """ Order Filters definition """ + + paid = forms.ChoiceField(choices=( + (None, '-----'), ('paid', _("Paid in full")), ('unpaid', _("Pending Payments"))), required=False) + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(OrderFilterForm, self).__init__(*args, **kwargs) + + if 'status' in skip: + del self.fields['status'] + else: + self.fields['status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_sales=True)) + self.fields['status'].required = False + self.fields['status'].label = _("Status") + + self.fields['paid'].label = _("Payment Status") + + self.fields['client'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['client'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['client'].required = False + self.fields['client'].label = _("Client") + + self.fields['source'].queryset = Object.filter_permitted( + user, SaleSource.objects.filter(active=True)) + self.fields['source'].required = False + self.fields['source'].label = _("Source") + + self.fields['assigned'].label = _("Assigned") + self.fields['assigned'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_user_lookup')}) + if 'assigned' in skip: + del self.fields['assigned'] + else: + self.fields['assigned'].help_text = "" + + class Meta: + + "Order Filter Form" + model = SaleOrder + fields = ('client', 'source', 'assigned', 'status') + + +class LeadForm(forms.ModelForm): + + """ Lead form """ + + def __init__(self, user, *args, **kwargs): + super(LeadForm, self).__init__(*args, **kwargs) + + self.fields['contact'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['contact'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['contact'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['contact'].label = _("Contact") + + self.fields['source'].queryset = Object.filter_permitted( + user, SaleSource.objects.filter(active=True)) + self.fields['source'].label = _("Source") + self.fields['products_interested'].queryset = Object.filter_permitted( + user, Product.objects) + self.fields['products_interested'].help_text = "" + self.fields['products_interested'].widget.attrs.update( + {'popuplink': reverse('sales_product_add')}) + self.fields['products_interested'].label = _("Products interested") + + self.fields['assigned'].help_text = "" + self.fields['assigned'].label = _("Assigned to") + self.fields['assigned'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_user_lookup')}) + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_product')[0] + self.fields['products_interested'].initial = [long(conf.value)] + except: + pass + + self.fields['status'].queryset = Object.filter_permitted( + user, SaleStatus.objects.filter(use_leads=True)) + self.fields['status'].label = _("Status") + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_lead_status')[0] + self.fields['status'].initial = long(conf.value) + except: + pass + + self.fields['contact_method'].label = _("Contact method") + self.fields['details'].label = _("Details") + + class Meta: + + "Lead Form" + model = Lead + fields = ('contact', 'source', 'products_interested', 'contact_method', + 'assigned', 'status', 'details') + + +class LeadFilterForm(forms.ModelForm): + + """ Ticket Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(LeadFilterForm, self).__init__(*args, **kwargs) + + self.fields['contact'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['contact'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['contact'].required = False + self.fields['contact'].label = _("Contact") + + self.fields['products_interested'].queryset = Object.filter_permitted( + user, Product.objects) + self.fields['products_interested'].required = False + self.fields['products_interested'].help_text = "" + self.fields['products_interested'].label = _("Products interested") + + self.fields['source'].queryset = Object.filter_permitted(user, + SaleSource.objects.filter(active=True)) + self.fields['source'].required = False + self.fields['source'].label = _("Source") + + self.fields['status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_leads=True)) + self.fields['status'].required = False + self.fields['status'].label = _("Status") + + self.fields['contact_method'].required = False + self.fields['contact_method'].label = _("Contact method") + + class Meta: + + "Lead Filter Form" + model = Lead + fields = ( + 'contact', 'source', 'products_interested', 'contact_method', 'status') + + +class OpportunityForm(forms.ModelForm): + + """ Opportunity form """ + + def __init__(self, user, lead, *args, **kwargs): + super(OpportunityForm, self).__init__(*args, **kwargs) + + self.fields['lead'].queryset = Object.filter_permitted( + user, Lead.objects) + self.fields['contact'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['contact'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['contact'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['products_interested'].queryset = Object.filter_permitted( + user, Product.objects) + self.fields['products_interested'].widget.attrs.update( + {'popuplink': reverse('sales_product_add')}) + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_product')[0] + self.fields['products_interested'].initial = [long(conf.value)] + except: + pass + self.fields['source'].queryset = Object.filter_permitted(user, + SaleSource.objects.filter(active=True)) + self.fields['status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_opportunities=True)) + self.fields['assigned'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('identities_ajax_user_lookup')}) + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_opportunity_status')[0] + self.fields['status'].initial = long(conf.value) + except: + pass + + if lead: + self.fields['lead'].initial = lead.id + self.fields['contact'].initial = lead.contact_id + self.fields['products_interested'].initial = [ + i.id for i in lead.products_interested.only('id')] + self.fields['source'].initial = lead.source_id + self.fields['assigned'].initial = [ + i.id for i in lead.assigned.only('id')] + else: + del self.fields['lead'] + + self.fields['products_interested'].help_text = "" + self.fields['assigned'].help_text = "" + + self.fields['expected_date'].widget.attrs.update( + {'class': 'datepicker'}) + self.fields['closed_date'].widget.attrs.update({'class': 'datepicker'}) + + self.fields['contact'].label = _("Contact") + self.fields['products_interested'].label = _("Products interested") + self.fields['source'].label = _("Source") + self.fields['expected_date'].label = _("Expected date") + self.fields['closed_date'].label = _("Closed date") + self.fields['assigned'].label = _("Assigned to") + self.fields['amount_display'].label = _("Amount") + self.fields['amount_currency'].label = _("Currency") + self.fields['amount_currency'].widget.attrs.update( + {'popuplink': reverse('finance_currency_add')}) + self.fields['amount_currency'].initial = Currency.objects.get( + is_default=True) + + self.fields['probability'].label = _("Probability") + self.fields['status'].label = _("Status") + self.fields['details'].label = _("Details") + + class Meta: + + "Opportunity Form" + model = Opportunity + fields = ('lead', 'contact', 'products_interested', 'source', + 'expected_date', 'closed_date', 'assigned', 'amount_currency', 'amount_display', 'probability', 'status', 'details') + + +class OpportunityFilterForm(forms.ModelForm): + + """ Opportunity Filters """ + + def __init__(self, user, skip=None, *args, **kwargs): + if skip is None: + skip = [] + super(OpportunityFilterForm, self).__init__(*args, **kwargs) + + self.fields['contact'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['contact'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['contact'].required = False + self.fields['contact'].label = _("Contact") + + self.fields['source'].queryset = Object.filter_permitted(user, + SaleSource.objects.filter(active=True)) + self.fields['source'].required = False + self.fields['source'].label = _("Source") + + self.fields['products_interested'].queryset = Object.filter_permitted(user, + Product.objects.filter(active=True)) + self.fields['products_interested'].required = False + self.fields['products_interested'].help_text = "" + self.fields['products_interested'].label = _("Products interested") + + self.fields['status'].queryset = Object.filter_permitted(user, + SaleStatus.objects.filter(use_opportunities=True)) + self.fields['status'].required = False + self.fields['status'].label = _("Status") + + class Meta: + + "Opportunity Filter Form" + model = Opportunity + fields = ('contact', 'products_interested', 'source', 'status') diff --git a/data/treeio/treeio/treeio/sales/models.py b/data/treeio/treeio/treeio/sales/models.py new file mode 100644 index 0000000..bda2fdb --- /dev/null +++ b/data/treeio/treeio/treeio/sales/models.py @@ -0,0 +1,560 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Sales module objects. + +""" +from django.core.urlresolvers import reverse +from django.db import models + +from treeio.core.models import Object, User, ModuleSetting +from treeio.identities.models import Contact +from treeio.finance.models import Transaction, Currency, Tax + +from datetime import datetime, timedelta, time +from dateutil.relativedelta import relativedelta +from decimal import Decimal, ROUND_UP +from time import time as ttime + + +class SaleStatus(Object): + "Status of the Sale" + name = models.CharField(max_length=512) + use_leads = models.BooleanField(default=False) + use_opportunities = models.BooleanField(default=False) + use_sales = models.BooleanField(default=False) + active = models.BooleanField(default=False) + hidden = models.BooleanField(default=False) + details = models.TextField(blank=True, null=True) + + searchable = False + + def __unicode__(self): + return unicode(self.name) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('sales_status_view', args=[self.id]) + except Exception: + return "" + + class Meta: + + "SalesStatus" + ordering = ('hidden', '-active', 'name') + + +class Product(Object): + "Single Product" + PRODUCT_TYPES = ( + ('service', 'Service'), + ('good', 'Good'), + ('subscription', 'Subscription'), + ('compound', 'Compound'), + ) + + ACTION_CHOICES = ( + ('inactive', 'Mark Inactive'), + ('notify', 'Notify'), + ('ignore', 'Ignore'), + ) + + name = models.CharField(max_length=512) + product_type = models.CharField(max_length=32, default='good', + choices=PRODUCT_TYPES) + parent = models.ForeignKey('self', blank=True, null=True, + related_name='child_set') + code = models.CharField(max_length=512, blank=True, null=True) + supplier = models.ForeignKey(Contact, blank=True, null=True, + on_delete=models.SET_NULL) + supplier_code = models.IntegerField(blank=True, null=True) + buy_price = models.DecimalField(max_digits=20, decimal_places=2, default=0) + sell_price = models.DecimalField( + max_digits=20, decimal_places=2, default=0) + stock_quantity = models.IntegerField(blank=True, null=True) + active = models.BooleanField(default=False) + runout_action = models.CharField(max_length=32, blank=True, null=True, + choices=ACTION_CHOICES) + details = models.TextField(blank=True, null=True) + + access_inherit = ('parent', '*module', '*user') + + def __unicode__(self): + return unicode(self.name) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('sales_product_view', args=[self.id]) + except: + return "" + + class Meta: + + "Product" + ordering = ['code'] + + +class SaleSource(Object): + "Source of Sale e.g. Search Engine" + name = models.CharField(max_length=512) + active = models.BooleanField(default=False) + details = models.TextField(blank=True, null=True) + + searchable = False + + def __unicode__(self): + return unicode(self.name) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('sales_source_view', args=[self.id]) + except Exception: + return "" + + class Meta: + ordering = ('-active', 'name') + + +class Lead(Object): + "Lead" + CONTACT_METHODS = ( + ('email', 'E-Mail'), + ('phone', 'Phone'), + ('post', 'Post'), + ('face', 'Face to Face') + ) + + contact = models.ForeignKey(Contact) + source = models.ForeignKey( + SaleSource, blank=True, null=True, on_delete=models.SET_NULL) + products_interested = models.ManyToManyField( + Product, blank=True, null=True) + contact_method = models.CharField(max_length=32, choices=CONTACT_METHODS) + assigned = models.ManyToManyField(User, related_name='sales_lead_assigned', + blank=True, null=True) + status = models.ForeignKey(SaleStatus) + details = models.TextField(blank=True, null=True) + + access_inherit = ('contact', '*module', '*user') + + def __unicode__(self): + return unicode(self.contact.name) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('sales_lead_view', args=[self.id]) + except Exception: + return "" + + class Meta: + + "Lead" + ordering = ['contact'] + + +class Opportunity(Object): + "Opportunity" + lead = models.ForeignKey( + Lead, blank=True, null=True, on_delete=models.SET_NULL) + contact = models.ForeignKey(Contact) + products_interested = models.ManyToManyField(Product) + source = models.ForeignKey( + SaleSource, blank=True, null=True, on_delete=models.SET_NULL) + expected_date = models.DateField(blank=True, null=True) + closed_date = models.DateField(blank=True, null=True) + assigned = models.ManyToManyField( + User, related_name='sales_opportunity_assigned', blank=True, null=True) + status = models.ForeignKey(SaleStatus) + probability = models.DecimalField( + max_digits=3, decimal_places=0, blank=True, null=True) + amount = models.DecimalField(max_digits=20, decimal_places=2, default=0) + amount_currency = models.ForeignKey(Currency) + amount_display = models.DecimalField( + max_digits=20, decimal_places=2, default=0) + details = models.TextField(blank=True, null=True) + + access_inherit = ('lead', 'contact', '*module', '*user') + + def __unicode__(self): + return unicode(self.contact) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('sales_opportunity_view', args=[self.id]) + except Exception: + return "" + + class Meta: + + "Opportunity" + ordering = ['-expected_date'] + + +class SaleOrder(Object): + "Sale Order" + reference = models.CharField(max_length=512, blank=True, null=True) + datetime = models.DateTimeField(default=datetime.now) + client = models.ForeignKey( + Contact, blank=True, null=True, on_delete=models.SET_NULL) + opportunity = models.ForeignKey( + Opportunity, blank=True, null=True, on_delete=models.SET_NULL) + payment = models.ManyToManyField(Transaction, blank=True, null=True) + source = models.ForeignKey(SaleSource) + assigned = models.ManyToManyField( + User, related_name='sales_saleorder_assigned', blank=True, null=True) + status = models.ForeignKey(SaleStatus) + currency = models.ForeignKey(Currency) + total = models.DecimalField(max_digits=20, decimal_places=2, default=0) + total_display = models.DecimalField( + max_digits=20, decimal_places=2, default=0) + details = models.TextField(blank=True, null=True) + + access_inherit = ('opportunity', 'client', '*module', '*user') + + def fulfil(self): + "Fulfil" + for p in self.orderedproduct_set.all(): + if not p.fulfilled: + product = p.product + product.stock_quantity -= p.quantity + product.save() + p.fulfilled = True + p.save() + if p.subscription: + p.subscription.renew() + + def get_next_reference(self): + try: + # Very dirty hack, but kinda works for reference (i.e. it doesn't + # have to be unique) + next_ref = SaleOrder.objects.all().aggregate( + models.Max('id'))['id__max'] + 1 + except: + next_ref = 1 + full_ref = '%.5d/%s' % (next_ref, str(str(ttime() * 10)[8:-2])) + return full_ref + + def save(self, *args, **kwargs): + "Automatically set order reference" + super(SaleOrder, self).save(*args, **kwargs) + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'order_fulfil_status')[0] + fulfil_status = long(conf.value) + if self.status.id == fulfil_status: + self.fulfil() + except Exception: + pass + + def __unicode__(self): + return unicode(self.reference) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('sales_order_view', args=[self.id]) + except Exception: + return "" + + def get_taxes(self, base=False): + # TODO: Compound taxes + taxes = {} + ops = self.orderedproduct_set.filter( + trash=False).filter(tax__isnull=False) + for p in ops: + if base: + item_total = p.get_total() + else: + item_total = p.get_total_display() + if p.tax.id in taxes: + taxes[p.tax.id]['amount'] += (item_total * (p.tax.rate / 100)).quantize(Decimal('.01'), + rounding=ROUND_UP) + else: + taxes[p.tax.id] = {'name': p.tax.name, 'rate': p.tax.rate, + 'amount': (item_total * (p.tax.rate / 100)) + .quantize(Decimal('.01'), rounding=ROUND_UP)} + return taxes + + def get_taxes_total(self): + taxes = self.get_taxes() + total = 0 + for tax in taxes.values(): + total += tax['amount'] + return total + + def get_subtotal(self): + sum = 0 + for p in self.orderedproduct_set.filter(trash=False): + sum += p.get_total() + self.total = sum + return sum + + def get_subtotal_display(self): + sum = 0 + for p in self.orderedproduct_set.filter(trash=False): + sum += p.get_total_display() + self.total_display = sum + return sum + + def get_total(self): + sum = 0 + for p in self.orderedproduct_set.filter(trash=False): + sum += p.get_total() + sum += self.get_taxes_total() + self.total = sum + return sum + + def get_total_display(self): + sum = 0 + for p in self.orderedproduct_set.filter(trash=False): + sum += p.get_total_display() + sum += self.get_taxes_total() + self.total_display = sum + return sum + + def update_total(self): + self.get_total() + self.get_total_display() + self.save() + + def get_total_paid(self): + return Decimal( + self.payment.filter(trash=False).aggregate(models.Sum('value_display'))['value_display__sum'] or '0') + + def balance_due(self): + return self.get_total() - self.get_total_paid() + + class Meta: + + "SaleOrder" + ordering = ['-datetime'] + + +class Subscription(Object): + "Subscription" + CYCLE_PERIODS = ( + ('daily', 'Daily'), + ('weekly', 'Weekly'), + ('monthly', 'Monthly'), + ('quarterly', 'Quarterly'), + ('yearly', 'Yearly') + ) + + client = models.ForeignKey( + Contact, blank=True, null=True, on_delete=models.SET_NULL) + product = models.ForeignKey(Product, blank=True, null=True) + start = models.DateField(default=datetime.now) + expiry = models.DateField(blank=True, null=True) + cycle_period = models.CharField(max_length=32, choices=CYCLE_PERIODS, default='month') + cycle_end = models.DateField(blank=True, null=True) + active = models.BooleanField(default=False) + details = models.CharField(max_length=512, blank=True, null=True) + + access_inherit = ('client', 'product', '*module', '*user') + + def get_cycle_start(self): + "Get the cycle start date" + if not self.cycle_end: + return None + + cycle_end = self.cycle_end + # check if we're in the 5 day window before the cycle ends for this + # subscription + if self.cycle_period == 'monthly': + p = relativedelta(months=+1) + elif self.cycle_period == 'weekly': + p = timedelta(weeks=1) + elif self.cycle_period == 'daily': + p = timedelta(days=1) + elif self.cycle_period == 'quarterly': + p = relativedelta(months=+4) + elif self.cycle_period == 'yearly': + p = relativedelta(years=1) + else: + p = relativedelta(months=+1) + + cycle_start = cycle_end - p + return cycle_start + + def renew(self): + "Renew" + if self.cycle_period == 'monthly': + p = relativedelta(months=+1) + elif self.cycle_period == 'daily': + p = timedelta(days=1) + elif self.cycle_period == 'weekly': + p = timedelta(weeks=1) + elif self.cycle_period == 'quarterly': + p = relativedelta(months=+4) + elif self.cycle_period == 'yearly': + p = relativedelta(years=1) + else: + p = relativedelta(months=+1) + + self.cycle_end = datetime.now().date() + p + self.save() + + def activate(self): + "Activate" + if self.active: + return + self.renew() + self.active = True + self.save() + + def deactivate(self): + "Deactivate" + if not self.active: + return + self.active = False + self.save() + + def invoice(self): + "Create a new sale order for self" + new_invoice = SaleOrder() + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_status')[0] + new_invoice.status = long(conf.value) + except Exception: + ss = SaleStatus.objects.all()[0] + new_invoice.status = ss + so = SaleSource.objects.all()[0] + new_invoice.source = so + new_invoice.client = self.client + new_invoice.reference = "Subscription Invoice " + \ + str(datetime.today().strftime('%Y-%m-%d')) + new_invoice.save() + try: + op = self.orderedproduct_set.filter( + trash=False).order_by('-date_created')[0] + opn = OrderedProduct() + opn.order = new_invoice + opn.product = self.product + opn.quantity = op.quantity + opn.discount = op.discount + opn.subscription = self + opn.save() + except IndexError: + opn = OrderedProduct() + opn.order = new_invoice + opn.product = self.product + opn.quantity = 1 + opn.subscription = self + opn.save() + return new_invoice.reference + + def check_status(self): + """ + Checks and sets the state of the subscription + """ + if not self.active: + return 'Inactive' + if self.expiry: + if datetime.now() > datetime.combine(self.expiry, time.min): + self.deactivate() + return 'Expired' + + if not self.cycle_end: + self.renew() + + cycle_end = self.cycle_end + # check if we're in the 5 day window before the cycle ends for this + # subscription + if datetime.now().date() >= cycle_end: + cycle_start = self.get_cycle_start() + # if we haven't already invoiced them, invoice them + grace = 3 + if (datetime.now().date() - cycle_end > timedelta(days=grace)): + # Subscription has overrun and must be shut down + return self.deactivate() + + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'order_fulfil_status')[0] + order_fulfil_status = SaleStatus.objects.get( + pk=long(conf.value)) + except Exception: + order_fulfil_status = None + + if self.orderedproduct_set.filter(order__datetime__gte=cycle_start).filter( + order__status=order_fulfil_status): + return 'Paid' + elif self.orderedproduct_set.filter(order__datetime__gte=cycle_start): + return 'Invoiced' + else: + self.invoice() + return 'Invoiced' + else: + return 'Active' + + def __unicode__(self): + return unicode(self.product) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('sales_subscription_view', args=[self.id]) + except Exception: + return "" + + class Meta: + "Subscription" + ordering = ['expiry'] + + +class OrderedProduct(Object): + "Ordered Product" + subscription = models.ForeignKey(Subscription, blank=True, null=True) + product = models.ForeignKey(Product) + quantity = models.DecimalField(max_digits=30, decimal_places=2, default=1) + discount = models.DecimalField(max_digits=5, decimal_places=2, default=0) + tax = models.ForeignKey( + Tax, blank=True, null=True, on_delete=models.SET_NULL) + rate = models.DecimalField(max_digits=20, decimal_places=2) + rate_display = models.DecimalField( + max_digits=20, decimal_places=2, default=0) + order = models.ForeignKey(SaleOrder) + description = models.TextField(blank=True, null=True) + fulfilled = models.BooleanField(default=False) + + access_inherit = ('order', '*module', '*user') + + def __unicode__(self): + return unicode(self.product) + + def get_absolute_url(self): + "Returns absolute URL" + try: + return reverse('sales_ordered_view', args=[self.id]) + except Exception: + return "" + + def get_total(self): + "Returns total sum for this item" + total = self.rate * self.quantity + if self.discount: + total = total - (total * self.discount / 100) + if total < 0: + total = Decimal(0) + return total.quantize(Decimal('.01'), rounding=ROUND_UP) + + def get_total_display(self): + "Returns total sum for this item in the display currency" + total = self.rate_display * self.quantity + if self.discount: + total = total - (total * self.discount / 100) + if total < 0: + total = Decimal(0) + return total.quantize(Decimal('.01'), rounding=ROUND_UP) + + class Meta: + ordering = ['product'] diff --git a/data/treeio/treeio/treeio/sales/south_migrations/0002_auto__del_updaterecord__add_field_orderedproduct_tax__add_field_ordere.py b/data/treeio/treeio/treeio/sales/south_migrations/0002_auto__del_updaterecord__add_field_orderedproduct_tax__add_field_ordere.py new file mode 100644 index 0000000..70247da --- /dev/null +++ b/data/treeio/treeio/treeio/sales/south_migrations/0002_auto__del_updaterecord__add_field_orderedproduct_tax__add_field_ordere.py @@ -0,0 +1,502 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models +from decimal import Decimal +from treeio.sales.models import OrderedProduct, Product + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + if not db.dry_run: + ops = OrderedProduct.objects.all() + ps = Product.objects.all() + + # Deleting model 'UpdateRecord' + db.delete_table('sales_updaterecord') + + # Adding field 'OrderedProduct.tax' + db.add_column('sales_orderedproduct', 'tax', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['finance.Tax'], null=True, blank=True), keep_default=False) + + # Adding field 'OrderedProduct.rate' + db.add_column('sales_orderedproduct', 'rate', self.gf('django.db.models.fields.DecimalField')( + default=1, max_digits=20, decimal_places=2), keep_default=False) + + # Adding field 'OrderedProduct.rate_display' + db.add_column('sales_orderedproduct', 'rate_display', self.gf('django.db.models.fields.DecimalField')( + default=0, max_digits=20, decimal_places=2), keep_default=False) + + # Adding field 'OrderedProduct.description' + db.add_column('sales_orderedproduct', 'description', self.gf( + 'django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) + + if not db.dry_run: + # convert old Float fields to temporary fields + for op in ops: + op.discount_f = op.discount + op.discount = 0 + op.quantity_f = op.quantity + op.quantity = 0 + op.save() + + # Changing field 'OrderedProduct.discount' + db.alter_column('sales_orderedproduct', 'discount', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=4, decimal_places=2)) + + # Changing field 'OrderedProduct.quantity' + db.alter_column('sales_orderedproduct', 'quantity', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=30, decimal_places=2)) + + if not db.dry_run: + for p in ps: + p.buy_price_f = p.buy_price + p.buy_price = 0 + p.sell_price_f = p.sell_price + p.sell_price = 0 + p.save() + + # Changing field 'Product.buy_price' + db.alter_column('sales_product', 'buy_price', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Changing field 'Product.sell_price' + db.alter_column('sales_product', 'sell_price', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Adding field 'Opportunity.amount_currency' + db.add_column('sales_opportunity', 'amount_currency', self.gf( + 'django.db.models.fields.related.ForeignKey')(default=1, to=orm['finance.Currency']), keep_default=False) + + # Adding field 'Opportunity.amount_display' + db.add_column('sales_opportunity', 'amount_display', self.gf('django.db.models.fields.DecimalField')( + default=1, max_digits=20, decimal_places=2), keep_default=False) + + # Changing field 'Opportunity.probability' + db.alter_column('sales_opportunity', 'probability', self.gf( + 'django.db.models.fields.DecimalField')(null=True, max_digits=3, decimal_places=0)) + + # Changing field 'Opportunity.amount' + db.alter_column('sales_opportunity', 'amount', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=20, decimal_places=2)) + + # Deleting field 'SaleOrder.payment' + db.delete_column('sales_saleorder', 'payment_id') + + # Adding field 'SaleOrder.currency' + db.add_column('sales_saleorder', 'currency', self.gf('django.db.models.fields.related.ForeignKey')( + default=1, to=orm['finance.Currency']), keep_default=False) + + # Adding field 'SaleOrder.total' + db.add_column('sales_saleorder', 'total', self.gf('django.db.models.fields.DecimalField')( + default=0, max_digits=20, decimal_places=2), keep_default=False) + + # Adding field 'SaleOrder.total_display' + db.add_column('sales_saleorder', 'total_display', self.gf('django.db.models.fields.DecimalField')( + default=0, max_digits=20, decimal_places=2), keep_default=False) + + # Adding M2M table for field payment on 'SaleOrder' + db.create_table('sales_saleorder_payment', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('saleorder', models.ForeignKey( + orm['sales.saleorder'], null=False)), + ('transaction', models.ForeignKey( + orm['finance.transaction'], null=False)) + )) + db.create_unique( + 'sales_saleorder_payment', ['saleorder_id', 'transaction_id']) + + if not db.dry_run: + # convert temporary fields back into decimal + for op in ops: + if op.discount_f: + op.discount = Decimal(unicode(op.discount_f)).quantize( + Decimal('.01'), 'ROUND_DOWN') + if op.quantity_f: + op.quantity = int(op.quantity_f) + op.save() + + for p in ps: + if p.buy_price_f: + p.buy_price = Decimal(unicode(p.buy_price_f)) + if p.sell_price_f: + p.sell_price = Decimal(unicode(p.sell_price_f)) + p.save() + + def backwards(self, orm): + + # Adding model 'UpdateRecord' + db.create_table('sales_updaterecord', ( + ('lead', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['sales.Lead'], null=True, blank=True)), + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('record_type', self.gf( + 'django.db.models.fields.CharField')(max_length=32)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('opportunity', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['sales.Opportunity'], null=True, blank=True)), + ('order', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['sales.SaleOrder'], null=True, blank=True)), + )) + db.send_create_signal('sales', ['UpdateRecord']) + + # Deleting field 'OrderedProduct.tax' + db.delete_column('sales_orderedproduct', 'tax_id') + + # Deleting field 'OrderedProduct.rate' + db.delete_column('sales_orderedproduct', 'rate') + + # Deleting field 'OrderedProduct.rate_display' + db.delete_column('sales_orderedproduct', 'rate_display') + + # Deleting field 'OrderedProduct.description' + db.delete_column('sales_orderedproduct', 'description') + + # Changing field 'OrderedProduct.discount' + db.alter_column('sales_orderedproduct', 'discount', self.gf( + 'django.db.models.fields.FloatField')(null=True)) + + # Changing field 'OrderedProduct.quantity' + db.alter_column('sales_orderedproduct', 'quantity', self.gf( + 'django.db.models.fields.PositiveIntegerField')()) + + # Changing field 'Product.buy_price' + db.alter_column('sales_product', 'buy_price', self.gf( + 'django.db.models.fields.FloatField')(null=True)) + + # Changing field 'Product.sell_price' + db.alter_column('sales_product', 'sell_price', self.gf( + 'django.db.models.fields.FloatField')(null=True)) + + # Deleting field 'Opportunity.amount_currency' + db.delete_column('sales_opportunity', 'amount_currency_id') + + # Deleting field 'Opportunity.amount_display' + db.delete_column('sales_opportunity', 'amount_display') + + # Changing field 'Opportunity.probability' + db.alter_column('sales_opportunity', 'probability', self.gf( + 'django.db.models.fields.FloatField')(null=True)) + + # Changing field 'Opportunity.amount' + db.alter_column('sales_opportunity', 'amount', self.gf( + 'django.db.models.fields.FloatField')()) + + # Adding field 'SaleOrder.payment' + db.add_column('sales_saleorder', 'payment', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['finance.Transaction'], null=True, blank=True), keep_default=False) + + # Deleting field 'SaleOrder.currency' + db.delete_column('sales_saleorder', 'currency_id') + + # Deleting field 'SaleOrder.total' + db.delete_column('sales_saleorder', 'total') + + # Deleting field 'SaleOrder.total_display' + db.delete_column('sales_saleorder', 'total_display') + + # Removing M2M table for field payment on 'SaleOrder' + db.delete_table('sales_saleorder_payment') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'finance.account': { + 'Meta': {'ordering': "['name']", 'object_name': 'Account', '_ormbases': ['core.Object']}, + 'balance': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'balance_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'balance_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}) + }, + 'finance.category': { + 'Meta': {'object_name': 'Category', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'finance.currency': { + 'Meta': {'object_name': 'Currency', '_ormbases': ['core.Object']}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + 'factor': ('django.db.models.fields.DecimalField', [], {'default': '1', 'max_digits': '10', 'decimal_places': '4'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'symbol': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True', 'blank': 'True'}) + }, + 'finance.liability': { + 'Meta': {'ordering': "['-due_date']", 'object_name': 'Liability', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'due_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'finance.tax': { + 'Meta': {'object_name': 'Tax', '_ormbases': ['core.Object']}, + 'compound': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '2'}) + }, + 'finance.transaction': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'Transaction', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'liability': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Liability']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'sales.lead': { + 'Meta': {'ordering': "['contact']", 'object_name': 'Lead', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_lead_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'contact_method': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'products_interested': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['sales.Product']", 'null': 'True', 'blank': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}) + }, + 'sales.opportunity': { + 'Meta': {'ordering': "['-expected_date']", 'object_name': 'Opportunity', '_ormbases': ['core.Object']}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'amount_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'amount_display': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_opportunity_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'closed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'expected_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'lead': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Lead']", 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'probability': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '0', 'blank': 'True'}), + 'products_interested': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sales.Product']", 'symmetrical': 'False'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}) + }, + 'sales.orderedproduct': { + 'Meta': {'ordering': "['product']", 'object_name': 'OrderedProduct', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'discount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '4', 'decimal_places': '2'}), + 'fulfilled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleOrder']"}), + 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Product']"}), + 'quantity': ('django.db.models.fields.DecimalField', [], {'default': '1', 'max_digits': '4', 'decimal_places': '2'}), + 'rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'rate_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Subscription']", 'null': 'True', 'blank': 'True'}), + 'tax': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Tax']", 'null': 'True', 'blank': 'True'}) + }, + 'sales.product': { + 'Meta': {'ordering': "['code']", 'object_name': 'Product', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'buy_price': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'code': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['sales.Product']"}), + 'product_type': ('django.db.models.fields.CharField', [], {'default': "'good'", 'max_length': '32'}), + 'runout_action': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'sell_price': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'stock_quantity': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'supplier': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'supplier_code': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'sales.saleorder': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'SaleOrder', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_saleorder_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opportunity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Opportunity']", 'null': 'True', 'blank': 'True'}), + 'payment': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['finance.Transaction']", 'null': 'True', 'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']"}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}), + 'total': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'total_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'sales.salesource': { + 'Meta': {'ordering': "('-active', 'name')", 'object_name': 'SaleSource', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'sales.salestatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'SaleStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'use_leads': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'use_opportunities': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'use_sales': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'sales.subscription': { + 'Meta': {'ordering': "['expiry']", 'object_name': 'Subscription', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'cycle_end': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'cycle_period': ('django.db.models.fields.CharField', [], {'default': "'month'", 'max_length': '32'}), + 'details': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'expiry': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Product']", 'null': 'True', 'blank': 'True'}), + 'start': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now'}) + } + } + + complete_apps = ['sales'] diff --git a/data/treeio/treeio/treeio/sales/south_migrations/0003_treeiocurrency.py b/data/treeio/treeio/treeio/sales/south_migrations/0003_treeiocurrency.py new file mode 100644 index 0000000..7cd1a72 --- /dev/null +++ b/data/treeio/treeio/treeio/sales/south_migrations/0003_treeiocurrency.py @@ -0,0 +1,339 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models +from treeio.finance.models import Currency +from treeio.sales.models import SaleOrder, Opportunity, OrderedProduct + + +class Migration(DataMigration): + + def forwards(self, orm): + "Add currencies to financial items" + try: + currency = Currency.objects.get(is_default=True) + except: + currency = Currency.objects.create() + currency.code = "USD" + currency.name = "USD United States of America, Dollars" + currency.symbol = u"$" + currency.is_default = True + currency.save() + + for obj in SaleOrder.objects.all(): + obj.currency = currency + obj.save() + + for obj in Opportunity.objects.all(): + obj.amount_currency = currency + obj.amount_display = obj.amount + obj.save() + + for obj in OrderedProduct.objects.all(): + obj.rate = obj.product.sell_price + obj.rate_display = obj.rate + obj.save() + + for obj in SaleOrder.objects.all(): + obj.update_total() + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'finance.account': { + 'Meta': {'ordering': "['name']", 'object_name': 'Account', '_ormbases': ['core.Object']}, + 'balance': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'balance_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'balance_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}) + }, + 'finance.category': { + 'Meta': {'object_name': 'Category', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'finance.currency': { + 'Meta': {'object_name': 'Currency', '_ormbases': ['core.Object']}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + 'factor': ('django.db.models.fields.DecimalField', [], {'default': '1', 'max_digits': '10', 'decimal_places': '4'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'symbol': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True', 'blank': 'True'}) + }, + 'finance.liability': { + 'Meta': {'ordering': "['-due_date']", 'object_name': 'Liability', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'due_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'finance.tax': { + 'Meta': {'object_name': 'Tax', '_ormbases': ['core.Object']}, + 'compound': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '2'}) + }, + 'finance.transaction': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'Transaction', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'liability': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Liability']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'sales.lead': { + 'Meta': {'ordering': "['contact']", 'object_name': 'Lead', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_lead_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'contact_method': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'products_interested': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['sales.Product']", 'null': 'True', 'blank': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}) + }, + 'sales.opportunity': { + 'Meta': {'ordering': "['-expected_date']", 'object_name': 'Opportunity', '_ormbases': ['core.Object']}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'amount_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'amount_display': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_opportunity_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'closed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'expected_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'lead': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Lead']", 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'probability': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '0', 'blank': 'True'}), + 'products_interested': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sales.Product']", 'symmetrical': 'False'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}) + }, + 'sales.orderedproduct': { + 'Meta': {'ordering': "['product']", 'object_name': 'OrderedProduct', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'discount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '4', 'decimal_places': '2'}), + 'fulfilled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleOrder']"}), + 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Product']"}), + 'quantity': ('django.db.models.fields.DecimalField', [], {'default': '1', 'max_digits': '4', 'decimal_places': '2'}), + 'rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'rate_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Subscription']", 'null': 'True', 'blank': 'True'}), + 'tax': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Tax']", 'null': 'True', 'blank': 'True'}) + }, + 'sales.product': { + 'Meta': {'ordering': "['code']", 'object_name': 'Product', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'buy_price': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'code': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['sales.Product']"}), + 'product_type': ('django.db.models.fields.CharField', [], {'default': "'good'", 'max_length': '32'}), + 'runout_action': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'sell_price': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'stock_quantity': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'supplier': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'supplier_code': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'sales.saleorder': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'SaleOrder', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_saleorder_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opportunity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Opportunity']", 'null': 'True', 'blank': 'True'}), + 'payment': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['finance.Transaction']", 'null': 'True', 'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']"}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}), + 'total': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'total_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'sales.salesource': { + 'Meta': {'ordering': "('-active', 'name')", 'object_name': 'SaleSource', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'sales.salestatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'SaleStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'use_leads': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'use_opportunities': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'use_sales': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'sales.subscription': { + 'Meta': {'ordering': "['expiry']", 'object_name': 'Subscription', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'cycle_end': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'cycle_period': ('django.db.models.fields.CharField', [], {'default': "'month'", 'max_length': '32'}), + 'details': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'expiry': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Product']", 'null': 'True', 'blank': 'True'}), + 'start': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now'}) + } + } + + complete_apps = ['sales'] diff --git a/data/treeio/treeio/treeio/sales/south_migrations/0004_auto__chg_field_orderedproduct_quantity.py b/data/treeio/treeio/treeio/sales/south_migrations/0004_auto__chg_field_orderedproduct_quantity.py new file mode 100644 index 0000000..4c972b3 --- /dev/null +++ b/data/treeio/treeio/treeio/sales/south_migrations/0004_auto__chg_field_orderedproduct_quantity.py @@ -0,0 +1,314 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'OrderedProduct.quantity' + db.alter_column('sales_orderedproduct', 'quantity', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=30, decimal_places=2)) + + def backwards(self, orm): + + # Changing field 'OrderedProduct.quantity' + db.alter_column('sales_orderedproduct', 'quantity', self.gf( + 'django.db.models.fields.DecimalField')(max_digits=4, decimal_places=2)) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'finance.account': { + 'Meta': {'ordering': "['name']", 'object_name': 'Account', '_ormbases': ['core.Object']}, + 'balance': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'balance_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'balance_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}) + }, + 'finance.category': { + 'Meta': {'object_name': 'Category', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'finance.currency': { + 'Meta': {'object_name': 'Currency', '_ormbases': ['core.Object']}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '3'}), + 'factor': ('django.db.models.fields.DecimalField', [], {'default': '1', 'max_digits': '10', 'decimal_places': '4'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'symbol': ('django.db.models.fields.CharField', [], {'max_length': '1', 'null': 'True', 'blank': 'True'}) + }, + 'finance.liability': { + 'Meta': {'ordering': "['-due_date']", 'object_name': 'Liability', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'due_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_liability_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'finance.tax': { + 'Meta': {'object_name': 'Tax', '_ormbases': ['core.Object']}, + 'compound': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '2'}) + }, + 'finance.transaction': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'Transaction', '_ormbases': ['core.Object']}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Account']"}), + 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Category']", 'null': 'True', 'blank': 'True'}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'liability': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Liability']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_source'", 'to': "orm['identities.Contact']"}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'finance_transaction_target'", 'to': "orm['identities.Contact']"}), + 'value': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'value_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'value_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'sales.lead': { + 'Meta': {'ordering': "['contact']", 'object_name': 'Lead', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_lead_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'contact_method': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'products_interested': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['sales.Product']", 'null': 'True', 'blank': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}) + }, + 'sales.opportunity': { + 'Meta': {'ordering': "['-expected_date']", 'object_name': 'Opportunity', '_ormbases': ['core.Object']}, + 'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'amount_currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'amount_display': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_opportunity_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'closed_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'expected_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'lead': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Lead']", 'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'probability': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '0', 'blank': 'True'}), + 'products_interested': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sales.Product']", 'symmetrical': 'False'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}) + }, + 'sales.orderedproduct': { + 'Meta': {'ordering': "['product']", 'object_name': 'OrderedProduct', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'discount': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '4', 'decimal_places': '2'}), + 'fulfilled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleOrder']"}), + 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Product']"}), + 'quantity': ('django.db.models.fields.DecimalField', [], {'default': '1', 'max_digits': '30', 'decimal_places': '2'}), + 'rate': ('django.db.models.fields.DecimalField', [], {'max_digits': '20', 'decimal_places': '2'}), + 'rate_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'subscription': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Subscription']", 'null': 'True', 'blank': 'True'}), + 'tax': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Tax']", 'null': 'True', 'blank': 'True'}) + }, + 'sales.product': { + 'Meta': {'ordering': "['code']", 'object_name': 'Product', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'buy_price': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'code': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['sales.Product']"}), + 'product_type': ('django.db.models.fields.CharField', [], {'default': "'good'", 'max_length': '32'}), + 'runout_action': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}), + 'sell_price': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'stock_quantity': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'supplier': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'supplier_code': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'sales.saleorder': { + 'Meta': {'ordering': "['-datetime']", 'object_name': 'SaleOrder', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'sales_saleorder_assigned'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'currency': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['finance.Currency']"}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opportunity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Opportunity']", 'null': 'True', 'blank': 'True'}), + 'payment': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['finance.Transaction']", 'null': 'True', 'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleSource']"}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.SaleStatus']"}), + 'total': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}), + 'total_display': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '20', 'decimal_places': '2'}) + }, + 'sales.salesource': { + 'Meta': {'ordering': "('-active', 'name')", 'object_name': 'SaleSource', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'sales.salestatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'SaleStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'use_leads': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'use_opportunities': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'use_sales': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'sales.subscription': { + 'Meta': {'ordering': "['expiry']", 'object_name': 'Subscription', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'cycle_end': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'cycle_period': ('django.db.models.fields.CharField', [], {'default': "'month'", 'max_length': '32'}), + 'details': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'expiry': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sales.Product']", 'null': 'True', 'blank': 'True'}), + 'start': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now'}) + } + } + + complete_apps = ['sales'] diff --git a/data/treeio/treeio/treeio/sales/views.py b/data/treeio/treeio/treeio/sales/views.py new file mode 100644 index 0000000..2bec31b --- /dev/null +++ b/data/treeio/treeio/treeio/sales/views.py @@ -0,0 +1,1604 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Sales module views +""" +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.db.models import Q +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse +from treeio.sales.models import Product, SaleOrder, SaleSource, Lead, Opportunity, \ + SaleStatus, Subscription, OrderedProduct +from treeio.sales.forms import SettingsForm, OrderForm, ProductForm, SaleStatusForm, UpdateRecordForm, \ + LeadForm, OpportunityForm, OrderedProductForm, SubscriptionForm, \ + OrderFilterForm, LeadFilterForm, OpportunityFilterForm, ProductFilterForm, \ + MassActionForm, ProductMassActionForm, LeadMassActionForm, \ + OpportunityMassActionForm, SaleSourceForm +from treeio.core.rendering import render_to_response +from treeio.core.models import Object, ModuleSetting, UpdateRecord +from treeio.core.views import user_denied +from treeio.core.decorators import treeio_login_required, handle_response_format, module_admin_required +from treeio.identities.models import Contact +from treeio.finance.models import Currency +from treeio.finance.helpers import convert + + +def _get_filter_query(args, model=SaleOrder): + "Compose a filter query based on filter form submission" + query = Q() + + for arg in args: + if args[arg]: + if hasattr(model, arg) or arg == 'products_interested': + kwargs = {str(arg + '__id'): long(args[arg])} + query = query & Q(**kwargs) + + return query + + +def _process_mass_form(f): + "Pre-process request to handle mass action form for Orders" + + def wrap(request, *args, **kwargs): + "Wrap" + if 'massform' in request.POST: + for key in request.POST: + if 'mass-order' in key: + try: + order = SaleOrder.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=order) + if form.is_valid() and request.user.profile.has_permission(order, mode='w'): + form.save() + except: + pass + + return f(request, *args, **kwargs) + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + + return wrap + + +def _process_mass_lead_form(f): + "Pre-process request to handle mass action form for Orders" + + def wrap(request, *args, **kwargs): + "Wrap" + if 'massform' in request.POST: + for key in request.POST: + if 'mass-lead' in key: + try: + lead = Lead.objects.get(pk=request.POST[key]) + form = LeadMassActionForm( + request.user.profile, request.POST, instance=lead) + if form.is_valid() and request.user.profile.has_permission(lead, mode='w'): + form.save() + except: + pass + + return f(request, *args, **kwargs) + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + + return wrap + + +def _process_mass_opportunity_form(f): + "Pre-process request to handle mass action form for Orders" + + def wrap(request, *args, **kwargs): + "Wrap" + if 'massform' in request.POST: + for key in request.POST: + if 'mass-opportunity' in key: + try: + opportunity = Opportunity.objects.get( + pk=request.POST[key]) + form = OpportunityMassActionForm(request.user.profile, + request.POST, instance=opportunity) + if form.is_valid() and request.user.profile.has_permission(opportunity, + mode='w'): + form.save() + except: + pass + + return f(request, *args, **kwargs) + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + + return wrap + + +def _do_update_record(profile, request, object): + "Get the Update Record Form" + if profile.has_permission(object, mode='x'): + if request.POST: + record = UpdateRecord() + record.object = object + record.record_type = 'manual' + form = UpdateRecordForm(request.POST, instance=record) + if form.is_valid(): + record = form.save() + record.set_user_from_request(request) + record.save() + record.about.add(object) + object.set_last_updated() + else: + form = UpdateRecordForm() + else: + form = None + return form + + +@treeio_login_required +@handle_response_format +@_process_mass_form +def index(request, response_format='html'): + "Sales index page" + + query = Q(status__hidden=False) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + orders = Object.filter_by_request( + request, SaleOrder.objects.filter(query), mode="r") + filters = OrderFilterForm(request.user.profile, '', request.GET) + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + + massform = MassActionForm(request.user.profile) + + return render_to_response('sales/index', + {'orders': orders, + 'filters': filters, + 'statuses': statuses, + 'massform': massform + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +@_process_mass_form +def index_assigned(request, response_format='html'): + "Orders assigned to current user" + + query = Q(status__hidden=False, assigned=request.user.profile) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + + orders = Object.filter_by_request( + request, SaleOrder.objects.filter(query), mode="r") + filters = OrderFilterForm( + request.user.profile, 'assigned', request.GET) + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + + massform = MassActionForm(request.user.profile) + + return render_to_response('sales/index_assigned', + {'orders': orders, + 'statuses': statuses, + 'massform': massform, + 'filters': filters}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def index_status(request, response_format='html'): + "Index Status" + query = Q(status__hidden=False) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + + orders = Object.filter_by_request( + request, SaleOrder.objects.filter(query), mode="r") + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + filters = OrderFilterForm(request.user.profile, '', request.GET) + + total = 0 + + for status in statuses: + status.count = 0 + for order in orders: + if order.status == status: + if order.status.hidden is False: + total += 1 + status.count += order.quantity + + return render_to_response('sales/index_status', + {'orders': orders, + 'statuses': statuses, + 'total': total, + 'filters': filters + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_open(request, response_format='html'): + "SaleOrders owned by current user" + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'order_fulfil_status')[0] + fulfil_status = long(conf.value) + query = Q(status__hidden=False) & ~Q(status=fulfil_status) + except Exception: + query = Q(status__hidden=False) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) & Q(user=request.user) + else: + query = query & _get_filter_query(request.GET) + + orders = Object.filter_by_request( + request, SaleOrder.objects.filter(query), mode="r") + statuses = Object.filter_by_request(request, SaleStatus.objects) + filters = OrderFilterForm(request.user.profile, '', request.GET) + + massform = MassActionForm(request.user.profile) + + return render_to_response('sales/index_open', + {'orders': orders, + 'statuses': statuses, + 'massform': massform, + 'filters': filters}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def ordered_product_add(request, order_id=None, response_format='html'): + "Add new Ordered Product" + + order = get_object_or_404(SaleOrder, pk=order_id) + if not request.user.profile.has_permission(order, mode='x'): + return user_denied("Sorry, you don't have access to this Sale Order") + + if request.POST: + if 'cancel' not in request.POST: + ordered_product = OrderedProduct() + ordered_product.order = order + form = OrderedProductForm( + request.user.profile, order, request.POST, instance=ordered_product) + if form.is_valid(): + ordered_product = form.save(commit=False) + convert( + ordered_product, 'rate', currency=ordered_product.order.currency) + ordered_product.set_user_from_request(request) + ordered_product.order.update_total() + if 'add_another' in request.POST: + return HttpResponseRedirect(reverse('sales_ordered_product_add', args=[order.id])) + return HttpResponseRedirect(reverse('sales_order_view', args=[order.id])) + else: + return HttpResponseRedirect(reverse('sales_order_view', args=[order.id])) + else: + form = OrderedProductForm(request.user.profile, order) + + return render_to_response('sales/ordered_product_add', + {'form': form, + 'order': order}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def ordered_product_view(request, ordered_product_id, response_format='html'): + "Ordered product view" + ordered_product = get_object_or_404(OrderedProduct, pk=ordered_product_id) + if not request.user.profile.has_permission(ordered_product) \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have access to this Ordered Product") + + return render_to_response('sales/ordered_product_view', + {'ordered_product': ordered_product}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def ordered_product_edit(request, ordered_product_id, response_format='html'): + "OrderedProduct edit" + + ordered_product = get_object_or_404(OrderedProduct, pk=ordered_product_id) + if not request.user.profile.has_permission(ordered_product, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this OrderedProduct", response_format) + + order = ordered_product.order + if request.POST: + if 'cancel' not in request.POST: + form = OrderedProductForm( + request.user.profile, order, request.POST, instance=ordered_product) + if form.is_valid(): + ordered_product = form.save(commit=False) + convert( + ordered_product, 'rate', currency=ordered_product.order.currency) + ordered_product.order.update_total() + return HttpResponseRedirect(reverse('sales_ordered_product_view', args=[ordered_product.id])) + else: + return HttpResponseRedirect(reverse('sales_ordered_product_view', args=[ordered_product.id])) + else: + form = OrderedProductForm( + request.user.profile, order, instance=ordered_product) + + return render_to_response('sales/ordered_product_edit', + {'form': form, + 'ordered_product': ordered_product, + 'order': order + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def ordered_product_delete(request, ordered_product_id, response_format='html'): + "OrderedProduct delete" + + ordered_product = get_object_or_404(OrderedProduct, pk=ordered_product_id) + if not request.user.profile.has_permission(ordered_product, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'delete' in request.POST: + order_id = ordered_product.order_id + if 'trash' in request.POST: + ordered_product.trash = True + ordered_product.save() + else: + ordered_product.delete() + ordered_product.order.update_total() + return HttpResponseRedirect(reverse('sales_order_view', args=[order_id])) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('sales_ordered_product_view', args=[ordered_product.id])) + + order = ordered_product.order + + return render_to_response('sales/ordered_product_delete', + {'ordered_product': ordered_product, + 'order': order}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def subscription_index(request, response_format='html'): + "Subscription index page" + + query = Q(status__hidden=False) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + + subscriptions = Object.filter_by_request( + request, Subscription.objects.filter(query), mode="r") + filters = OrderFilterForm(request.user.profile, '', request.GET) + ordered_products = subscriptions.orderedproduct_set.all() + orders = ordered_products.order_set.all() + # orders = Object.filter_by_request(request, SaleOrder.objects, mode = "r") + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + + return render_to_response('sales/index', + {'orders': orders, + 'products': ordered_products, + 'filters': filters, + 'statuses': statuses + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def subscription_add(request, order_id=None, product_id=None, productset_id=None, response_format='html'): + "Product add" + if order_id: + order = get_object_or_404(SaleOrder, pk=order_id) + if product_id: + product = get_object_or_404(OrderedProduct, pk=product_id) + if productset_id: + productset = get_object_or_404(Product, pk=productset_id) + + subscription = Subscription() + if order_id: + subscription.client = order.client + if product_id: + subscription.product = product.product + if productset_id: + subscription.product = productset + + if request.POST: + if 'cancel' not in request.POST: + form = SubscriptionForm( + request.user.profile, request.POST, instance=subscription) + if form.is_valid(): + subscription = form.save(commit=False) + subscription.renew() + subscription.save() + subscription.set_user_from_request(request) + if product_id: + product.subscription = subscription + product.save() + if order_id: + return HttpResponseRedirect(reverse('sales_order_view', args=[order_id])) + else: + return HttpResponseRedirect(reverse('sales_subscription_view', args=[subscription.id])) + else: + return HttpResponseRedirect(reverse('sales_product_index')) + else: + form = SubscriptionForm( + request.user.profile, instance=subscription) + + return render_to_response('sales/subscription_add', + {'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def subscription_view(request, subscription_id, response_format='html'): + "Subscription view" + + subscription = get_object_or_404(Subscription, pk=subscription_id) + if not request.user.profile.has_permission(subscription): + return user_denied(request, message="You don't have access to this Subscription") + + query = Q(subscription=subscription) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + ops = subscription.orderedproduct_set.all() + orders = [] + for op in ops: + orders.append(op.order) + + return render_to_response('sales/subscription_view', + {'subscription': subscription, + + 'orders': orders}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def subscription_edit(request, subscription_id, response_format='html'): + "Subscription edit" + + subscription = get_object_or_404(Subscription, pk=subscription_id) + if not request.user.profile.has_permission(subscription, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Subscription", response_format) + + if request.POST: + form = SubscriptionForm( + request.user.profile, request.POST, instance=subscription) + else: + form = SubscriptionForm( + request.user.profile, instance=subscription) + if form.is_valid(): + subscription = form.save() + return HttpResponseRedirect(reverse('sales_subscription_view', args=[subscription.id])) + + return render_to_response('sales/subscription_edit', + {'form': form, + 'subscription': subscription, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def subscription_delete(request, subscription_id, response_format='html'): + "Subscription delete" + + subscription = get_object_or_404(Subscription, pk=subscription_id) + if not request.user.profile.has_permission(subscription, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + subscription.trash = True + subscription.save() + else: + subscription.delete() + return HttpResponseRedirect(reverse('sales_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('sales_subscription_view', args=[subscription.id])) + + return render_to_response('sales/subscription_delete', + {'subscription': subscription, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def product_index(request, response_format='html'): + "Product index page" + + query = Q(status__hidden=False) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + + if 'massform' in request.POST: + for key in request.POST: + if 'mass-product' in key: + try: + product = Product.objects.get(pk=request.POST[key]) + form = ProductMassActionForm( + request.user.profile, request.POST, instance=product) + if form.is_valid() and request.user.profile.has_permission(product, mode='w'): + form.save() + except: + pass + + massform = ProductMassActionForm(request.user.profile) + + query = Q(parent__isnull=True) + if request.GET: + query = query & _get_filter_query(request.GET, model=Product) + + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + products = Object.filter_by_request( + request, Product.objects.filter(query), mode="r") + filters = ProductFilterForm(request.user.profile, '', request.GET) + + return render_to_response('sales/product_index', + {'products': products, + 'filters': filters, + 'massform': massform, + 'statuses': statuses + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +@module_admin_required('treeio.sales') +def product_add(request, parent_id=None, response_format='html'): + "Product add" + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + + if request.POST: + if 'cancel' not in request.POST: + product = Product() + form = ProductForm( + request.user.profile, None, request.POST, instance=product) + if form.is_valid(): + product = form.save() + product.set_user_from_request(request) + return HttpResponseRedirect(reverse('sales_product_view', args=[product.id])) + else: + return HttpResponseRedirect(reverse('sales_product_index')) + else: + form = ProductForm(request.user.profile, parent_id) + + return render_to_response('sales/product_add', + {'form': form, + 'products': all_products}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def product_edit(request, product_id, response_format='html'): + "Product edit" + + product = get_object_or_404(Product, pk=product_id) + if not request.user.profile.has_permission(product, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Product", response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = ProductForm( + request.user.profile, None, request.POST, instance=product) + if form.is_valid(): + product = form.save() + return HttpResponseRedirect(reverse('sales_product_view', args=[product.id])) + else: + return HttpResponseRedirect(reverse('sales_product_view', args=[product.id])) + else: + form = ProductForm(request.user.profile, None, instance=product) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + + return render_to_response('sales/product_edit', + {'form': form, + 'product': product, + 'products': all_products}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def product_view(request, product_id, response_format='html'): + "Product view" + + product = get_object_or_404(Product, pk=product_id) + if not request.user.profile.has_permission(product) \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have access to this Product") + + query = Q(product=product) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + + subproducts = Object.filter_by_request( + request, Product.objects.filter(parent=product)) + subscriptions = product.subscription_set.all() + + return render_to_response('sales/product_view', + {'product': product, + 'subproducts': subproducts, + 'subscriptions': subscriptions, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def product_delete(request, product_id, response_format='html'): + "Product delete" + + product = get_object_or_404(Product, pk=product_id) + if not request.user.profile.has_permission(product, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + product.trash = True + product.save() + else: + product.delete() + return HttpResponseRedirect(reverse('sales_product_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('sales_product_view', args=[product.id])) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + subproducts = Object.filter_by_request( + request, Product.objects.filter(parent=product)) + + return render_to_response('sales/product_delete', + {'product': product, + 'subproducts': subproducts, + 'products': all_products, + 'statuses': statuses}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +@_process_mass_lead_form +def lead_index(request, response_format='html'): + "Lead index page" + + query = Q(status__hidden=False) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + + filters = LeadFilterForm(request.user.profile, '', request.GET) + + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + leads = Object.filter_by_request( + request, Lead.objects.filter(query), mode="r") + + massform = LeadMassActionForm(request.user.profile) + + return render_to_response('sales/lead_index', + {'leads': leads, + 'filters': filters, + 'massform': massform, + 'statuses': statuses + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_lead_form +def lead_index_assigned(request, response_format='html'): + "Leads owned by current user" + + query = Q(status__hidden=False, assigned=request.user.profile) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + leads = Object.filter_by_request( + request, Lead.objects.filter(query), mode="r") + filters = LeadFilterForm(request.user.profile, '', request.GET) + + massform = LeadMassActionForm(request.user.profile) + + return render_to_response('sales/lead_index_assigned', + {'leads': leads, + 'filters': filters, + 'massform': massform, + 'statuses': statuses + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def lead_add(request, lead_id=None, response_format='html'): + "Lead add" + + all_leads = Object.filter_by_request(request, Lead.objects) + + if request.POST: + if 'cancel' not in request.POST: + lead = Lead() + form = LeadForm( + request.user.profile, request.POST, instance=lead) + if form.is_valid(): + lead = form.save() + lead.set_user_from_request(request) + return HttpResponseRedirect(reverse('sales_lead_view', args=[lead.id])) + else: + return HttpResponseRedirect(reverse('sales_lead_index')) + else: + form = LeadForm(request.user.profile) + + return render_to_response('sales/lead_add', + {'form': form, + 'leads': all_leads}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def lead_edit(request, lead_id, response_format='html'): + "Lead edit" + + lead = get_object_or_404(Lead, pk=lead_id) + if not request.user.profile.has_permission(lead, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Lead", response_format) + + if request.POST: + form = LeadForm( + request.user.profile, request.POST, instance=lead) + else: + form = LeadForm(request.user.profile, instance=lead) + if form.is_valid(): + lead = form.save() + return HttpResponseRedirect(reverse('sales_lead_view', args=[lead.id])) + + return render_to_response('sales/lead_edit', + {'form': form, + 'lead': lead, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def lead_view(request, lead_id, response_format='html'): + "Queue view" + profile = request.user.profile + lead = get_object_or_404(Lead, pk=lead_id) + + if not profile.has_permission(lead) \ + and not profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have access to this Lead") + + form = _do_update_record(profile, request, lead) + if form.is_valid(): + record = form.save() + record.set_user_from_request(request) + lead = record.object + + return render_to_response('sales/lead_view', + {'lead': lead, + 'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def lead_delete(request, lead_id, response_format='html'): + "Lead delete" + + lead = get_object_or_404(Lead, pk=lead_id) + if not request.user.profile.has_permission(lead, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + lead.trash = True + lead.save() + else: + lead.delete() + return HttpResponseRedirect(reverse('sales_lead_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('sales_lead_view', args=[lead.id])) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + all_leads = Object.filter_by_request(request, Lead.objects) + + return render_to_response('sales/lead_delete', + {'lead': lead, + 'leads': all_leads, + 'products': all_products}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +@_process_mass_opportunity_form +def opportunity_index(request, response_format='html'): + "Sales index page" + + query = Q(status__hidden=False) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + + filters = OpportunityFilterForm( + request.user.profile, '', request.GET) + + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + opportunities = Object.filter_by_request( + request, Opportunity.objects.filter(query), mode="r") + + massform = OpportunityMassActionForm(request.user.profile) + + return render_to_response('sales/opportunity_index', + {'opportunities': opportunities, + 'filters': filters, + 'massform': massform, + 'statuses': statuses + }, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_opportunity_form +def opportunity_index_assigned(request, response_format='html'): + "Opportunities owned by current user" + + query = Q(status__hidden=False, assigned=request.user.profile) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = query & _get_filter_query(request.GET) + + statuses = Object.filter_by_request(request, SaleStatus.objects, mode="r") + opportunities = Object.filter_by_request( + request, Opportunity.objects.filter(query), mode="r") + filters = OpportunityFilterForm( + request.user.profile, '', request.GET) + + massform = OpportunityMassActionForm(request.user.profile) + + return render_to_response('sales/opportunity_index_assigned', + {'opportunities': opportunities, + 'filters': filters, + 'massform': massform, + 'statuses': statuses + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def opportunity_add(request, lead_id=None, response_format='html'): + "Opportunity add" + lead = None + if lead_id: + lead = get_object_or_404(Lead, pk=lead_id) + + if request.POST: + if 'cancel' not in request.POST: + form = OpportunityForm( + request.user.profile, lead, request.POST) + if form.is_valid(): + opportunity = form.save(commit=False) + convert(opportunity, 'amount') + opportunity.set_user_from_request(request) + return HttpResponseRedirect(reverse('sales_opportunity_view', args=[opportunity.id])) + else: + return HttpResponseRedirect(reverse('sales_opportunity_index')) + else: + form = OpportunityForm(request.user.profile, lead) + + return render_to_response('sales/opportunity_add', + {'form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def opportunity_edit(request, opportunity_id, response_format='html'): + "Opportunity edit" + + opportunity = get_object_or_404(Opportunity, pk=opportunity_id) + if not request.user.profile.has_permission(opportunity, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Opportunity", response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = OpportunityForm( + request.user.profile, None, request.POST, instance=opportunity) + if form.is_valid(): + opportunity = form.save() + convert(opportunity, 'amount') + return HttpResponseRedirect(reverse('sales_opportunity_view', args=[opportunity.id])) + else: + return HttpResponseRedirect(reverse('sales_opportunity_view', args=[opportunity.id])) + else: + form = OpportunityForm( + request.user.profile, None, instance=opportunity) + + all_opportunities = Object.filter_by_request(request, Opportunity.objects) + + return render_to_response('sales/opportunity_edit', + {'form': form, + 'opportunity': opportunity, + 'opportunities': all_opportunities, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def opportunity_view(request, opportunity_id, response_format='html'): + "Opportunity view" + + profile = request.user.profile + opportunity = get_object_or_404(Opportunity, pk=opportunity_id) + if not profile.has_permission(opportunity) \ + and not profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have access to this Opportunity") + + form = _do_update_record(profile, request, opportunity) + + return render_to_response('sales/opportunity_view', + {'opportunity': opportunity, + 'record_form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def opportunity_delete(request, opportunity_id, response_format='html'): + "Opportunity delete" + + opportunity = get_object_or_404(Opportunity, pk=opportunity_id) + if not request.user.profile.has_permission(opportunity, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + opportunity.trash = True + opportunity.save() + else: + opportunity.delete() + return HttpResponseRedirect(reverse('sales_opportunity_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('sales_opportunity_view', args=[opportunity.id])) + + all_opportunities = Object.filter_by_request(request, Opportunity.objects) + + return render_to_response('sales/opportunity_delete', + {'opportunity': opportunity, + 'opportunities': all_opportunities, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def order_add(request, lead_id=None, opportunity_id=None, response_format='html'): + "Order add" + + lead = None + opportunity = None + if lead_id: + lead = get_object_or_404(Lead, pk=lead_id) + if opportunity_id: + opportunity = get_object_or_404(Opportunity, pk=opportunity_id) + + if request.POST: + if 'cancel' not in request.POST: + order = SaleOrder() + form = OrderForm( + request.user.profile, lead, opportunity, request.POST, instance=order) + if form.is_valid(): + order = form.save() + order.set_user_from_request(request) + return HttpResponseRedirect(reverse('sales_order_view', args=[order.id])) + else: + return HttpResponseRedirect(reverse('sales')) + else: + form = OrderForm(request.user.profile, lead, opportunity) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + + return render_to_response('sales/order_add', + {'form': form, + 'products': all_products}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def order_edit(request, order_id, response_format='html'): + "SaleOrder edit" + + order = get_object_or_404(SaleOrder, pk=order_id) + if not request.user.profile.has_permission(order, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this SaleOrder", response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = OrderForm( + request.user.profile, None, None, request.POST, instance=order) + if form.is_valid(): + order = form.save() + return HttpResponseRedirect(reverse('sales_order_view', args=[order.id])) + else: + return HttpResponseRedirect(reverse('sales_order_view', args=[order.id])) + else: + form = OrderForm( + request.user.profile, None, None, instance=order) + + all_orders = Object.filter_by_request(request, SaleOrder.objects) + + return render_to_response('sales/order_edit', + {'form': form, + 'order': order, + 'orders': all_orders, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def order_view(request, order_id, response_format='html'): + "SaleOrder view" + profile = request.user.profile + order = get_object_or_404(SaleOrder, pk=order_id) + + form = _do_update_record(profile, request, order) + if form.is_valid(): + record = form.save() + record.set_user_from_request(request) + order = record.object + + if not profile.has_permission(order) \ + and not profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have access to this Sale") + + ordered_products = order.orderedproduct_set.filter(trash=False) + + return render_to_response('sales/order_view', + {'order': order, + 'ordered_products': ordered_products, + 'record_form': form}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def order_invoice_view(request, order_id, response_format='html'): + "Order view as Invoice" + order = get_object_or_404(SaleOrder, pk=order_id) + if not request.user.profile.has_permission(order) \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have access to this Sale") + + ordered_products = order.orderedproduct_set.filter(trash=False) + + # default company + try: + conf = ModuleSetting.get_for_module('treeio.finance', 'my_company')[0] + my_company = Contact.objects.get(pk=long(conf.value)) + + except: + my_company = None + + return render_to_response('sales/order_invoice_view', + {'order': order, + 'ordered_products': ordered_products, + 'my_company': my_company}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def order_delete(request, order_id, response_format='html'): + "SaleOrder delete" + + order = get_object_or_404(SaleOrder, pk=order_id) + if not request.user.profile.has_permission(order, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + order.trash = True + order.save() + else: + order.delete() + return HttpResponseRedirect(reverse('sales_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('sales_order_view', args=[order.id])) + + all_orders = Object.filter_by_request(request, SaleOrder.objects) + + return render_to_response('sales/order_delete', + {'order': order, + 'orders': all_orders, + }, + context_instance=RequestContext(request), response_format=response_format) + +# +# Settings +# + + +@treeio_login_required +@handle_response_format +def settings_view(request, response_format='html'): + "Settings" + + if not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have administrator access to the Sales module") + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + all_statuses = Object.filter_by_request(request, SaleStatus.objects) + all_sources = Object.filter_by_request(request, SaleSource.objects) + + # default currency + try: + default_currency = Currency.objects.get(is_default=True) + except: + default_currency = None + + # all currencies + currencies = Object.filter_by_request( + request, Currency.objects.filter(trash=False)) + + # default lead status + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_lead_status')[0] + default_lead_status = SaleStatus.objects.get(pk=long(conf.value)) + except: + default_lead_status = None + + # default opportunity status + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_opportunity_status')[0] + default_opportunity_status = SaleStatus.objects.get( + pk=long(conf.value)) + except: + default_opportunity_status = None + + # default order status + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_status')[0] + default_order_status = SaleStatus.objects.get(pk=long(conf.value)) + except: + default_order_status = None + + # default order source + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_source')[0] + default_order_source = SaleSource.objects.get(pk=long(conf.value)) + except: + default_order_source = None + + # order fulfilment status + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'order_fulfil_status')[0] + order_fulfil_status = SaleStatus.objects.get(pk=long(conf.value)) + except: + order_fulfil_status = None + + # default product + try: + conf = ModuleSetting.get_for_module( + 'treeio.sales', 'default_order_product')[0] + default_order_product = Product.objects.get(pk=long(conf.value)) + except: + default_order_product = None + + # check not trashed + + if default_opportunity_status: + if default_opportunity_status.trash: + default_opportunity_status = None + if default_lead_status: + if default_lead_status.trash: + default_lead_status = None + if default_order_status: + if default_order_status.trash: + default_order_status = None + if default_order_source: + if default_order_source.trash: + default_order_source = None + if order_fulfil_status: + if order_fulfil_status.trash: + order_fulfil_status = None + if default_order_product: + if default_order_product.trash: + default_order_product = None + + return render_to_response('sales/settings_view', + { + 'products': all_products, + 'statuses': all_statuses, + 'sources': all_sources, + 'currencies': currencies, + 'default_opportunity_status': default_opportunity_status, + 'default_lead_status': default_lead_status, + 'default_order_status': default_order_status, + 'default_order_source': default_order_source, + 'order_fulfil_status': order_fulfil_status, + 'default_order_product': default_order_product, + 'default_currency': default_currency, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def settings_edit(request, response_format='html'): + "Settings" + + if not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have administrator access to the Sales module") + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + + if request.POST: + if 'cancel' not in request.POST: + form = SettingsForm(request.user.profile, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('sales_settings_view')) + else: + return HttpResponseRedirect(reverse('sales_settings_view')) + else: + form = SettingsForm(request.user.profile) + + return render_to_response('sales/settings_edit', + { + 'products': all_products, + + 'form': form, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def status_add(request, response_format='html'): + "TicketStatus add" + + if not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have administrator access to the Sales module") + + status = None + if request.POST: + if 'cancel' not in request.POST: + status = SaleStatus() + form = SaleStatusForm( + request.user.profile, request.POST, instance=status) + if form.is_valid(): + status = form.save() + status.set_user_from_request(request) + return HttpResponseRedirect(reverse('sales_status_view', args=[status.id])) + else: + return HttpResponseRedirect(reverse('sales_settings_view')) + else: + form = SaleStatusForm(request.user.profile) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + + return render_to_response('sales/status_add', + {'form': form, + 'status': status, + 'products': all_products, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def status_view(request, status_id, response_format='html'): + "Tickets filtered by status" + + status = get_object_or_404(SaleStatus, pk=status_id) + if not request.user.profile.has_permission(status) \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have access to this Sale Status") + + query = Q(status=status) + if request.GET: + query = query & _get_filter_query(request.GET) + orders = Object.filter_by_request(request, SaleOrder.objects.filter(query)) + + return render_to_response('sales/status_view', + {'status': status, + 'orders': orders}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def status_edit(request, status_id, response_format='html'): + "SaleStatus edit" + + status = get_object_or_404(SaleStatus, pk=status_id) + if not request.user.profile.has_permission(status, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = SaleStatusForm( + request.user.profile, request.POST, instance=status) + if form.is_valid(): + status = form.save() + return HttpResponseRedirect(reverse('sales_status_view', args=[status.id])) + else: + return HttpResponseRedirect(reverse('sales_status_view', args=[status.id])) + else: + form = SaleStatusForm(request.user.profile, instance=status) + + return render_to_response('sales/status_edit', + {'form': form, + 'status': status}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def status_delete(request, status_id, response_format='html'): + "SaleStatus delete" + + status = get_object_or_404(SaleStatus, pk=status_id) + if not request.user.profile.has_permission(status, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + status.trash = True + status.save() + else: + status.delete() + return HttpResponseRedirect(reverse('sales_settings_view')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('sales_status_view', args=[status.id])) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + + return render_to_response('sales/status_delete', + {'status': status, + 'products': all_products}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def source_add(request, response_format='html'): + "TicketStatus add" + + if not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have administrator access to the Sales module") + + if request.POST: + if 'cancel' not in request.POST: + source = SaleSource() + form = SaleSourceForm( + request.user.profile, request.POST, instance=source) + if form.is_valid(): + source = form.save() + source.set_user_from_request(request) + return HttpResponseRedirect(reverse('sales_source_view', args=[source.id])) + else: + return HttpResponseRedirect(reverse('sales_settings_view')) + else: + form = SaleSourceForm(request.user.profile) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + all_sources = Object.filter_by_request(request, SaleSource.objects) + + return render_to_response('sales/source_add', + {'form': form, + 'sources': all_sources, + 'products': all_products, + }, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def source_view(request, source_id, response_format='html'): + "Orders filtered by source" + + source = get_object_or_404(SaleSource, pk=source_id) + if not request.user.profile.has_permission(source) \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, message="You don't have access to this Sale Status") + + query = Q(source=source) + if request.GET: + query = query & _get_filter_query(request.GET) + orders = Object.filter_by_request(request, SaleOrder.objects.filter(query)) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + all_sources = Object.filter_by_request(request, SaleSource.objects) + + return render_to_response('sales/source_view', + {'source': source, + 'sources': all_sources, + 'products': all_products, + 'orders': orders}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def source_edit(request, source_id, response_format='html'): + "SaleSource edit" + + source = get_object_or_404(SaleSource, pk=source_id) + if not request.user.profile.has_permission(source, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = SaleSourceForm( + request.user.profile, request.POST, instance=source) + if form.is_valid(): + source = form.save() + return HttpResponseRedirect(reverse('sales_source_view', args=[source.id])) + else: + return HttpResponseRedirect(reverse('sales_source_view', args=[source.id])) + else: + form = SaleSourceForm(request.user.profile, instance=source) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + all_sources = Object.filter_by_request(request, SaleSource.objects) + + return render_to_response('sales/source_edit', + {'form': form, + 'source': source, + 'sources': all_sources, + 'products': all_products}, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +@handle_response_format +def source_delete(request, source_id, response_format='html'): + "SaleSource delete" + + source = get_object_or_404(SaleSource, pk=source_id) + if not request.user.profile.has_permission(source, mode='w') \ + and not request.user.profile.is_admin('treeio.sales'): + return user_denied(request, "You don't have access to this Sale Status", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + source.trash = True + source.save() + else: + source.delete() + return HttpResponseRedirect(reverse('sales_settings_view')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('sales_source_view', args=[source.id])) + + all_products = Object.filter_by_request( + request, Product.objects.filter(parent__isnull=True)) + all_sources = Object.filter_by_request(request, SaleSource.objects) + + return render_to_response('sales/source_delete', + {'source': source, + 'sources': all_sources, + 'products': all_products}, + context_instance=RequestContext(request), response_format=response_format) + + +# +# AJAX handlers + + +@treeio_login_required +def ajax_subscription_lookup(request, response_format='html'): + "Returns a list of matching tasks" + + subscriptions = [] + if request.GET and 'term' in request.GET: + subscriptions = Subscription.objects.filter( + Q(client__name__icontains=request.GET['term']) | Q( + product__name__icontains=request.GET['term']) | Q( + details__icontains=request.GET['term']))[:10] + + return render_to_response('sales/ajax_subscription_lookup', + {'subscriptions': subscriptions}, + context_instance=RequestContext(request), + response_format=response_format) +# diff --git a/data/treeio/treeio/treeio/services/api/handlers.py b/data/treeio/treeio/treeio/services/api/handlers.py new file mode 100644 index 0000000..4d39f1e --- /dev/null +++ b/data/treeio/treeio/treeio/services/api/handlers.py @@ -0,0 +1,260 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# -*- coding: utf-8 -*- + +from __future__ import absolute_import, with_statement + +__all__ = ['TicketStatusHandler', 'ServiceHandler', + 'ServiceLevelAgreementHandler', 'ServiceAgentHandler', + 'TicketQueueHandler', 'TicketRecordHandler', 'TicketHandler'] + +from treeio.core.api.utils import rc +from piston3.handler import BaseHandler +from treeio.core.models import ModuleSetting +from treeio.core.api.handlers import ObjectHandler +from treeio.services.models import TicketStatus, Service, ServiceLevelAgreement, ServiceAgent, TicketQueue, Ticket, \ + TicketRecord +from treeio.services.forms import TicketForm, TicketStatusForm, TicketRecordForm, QueueForm, \ + ServiceForm, ServiceLevelAgreementForm, AgentForm +from treeio.services.views import _get_default_context + + +class TicketStatusHandler(ObjectHandler): + "Entrypoint for TicketStatus model." + model = TicketStatus + form = TicketStatusForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_services_status', [object_id]) + + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.services') + + +class ServiceHandler(ObjectHandler): + "Entrypoint for Service model." + model = Service + form = ServiceForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_services', [object_id]) + + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.services') + + def check_instance_permission(self, request, inst, mode): + return request.user.profile.has_permission(inst, mode=mode) \ + or request.user.profile.is_admin('treeio_services') + + +class ServiceLevelAgreementHandler(ObjectHandler): + "Entrypoint for ServiceLevelAgreement model." + model = ServiceLevelAgreement + form = ServiceLevelAgreementForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_services_sla', [object_id]) + + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.services') + + +class ServiceAgentHandler(ObjectHandler): + "Entrypoint for ServiceAgent model." + model = ServiceAgent + form = AgentForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_services_agents', [object_id]) + + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.services') + + +class TicketQueueHandler(ObjectHandler): + "Entrypoint for TicketQueue model." + model = TicketQueue + form = QueueForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_services_queues', [object_id]) + + def check_create_permission(self, request, mode): + return request.user.profile.is_admin('treeio.services') + + +class TicketRecordHandler(BaseHandler): + "Entrypoint for TicketRecord model." + model = TicketRecord + allowed_methods = ('GET', 'POST') + fields = ('body', 'record_type', 'author', 'comments') + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_services_ticket_records', [object_id]) + + @staticmethod + def get_ticket(request, kwargs): + if 'ticket_id' not in kwargs: + return rc.BAD_REQUEST + try: + ticket = Ticket.objects.get(pk=kwargs['ticket_id']) + except Ticket.DoesNotExist: + return rc.NOT_FOUND + + if not request.user.profile.has_permission(ticket): + return rc.FORBIDDEN + return ticket + + def read(self, request, *args, **kwargs): + ticket = self.get_ticket(request, kwargs) + + if isinstance(ticket, Ticket): + return ticket.updates.all().order_by('date_created') + else: + return ticket + + def create(self, request, *args, **kwargs): + ticket = self.get_ticket(request, kwargs) + if isinstance(ticket, Ticket): + profile = request.user.profile + if profile.has_permission(ticket, mode='x'): + context = _get_default_context(request) + agent = context['agent'] + + record = TicketRecord(sender=profile.get_contact()) + record.record_type = 'manual' + if ticket.message: + record.message = ticket.message + form = TicketRecordForm( + agent, ticket, request.data, instance=record) + if form.is_valid(): + record = form.save() + record.save() + record.set_user_from_request(request) + record.about.add(ticket) + ticket.set_last_updated() + return record + else: + self.status = 400 + return form.errors + else: + return rc.FORBIDDEN + else: + return ticket + + +class TicketHandler(ObjectHandler): + "Entrypoint for Ticket model." + model = Ticket + form = TicketForm + + @classmethod + def resource_uri(cls, obj=None): + object_id = "id" + if obj is not None: + object_id = obj.id + return ('api_services_tickets', [object_id]) + + def check_create_permission(self, request, mode): + request.context = _get_default_context(request) + request.agent = request.context['agent'] + request.profile = request.user.profile + + request.queue = None + if 'queue_id' in request.GET: + try: + request.queue = TicketQueue.objects.get( + pk=request.GET['queue_id']) + except self.model.DoesNotExist: + return False + if not request.user.profile.has_permission(request.queue, mode='x'): + request.queue = None + return True + + def check_instance_permission(self, request, inst, mode): + context = _get_default_context(request) + request.agent = context['agent'] + request.queue = None + return request.user.profile.has_permission(inst, mode=mode) + + def flatten_dict(self, request): + dct = super(TicketHandler, self).flatten_dict(request) + dct['agent'] = request.agent + dct['queue'] = request.queue + return dct + + def create_instance(self, request, *args, **kwargs): + ticket = Ticket(creator=request.user.profile) + if not request.agent: + if request.queue: + ticket.queue = request.queue + if request.queue.default_ticket_status: + ticket.status = request.queue.default_ticket_status + else: + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + ticket.status = TicketStatus.objects.get( + pk=long(conf.value)) + except: + if 'statuses' in request.context: + try: + ticket.status = request.context['statuses'][0] + except: + pass + ticket.priority = request.queue.default_ticket_priority + ticket.service = request.queue.default_service + else: + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + ticket.status = TicketStatus.objects.get( + pk=long(conf.value)) + except: + if 'statuses' in request.context: + try: + ticket.status = request.context['statuses'][0] + except: + pass + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_queue')[0] + ticket.queue = TicketQueue.objects.get(pk=long(conf.value)) + except: + if 'queues' in request.context: + try: + ticket.queue = request.context['queues'][0] + except: + pass + try: + ticket.caller = request.user.profile.get_contact() + except: + pass + return ticket diff --git a/data/treeio/treeio/treeio/services/forms.py b/data/treeio/treeio/treeio/services/forms.py new file mode 100644 index 0000000..aa05373 --- /dev/null +++ b/data/treeio/treeio/treeio/services/forms.py @@ -0,0 +1,611 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Services module forms +""" +from django import forms +from django.db.models import Q +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ +from treeio.core.conf import settings +from treeio.identities.models import Contact +from treeio.core.decorators import preprocess_form +from treeio.core.models import Object, ModuleSetting +from treeio.core.rendering import get_template_source +from treeio.messaging.models import Message +from treeio.messaging.emails import EmailMessage +from treeio.services.models import Ticket, TicketRecord, ServiceAgent, TicketStatus, Service +from treeio.services.models import ServiceLevelAgreement, TicketQueue + +preprocess_form() + + +class SettingsForm(forms.Form): + """ Administration settings form """ + + default_ticket_status = forms.ModelChoiceField( + label='Default Ticket Status', queryset=[]) + default_ticket_queue = forms.ModelChoiceField( + label='Default Queue', queryset=[]) + send_email_to_caller = forms.ChoiceField(label="Notify Caller By E-mail", choices=((True, _('Yes')), + (False, _('No'))), + required=False) + send_email_template = forms.CharField( + label="E-mail Template", widget=forms.Textarea, required=False) + + def __init__(self, user, *args, **kwargs): + "Sets choices and initial value" + super(SettingsForm, self).__init__(*args, **kwargs) + + # Translate + self.fields['default_ticket_status'].label = _('Default Ticket Status') + self.fields['default_ticket_queue'].label = _('Default Queue') + self.fields['send_email_to_caller'].label = _( + "Notify Caller By E-mail") + self.fields['send_email_template'].label = _("E-mail Template") + + self.fields['default_ticket_status'].queryset = Object.filter_permitted( + user, TicketStatus.objects, mode='x') + self.fields['default_ticket_queue'].queryset = Object.filter_permitted( + user, TicketQueue.objects, mode='x') + + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + default_ticket_status = TicketStatus.objects.get( + pk=long(conf.value)) + self.fields[ + 'default_ticket_status'].initial = default_ticket_status.id + except Exception: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_queue')[0] + default_ticket_queue = TicketQueue.objects.get(pk=long(conf.value)) + self.fields[ + 'default_ticket_queue'].initial = default_ticket_queue.id + except Exception: + pass + + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'send_email_to_caller')[0] + self.fields['send_email_to_caller'].initial = conf.value + except: + self.fields[ + 'send_email_to_caller'].initial = settings.HARDTREE_SEND_EMAIL_TO_CALLER + + # notification template + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'send_email_template')[0] + self.fields['send_email_template'].initial = conf.value + except Exception: + self.fields['send_email_template'].initial = get_template_source( + 'services/emails/notify_caller.html') + + def save(self): + "Form processor" + try: + ModuleSetting.set_for_module('default_ticket_status', + self.cleaned_data[ + 'default_ticket_status'].id, + 'treeio.services') + ModuleSetting.set_for_module('default_ticket_queue', + self.cleaned_data[ + 'default_ticket_queue'].id, + 'treeio.services') + ModuleSetting.set_for_module('send_email_to_caller', + self.cleaned_data[ + 'send_email_to_caller'], + 'treeio.services') + ModuleSetting.set_for_module('send_email_template', + self.cleaned_data[ + 'send_email_template'], + 'treeio.services') + return True + + except Exception: + return False + + +class MassActionForm(forms.Form): + """ Mass action form for Tickets """ + + status = forms.ModelChoiceField(queryset=[], required=False) + service = forms.ModelChoiceField(queryset=[], required=False) + queue = forms.ModelChoiceField(queryset=[], required=False) + delete = forms.ChoiceField(label=_("Delete"), choices=(('', '-----'), ('delete', _('Delete Completely')), + ('trash', _('Move to Trash'))), required=False) + instance = None + + def __init__(self, user, *args, **kwargs): + "Sets allowed values" + if 'instance' in kwargs: + self.instance = kwargs['instance'] + del kwargs['instance'] + + super(MassActionForm, self).__init__(*args, **kwargs) + + self.fields['status'].queryset = Object.filter_permitted( + user, TicketStatus.objects, mode='x') + self.fields['status'].label = _("Status") + self.fields['service'].queryset = Object.filter_permitted( + user, Service.objects, mode='x') + self.fields['service'].label = _("Service") + self.fields['queue'].queryset = Object.filter_permitted( + user, TicketQueue.objects, mode='x') + self.fields['queue'].label = _("Queue") + self.fields['delete'] = forms.ChoiceField(label=_("Delete"), choices=(('', '-----'), + ('delete', _( + 'Delete Completely')), + ('trash', _('Move to Trash'))), + required=False) + + def save(self, *args, **kwargs): + "Process form" + if self.instance: + if self.is_valid(): + if self.cleaned_data['service']: + self.instance.service = self.cleaned_data['service'] + if self.cleaned_data['status']: + self.instance.status = self.cleaned_data['status'] + if self.cleaned_data['queue']: + self.instance.queue = self.cleaned_data['queue'] + self.instance.save() + if self.cleaned_data['delete']: + if self.cleaned_data['delete'] == 'delete': + self.instance.delete() + if self.cleaned_data['delete'] == 'trash': + self.instance.trash = True + self.instance.save() + + +class TicketForm(forms.ModelForm): + """ Ticket form """ + name = forms.CharField( + label='Title', widget=forms.TextInput(attrs={'size': '50'})) + + def __init__(self, user, queue, agent, *args, **kwargs): + "Sets allowed values" + super(TicketForm, self).__init__(*args, **kwargs) + + # Filter allowed selections for TicketForm + self.fields['reference'].required = False + self.fields['reference'].label = _("Reference") + self.fields['caller'].queryset = Object.filter_permitted( + user, Contact.objects) + self.fields['caller'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['caller'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['caller'].label = _("Caller") + + self.fields['assigned'].queryset = Object.filter_permitted( + user, ServiceAgent.objects, mode='x') + self.fields['assigned'].label = _("Assigned to") + self.fields['assigned'].help_text = "" + self.fields['assigned'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('services_ajax_agent_lookup')}) + self.fields['assigned'].widget.attrs.update( + {'popuplink': reverse('services_agent_add')}) + + self.fields['status'].queryset = Object.filter_permitted( + user, TicketStatus.objects, mode='x') + self.fields['status'].label = _("Status") + self.fields['service'].queryset = Object.filter_permitted( + user, Service.objects, mode='x') + self.fields['service'].label = _("Service") + self.fields['queue'].queryset = Object.filter_permitted( + user, TicketQueue.objects, mode='x') + self.fields['queue'].label = _("Queue") + self.fields['sla'].queryset = Object.filter_permitted( + user, ServiceLevelAgreement.objects, mode='x') + self.fields['sla'].label = _("Service Level Agreement") + + self.fields['resolution'].label = _("Resolution") + + # Set default values if not editing + if 'instance' not in kwargs: + try: + self.fields['caller'].initial = user.get_contact().id + except Exception: + pass + + if queue: + self.fields['queue'].initial = queue.id + if queue.default_ticket_status and queue.default_ticket_status in self.fields['status'].queryset: + self.fields[ + 'status'].initial = queue.default_ticket_status_id + else: + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + self.fields['status'].initial = long(conf.value) + except: + pass + + if queue.default_ticket_priority: + self.fields[ + 'priority'].initial = queue.default_ticket_priority + if queue.default_service: + self.fields['service'].initial = queue.default_service_id + try: + default_sla = ServiceLevelAgreement.objects.get( + service=queue.default_service, default=True) + if default_sla: + self.fields['sla'].initial = default_sla.id + except: + pass + else: + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + self.fields['status'].initial = long(conf.value) + except: + pass + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_queue')[0] + self.fields['queue'].initial = long(conf.value) + except: + pass + + self.fields['name'].label = _("Name") + self.fields['name'].widget.attrs.update({'class': 'duplicates', + 'callback': reverse('services_ajax_ticket_lookup')}) + self.fields['priority'].label = _("Priority") + self.fields['priority'].choices = ((5, _('Highest')), ( + 4, _('High')), (3, _('Normal')), (2, _('Low')), (1, _('Lowest'))) + self.fields['urgency'].label = _("Urgency") + self.fields['urgency'].choices = ((5, _('Highest')), ( + 4, _('High')), (3, _('Normal')), (2, _('Low')), (1, _('Lowest'))) + self.fields['details'].label = _("Details") + + if not agent: + del self.fields['caller'] + del self.fields['reference'] + del self.fields['priority'] + del self.fields['status'] + del self.fields['queue'] + del self.fields['sla'] + del self.fields['assigned'] + del self.fields['resolution'] + + class Meta: + + "Ticket specified as model" + model = Ticket + fields = ('name', 'reference', 'caller', 'assigned', 'urgency', 'priority', + 'status', 'service', 'sla', 'queue', 'details', 'resolution') + + +class TicketStatusForm(forms.ModelForm): + """ TicketStatus form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '30'})) + + def __init__(self, user, *args, **kwargs): + "Sets allowed values" + super(TicketStatusForm, self).__init__(*args, **kwargs) + + class Meta: + "TicketStatus specified as model" + model = TicketStatus + fields = ('name', 'active', 'hidden', 'details') + + +class TicketRecordForm(forms.ModelForm): + """ TicketRecord form """ + + def __init__(self, agent, ticket, *args, **kwargs): + super(TicketRecordForm, self).__init__(*args, **kwargs) + + self.ticket = ticket + + self.fields['body'].label = _("body") + self.fields['body'].required = True + self.fields['notify'].label = _("Notify caller") + self.fields['resolution'] = forms.BooleanField( + label=_("Set as Resolution"), required=False) + + if not agent: + del self.fields['notify'] + del self.fields['resolution'] + + def save(self, *args, **kwargs): + "Set Resolution if selected" + instance = super(TicketRecordForm, self).save(*args, **kwargs) + ticket = self.ticket + if 'resolution' in self.cleaned_data and self.cleaned_data['resolution']: + ticket.resolution = self.cleaned_data['body'] + ticket.save() + + # Send update if notify clicked + if 'notify' in self.cleaned_data and self.cleaned_data['notify'] and ticket.caller: + toaddr = ticket.caller.get_email() + if ticket.message or toaddr: + reply = Message() + reply.author = instance.sender + reply.body = instance.body + reply.auto_notify = False + if ticket.message: + reply.stream = ticket.message.stream + reply.reply_to = ticket.message + else: + reply.stream = ticket.queue.message_stream if ticket.queue else None + reply.title = "[#%s] %s" % (ticket.reference, ticket.name) + reply.save() + if not ticket.message: + ticket.message = reply + reply.recipients.add(ticket.caller) + email = EmailMessage(reply) + email.send_email() + + return instance + + class Meta: + + "TicketRecord specified as model" + model = TicketRecord + fields = ['body', 'notify'] + + +class QueueForm(forms.ModelForm): + """ Queue form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '50'})) + + def __init__(self, user, *args, **kwargs): + "Sets allowed values" + super(QueueForm, self).__init__(*args, **kwargs) + + manager = TicketQueue.objects + if 'instance' in kwargs: + instance = kwargs['instance'] + manager = manager.exclude(Q(parent=instance) & Q(pk=instance.id)) + self.fields['parent'].queryset = Object.filter_permitted( + user, manager, mode='x') + + self.fields['default_service'].queryset = Object.filter_permitted( + user, Service.objects, mode='x') + + self.fields['waiting_time'].help_text = "seconds" + + self.fields['name'].label = _("Name") + self.fields['active'].label = _("Active") + self.fields['parent'].label = _("Parent") + self.fields['default_ticket_status'].label = _("Default ticket status") + self.fields['default_ticket_priority'].label = _( + "Default ticket priority") + self.fields['default_service'].label = _("Default service") + self.fields['waiting_time'].label = _("Waiting time") + self.fields['next_queue'].queryset = Object.filter_permitted( + user, TicketQueue.objects, mode='x') + self.fields['next_queue'].label = _("Next queue") + self.fields['ticket_code'].label = _("Ticket code") + self.fields['message_stream'].label = _("Message stream") + self.fields['message_stream'].widget.attrs.update( + {'popuplink': reverse('messaging_stream_add')}) + self.fields['details'].label = _("Details") + + class Meta: + "TicketQueue specified as model" + model = TicketQueue + fields = ('name', 'active', 'parent', 'default_ticket_status', + 'default_ticket_priority', 'default_service', 'waiting_time', + 'next_queue', 'ticket_code', 'message_stream', 'details') + + +class ServiceForm(forms.ModelForm): + """ Service form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '50'})) + + def __init__(self, user, *args, **kwargs): + "Sets allowed values" + super(ServiceForm, self).__init__(*args, **kwargs) + + manager = Service.objects + if 'instance' in kwargs: + instance = kwargs['instance'] + manager = manager.exclude(Q(parent=instance) & Q(pk=instance.id)) + self.fields['parent'].queryset = Object.filter_permitted( + user, manager, mode='x') + + self.fields['name'].label = _("Name") + self.fields['parent'].label = _("Parent") + self.fields['details'].label = _("Details") + + class Meta: + "Service specified as model" + model = Service + fields = ('name', 'parent', 'details') + + +class ServiceLevelAgreementForm(forms.ModelForm): + """ ServiceLevelAgreement form """ + name = forms.CharField(widget=forms.TextInput(attrs={'size': '50'})) + + def __init__(self, user, *args, **kwargs): + "Sets allowed values" + super(ServiceLevelAgreementForm, self).__init__(*args, **kwargs) + + self.fields['name'].label = _("Name") + + self.fields['response_time'].help_text = 'minutes' + self.fields['response_time'].widget.attrs.update({'size': 10}) + self.fields['response_time'].label = _("Response time") + + self.fields['uptime_rate'].help_text = 'percent' + self.fields['uptime_rate'].widget.attrs.update({'size': 5}) + self.fields['uptime_rate'].label = _("Uptime rate") + + self.fields['service'].queryset = Object.filter_permitted( + user, Service.objects, mode='x') + self.fields['service'].label = _("Service") + + self.fields['client'].queryset = Object.filter_permitted( + user, Contact.objects, mode='x') + self.fields['client'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['client'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['client'].label = _("Client") + + self.fields['provider'].queryset = Object.filter_permitted( + user, Contact.objects, mode='x') + self.fields['provider'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['provider'].widget.attrs.update( + {'popuplink': reverse('identities_contact_add')}) + self.fields['provider'].label = _("Provider") + + self.fields['available_from'].initial = "09:00" + self.fields['available_from'].widget.attrs.update({'size': 10}) + self.fields['available_from'].label = _("Available from") + self.fields['available_to'].initial = "18:00" + self.fields['available_to'].widget.attrs.update({'size': 10}) + self.fields['available_to'].label = _("Available to") + + contact = user.default_group.get_contact() + if contact: + self.fields['provider'].initial = contact.id + + class Meta: + "ServiceLevelAgreement specified as model" + model = ServiceLevelAgreement + fields = ('name', 'service', 'client', 'provider', 'response_time', 'uptime_rate', 'available_from', + 'available_to') + + +class AgentForm(forms.ModelForm): + """ Agent form """ + + def __init__(self, user, *args, **kwargs): + "Sets allowed values" + super(AgentForm, self).__init__(*args, **kwargs) + + self.fields['related_user'].label = _("Related user") + self.fields['related_user'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_user_lookup')}) + self.fields['active'].label = _("Active") + self.fields['occupied'].label = _("Occupied") + self.fields['available_from'].label = _("Available from") + self.fields['available_to'].label = _("Available to") + + class Meta: + "Agent specified as model" + model = ServiceAgent + fields = ('related_user', 'active', 'occupied', + 'available_from', 'available_to') + + +class FilterForm(forms.ModelForm): + """ Ticket Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + "Sets allowed values" + if skip is None: + skip = [] + super(FilterForm, self).__init__(*args, **kwargs) + + if 'caller' in skip: + del self.fields['caller'] + else: + self.fields['caller'].queryset = Object.filter_permitted( + user, Contact.objects, mode='x') + self.fields['caller'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['caller'].label = _("Caller") + + if 'status' in skip: + del self.fields['status'] + else: + self.fields['status'].queryset = Object.filter_permitted( + user, TicketStatus.objects, mode='x') + self.fields['status'].label = _("Status") + + self.fields['service'].queryset = Object.filter_permitted( + user, Service.objects, mode='x') + self.fields['service'].label = _("Service") + + self.fields['sla'].queryset = Object.filter_permitted( + user, ServiceLevelAgreement.objects, mode='x') + self.fields['sla'].label = _("SLA") + + if 'queue' in skip: + del self.fields['queue'] + else: + self.fields['queue'].queryset = Object.filter_permitted( + user, TicketQueue.objects, mode='x') + self.fields['queue'].label = _("Queue") + + if 'assigned' in skip: + del self.fields['assigned'] + else: + self.fields['assigned'].queryset = Object.filter_permitted( + user, ServiceAgent.objects, mode='x') + self.fields['assigned'].widget.attrs.update({'class': 'multicomplete', + 'callback': reverse('services_ajax_agent_lookup')}) + self.fields['assigned'].label = _("Assigned to") + self.fields['assigned'].help_text = "" + + class Meta: + + "Ticket specified as model" + model = Ticket + fields = ('caller', 'status', 'service', 'sla', 'queue', 'assigned') + + +class SLAFilterForm(forms.ModelForm): + """ SLA Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + "Sets allowed values" + if skip is None: + skip = [] + super(SLAFilterForm, self).__init__(*args, **kwargs) + + self.fields['client'].queryset = Object.filter_permitted( + user, Contact.objects, mode='x') + self.fields['client'].required = False + self.fields['client'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['client'].label = _("Client") + + self.fields['provider'].queryset = Object.filter_permitted( + user, Contact.objects, mode='x') + self.fields['provider'].required = False + self.fields['provider'].widget.attrs.update({'class': 'autocomplete', + 'callback': reverse('identities_ajax_contact_lookup')}) + self.fields['provider'].label = _("Provider") + + self.fields['service'].queryset = Object.filter_permitted( + user, Service.objects, mode='x') + self.fields['service'].required = False + self.fields['service'].label = _("Service") + + class Meta: + "ServiceLevelAgreement specified as model" + model = ServiceLevelAgreement + fields = ('service', 'client', 'provider') + + +class AgentFilterForm(forms.ModelForm): + """ Agent Filters definition """ + + def __init__(self, user, skip=None, *args, **kwargs): + "Sets allowed values" + if skip is None: + skip = [] + super(AgentFilterForm, self).__init__(*args, **kwargs) + + self.fields['related_user'].required = True + self.fields['related_user'].label = _("Related user") + + class Meta: + "ServiceAgent specified as model" + model = ServiceAgent + fields = ['related_user'] diff --git a/data/treeio/treeio/treeio/services/identities.py b/data/treeio/treeio/treeio/services/identities.py new file mode 100644 index 0000000..5301daf --- /dev/null +++ b/data/treeio/treeio/treeio/services/identities.py @@ -0,0 +1,67 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Handle objects from this module relevant to a Contact or a User +""" +from treeio.core.models import Object +from treeio.services.models import Ticket +from treeio.services.templatetags.services import services_ticket_list + +CONTACT_OBJECTS = {'ticket_set': {'label': 'Tickets', + 'objects': [], + 'templatetag': services_ticket_list}, + 'client_sla': {'label': 'Service Level Agreements', + 'objects': [], + 'templatetag': None}, 'provider_sla': {'label': 'Provided SLAs', + 'objects': [], + 'templatetag': None}} + +USER_OBJECTS = {'serviceagent_set': {'label': 'Assigned Tickets', + 'objects': [], + 'templatetag': services_ticket_list}} + + +def get_contact_objects(current_user, contact): + """ + Returns a dictionary with keys specified as contact attributes + and values as dictionaries with labels and set of relevant objects. + """ + + objects = dict(CONTACT_OBJECTS) + + for key in objects: + if hasattr(contact, key): + manager = getattr(contact, key) + try: + manager = manager.filter(status__hidden=False) + except: + pass + objects[key]['objects'] = Object.filter_permitted( + current_user, manager) + + return objects + + +def get_user_objects(current_user, user): + """ + Returns a dictionary with keys specified as contact attributes + and values as dictionaries with labels and set of relevant objects. + """ + + objects = dict(USER_OBJECTS) + + for key in objects: + if hasattr(user, key): + if key == 'serviceagent_set': + manager = Ticket.objects.filter(assigned__related_user=user) + else: + manager = getattr(user, key) + if hasattr(manager, 'status'): + manager = manager.filter(status__hidden=False) + objects[key]['objects'] = Object.filter_permitted( + current_user, manager) + + return objects diff --git a/data/treeio/treeio/treeio/services/models.py b/data/treeio/treeio/treeio/services/models.py new file mode 100644 index 0000000..af772c2 --- /dev/null +++ b/data/treeio/treeio/treeio/services/models.py @@ -0,0 +1,370 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +ServiceSupport module objects. + +Depends on: treeio.core, treeio.identities +""" + +from django.core.urlresolvers import reverse +from django.db.models import signals +from django.db import models +from django.utils.translation import ugettext as _ +from django.utils.html import strip_tags +from treeio.core.conf import settings +from treeio.identities.models import Contact +from treeio.core.models import User, Object, ModuleSetting, UpdateRecord +from treeio.core.mail import BaseEmail +from treeio.core.rendering import render_to_string, render_string_template +from treeio.messaging.models import Message, MessageStream + + +class TicketStatus(Object): + + "State information about a ticket" + name = models.CharField(max_length=256) + details = models.TextField(blank=True, null=True) + active = models.BooleanField(default=True) + hidden = models.BooleanField(default=False) + + searchable = False + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + "Returns absolute URL of the object" + return reverse('services_status_view', args=[self.id]) + + class Meta: + + "TicketStatus" + ordering = ('hidden', '-active', 'name') + + +class Service(Object): + + "Technical service supported by a company" + name = models.CharField(max_length=256) + parent = models.ForeignKey( + 'self', blank=True, null=True, related_name='child_set') + details = models.TextField(blank=True, null=True) + + access_inherit = ('parent', '*module', '*user') + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + "Returns absolute URL of the object" + try: + return reverse('services_service_view', args=[self.id]) + except Exception: + return "" + + class Meta: + + "Service" + ordering = ['name'] + + +class ServiceLevelAgreement(Object): + + "Formal terms for service support" + name = models.CharField(max_length=256) + service = models.ForeignKey(Service) + default = models.BooleanField(default=False) + response_time = models.PositiveIntegerField(blank=True, null=True) + uptime_rate = models.FloatField(blank=True, null=True) + available_from = models.TimeField(blank=True, null=True) + available_to = models.TimeField(blank=True, null=True) + client = models.ForeignKey( + Contact, related_name="client_sla", blank=True, null=True) + provider = models.ForeignKey(Contact, related_name="provider_sla") + + access_inherit = ('service', '*module', '*user') + + class Meta: + + "SLA" + ordering = ('name', 'client') + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + "Returns absolute URL of the object" + try: + return reverse('services_sla_view', args=[self.id]) + except Exception: + return "" + + +class ServiceAgent(Object): + + "User responsible for service support" + related_user = models.ForeignKey(User) + active = models.BooleanField(default=True) + occupied = models.BooleanField(default=False) + available_from = models.TimeField(blank=True, null=True) + available_to = models.TimeField(blank=True, null=True) + + class Meta: + + "ServiceAgent" + ordering = ('related_user', '-active', 'occupied') + + def __unicode__(self): + return unicode(self.related_user) + + def get_absolute_url(self): + "Returns absolute URL of the object" + try: + return reverse('services_agent_view', args=[self.id]) + except Exception: + return "" + + +class TicketQueue(Object): + + "Queue for incoming tickets" + name = models.CharField(max_length=256) + active = models.BooleanField(default=True) + parent = models.ForeignKey( + 'self', blank=True, null=True, related_name='child_set') + default_ticket_status = models.ForeignKey( + TicketStatus, blank=True, null=True, on_delete=models.SET_NULL) + default_ticket_priority = models.IntegerField(default=3, + choices=((5, 'Highest'), (4, 'High'), (3, 'Normal'), + (2, 'Low'), (1, 'Lowest'))) + default_service = models.ForeignKey( + Service, blank=True, null=True, on_delete=models.SET_NULL) + waiting_time = models.PositiveIntegerField(blank=True, null=True) + next_queue = models.ForeignKey( + 'self', blank=True, null=True, related_name='previous_set', on_delete=models.SET_NULL) + ticket_code = models.CharField( + max_length=8, blank=True, null=True, default='') + message_stream = models.ForeignKey( + MessageStream, blank=True, null=True, on_delete=models.SET_NULL) # Messaging integration + details = models.TextField(blank=True, null=True) + + class Meta: + + "TicketQueue" + ordering = ('name', '-active', 'ticket_code') + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + "Returns absolute URL of the object" + try: + return reverse('services_queue_view', args=[self.id]) + except Exception: + return "" + + +class Ticket(Object): + + "Problem or support request ticket" + reference = models.CharField(max_length=256) + name = models.CharField(max_length=256) + caller = models.ForeignKey( + Contact, blank=True, null=True, on_delete=models.SET_NULL) + urgency = models.IntegerField(default=3, + choices=((5, _('Highest')), (4, _('High')), (3, _('Normal')), (2, _('Low')), + (1, _('Lowest')))) + priority = models.IntegerField(default=3, + choices=((5, _('Highest')), (4, _('High')), (3, _('Normal')), (2, _('Low')), + (1, _('Lowest')))) + status = models.ForeignKey(TicketStatus) + service = models.ForeignKey( + Service, blank=True, null=True, on_delete=models.SET_NULL) + sla = models.ForeignKey( + ServiceLevelAgreement, blank=True, null=True, on_delete=models.SET_NULL) + queue = models.ForeignKey(TicketQueue, blank=True, null=True) + assigned = models.ManyToManyField(ServiceAgent, blank=True, null=True) + message = models.ForeignKey( + Message, blank=True, null=True, on_delete=models.SET_NULL) # Messaging integration + details = models.TextField(blank=True, null=True) + resolution = models.TextField(blank=True, null=True) + + #access_inherit = ('queue', '*module', '*user') + + class Meta: + + "Ticket" + ordering = ('-priority', 'reference') + + def __unicode__(self): + return self.name + + def priority_human(self): + "Returns a Human-friendly priority name" + choices = ((5, _('Highest')), (4, _('High')), ( + 3, _('Normal')), (2, _('Low')), (1, _('Lowest'))) + for choice in choices: + if choice[0] == self.priority: + return choice[1] + + def urgency_human(self): + "Returns a Human-friendly urgency name" + choices = ((5, _('Highest')), (4, _('High')), ( + 3, _('Normal')), (2, _('Low')), (1, _('Lowest'))) + for choice in choices: + if choice[0] == self.priority: + return choice[1] + + def save(self, *args, **kwargs): + "Automatically set ticket reference and send message to caller" + super(Ticket, self).save(*args, **kwargs) + + if not self.reference: + if self.queue: + self.reference = self.queue.ticket_code + str(self.id) + else: + self.reference = str(self.id) + self.save() + + def get_absolute_url(self): + "Returns absolute URL of the object" + try: + return reverse('services_ticket_view', args=[self.id]) + except Exception: + return "" + + +class TicketRecord(UpdateRecord): + + "Update for a Ticket" + # Messaging integration + message = models.ForeignKey(Message, blank=True, null=True) + notify = models.BooleanField(default=False, blank=True) + + +""" +Service Support signals +""" + + +def email_caller_on_new_ticket(sender, instance, created, **kwargs): + "When a new ticket is created send an email to the caller" + if created: + send_email_to_caller = False + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'send_email_to_caller')[0] + send_email_to_caller = conf.value + except: + send_email_to_caller = getattr( + settings, 'HARDTREE_SEND_EMAIL_TO_CALLER', True) + + if send_email_to_caller: + # don't send email to yourself + creator_contact = None + if instance.creator: + creator_contact = instance.creator.get_contact() + + if instance.caller and instance.caller != creator_contact: + if not instance.reference: + if instance.queue: + instance.reference = instance.queue.ticket_code + \ + str(instance.id) + else: + instance.reference = str(instance.id) + instance.save() + subject = "[#%s] %s" % (instance.reference, instance.name) + + # Construct context and render to html, body + context = {'ticket': instance} + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'send_email_template')[0] + send_email_template = conf.value + html = render_string_template(send_email_template, context) + except: + html = render_to_string( + 'services/emails/notify_caller', context, response_format='html') + body = strip_tags(html) + + if instance.queue and instance.queue.message_stream: + stream = instance.queue.message_stream + if stream.outgoing_server_name: + try: + caller_email = instance.caller.get_email() + if caller_email: + toaddr = caller_email + ssl = False + if stream.outgoing_server_type == 'SMTP-SSL': + ssl = True + email = BaseEmail(stream.outgoing_server_name, + stream.outgoing_server_username, + stream.outgoing_password, + stream.outgoing_email, + toaddr, subject, body, html=html, + ssl=ssl) + email.process_email() + except: + pass + +signals.post_save.connect(email_caller_on_new_ticket, sender=Ticket) + + +def create_ticket_from_message(sender, instance, created, **kwargs): + """ + Get a signal from messaging.models + Check if (new) message's stream is also assigned to Ticket Queue + Create a new ticket from that message + Rename original message title + """ + + if created and getattr(instance, 'auto_notify', True): + if instance.reply_to: + tickets = instance.reply_to.ticket_set.all() + for ticket in tickets: + record = TicketRecord() + record.sender = instance.author + record.record_type = 'manual' + record.body = instance.body + record.save() + record.about.add(ticket) + ticket.set_last_updated() + else: + stream = instance.stream + queues = TicketQueue.objects.filter(message_stream=stream) + if stream and queues: + queue = queues[0] + ticket = Ticket() + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + ticket.status = TicketStatus.objects.get( + pk=long(conf.value)) + except: + statuses = TicketStatus.objects.all() + ticket.status = statuses[0] + ticket.queue = queue + ticket.caller = instance.author + ticket.details = instance.body + ticket.message = instance + ticket.name = instance.title + ticket.auto_notify = False + ticket.save() + try: + if stream.creator: + ticket.set_user(stream.creator) + elif queue.creator: + ticket.set_user(queue.creator) + else: + ticket.copy_permissions(queue) + except: + pass + + # Rename original message title + instance.title = "[#" + ticket.reference + "] " + instance.title + instance.save() + +signals.post_save.connect(create_ticket_from_message, sender=Message) diff --git a/data/treeio/treeio/treeio/services/south_migrations/0001_initial.py b/data/treeio/treeio/treeio/services/south_migrations/0001_initial.py new file mode 100644 index 0000000..1034c0c --- /dev/null +++ b/data/treeio/treeio/treeio/services/south_migrations/0001_initial.py @@ -0,0 +1,412 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'TicketStatus' + db.create_table('services_ticketstatus', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('active', self.gf('django.db.models.fields.BooleanField') + (default=True)), + ('hidden', self.gf('django.db.models.fields.BooleanField') + (default=False)), + )) + db.send_create_signal('services', ['TicketStatus']) + + # Adding model 'Service' + db.create_table('services_service', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['services.Service'])), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('services', ['Service']) + + # Adding model 'ServiceLevelAgreement' + db.create_table('services_servicelevelagreement', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('service', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['services.Service'])), + ('default', self.gf('django.db.models.fields.BooleanField') + (default=False)), + ('response_time', self.gf('django.db.models.fields.PositiveIntegerField')( + null=True, blank=True)), + ('uptime_rate', self.gf('django.db.models.fields.FloatField') + (null=True, blank=True)), + ('available_from', self.gf('django.db.models.fields.TimeField') + (null=True, blank=True)), + ('available_to', self.gf('django.db.models.fields.TimeField') + (null=True, blank=True)), + ('client', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='client_sla', null=True, to=orm['identities.Contact'])), + ('provider', self.gf('django.db.models.fields.related.ForeignKey') + (related_name='provider_sla', to=orm['identities.Contact'])), + )) + db.send_create_signal('services', ['ServiceLevelAgreement']) + + # Adding model 'ServiceAgent' + db.create_table('services_serviceagent', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('related_user', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['core.User'])), + ('active', self.gf('django.db.models.fields.BooleanField') + (default=True)), + ('occupied', self.gf( + 'django.db.models.fields.BooleanField')(default=False)), + ('available_from', self.gf('django.db.models.fields.TimeField') + (null=True, blank=True)), + ('available_to', self.gf('django.db.models.fields.TimeField') + (null=True, blank=True)), + )) + db.send_create_signal('services', ['ServiceAgent']) + + # Adding model 'TicketQueue' + db.create_table('services_ticketqueue', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('active', self.gf('django.db.models.fields.BooleanField') + (default=True)), + ('parent', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='child_set', null=True, to=orm['services.TicketQueue'])), + ('default_ticket_status', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['services.TicketStatus'], null=True, blank=True)), + ('default_ticket_priority', self.gf( + 'django.db.models.fields.IntegerField')(default=3)), + ('default_service', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['services.Service'], null=True, blank=True)), + ('waiting_time', self.gf('django.db.models.fields.PositiveIntegerField')( + null=True, blank=True)), + ('next_queue', self.gf('django.db.models.fields.related.ForeignKey')( + blank=True, related_name='previous_set', null=True, to=orm['services.TicketQueue'])), + ('ticket_code', self.gf('django.db.models.fields.CharField') + (default='', max_length=8, null=True, blank=True)), + ('message_stream', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['messaging.MessageStream'], null=True, blank=True)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('services', ['TicketQueue']) + + # Adding model 'Ticket' + db.create_table('services_ticket', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('reference', self.gf( + 'django.db.models.fields.CharField')(max_length=256)), + ('name', self.gf('django.db.models.fields.CharField') + (max_length=256)), + ('caller', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['identities.Contact'], null=True, blank=True)), + ('urgency', self.gf( + 'django.db.models.fields.IntegerField')(default=3)), + ('priority', self.gf( + 'django.db.models.fields.IntegerField')(default=3)), + ('status', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['services.TicketStatus'])), + ('service', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['services.Service'], null=True, blank=True)), + ('sla', self.gf('django.db.models.fields.related.ForeignKey')( + to=orm['services.ServiceLevelAgreement'], null=True, blank=True)), + ('queue', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['services.TicketQueue'], null=True, blank=True)), + ('message', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['messaging.Message'], null=True, blank=True)), + ('details', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + ('resolution', self.gf('django.db.models.fields.TextField') + (null=True, blank=True)), + )) + db.send_create_signal('services', ['Ticket']) + + # Adding M2M table for field assigned on 'Ticket' + db.create_table('services_ticket_assigned', ( + ('id', models.AutoField( + verbose_name='ID', primary_key=True, auto_created=True)), + ('ticket', models.ForeignKey(orm['services.ticket'], null=False)), + ('serviceagent', models.ForeignKey( + orm['services.serviceagent'], null=False)) + )) + db.create_unique( + 'services_ticket_assigned', ['ticket_id', 'serviceagent_id']) + + # Adding model 'TicketRecord' + db.create_table('services_ticketrecord', ( + ('object_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.Object'], unique=True, primary_key=True)), + ('ticket', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['services.Ticket'])), + ('record_type', self.gf( + 'django.db.models.fields.CharField')(max_length=256)), + ('message', self.gf('django.db.models.fields.related.ForeignKey') + (to=orm['messaging.Message'], null=True, blank=True)), + ('details', self.gf('django.db.models.fields.TextField')()), + ('notify', self.gf('django.db.models.fields.BooleanField') + (default=False)), + )) + db.send_create_signal('services', ['TicketRecord']) + + def backwards(self, orm): + + # Deleting model 'TicketStatus' + db.delete_table('services_ticketstatus') + + # Deleting model 'Service' + db.delete_table('services_service') + + # Deleting model 'ServiceLevelAgreement' + db.delete_table('services_servicelevelagreement') + + # Deleting model 'ServiceAgent' + db.delete_table('services_serviceagent') + + # Deleting model 'TicketQueue' + db.delete_table('services_ticketqueue') + + # Deleting model 'Ticket' + db.delete_table('services_ticket') + + # Removing M2M table for field assigned on 'Ticket' + db.delete_table('services_ticket_assigned') + + # Deleting model 'TicketRecord' + db.delete_table('services_ticketrecord') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'everybody_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'everybody_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']"}), + 'group_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'group_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}), + 'user_execute': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_read': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user_write': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'messaging.emailbox': { + 'Meta': {'ordering': "['last_updated']", 'object_name': 'EmailBox', '_ormbases': ['core.Object']}, + 'email_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'email_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'server_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_password': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_username': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'messaging.message': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Message', '_ormbases': ['core.Object']}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'body': ('django.db.models.fields.TextField', [], {}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'read_by': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read_by_user'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['messaging.Message']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stream'", 'to': "orm['messaging.MessageStream']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.messagestream': { + 'Meta': {'ordering': "['name']", 'object_name': 'MessageStream', '_ormbases': ['core.Object']}, + 'email_incoming': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'incoming'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'email_outgoing': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'outgoing'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'services.service': { + 'Meta': {'ordering': "['name']", 'object_name': 'Service', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['services.Service']"}) + }, + 'services.serviceagent': { + 'Meta': {'ordering': "('related_user', '-active', 'occupied')", 'object_name': 'ServiceAgent', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'available_from': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'available_to': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'occupied': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + }, + 'services.servicelevelagreement': { + 'Meta': {'ordering': "('name', 'client')", 'object_name': 'ServiceLevelAgreement', '_ormbases': ['core.Object']}, + 'available_from': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'available_to': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client_sla'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provider_sla'", 'to': "orm['identities.Contact']"}), + 'response_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']"}), + 'uptime_rate': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) + }, + 'services.ticket': { + 'Meta': {'ordering': "('-priority', 'reference')", 'object_name': 'Ticket', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['services.ServiceAgent']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Message']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'queue': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketQueue']", 'null': 'True', 'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'resolution': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']", 'null': 'True', 'blank': 'True'}), + 'sla': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.ServiceLevelAgreement']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketStatus']"}), + 'urgency': ('django.db.models.fields.IntegerField', [], {'default': '3'}) + }, + 'services.ticketqueue': { + 'Meta': {'ordering': "('name', '-active', 'ticket_code')", 'object_name': 'TicketQueue', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'default_service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']", 'null': 'True', 'blank': 'True'}), + 'default_ticket_priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'default_ticket_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketStatus']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'message_stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.MessageStream']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'next_queue': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'previous_set'", 'null': 'True', 'to': "orm['services.TicketQueue']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['services.TicketQueue']"}), + 'ticket_code': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'null': 'True', 'blank': 'True'}), + 'waiting_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'services.ticketrecord': { + 'Meta': {'ordering': "['ticket']", 'object_name': 'TicketRecord', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Message']", 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Ticket']"}) + }, + 'services.ticketstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TicketStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['services'] diff --git a/data/treeio/treeio/treeio/services/south_migrations/0002_auto__add_field_ticketrecord_updaterecord_ptr.py b/data/treeio/treeio/treeio/services/south_migrations/0002_auto__add_field_ticketrecord_updaterecord_ptr.py new file mode 100644 index 0000000..cd74433 --- /dev/null +++ b/data/treeio/treeio/treeio/services/south_migrations/0002_auto__add_field_ticketrecord_updaterecord_ptr.py @@ -0,0 +1,305 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'TicketRecord.updaterecord_ptr' + db.add_column('services_ticketrecord', 'updaterecord_ptr', self.gf('django.db.models.fields.related.OneToOneField')( + to=orm['core.UpdateRecord'], unique=True, null=True, blank=True), keep_default=False) + + def backwards(self, orm): + + # Deleting field 'TicketRecord.updaterecord_ptr' + db.delete_column('services_ticketrecord', 'updaterecord_ptr_id') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'messaging.emailbox': { + 'Meta': {'ordering': "['last_updated']", 'object_name': 'EmailBox', '_ormbases': ['core.Object']}, + 'email_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'email_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'server_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_password': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_username': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'messaging.mailinglist': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'MailingList', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_contact_set'", 'to': "orm['identities.Contact']"}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'members_set'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opt_in': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Template']", 'null': 'True', 'blank': 'True'}) + }, + 'messaging.message': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Message', '_ormbases': ['core.Object']}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'body': ('django.db.models.fields.TextField', [], {}), + 'mlist': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'mlist'", 'null': 'True', 'to': "orm['messaging.MailingList']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'read_by': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read_by_user'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'message_recipients'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['messaging.Message']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'stream'", 'null': 'True', 'to': "orm['messaging.MessageStream']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.messagestream': { + 'Meta': {'ordering': "['name', 'last_updated']", 'object_name': 'MessageStream', '_ormbases': ['core.Object']}, + 'email_incoming': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'incoming'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'email_outgoing': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'outgoing'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'faulty': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'incoming_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'outgoing_email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.template': { + 'Meta': {'object_name': 'Template', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'services.service': { + 'Meta': {'ordering': "['name']", 'object_name': 'Service', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['services.Service']"}) + }, + 'services.serviceagent': { + 'Meta': {'ordering': "('related_user', '-active', 'occupied')", 'object_name': 'ServiceAgent', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'available_from': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'available_to': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'occupied': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + }, + 'services.servicelevelagreement': { + 'Meta': {'ordering': "('name', 'client')", 'object_name': 'ServiceLevelAgreement', '_ormbases': ['core.Object']}, + 'available_from': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'available_to': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client_sla'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provider_sla'", 'to': "orm['identities.Contact']"}), + 'response_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']"}), + 'uptime_rate': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) + }, + 'services.ticket': { + 'Meta': {'ordering': "('-priority', 'reference')", 'object_name': 'Ticket', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['services.ServiceAgent']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Message']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'queue': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketQueue']", 'null': 'True', 'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'resolution': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']", 'null': 'True', 'blank': 'True'}), + 'sla': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.ServiceLevelAgreement']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketStatus']"}), + 'urgency': ('django.db.models.fields.IntegerField', [], {'default': '3'}) + }, + 'services.ticketqueue': { + 'Meta': {'ordering': "('name', '-active', 'ticket_code')", 'object_name': 'TicketQueue', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'default_service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']", 'null': 'True', 'blank': 'True'}), + 'default_ticket_priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'default_ticket_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketStatus']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'message_stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.MessageStream']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'next_queue': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'previous_set'", 'null': 'True', 'to': "orm['services.TicketQueue']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['services.TicketQueue']"}), + 'ticket_code': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'null': 'True', 'blank': 'True'}), + 'waiting_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'services.ticketrecord': { + 'Meta': {'ordering': "['ticket']", 'object_name': 'TicketRecord', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Message']", 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Ticket']"}), + 'updaterecord_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.UpdateRecord']", 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'services.ticketstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TicketStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['services'] diff --git a/data/treeio/treeio/treeio/services/south_migrations/0003_updaterecords.py b/data/treeio/treeio/treeio/services/south_migrations/0003_updaterecords.py new file mode 100644 index 0000000..8d415fb --- /dev/null +++ b/data/treeio/treeio/treeio/services/south_migrations/0003_updaterecords.py @@ -0,0 +1,310 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + +class Migration(DataMigration): + + def forwards(self, orm): + "Migrate the UpdateRecords" + for record in orm['services.TicketRecord'].objects.all(): + update = orm['core.UpdateRecord'].objects.create() + update.author = record.creator + if record.record_type == 'manual': + update.record_type = 'manual' + else: + update.record_type = 'update' + update.body = record.details + update.date_created = record.date_created + update.save() + update.about.add(record.ticket) + + def backwards(self, orm): + raise RuntimeError("Cannot reverse this migration.") + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User'}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.AccessEntity']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'messaging.emailbox': { + 'Meta': {'ordering': "['last_updated']", 'object_name': 'EmailBox', '_ormbases': ['core.Object']}, + 'email_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'email_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'server_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_password': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_type': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_username': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'messaging.mailinglist': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'MailingList', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_contact_set'", 'to': "orm['identities.Contact']"}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'members_set'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opt_in': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Template']", 'null': 'True', 'blank': 'True'}) + }, + 'messaging.message': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Message', '_ormbases': ['core.Object']}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'body': ('django.db.models.fields.TextField', [], {}), + 'mlist': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'mlist'", 'null': 'True', 'to': "orm['messaging.MailingList']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'read_by': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read_by_user'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'message_recipients'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['messaging.Message']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'stream'", 'null': 'True', 'to': "orm['messaging.MessageStream']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.messagestream': { + 'Meta': {'ordering': "['name', 'last_updated']", 'object_name': 'MessageStream', '_ormbases': ['core.Object']}, + 'email_incoming': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'incoming'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'email_outgoing': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'outgoing'", 'null': 'True', 'to': "orm['messaging.EmailBox']"}), + 'faulty': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'incoming_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'outgoing_email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.template': { + 'Meta': {'object_name': 'Template', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'services.service': { + 'Meta': {'ordering': "['name']", 'object_name': 'Service', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['services.Service']"}) + }, + 'services.serviceagent': { + 'Meta': {'ordering': "('related_user', '-active', 'occupied')", 'object_name': 'ServiceAgent', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'available_from': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'available_to': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'occupied': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + }, + 'services.servicelevelagreement': { + 'Meta': {'ordering': "('name', 'client')", 'object_name': 'ServiceLevelAgreement', '_ormbases': ['core.Object']}, + 'available_from': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'available_to': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client_sla'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provider_sla'", 'to': "orm['identities.Contact']"}), + 'response_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']"}), + 'uptime_rate': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) + }, + 'services.ticket': { + 'Meta': {'ordering': "('-priority', 'reference')", 'object_name': 'Ticket', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['services.ServiceAgent']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Message']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'queue': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketQueue']", 'null': 'True', 'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'resolution': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']", 'null': 'True', 'blank': 'True'}), + 'sla': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.ServiceLevelAgreement']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketStatus']"}), + 'urgency': ('django.db.models.fields.IntegerField', [], {'default': '3'}) + }, + 'services.ticketqueue': { + 'Meta': {'ordering': "('name', '-active', 'ticket_code')", 'object_name': 'TicketQueue', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'default_service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']", 'null': 'True', 'blank': 'True'}), + 'default_ticket_priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'default_ticket_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketStatus']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'message_stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.MessageStream']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'next_queue': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'previous_set'", 'null': 'True', 'to': "orm['services.TicketQueue']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['services.TicketQueue']"}), + 'ticket_code': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'null': 'True', 'blank': 'True'}), + 'waiting_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'services.ticketrecord': { + 'Meta': {'ordering': "['ticket']", 'object_name': 'TicketRecord', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Message']", 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'ticket': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Ticket']"}), + 'updaterecord_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.UpdateRecord']", 'unique': 'True', 'null': 'True', 'blank': 'True'}) + }, + 'services.ticketstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TicketStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['services'] diff --git a/data/treeio/treeio/treeio/services/south_migrations/0004_auto__del_field_ticketrecord_record_type__del_field_ticketrecord_detai.py b/data/treeio/treeio/treeio/services/south_migrations/0004_auto__del_field_ticketrecord_record_type__del_field_ticketrecord_detai.py new file mode 100644 index 0000000..2bcc710 --- /dev/null +++ b/data/treeio/treeio/treeio/services/south_migrations/0004_auto__del_field_ticketrecord_record_type__del_field_ticketrecord_detai.py @@ -0,0 +1,317 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting field 'TicketRecord.record_type' + db.delete_column('services_ticketrecord', 'record_type') + + # Deleting field 'TicketRecord.details' + db.delete_column('services_ticketrecord', 'details') + + # Deleting field 'TicketRecord.object_ptr' + db.delete_column('services_ticketrecord', 'object_ptr_id') + + # Deleting field 'TicketRecord.ticket' + db.delete_column('services_ticketrecord', 'ticket_id') + + # Changing field 'TicketRecord.updaterecord_ptr' + db.alter_column('services_ticketrecord', 'updaterecord_ptr_id', self.gf( + 'django.db.models.fields.related.OneToOneField')(default=1, to=orm['core.UpdateRecord'], unique=True, primary_key=True)) + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for + # 'TicketRecord.record_type' + raise RuntimeError( + "Cannot reverse this migration. 'TicketRecord.record_type' and its values cannot be restored.") + + # User chose to not deal with backwards NULL issues for + # 'TicketRecord.details' + raise RuntimeError( + "Cannot reverse this migration. 'TicketRecord.details' and its values cannot be restored.") + + # User chose to not deal with backwards NULL issues for + # 'TicketRecord.object_ptr' + raise RuntimeError( + "Cannot reverse this migration. 'TicketRecord.object_ptr' and its values cannot be restored.") + + # User chose to not deal with backwards NULL issues for + # 'TicketRecord.ticket' + raise RuntimeError( + "Cannot reverse this migration. 'TicketRecord.ticket' and its values cannot be restored.") + + # Changing field 'TicketRecord.updaterecord_ptr' + db.alter_column('services_ticketrecord', 'updaterecord_ptr_id', self.gf( + 'django.db.models.fields.related.OneToOneField')(to=orm['core.UpdateRecord'], unique=True, null=True)) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'core.accessentity': { + 'Meta': {'object_name': 'AccessEntity'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'core.comment': { + 'Meta': {'object_name': 'Comment'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']", 'null': 'True', 'blank': 'True'}), + 'body': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}) + }, + 'core.group': { + 'Meta': {'ordering': "['name']", 'object_name': 'Group', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['core.Group']"}) + }, + 'core.object': { + 'Meta': {'object_name': 'Object'}, + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'objects_created'", 'null': 'True', 'to': "orm['core.User']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'full_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_full_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'links': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'links_rel_+'", 'null': 'True', 'to': "orm['core.Object']"}), + 'nuvius_resource': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'object_name': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'object_type': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}), + 'read_access': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'objects_read_access'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'subscriptions'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Tag']", 'null': 'True', 'blank': 'True'}), + 'trash': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'core.tag': { + 'Meta': {'ordering': "['name']", 'object_name': 'Tag'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}) + }, + 'core.updaterecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'UpdateRecord'}, + 'about': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Object']"}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.User']"}), + 'body': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'comments': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'comments_on_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.Comment']"}), + 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'dislikes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_disliked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'format_message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'format_strings': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'likes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'updates_liked'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'received_updates'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.AccessEntity']"}), + 'record_type': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'sender': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sent_updates'", 'null': 'True', 'to': "orm['core.Object']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}) + }, + 'core.user': { + 'Meta': {'ordering': "['name']", 'object_name': 'User', '_ormbases': ['core.AccessEntity']}, + 'accessentity_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.AccessEntity']", 'unique': 'True', 'primary_key': 'True'}), + 'default_group': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'default_user_set'", 'null': 'True', 'to': "orm['core.Group']"}), + 'disabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_access': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'other_groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['core.Group']", 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'identities.contact': { + 'Meta': {'ordering': "['name']", 'object_name': 'Contact', '_ormbases': ['core.Object']}, + 'contact_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.ContactType']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.AccessEntity']", 'null': 'True', 'blank': 'True'}) + }, + 'identities.contactfield': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactField', '_ormbases': ['core.Object']}, + 'allowed_values': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'field_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'label': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'identities.contacttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'ContactType', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'fields': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['identities.ContactField']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '256'}) + }, + 'messaging.mailinglist': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'MailingList', '_ormbases': ['core.Object']}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'from_contact': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'from_contact_set'", 'to': "orm['identities.Contact']"}), + 'members': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'members_set'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'opt_in': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Template']", 'null': 'True', 'blank': 'True'}) + }, + 'messaging.message': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'Message', '_ormbases': ['core.Object']}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']"}), + 'body': ('django.db.models.fields.TextField', [], {}), + 'mlist': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'mlist'", 'null': 'True', 'to': "orm['messaging.MailingList']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'read_by': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'read_by_user'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['core.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'message_recipients'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['identities.Contact']"}), + 'reply_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['messaging.Message']"}), + 'stream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'stream'", 'null': 'True', 'to': "orm['messaging.MessageStream']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.messagestream': { + 'Meta': {'ordering': "['name', 'last_updated']", 'object_name': 'MessageStream', '_ormbases': ['core.Object']}, + 'faulty': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'incoming_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'incoming_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'last_checked': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'outgoing_email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_password': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'outgoing_server_username': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'messaging.template': { + 'Meta': {'object_name': 'Template', '_ormbases': ['core.Object']}, + 'body': ('django.db.models.fields.TextField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'services.service': { + 'Meta': {'ordering': "['name']", 'object_name': 'Service', '_ormbases': ['core.Object']}, + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['services.Service']"}) + }, + 'services.serviceagent': { + 'Meta': {'ordering': "('related_user', '-active', 'occupied')", 'object_name': 'ServiceAgent', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'available_from': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'available_to': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'occupied': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'related_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.User']"}) + }, + 'services.servicelevelagreement': { + 'Meta': {'ordering': "('name', 'client')", 'object_name': 'ServiceLevelAgreement', '_ormbases': ['core.Object']}, + 'available_from': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'available_to': ('django.db.models.fields.TimeField', [], {'null': 'True', 'blank': 'True'}), + 'client': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'client_sla'", 'null': 'True', 'to': "orm['identities.Contact']"}), + 'default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'provider': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provider_sla'", 'to': "orm['identities.Contact']"}), + 'response_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']"}), + 'uptime_rate': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) + }, + 'services.ticket': { + 'Meta': {'ordering': "('-priority', 'reference')", 'object_name': 'Ticket', '_ormbases': ['core.Object']}, + 'assigned': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['services.ServiceAgent']", 'null': 'True', 'blank': 'True'}), + 'caller': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['identities.Contact']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Message']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'queue': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketQueue']", 'null': 'True', 'blank': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'resolution': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']", 'null': 'True', 'blank': 'True'}), + 'sla': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.ServiceLevelAgreement']", 'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketStatus']"}), + 'urgency': ('django.db.models.fields.IntegerField', [], {'default': '3'}) + }, + 'services.ticketqueue': { + 'Meta': {'ordering': "('name', '-active', 'ticket_code')", 'object_name': 'TicketQueue', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'default_service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.Service']", 'null': 'True', 'blank': 'True'}), + 'default_ticket_priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}), + 'default_ticket_status': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['services.TicketStatus']", 'null': 'True', 'blank': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'message_stream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.MessageStream']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'next_queue': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'previous_set'", 'null': 'True', 'to': "orm['services.TicketQueue']"}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_set'", 'null': 'True', 'to': "orm['services.TicketQueue']"}), + 'ticket_code': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'null': 'True', 'blank': 'True'}), + 'waiting_time': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'services.ticketrecord': { + 'Meta': {'ordering': "['-date_created']", 'object_name': 'TicketRecord', '_ormbases': ['core.UpdateRecord']}, + 'message': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['messaging.Message']", 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'updaterecord_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.UpdateRecord']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'services.ticketstatus': { + 'Meta': {'ordering': "('hidden', '-active', 'name')", 'object_name': 'TicketStatus', '_ormbases': ['core.Object']}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'object_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['core.Object']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['services'] diff --git a/data/treeio/treeio/treeio/services/views.py b/data/treeio/treeio/treeio/services/views.py new file mode 100644 index 0000000..ca48e0f --- /dev/null +++ b/data/treeio/treeio/treeio/services/views.py @@ -0,0 +1,1197 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +""" +Service Support module: views +""" +from django.shortcuts import get_object_or_404 +from django.template import RequestContext +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse +from django.db.models import Q +from treeio.core.conf import settings +from treeio.core.rendering import render_to_response, render_string_template, render_to_string +from treeio.core.decorators import treeio_login_required, handle_response_format +from treeio.core.views import user_denied +from treeio.core.models import Object, ModuleSetting +from treeio.services.models import Ticket, TicketRecord, TicketStatus, TicketQueue, Service, \ + ServiceLevelAgreement, ServiceAgent +from treeio.services.forms import SettingsForm, MassActionForm, TicketForm, TicketStatusForm, \ + TicketRecordForm, QueueForm, ServiceForm, ServiceLevelAgreementForm, \ + AgentForm, FilterForm, SLAFilterForm, AgentFilterForm +from treeio.identities.models import Contact + + +def _get_filter_query(args, model=Ticket): + "Creates a query to filter Tickets based on FilterForm arguments" + query = Q() + + for arg in args: + if hasattr(model, arg) and args[arg]: + kwargs = {str(arg + '__id'): long(args[arg])} + query = query & Q(**kwargs) + + return query + + +def _get_default_context(request): + "Returns default context for all views as dict()" + + queues = Object.filter_by_request( + request, TicketQueue.objects.filter(active=True, parent__isnull=True)) + statuses = Object.filter_by_request(request, TicketStatus.objects) + try: + agent = request.user.profile.serviceagent_set.all()[0] + except Exception: + agent = None + + massform = MassActionForm(request.user.profile) + + context = { + 'statuses': statuses, + 'queues': queues, + 'agent': agent, + 'massform': massform + } + + return context + + +def _process_mass_form(f): + "Pre-process request to handle mass action form for Tasks and Milestones" + + def wrap(request, *args, **kwargs): + "wrap" + if 'massform' in request.POST: + for key in request.POST: + if 'mass-ticket' in key: + try: + ticket = Ticket.objects.get(pk=request.POST[key]) + form = MassActionForm( + request.user.profile, request.POST, instance=ticket) + if form.is_valid() and request.user.profile.has_permission(ticket, mode='w'): + form.save() + except Exception: + pass + + return f(request, *args, **kwargs) + + wrap.__doc__ = f.__doc__ + wrap.__name__ = f.__name__ + + return wrap + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index(request, response_format='html'): + "All available tickets" + + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = _get_filter_query(request.GET) + else: + query = Q(status__hidden=False) & _get_filter_query(request.GET) + tickets = Object.filter_by_request( + request, Ticket.objects.filter(query)) + else: + tickets = Object.filter_by_request( + request, Ticket.objects.filter(status__hidden=False)) + + filters = FilterForm(request.user.profile, '', request.GET) + + context = _get_default_context(request) + context.update({'tickets': tickets, + 'filters': filters, }) + + return render_to_response('services/index', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_assigned(request, response_format='html'): + "Tickets assigned to current user" + + context = _get_default_context(request) + agent = context['agent'] + + if agent: + query = Q(assigned=agent) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + tickets = Object.filter_by_request( + request, Ticket.objects.filter(query)) + else: + return user_denied(request, "You are not a Service Support Agent.", response_format=response_format) + + filters = FilterForm(request.user.profile, 'assigned', request.GET) + + context.update({'tickets': tickets, + 'filters': filters}) + + return render_to_response('services/index_assigned', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def index_owned(request, response_format='html'): + "Tickets owned by current user" + + context = _get_default_context(request) + + query = Q(caller__related_user=request.user.profile) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + + tickets = Object.filter_by_request(request, Ticket.objects.filter(query)) + + filters = FilterForm(request.user.profile, 'caller', request.GET) + + context.update({'tickets': tickets, + 'filters': filters}) + + return render_to_response('services/index_owned', context, + context_instance=RequestContext(request), response_format=response_format) + +# +# Ticket Statuses +# + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def status_view(request, status_id, response_format='html'): + "Tickets filtered by status" + + status = get_object_or_404(TicketStatus, pk=status_id) + if not request.user.profile.has_permission(status): + return user_denied(request, message="You don't have access to this Ticket Status") + + query = Q(status=status) + if request.GET: + query = query & _get_filter_query(request.GET) + tickets = Object.filter_by_request(request, Ticket.objects.filter(query)) + + filters = FilterForm(request.user.profile, 'status', request.GET) + + context = _get_default_context(request) + context.update({'status': status, + 'filters': filters, + 'tickets': tickets}) + + return render_to_response('services/status_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def status_edit(request, status_id, response_format='html'): + "TicketStatus edit" + + status = get_object_or_404(TicketStatus, pk=status_id) + if not request.user.profile.has_permission(status, mode='w') \ + and not request.user.profile.is_admin('treeio_services'): + return user_denied(request, "You don't have access to this Ticket Status", response_format) + + if request.POST: + if 'cancel' not in request.POST: + form = TicketStatusForm( + request.user.profile, request.POST, instance=status) + if form.is_valid(): + status = form.save() + return HttpResponseRedirect(reverse('services_status_view', args=[status.id])) + else: + return HttpResponseRedirect(reverse('services_status_view', args=[status.id])) + else: + form = TicketStatusForm(request.user.profile, instance=status) + + context = _get_default_context(request) + context.update({'form': form, + 'status': status}) + + return render_to_response('services/status_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def status_delete(request, status_id, response_format='html'): + "TicketStatus delete" + + status = get_object_or_404(TicketStatus, pk=status_id) + if not request.user.profile.has_permission(status, mode='w'): + return user_denied(request, "You don't have access to this Ticket Status", response_format) + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + status.trash = True + status.save() + else: + status.delete() + return HttpResponseRedirect(reverse('services_settings_view')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('services_status_view', args=[status.id])) + + context = _get_default_context(request) + context.update({'status': status}) + + return render_to_response('services/status_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def status_add(request, response_format='html'): + "TicketStatus add" + + if not request.user.profile.is_admin('treeio.services'): + return user_denied(request, + message="You don't have administrator access to the Service Support module") + + if request.POST: + if 'cancel' not in request.POST: + status = TicketStatus() + form = TicketStatusForm( + request.user.profile, request.POST, instance=status) + if form.is_valid(): + status = form.save() + status.set_user_from_request(request) + return HttpResponseRedirect(reverse('services_status_view', args=[status.id])) + else: + return HttpResponseRedirect(reverse('services_settings_view')) + else: + form = TicketStatusForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('services/status_add', context, + context_instance=RequestContext(request), response_format=response_format) + +# +# Queues +# + + +@handle_response_format +@treeio_login_required +@_process_mass_form +def queue_view(request, queue_id, response_format='html'): + "Queue view" + + queue = get_object_or_404(TicketQueue, pk=queue_id) + if not request.user.profile.has_permission(queue): + return user_denied(request, message="You don't have access to this Queue") + + query = Q(queue=queue) + if request.GET: + if 'status' in request.GET and request.GET['status']: + query = query & _get_filter_query(request.GET) + else: + query = query & Q( + status__hidden=False) & _get_filter_query(request.GET) + else: + query = query & Q(status__hidden=False) + tickets = Object.filter_by_request(request, Ticket.objects.filter(query)) + + filters = FilterForm(request.user.profile, 'queue', request.GET) + subqueues = Object.filter_by_request( + request, TicketQueue.objects.filter(parent=queue)) + + context = _get_default_context(request) + context.update({'queue': queue, + 'subqueues': subqueues, + 'filters': filters, + 'tickets': tickets}) + + return render_to_response('services/queue_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def queue_edit(request, queue_id, response_format='html'): + "Queue edit" + + queue = get_object_or_404(TicketQueue, pk=queue_id) + if not request.user.profile.has_permission(queue, mode='w'): + return user_denied(request, message="You don't have access to this Queue") + + if request.POST: + if 'cancel' not in request.POST: + form = QueueForm( + request.user.profile, request.POST, instance=queue) + if form.is_valid(): + queue = form.save() + return HttpResponseRedirect(reverse('services_queue_view', args=[queue.id])) + else: + return HttpResponseRedirect(reverse('services_queue_view', args=[queue.id])) + else: + form = QueueForm(request.user.profile, instance=queue) + + context = _get_default_context(request) + context.update({'queue': queue, 'form': form}) + + return render_to_response('services/queue_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def queue_delete(request, queue_id, response_format='html'): + "Queue delete" + + queue = get_object_or_404(TicketQueue, pk=queue_id) + if not request.user.profile.has_permission(queue, mode='w'): + return user_denied(request, message="You don't have access to this Queue") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + queue.trash = True + queue.save() + else: + queue.delete() + return HttpResponseRedirect(reverse('services_settings_view')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('services_queue_view', args=[queue.id])) + + query = Q(queue=queue) & Q(status__hidden=False) + tickets = Object.filter_by_request(request, Ticket.objects.filter(query)) + subqueues = Object.filter_by_request( + request, TicketQueue.objects.filter(parent=queue)) + + context = _get_default_context(request) + context.update({'queue': queue, + 'subqueues': subqueues, + 'tickets': tickets}) + + return render_to_response('services/queue_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def queue_add(request, response_format='html'): + "Queue add" + + if not request.user.profile.is_admin('treeio.services'): + return user_denied(request, + message="You don't have administrator access to the Service Support module") + + if request.POST: + if 'cancel' not in request.POST: + queue = TicketQueue() + form = QueueForm( + request.user.profile, request.POST, instance=queue) + if form.is_valid(): + queue = form.save() + queue.set_user_from_request(request) + return HttpResponseRedirect(reverse('services_queue_view', args=[queue.id])) + else: + return HttpResponseRedirect(reverse('services_settings_view')) + else: + form = QueueForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('services/queue_add', context, + context_instance=RequestContext(request), response_format=response_format) + +# +# Tickets +# + + +@handle_response_format +@treeio_login_required +def ticket_view(request, ticket_id, response_format='html'): + "Ticket view" + + context = _get_default_context(request) + agent = context['agent'] + profile = request.user.profile + + ticket = get_object_or_404(Ticket, pk=ticket_id) + if not profile.has_permission(ticket): + return user_denied(request, message="You don't have access to this Ticket") + + if ticket.message: + ticket.message.read_by.add(profile) + + if profile.has_permission(ticket, mode='x'): + if request.POST: + record = TicketRecord(sender=profile.get_contact()) + record.record_type = 'manual' + if ticket.message: + record.message = ticket.message + form = TicketRecordForm( + agent, ticket, request.POST, instance=record) + if form.is_valid(): + record = form.save() + record.save() + record.set_user_from_request(request) + record.about.add(ticket) + ticket.set_last_updated() + return HttpResponseRedirect(reverse('services_ticket_view', args=[ticket.id])) + + else: + form = TicketRecordForm(agent, ticket) + else: + form = None + + context.update({'ticket': ticket, 'record_form': form}) + + return render_to_response('services/ticket_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def ticket_edit(request, ticket_id, response_format='html'): + "Ticket edit" + + context = _get_default_context(request) + agent = context['agent'] + + ticket = get_object_or_404(Ticket, pk=ticket_id) + if not request.user.profile.has_permission(ticket, mode='w'): + return user_denied(request, message="You don't have access to this Ticket") + + if request.POST: + if 'cancel' not in request.POST: + form = TicketForm( + request.user.profile, None, agent, request.POST, instance=ticket) + if form.is_valid(): + ticket = form.save() + return HttpResponseRedirect(reverse('services_ticket_view', args=[ticket.id])) + else: + return HttpResponseRedirect(reverse('services_ticket_view', args=[ticket.id])) + else: + form = TicketForm( + request.user.profile, None, agent, instance=ticket) + + context.update({'form': form, + 'ticket': ticket}) + + return render_to_response('services/ticket_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def ticket_set_status(request, ticket_id, status_id, response_format='html'): + "Ticket quick set: Status" + ticket = get_object_or_404(Ticket, pk=ticket_id) + if not request.user.profile.has_permission(ticket, mode='w'): + return user_denied(request, message="You don't have access to this Ticket") + + status = get_object_or_404(TicketStatus, pk=status_id) + if not request.user.profile.has_permission(status): + return user_denied(request, message="You don't have access to this Ticket Status") + + if not ticket.status == status: + ticket.status = status + ticket.save() + + return ticket_view(request, ticket_id, response_format) + + +@handle_response_format +@treeio_login_required +def ticket_delete(request, ticket_id, response_format='html'): + "Ticket delete" + + ticket = get_object_or_404(Ticket, pk=ticket_id) + if not request.user.profile.has_permission(ticket, mode='w'): + return user_denied(request, message="You don't have access to this Ticket") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + ticket.trash = True + ticket.save() + else: + ticket.delete() + return HttpResponseRedirect(reverse('services_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('services_ticket_view', args=[ticket.id])) + + context = _get_default_context(request) + context.update({'ticket': ticket}) + + return render_to_response('services/ticket_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def ticket_add(request, queue_id=None, response_format='html'): + "Ticket add" + + context = _get_default_context(request) + agent = context['agent'] + profile = request.user.profile + + queue = None + if queue_id: + queue = get_object_or_404(TicketQueue, pk=queue_id) + if not profile.has_permission(queue, mode='x'): + queue = None + + if request.POST: + if 'cancel' not in request.POST: + ticket = Ticket(creator=profile) + if not agent: + if queue: + ticket.queue = queue + if queue.default_ticket_status: + ticket.status = queue.default_ticket_status + else: + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + ticket.status = TicketStatus.objects.get( + pk=long(conf.value)) + except: + if 'statuses' in context: + try: + ticket.status = context['statuses'][0] + except: + pass + ticket.priority = queue.default_ticket_priority + ticket.service = queue.default_service + else: + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + ticket.status = TicketStatus.objects.get( + pk=long(conf.value)) + except: + if 'statuses' in context: + try: + ticket.status = context['statuses'][0] + except: + pass + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_queue')[0] + ticket.queue = TicketQueue.objects.get( + pk=long(conf.value)) + except: + if 'queues' in context: + try: + ticket.queue = context['queues'][0] + except: + pass + try: + ticket.caller = profile.get_contact() + except: + pass + form = TicketForm( + profile, queue, agent, request.POST, instance=ticket) + if form.is_valid(): + ticket = form.save() + ticket.set_user_from_request(request) + return HttpResponseRedirect(reverse('services_ticket_view', args=[ticket.id])) + else: + return HttpResponseRedirect(reverse('services')) + else: + form = TicketForm(request.user.profile, queue, agent) + + context.update({'form': form, 'queue': queue}) + + return render_to_response('services/ticket_add', context, + context_instance=RequestContext(request), response_format=response_format) + +# +# Services +# + + +@handle_response_format +@treeio_login_required +def service_catalogue(request, response_format='html'): + "All available Services" + + services = Object.filter_by_request( + request, Service.objects.filter(parent__isnull=True)) + + filters = FilterForm(request.user.profile, '', request.GET) + + context = _get_default_context(request) + context.update({'services': services, 'filters': filters}) + + return render_to_response('services/service_catalogue', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def service_view(request, service_id, response_format='html'): + "Service view" + + service = get_object_or_404(Service, pk=service_id) + if not request.user.profile.has_permission(service) \ + and not request.user.profile.is_admin('treeio_services'): + return user_denied(request, message="You don't have access to this Service") + + context = _get_default_context(request) + context.update({'service': service}) + + return render_to_response('services/service_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def service_edit(request, service_id, response_format='html'): + "Service edit" + + service = get_object_or_404(Service, pk=service_id) + if not request.user.profile.has_permission(service, mode='w') \ + and not request.user.profile.is_admin('treeio_services'): + return user_denied(request, message="You don't have access to this Service") + + if request.POST: + if 'cancel' not in request.POST: + form = ServiceForm( + request.user.profile, request.POST, instance=service) + if form.is_valid(): + service = form.save() + return HttpResponseRedirect(reverse('services_service_view', args=[service.id])) + else: + return HttpResponseRedirect(reverse('services_service_view', args=[service.id])) + else: + form = ServiceForm(request.user.profile, instance=service) + + context = _get_default_context(request) + context.update({'form': form, 'service': service}) + + return render_to_response('services/service_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def service_delete(request, service_id, response_format='html'): + "Service delete" + + service = get_object_or_404(Service, pk=service_id) + if not request.user.profile.has_permission(service, mode='w') \ + and not request.user.profile.is_admin('treeio_services'): + return user_denied(request, message="You don't have access to this Service") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + service.trash = True + service.save() + else: + service.delete() + return HttpResponseRedirect(reverse('services_service_catalogue')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('services_service_view', args=[service.id])) + + context = _get_default_context(request) + context.update({'service': service}) + + return render_to_response('services/service_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def service_add(request, response_format='html'): + "Service add" + + if not request.user.profile.is_admin('treeio.services'): + return user_denied(request, + message="You don't have administrator access to the Service Support module") + + if request.POST: + if 'cancel' not in request.POST: + service = Service() + form = ServiceForm( + request.user.profile, request.POST, instance=service) + if form.is_valid(): + service = form.save() + service.set_user_from_request(request) + return HttpResponseRedirect(reverse('services_service_view', args=[service.id])) + else: + return HttpResponseRedirect(reverse('services')) + else: + form = ServiceForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('services/service_add', context, + context_instance=RequestContext(request), response_format=response_format) + + +# +# ServiceLevelAgreements +# + +@handle_response_format +@treeio_login_required +def sla_index(request, response_format='html'): + "All available Service Level Agreements" + + if request.GET: + query = _get_filter_query(request.GET, ServiceLevelAgreement) + slas = Object.filter_by_request(request, + ServiceLevelAgreement.objects.filter(query)) + else: + slas = Object.filter_by_request(request, + ServiceLevelAgreement.objects) + + filters = SLAFilterForm(request.user.profile, '', request.GET) + + context = _get_default_context(request) + context.update({'slas': slas, 'filters': filters}) + + return render_to_response('services/sla_index', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def sla_view(request, sla_id, response_format='html'): + "ServiceLevelAgreement view" + + sla = get_object_or_404(ServiceLevelAgreement, pk=sla_id) + if not request.user.profile.has_permission(sla): + return user_denied(request, message="You don't have access to this Service Level Agreement") + + context = _get_default_context(request) + context.update({'sla': sla}) + + return render_to_response('services/sla_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def sla_edit(request, sla_id, response_format='html'): + "ServiceLevelAgreement edit" + + sla = get_object_or_404(ServiceLevelAgreement, pk=sla_id) + if not request.user.profile.has_permission(sla, mode='w'): + return user_denied(request, message="You don't have access to this Service Level Agreement") + + if request.POST: + if 'cancel' not in request.POST: + form = ServiceLevelAgreementForm( + request.user.profile, request.POST, instance=sla) + if form.is_valid(): + sla = form.save() + return HttpResponseRedirect(reverse('services_sla_view', args=[sla.id])) + else: + return HttpResponseRedirect(reverse('services_sla_view', args=[sla.id])) + else: + form = ServiceLevelAgreementForm( + request.user.profile, instance=sla) + + context = _get_default_context(request) + context.update({'sla': sla, 'form': form}) + + return render_to_response('services/sla_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def sla_delete(request, sla_id, response_format='html'): + "ServiceLevelAgreement delete" + + sla = get_object_or_404(ServiceLevelAgreement, pk=sla_id) + if not request.user.profile.has_permission(sla, mode='w'): + return user_denied(request, message="You don't have access to this Service Level Agreement") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + sla.trash = True + sla.save() + else: + sla.delete() + return HttpResponseRedirect(reverse('services_sla_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('services_sla_view', args=[sla.id])) + + context = _get_default_context(request) + context.update({'sla': sla}) + + return render_to_response('services/sla_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def sla_add(request, response_format='html'): + "ServiceLevelAgreement add" + + if not request.user.profile.is_admin('treeio.services'): + return user_denied(request, + message="You don't have administrator access to the Service Support module") + + if request.POST: + if 'cancel' not in request.POST: + sla = ServiceLevelAgreement() + form = ServiceLevelAgreementForm( + request.user.profile, request.POST, instance=sla) + if form.is_valid(): + sla = form.save() + sla.set_user_from_request(request) + return HttpResponseRedirect(reverse('services_sla_view', args=[sla.id])) + else: + return HttpResponseRedirect(reverse('services')) + else: + form = ServiceLevelAgreementForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('services/sla_add', context, + context_instance=RequestContext(request), response_format=response_format) + + +# +# Settings +# +@handle_response_format +@treeio_login_required +def settings_view(request, response_format='html'): + "Settings" + + if not request.user.profile.is_admin('treeio.services'): + return user_denied(request, + message="You don't have administrator access to the Service Support module") + + # default ticket status + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_status')[0] + default_ticket_status = TicketStatus.objects.get(pk=long(conf.value)) + except Exception: + default_ticket_status = None + + # default queue + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'default_ticket_queue')[0] + default_ticket_queue = TicketQueue.objects.get(pk=long(conf.value)) + except Exception: + default_ticket_queue = None + + # notify ticket caller by email + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'send_email_to_caller')[0] + send_email_to_caller = conf.value + except Exception: + send_email_to_caller = settings.HARDTREE_SEND_EMAIL_TO_CALLER + + # notification template + send_email_example = '' + try: + conf = ModuleSetting.get_for_module( + 'treeio.services', 'send_email_template')[0] + send_email_template = conf.value + except Exception: + send_email_template = None + + queues = TicketQueue.objects.filter(trash=False, parent__isnull=True) + statuses = TicketStatus.objects.filter(trash=False) + + if send_email_to_caller: + # Render example e-mail + try: + ticket = Object.filter_by_request( + request, Ticket.objects.filter(status__hidden=False, caller__isnull=False))[0] + except IndexError: + ticket = Ticket(reference='REF123', name='New request') + if not ticket.caller: + try: + caller = Object.filter_by_request(request, Contact.objects)[0] + except IndexError: + caller = Contact(name='John Smith') + ticket.caller = caller + try: + ticket.status + except: + try: + ticket.status = statuses[0] + except IndexError: + ticket.status = TicketStatus(name='Open') + if send_email_template: + try: + send_email_example = render_string_template( + send_email_template, {'ticket': ticket}) + except: + send_email_example = render_to_string( + 'services/emails/notify_caller', {'ticket': ticket}, response_format='html') + else: + send_email_example = render_to_string( + 'services/emails/notify_caller', {'ticket': ticket}, response_format='html') + + context = _get_default_context(request) + context.update({'settings_queues': queues, + 'settings_statuses': statuses, + 'default_ticket_status': default_ticket_status, + 'default_ticket_queue': default_ticket_queue, + 'send_email_to_caller': send_email_to_caller, + 'send_email_example': send_email_example}) + + return render_to_response('services/settings_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def settings_edit(request, response_format='html'): + "Settings" + + if not request.user.profile.is_admin('treeio.services'): + return user_denied(request, + message="You don't have administrator access to the Service Support module") + + if request.POST: + if 'cancel' not in request.POST: + form = SettingsForm(request.user.profile, request.POST) + if form.is_valid(): + form.save() + return HttpResponseRedirect(reverse('services_settings_view')) + else: + return HttpResponseRedirect(reverse('services_settings_view')) + else: + form = SettingsForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('services/settings_edit', context, + context_instance=RequestContext(request), response_format=response_format) + +# +# Agents +# + + +@handle_response_format +@treeio_login_required +def agent_index(request, response_format='html'): + "All available Agents" + + if not request.user.profile.is_admin('treeio.services'): + return user_denied(request, + message="You don't have administrator access to the Service Support module") + + if request.GET: + query = _get_filter_query(request.GET, ServiceAgent) + agents = Object.filter_by_request(request, + ServiceAgent.objects.filter(query)) + else: + agents = Object.filter_by_request(request, + ServiceAgent.objects) + + filters = AgentFilterForm(request.user.profile, '', request.GET) + + context = _get_default_context(request) + context.update({'agents': agents, 'filters': filters}) + + return render_to_response('services/agent_index', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def agent_view(request, agent_id, response_format='html'): + "Agent view" + + view_agent = get_object_or_404(ServiceAgent, pk=agent_id) + if not request.user.profile.has_permission(view_agent): + return user_denied(request, message="You don't have access to this Service Agent") + + context = _get_default_context(request) + context.update({'view_agent': view_agent}) + + return render_to_response('services/agent_view', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def agent_edit(request, agent_id, response_format='html'): + "Agent edit" + + view_agent = get_object_or_404(ServiceAgent, pk=agent_id) + if not request.user.profile.has_permission(view_agent): + return user_denied(request, message="You don't have access to this Service Agent") + + if request.POST: + if 'cancel' not in request.POST: + form = AgentForm( + request.user.profile, request.POST, instance=view_agent) + if form.is_valid(): + view_agent = form.save() + return HttpResponseRedirect(reverse('services_agent_view', args=[view_agent.id])) + else: + return HttpResponseRedirect(reverse('services_agent_view', args=[view_agent.id])) + else: + form = AgentForm(request.user.profile, instance=view_agent) + + context = _get_default_context(request) + context.update({'form': form, 'view_agent': view_agent}) + + return render_to_response('services/agent_edit', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def agent_delete(request, agent_id, response_format='html'): + "Agent delete" + + view_agent = get_object_or_404(ServiceAgent, pk=agent_id) + if not request.user.profile.has_permission(view_agent, mode='w'): + return user_denied(request, message="You don't have access to this Service Agent") + + if request.POST: + if 'delete' in request.POST: + if 'trash' in request.POST: + view_agent.trash = True + view_agent.save() + else: + view_agent.delete() + return HttpResponseRedirect(reverse('services_agent_index')) + elif 'cancel' in request.POST: + return HttpResponseRedirect(reverse('services_agent_view', args=[view_agent.id])) + + context = _get_default_context(request) + context.update({'view_agent': view_agent}) + + return render_to_response('services/agent_delete', context, + context_instance=RequestContext(request), response_format=response_format) + + +@handle_response_format +@treeio_login_required +def agent_add(request, response_format='html'): + "Agent add" + + if not request.user.profile.is_admin('treeio.services'): + return user_denied(request, + message="You don't have administrator access to the Service Support module") + + if request.POST: + if 'cancel' not in request.POST: + new_agent = ServiceAgent() + form = AgentForm( + request.user.profile, request.POST, instance=new_agent) + if form.is_valid(): + new_agent = form.save() + new_agent.set_user_from_request(request) + return HttpResponseRedirect(reverse('services_agent_view', args=[new_agent.id])) + else: + return HttpResponseRedirect(reverse('services_agent_index')) + else: + form = AgentForm(request.user.profile) + + context = _get_default_context(request) + context.update({'form': form}) + + return render_to_response('services/agent_add', context, + context_instance=RequestContext(request), response_format=response_format) + + +@treeio_login_required +def widget_index(request, response_format='html'): + "All Active Tickets" + + tickets = Object.filter_by_request( + request, Ticket.objects.filter(status__hidden=False)) + + context = _get_default_context(request) + context.update({'tickets': tickets}) + + return render_to_response('services/widgets/index', context, + context_instance=RequestContext(request), + response_format=response_format) + + +@treeio_login_required +def widget_index_assigned(request, response_format='html'): + "Tickets assigned to current user" + + context = _get_default_context(request) + agent = context['agent'] + + if agent: + tickets = Object.filter_by_request(request, Ticket.objects.filter(assigned=agent, + status__hidden=False)) + else: + return user_denied(request, "You are not a Service Support Agent.") + + context.update({'tickets': tickets}) + + return render_to_response('services/widgets/index_assigned', context, + context_instance=RequestContext(request), response_format=response_format) + + +# +# AJAX lookups +# +@treeio_login_required +def ajax_ticket_lookup(request, response_format='html'): + "Returns a list of matching tickets" + + tickets = [] + if request.GET and 'term' in request.GET: + tickets = Ticket.objects.filter( + name__icontains=request.GET['term'])[:10] + + return render_to_response('services/ajax_ticket_lookup', + {'tickets': tickets}, + context_instance=RequestContext(request), + response_format=response_format) + + +@treeio_login_required +def ajax_agent_lookup(request, response_format='html'): + "Returns a list of matching agents" + + agents = [] + if request.GET and 'term' in request.GET: + agents = ServiceAgent.objects.filter(Q(related_user__name__icontains=request.GET['term']) | + Q(related_user__contact__name__icontains=request.GET['term'])) + + return render_to_response('services/ajax_agent_lookup', + {'agents': agents}, + context_instance=RequestContext(request), + response_format=response_format) diff --git a/data/treeio/treeio/treeio_project/settings.py b/data/treeio/treeio/treeio_project/settings.py new file mode 100644 index 0000000..c0410e1 --- /dev/null +++ b/data/treeio/treeio/treeio_project/settings.py @@ -0,0 +1,590 @@ +# encoding: utf-8 +# Copyright 2011 Tree.io Limited +# This file is part of Treeio. +# License www.tree.io/license + +# coding=utf-8 + +""" +Django settings for treeio project. +""" + +import os +from os import path +from whoosh import fields +import ConfigParser +import sys + +BASE_DIR = path.dirname(path.dirname(path.abspath(__file__))) +CONFIG_FILE = 'treeio.ini' +USER_CONFIG_FILE = path.join(path.dirname(BASE_DIR), CONFIG_FILE) +DEFAULT_CONFIG_FILE = path.join(BASE_DIR, CONFIG_FILE) +DEBUG = (True if 'DEBUG' not in os.environ else {'true': True, 'false': False}[os.environ['DEBUG'].lower()]) +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +QUERY_DEBUG = False +QUERY_DEBUG_FULL = False + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS +DATABASES = {} +TESTING = 'test' in sys.argv or 'test_coverage' in sys.argv # Covers regular testing and django-coverage + +if TESTING: + test_db = os.environ.get('DB', 'sqlite') + if test_db == 'mysql': + DATABASES = {'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'treeio', + 'USER': 'root', + }} + elif test_db == 'postgres': + DATABASES = {'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'treeio', + 'USER': 'postgres', + }} + elif test_db == 'sqlite': + DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3'}} + elif test_db == 'oracle': + DATABASES = {'default': { + 'ENGINE': 'django.db.backends.oracle', + 'NAME': 'treeio', + 'USER': 'treeio', + 'PASSWORD': 'treeio', + }} + + if os.environ.get('MC') == '1': + CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', + 'LOCATION': '127.0.0.1:11211', + } + } + + PASSWORD_HASHERS = ('django.contrib.auth.hashers.MD5PasswordHasher',) + HARDTREE_API_AUTH_ENGINE = 'basic' +else: + CONF = ConfigParser.ConfigParser() + CONF.optionxform = str # to preserve case for the options names + CONF.read((DEFAULT_CONFIG_FILE, USER_CONFIG_FILE)) + DATABASES = { + 'default': dict(CONF.items('db')) + } + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'UTC' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-GB' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True +FORMAT_MODULE_PATH = 'treeio.formats' + +HARDTREE_API_CONSUMER_DB = 'default' +# OAUTH_DATA_STORE is needed for correct database setting up +OAUTH_DATA_STORE = 'treeio.core.api.auth.store.store' + +# Static files location for Tree.io +if not DEBUG: + STATIC_URL = path.join(BASE_DIR, 'static/') +else: + STATICFILES_DIRS = ( + path.join(BASE_DIR, 'static'), + ) +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(path.dirname(BASE_DIR), 'static') +STATIC_DOC_ROOT = os.path.join(BASE_DIR, 'static') +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'dajaxice.finders.DajaxiceFinder', +) + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = os.path.join(STATIC_DOC_ROOT, 'media/') + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '/static/media/' + +# Captcha Settings +CAPTCHA_FONT_SIZE = 30 +CAPTCHA_LENGTH = 6 +CAPTCHA_DISABLE = True +CAPTCHA_FOREGROUND_COLOR = '#333333' +CAPTCHA_NOISE_FUNCTIONS = [] + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/static-admin/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'z_#oc^n&z0c2lix=s$4+z#lsb9qd32qtb!#78nk7=5$_k3lq16' + +# List of callables that know how to import templates from various sources. +# TEMPLATE_LOADERS = ( +# 'django.template.loaders.filesystem.load_template_source', +# 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +# ) +if DEBUG or TESTING: + TEMPLATE_LOADERS = [ + 'django.template.loaders.app_directories.Loader', + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.eggs.Loader', + ] +else: + TEMPLATE_LOADERS = [ + ('django.template.loaders.cached.Loader', ( + 'django.template.loaders.app_directories.Loader', + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.eggs.Loader', + )), + ] + +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.i18n", + "django.core.context_processors.debug", + "django.core.context_processors.media", + "django.core.context_processors.static", + "django.core.context_processors.tz", + "django.core.context_processors.request", + 'django.contrib.messages.context_processors.messages', +) + + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'treeio.core.middleware.user.AuthMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + "treeio.core.middleware.user.LanguageMiddleware", + "django.middleware.locale.LocaleMiddleware", + 'django.middleware.common.CommonMiddleware', + 'johnny.middleware.LocalStoreClearMiddleware', + 'johnny.middleware.QueryCacheMiddleware', + 'django.middleware.gzip.GZipMiddleware', + # 'treeio.core.middleware.domain.DomainMiddleware', + 'treeio.core.middleware.user.SSLMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'treeio.core.middleware.chat.ChatAjaxMiddleware', + "django.contrib.messages.middleware.MessageMiddleware", + "treeio.core.middleware.modules.ModuleDetect", + "minidetector.Middleware", + "treeio.core.middleware.user.CommonMiddleware", + "treeio.core.middleware.user.PopupMiddleware", +) + + +ROOT_URLCONF = 'treeio_project.urls' + +TEMPLATE_DIRS = ( + os.path.join(BASE_DIR, 'templates'), +) + + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'django.contrib.staticfiles', + 'django_websocket', + 'django.contrib.messages', + 'treeio.account', + 'treeio.core', + 'treeio.core.api', + 'treeio.core.search', + 'treeio.documents', + 'treeio.events', + 'treeio.finance', + 'treeio.identities', + 'treeio.infrastructure', + 'treeio.knowledge', + 'treeio.messaging', + 'treeio.news', + 'treeio.projects', + 'treeio.reports', + 'treeio.sales', + 'treeio.services', + 'dajaxice', + 'dajax', + 'coffin', + 'captcha', + 'markup_deprecated', +) +try: + import rosetta + INSTALLED_APPS += ('rosetta',) +except ImportError: + pass +if not DEBUG: + INSTALLED_APPS += ('django.contrib.staticfiles',) + +AUTH_PROFILE_MODULE = 'core.User' + +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', + 'treeio.core.auth.HashBackend', + 'treeio.core.auth.EmailBackend', +) + +# LDAP Configuration +# AUTH_LDAP_SERVER_URI = 'ldap://' +# AUTH_LDAP_BIND_DN = "" +# AUTH_LDAP_BIND_PASSWORD = "" +# AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com", +# ldap.SCOPE_SUBTREE, "(uid=%(user)s)") +# AUTH_LDAP_START_TLS = True + +# +# Hardtree configuration +# +HARDTREE_MODULE_IDENTIFIER = 'hmodule' + +HARDTREE_DEFAULT_USER_ID = 1 + +HARDTREE_DEFAULT_PERMISSIONS = 'everyone' + +HARDTREE_SEND_EMAIL_TO_CALLER = True + +HARDTREE_ALLOW_EMAIL_NOTIFICATIONS = True +HARDTREE_ALLOW_GRITTER_NOTIFICATIONS = True + +HARDTREE_PASSWORD_LENGTH_MIN = 4 + +HARDTREE_RESPONSE_FORMATS = { + 'html': 'text/html', + 'mobile': 'text/html', + 'json': 'text/plain', + # 'json': 'application/json', + 'ajax': 'text/plain', + # 'ajax': 'application/json', + 'csv': 'text/csv', + 'xls': 'text/xls', + 'pdf': 'application/pdf', + 'rss': 'application/rss+xml', +} + +HARDTREE_IMAGE_MAX_SIZE = (300, 400) +HARDTREE_IMAGE_RESIZE_FILTER = 'ANTIALIAS' + +HARDTREE_MINIFY_JSON = False + +HARDTREE_PAGINATOR_LENGTH = 20 +HARDTREE_PAGINATOR_PAGES = 15 + +# +# CRON Fine-tuning +# + +# How often should we loop through jobs, add/remove from pool, recycle jobs: +HARDTREE_CRON_PERIOD = 10 # seconds, default 60 + +# Number of cycles to keep HIGH priority jobs before forcefully terminating +# HARDTREE_CRON_HIGH_PRIORITY = 10 # default 10 cycles + +# Number of cycles to keep LOW priority jobs before forcefully terminating +# HARDTREE_CRON_LOW_PRIORITY = 3 # default 3 cycles + +# Number of seconds since last access to domain to give the job HIGH priority +# HARDTREE_CRON_QUALIFY_HIGH = 10 # default 10 cycles + +# Number of seconds since last access to domain to run cron jobs for the domain +# HARDTREE_CRON_QUALIFY_RUN = 86400 # seconds, default 86400, i.e. 1 day + +# Number of jobs to keep in the pool at the same time +# HARDTREE_CRON_POOL_SIZE = 10 # default 10 + +# Priority value at which we should try to gracefully end a job +# HARDTREE_CRON_SOFT_KILL = 0 # default 0 + +# Priority value at which we must kill a job using any possible means (kill -9 job) +# HARDTREE_CRON_HARD_KILL = -1 # defualt -1 + +# Seconds to wait between SIGKILL signals to a dead job +# HARDTREE_CRON_GRACE_WAIT = 5 # default 5 + +# CHAT CRON! +HARDTREE_CRON_DISABLED = True # Run chat? + +# ## CRON config ends here + +HARDTREE_MULTIPLE_LOGINS_DISABLED = False + +HARDTREE_SERVER_DEFAULT_TIMEZONE = 49 # (GMT+00:00) UTC +HARDTREE_SERVER_TIMEZONE = ( + ('0', u'(GMT-11:00) International Date Line West'), + ('1', u'(GMT-11:00) Midway Island'), ('2', u'(GMT-11:00) Samoa'), + ('3', u'(GMT-10:00) Hawaii'), ('4', u'(GMT-09:00) Alaska'), + ('5', u'(GMT-08:00) Tijuana'), ('6', u'(GMT-08:00) Pacific Time (US & Canada)'), + ('7', u'(GMT-07:00) Arizona'), ('8', u'(GMT-07:00) Arizona'), + ('9', u'(GMT-08:00) Pacific Time (US & Canada)'), ('10', u'(GMT-07:00) Arizona'), + ('11', u'(GMT-07:00) Mountain Time (US & Canada)'), ('12', u'(GMT-07:00) Chihuahua'), + ('13', u'(GMT-07:00) Mazatlan'), ('14', u'(GMT-06:00) Central Time (US & Canada)'), + ('15', u'(GMT-06:00) Guadalajara'), ('16', u'(GMT-06:00) Mexico City'), + ('17', u'(GMT-06:00) Monterrey'), ('18', u'(GMT-06:00) Saskatchewan'), + ('19', u'(GMT-05:00) Eastern Time (US & Canada)'), ('20', u'(GMT-05:00) Indiana (East)'), + ('21', u'(GMT-05:00) Bogota'), ('22', u'(GMT-05:00) Lima'), + ('23', u'(GMT-05:00) Quito'), ('24', u'(GMT-04:30) Caracas'), + ('25', u'(GMT-04:00) Atlantic Time (Canada)'), ('26', u'(GMT-04:00) La Paz'), + ('27', u'(GMT-04:00) Santiago'), ('28', u'(GMT-03:30) Newfoundland'), + ('29', u'(GMT-08:00) Pacific Time (US & Canada)'), ('30', u'(GMT-03:00) Brasilia'), + ('31', u'(GMT-03:00) Buenos Aires'), ('32', u'(GMT-03:00) Georgetown'), + ('33', u'(GMT-03:00) Greenland'), ('34', u'(GMT-02:00) Mid-Atlantic'), + ('35', u'(GMT-01:00) Azores'), ('36', u'(GMT-01:00) Cape Verde Is.'), + ('37', u'(GMT+00:00) Casablanca'), ('38', u'(GMT+00:00) Dublin'), + ('39', u'(GMT+00:00) Edinburgh'), ('40', u'(GMT+00:00) Lisbon'), + ('41', u'(GMT+00:00) London'), ('42', u'(GMT+00:00) Monrovia'), + ('43', u'(GMT+00:00) UTC'), ('44', u'(GMT+01:00) Amsterdam'), + ('45', u'(GMT+01:00) Belgrade'), ('46', u'(GMT+01:00) Berlin'), + ('47', u'(GMT+01:00) Bern'), ('48', u'(GMT+01:00) Bratislava'), + ('49', u'(GMT+01:00) Brussels'), ('50', u'(GMT+01:00) Budapest'), + ('51', u'(GMT+01:00) Copenhagen'), ('52', u'(GMT+01:00) Ljubljana'), + ('53', u'(GMT+01:00) Madrid'), ('54', u'(GMT+01:00) Paris'), + ('55', u'(GMT+01:00) Prague'), ('56', u'(GMT+01:00) Rome'), + ('57', u'(GMT+01:00) Sarajevo'), ('58', u'(GMT+01:00) Skopje'), + ('59', u'(GMT+01:00) Stockholm'), ('60', u'(GMT+01:00) Vienna'), + ('61', u'(GMT+01:00) Warsaw'), ('62', u'(GMT+01:00) West Central Africa'), + ('63', u'(GMT+01:00) Zagreb'), ('64', u'(GMT+02:00) Athens'), + ('65', u'(GMT+02:00) Bucharest'), ('66', u'(GMT+02:00) Cairo'), + ('67', u'(GMT+02:00) Harare'), ('68', u'(GMT+02:00) Helsinki'), + ('69', u'(GMT+02:00) Istanbul'), ('70', u'(GMT+02:00) Jerusalem'), + ('71', u'(GMT+02:00) Kyev'), ('72', u'(GMT+02:00) Minsk'), + ('73', u'(GMT+02:00) Pretoria'), ('74', u'(GMT+02:00) Riga'), + ('75', u'(GMT+02:00) Sofia'), ('76', u'(GMT+02:00) Tallinn'), + ('77', u'(GMT+02:00) Vilnius'), ('78', u'(GMT+03:00) Baghdad'), + ('79', u'(GMT+03:00) Kuwait'), ('80', u'(GMT+03:00) Moscow'), + ('81', u'(GMT+03:00) Nairobi'), ('82', u'(GMT+03:00) Riyadh'), + ('83', u'(GMT+03:00) St. Petersburg'), ('84', u'(GMT+03:00) Volgograd'), + ('85', u'(GMT+03:30) Tehran'), ('86', u'(GMT+04:00) Abu Dhabi'), + ('87', u'(GMT+04:00) Baku'), ('88', u'(GMT+04:00) Muscat'), + ('89', u'(GMT+04:00) Tbilisi'), ('90', u'(GMT+04:00) Yerevan'), + ('91', u'(GMT+04:30) Kabul'), ('92', u'(GMT+05:00) Ekaterinburg'), + ('93', u'(GMT+05:00) Islamabad'), ('94', u'(GMT+05:00) Karachi'), + ('95', u'(GMT+05:00) Tashkent'), ('96', u'(GMT+05:30) Chennai'), + ('97', u'(GMT+05:30) Kolkata'), ('98', u'(GMT+05:30) Mumbai'), + ('99', u'(GMT+05:30) New Delhi'), ('100', u'(GMT+05:30) Sri Jayawardenepura'), + ('101', u'(GMT+05:45) Kathmandu'), ('102', u'(GMT+06:00) Almaty'), + ('103', u'(GMT+06:00) Astana'), ('104', u'(GMT+06:00) Dhaka'), + ('105', u'(GMT+06:00) Novosibirsk'), ('106', u'(GMT+06:30) Rangoon'), + ('107', u'(GMT+07:00) Bangkok'), ('108', u'(GMT+07:00) Hanoi'), + ('109', u'(GMT+07:00) Jakarta'), ('110', u'(GMT+07:00) Krasnoyarsk'), + ('111', u'(GMT+08:00) Beijing'), ('112', u'(GMT+08:00) Chongqing'), + ('113', u'(GMT+08:00) Hong Kong'), ('114', u'(GMT+08:00) Irkutsk'), + ('115', u'(GMT+08:00) Kuala Lumpur'), ('116', u'(GMT+08:00) Perth'), + ('117', u'(GMT+08:00) Singapore'), ('118', u'(GMT+08:00) Taipei'), + ('119', u'(GMT+08:00) Ulaan Bataar'), ('120', u'(GMT+08:00) Urumqi'), + ('121', u'(GMT+09:00) Osaka'), ('122', u'(GMT+09:00) Sapporo'), + ('123', u'(GMT+09:00) Seoul'), ('124', u'(GMT+09:00) Tokyo'), + ('125', u'(GMT+09:00) Yakutsk'), ('126', u'(GMT+09:30) Adelaide'), + ('127', u'(GMT+09:30) Darwin'), ('128', u'(GMT+10:00) Brisbane'), + ('129', u'(GMT+10:00) Canberra'), ('130', u'(GMT+10:00) Guam'), + ('131', u'(GMT+10:00) Hobart'), ('132', u'(GMT+10:00) Melbourne'), + ('133', u'(GMT+10:00) Port Moresby'), ('134', u'(GMT+10:00) Sydney'), + ('135', u'(GMT+10:00) Vladivostok'), ('136', u'(GMT+11:00) Magadan'), + ('137', u'(GMT+11:00) New Caledonia'), ('138', u'(GMT+11:00) Solomon Is.'), + ('139', u'(GMT+12:00) Auckland'), ('140', u'(GMT+12:00) Fiji'), + ('141', u'(GMT+12:00) Kamchatka'), ('142', u'(GMT+12:00) Marshall Is.'), + ('143', u'(GMT+12:00) Wellington'), ('144', u'(GMT+13:00) Nukualofa'), +) + +# +# Messaging +# +HARDTREE_MESSAGING_POP3_LIMIT = 100 # number of emails +HARDTREE_MESSAGING_IMAP_LIMIT = 200 # number of emails + +HARDTREE_MESSAGING_UNSAFE_BLOCKS = ( + 'head', 'object', 'embed', 'applet', 'noframes', 'noscript', 'noembed', + 'iframe', 'frame', 'frameset' +) + +HARDTREE_MESSAGING_IMAP_DEFAULT_FOLDER_NAME = 'UNSEEN' + +HARDTREE_SIGNALS_AUTOCREATE_USER = True + +HARDTREE_HELP_LINK_PREFIX = '/help/' +HARDTREE_HELP_SOURCE = 'http://www.tree.io/help' + +HARDTREE_LANGUAGES = ( + ('en', u'English'), + ('ru', u'Русский'), + ('es', u'Español'), + ('de', u'Deutsche'), + ('zh_CN', u'简体中文'), + ('fr', u'Français'), + ('el', u'ελληνικά'), + ('pt_BR', u'português') +) + +HARDTREE_LANGUAGES_DEFAULT = 'en' + +LOCALE_PATHS = (BASE_DIR + "/locale",) + +HARDTREE_AJAX_RELOAD_ON_REDIRECT = ('home', + 'user_login', + 'account_settings_view', + 'core_admin_index_perspectives', + 'core_admin_perspective_view', + 'core_settings_view') + +HARDTREE_FORCE_AJAX_RENDERING = True + +# +# htsafe settings +# + +# Replace unsafe tags +HARDTREE_SAFE_TAGS = ('div', 'ul', 'li', 'label', 'span', 'strong', 'em', 'p', 'input', + 'select', 'textarea', 'br') +HARDTREE_UNSAFE_TAGS = ('script', 'object', 'embed', + 'applet', 'noframes', 'noscript', 'noembed', 'iframe', + 'frame', 'frameset') + + +# +# Hardtree Subcription settings +# + +EVERGREEN_FREE_USERS = 3 + +USER_PRICE = 15 + +HARDTREE_SUBSCRIPTION_CUSTOMIZATION = True + +HARDTREE_SUBSCRIPTION_USER_LIMIT = 0 + +HARDTREE_SUBSCRIPTION_BLOCKED = False + +HARDTREE_SUBSCRIPTION_SSL_ENABLED = False +HARDTREE_SUBSCRIPTION_SSL_ENFORCE = False + +HARDTREE_DEMO_MODE = False + + +# +# Nuvius settings (for integration) +# +NUVIUS_URL = "http://nuvius.com" +NUVIUS_KEY = '28563.ff6ed93307fc398a52d312966c122660' +NUVIUS_SOURCE_ID = "28563" +NUVIUS_NEXT = "iframe" +NUVIUS_CHECK_USER_KEYS = True + +NUVIUS_DATA_CACHE_LIFE = 600 +CACHE_KEY_PREFIX = 'treeio_' + +# +# Email settings +# + +EMAIL_SERVER = 'localhost' +IMAP_SERVER = '' +EMAIL_USERNAME = None +EMAIL_PASSWORD = None +EMAIL_FROM = 'noreply@tree.io' +DEFAULT_SIGNATURE = """ +Thanks! +The Tree.io Team +http://www.tree.io + """ + + +# +# Search index (Whoosh) +# +SEARCH_DISABLED = False +SEARCH_ENGINE = 'db' + +WHOOSH_SCHEMA = fields.Schema(id=fields.ID(stored=True, unique=True), + name=fields.TEXT(stored=True), + type=fields.TEXT(stored=True), + content=fields.TEXT, + url=fields.ID(stored=True)) + +WHOOSH_INDEX = os.path.join(BASE_DIR, 'storage/search') + +# +# CACHING +# +if not TESTING: + try: + import pylibmc + CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', + 'LOCATION': CONF.get('memcached', 'location'), + } + } + except ImportError: + import tempfile + CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + 'LOCATION': tempfile.mkdtemp('django_cache'), + } + } + +# CACHE_BACKEND="johnny.backends.locmem://" + +JOHNNY_MIDDLEWARE_KEY_PREFIX = 'jc_treeio' + +DISABLE_QUERYSET_CACHE = False + +HARDTREE_OBJECT_BLACKLIST = [ + 'id', 'creator', 'object_name', 'object_type', 'trash', 'full_access', + 'read_access', 'nuvius_resource', 'object_ptr', 'comments', 'likes', + 'dislikes', 'tags', 'links', 'subscribers', 'read_by'] + +HARDTREE_UPDATE_BLACKLIST = [ + 'likes', 'dislikes', 'tags', 'reference', 'total', 'links', 'subscribers', + 'read_by', 'date_created', 'last_updated'] + +HARDTREE_TIMEZONE_BLACKLIST = [ + 'date_created', 'last_updated', 'time_from', 'time_to'] + +WKPATH = os.path.join(BASE_DIR, 'bin/wkhtmltopdf') +WKCWD = BASE_DIR + +CHAT_LONG_POLLING = False +CHAT_TIMEOUT = 25 # response time if not new data +CHAT_TIME_SLEEP_THREAD = 25 # interval for "Delete inactive users" +CHAT_TIME_SLEEP_NEWDATA = 1 # time sleep in expectation of new data + +MESSAGE_STORAGE = 'treeio.core.contrib.messages.storage.cache.CacheStorage' + +# Dajaxice settings +DAJAXICE_MEDIA_PREFIX = "dajaxice" + +ALLOWED_HOSTS = [ + 'localhost', + '127.0.0.1', + # '.example.com', # Allow domain and subdomains + # '.example.com.', # Also allow FQDN and subdomains + ] + +TEST_RUNNER = 'django.test.runner.DiscoverRunner' + +# monkey patch because dajax still tries to import django simplejson +import json +import django.utils +django.utils.simplejson = json